Files
LogMaster/fresh-start.js
Kevin 44cd9ab001
Some checks failed
LogMaster CI/CD / build-and-test (push) Has been cancelled
LogMaster CI/CD / docker (push) Has been cancelled
Initial commit: LogMaster with Viewer Collector Monitor modes
2026-05-04 13:50:15 +02:00

313 lines
14 KiB
JavaScript

#!/usr/bin/env node
// ============================================
// Fresh Start: Configure, Test, and Run
// ============================================
const http = require('http');
const { spawn } = require('child_process');
const COLLECTOR_PORT = 8081;
const VIEWER_PORT = 9090;
const MONITOR_PORT = 8082;
// ---- Helpers ----
function req(port, method, path, data) {
return new Promise((resolve, reject) => {
const payload = data ? (typeof data === 'string' ? data : JSON.stringify(data)) : '';
const isJson = typeof data === 'object';
const r = http.request({
hostname: 'localhost', port, path, method,
headers: {
'Content-Type': isJson ? 'application/json' : 'text/plain',
...(payload ? { 'Content-Length': Buffer.byteLength(payload) } : {}),
},
}, (res) => {
let body = '';
res.on('data', c => body += c);
res.on('end', () => {
try { resolve({ status: res.statusCode, data: JSON.parse(body) }); }
catch { resolve({ status: res.statusCode, data: body }); }
});
});
r.on('error', reject);
if (payload) r.write(payload);
r.end();
});
}
function sleep(ms) { return new Promise(r => setTimeout(r, ms)); }
let pass = 0, fail = 0;
function ok(name, cond) { if (cond) { console.log(`${name}`); pass++; } else { console.log(`${name}`); fail++; } }
// ---- Start Services ----
function startService(envFile) {
// Read env file and pass as environment
const fs = require('fs');
const env = { ...process.env };
try {
const content = fs.readFileSync(envFile, 'utf-8');
for (const line of content.split('\n')) {
const trimmed = line.trim();
if (trimmed && !trimmed.startsWith('#')) {
const eq = trimmed.indexOf('=');
if (eq > 0) env[trimmed.substring(0, eq).trim()] = trimmed.substring(eq + 1).trim();
}
}
} catch {}
const child = spawn('node', ['dist-server/server.js'], {
cwd: process.cwd(), stdio: 'pipe', env, detached: false,
});
child.stderr.on('data', () => {});
child.stdout.on('data', () => {});
return child;
}
async function waitForPort(port, maxWait = 15000) {
const start = Date.now();
while (Date.now() - start < maxWait) {
try { await req(port, 'GET', '/api/mode'); return true; } catch { await sleep(300); }
}
return false;
}
// ---- Main ----
async function main() {
console.log('\n ╔══════════════════════════════════════════════╗');
console.log(' ║ LogViewer — Fresh Start & Full Test ║');
console.log(' ╚══════════════════════════════════════════════╝\n');
// Kill any existing processes on our ports
console.log(' Cleaning up old processes...');
const { execSync } = require('child_process');
for (const port of [COLLECTOR_PORT, VIEWER_PORT, MONITOR_PORT]) {
try { execSync(`cmd /c "for /f "tokens=5" %a in ('netstat -aon ^| findstr :${port} ^| findstr LISTENING') do taskkill /F /PID %a"`, { stdio: 'pipe' }); } catch {}
}
await sleep(1000);
// Start all 3 services
console.log(' Starting services...');
const procs = [];
procs.push(startService('.env.collector'));
procs.push(startService('.env.viewer'));
procs.push(startService('.env.monitor'));
const c = await waitForPort(COLLECTOR_PORT);
const v = await waitForPort(VIEWER_PORT);
const m = await waitForPort(MONITOR_PORT);
ok('Collector started (:8081)', c);
ok('Viewer started (:9090)', v);
ok('Monitor started (:8082)', m);
if (!c || !v || !m) { console.log('\n ❌ Services failed to start. Aborting.\n'); process.exit(1); }
// ═══════════════════════════════════════
// COLLECTOR TESTS
// ═══════════════════════════════════════
console.log('\n ── Collector Tests ──\n');
// Ingest plain text
const r1 = await req(COLLECTOR_PORT, 'POST', '/collect/web-app', '2026-05-01T08:00:00Z INFO [http] GET /dashboard 200 (12ms)\n2026-05-01T08:00:01Z INFO [http] GET /api/users 200 (45ms)\n2026-05-01T08:00:02Z WARN [http] Slow request: GET /api/reports took 3200ms\n2026-05-01T08:00:03Z ERROR [db] Query timeout: SELECT * FROM orders WHERE status=pending\n2026-05-01T08:00:04Z INFO [auth] User admin@corp.com logged in');
ok('Ingest plain text (5 lines)', r1.data.accepted === 5);
// Ingest JSON
const r2 = await req(COLLECTOR_PORT, 'POST', '/collect/auth-service/json', [
'2026-05-01T08:01:00Z INFO [auth] Token issued for user alice',
'2026-05-01T08:01:01Z WARN [auth] Failed login: bob (wrong password)',
'2026-05-01T08:01:02Z ERROR [auth] Account locked: bob (5 attempts)',
'2026-05-01T08:01:03Z INFO [auth] Password reset requested: bob',
]);
ok('Ingest JSON (4 lines)', r2.data.accepted === 4);
// Ingest to third stream
const r3 = await req(COLLECTOR_PORT, 'POST', '/collect/payment-api', '2026-05-01T08:02:00Z INFO [pay] Payment processed: order-1234 ($49.99)\n2026-05-01T08:02:01Z ERROR [pay] Payment failed: order-1235 (card declined)\n2026-05-01T08:02:02Z WARN [pay] Retry 1/3 for order-1235');
ok('Ingest third stream (3 lines)', r3.data.accepted === 3);
// List streams
const streams = await req(COLLECTOR_PORT, 'GET', '/collector/streams');
ok('3 streams created', streams.data.length === 3);
// Read stream
const read = await req(COLLECTOR_PORT, 'GET', '/collector/stream/web-app');
ok('Read web-app stream (5 lines)', read.data.lines.length === 5);
// Tail
const tail = await req(COLLECTOR_PORT, 'GET', '/collector/stream/auth-service?tail=2');
ok('Tail=2 works', tail.data.lines.length === 2);
// Config
const cfg = await req(COLLECTOR_PORT, 'GET', '/collector/config');
ok('Config has rotation', !!cfg.data.rotation);
ok('Config has UDP port', cfg.data.udpPort === 5140);
// Delete stream
const del = await req(COLLECTOR_PORT, 'DELETE', '/collector/stream/payment-api');
ok('Delete stream', del.data.ok === true);
const after = await req(COLLECTOR_PORT, 'GET', '/collector/streams');
ok('Stream removed', after.data.length === 2);
// ═══════════════════════════════════════
// VIEWER TESTS
// ═══════════════════════════════════════
console.log('\n ── Viewer Tests ──\n');
// HTML
const html = await req(VIEWER_PORT, 'GET', '/');
ok('Viewer HTML (200)', html.status === 200);
// Sources
const src = await req(VIEWER_PORT, 'GET', '/api/sources');
ok('File sources loaded', src.data.length >= 4);
// Read all logs
const logs = await req(VIEWER_PORT, 'GET', '/api/logs');
ok('Logs loaded (>100 lines)', logs.data.length > 100);
// Log structure
const entry = logs.data[0];
ok('Entry has line', !!entry.line);
ok('Entry has source', !!entry.source);
ok('Entry has level', entry.level !== undefined);
// Remote streams
const remote = await req(VIEWER_PORT, 'GET', '/api/remote/streams');
ok('Remote streams from collector', remote.data.length >= 2);
// Read remote stream
if (remote.data.length > 0) {
const rs = await req(VIEWER_PORT, 'GET', `/api/remote/stream/${remote.data[0].id}`);
ok('Read remote stream', Array.isArray(rs.data) && rs.data.length > 0);
}
// Config
const vcfg = await req(VIEWER_PORT, 'GET', '/api/config');
ok('Viewer config', vcfg.data.theme === 'dark');
// ═══════════════════════════════════════
// MONITOR TESTS
// ═══════════════════════════════════════
console.log('\n ── Monitor Tests ──\n');
// HTML
const mhtml = await req(MONITOR_PORT, 'GET', '/');
ok('Monitor HTML (200)', mhtml.status === 200);
// Create rules
const rule1 = await req(MONITOR_PORT, 'POST', '/api/monitor/rules', {
id: 'error-check', name: 'Error & Fatal Detection', enabled: true,
sources: ['sample-logs/app.log'],
patterns: ['\\bERROR\\b', '\\bFATAL\\b'],
patternLogic: 'any', timeRange: { type: 'all' },
notifications: [], maxLinesInMessage: 10, cooldownMs: 5000,
schedule: { mode: 'interval' },
});
ok('Create rule: Error Detection', rule1.data.ok);
const rule2 = await req(MONITOR_PORT, 'POST', '/api/monitor/rules', {
id: 'security-audit', name: 'Security: Root Access', enabled: true,
sources: ['sample-logs/security-audit.log'],
patterns: ['root.*login', 'GRANT.*PRIVILEGES', 'DROP TABLE'],
patternLogic: 'any', timeRange: { type: 'all' },
notifications: [{ type: 'telegram', botToken: '8116790669:AAE_llOFDFSwDhPx40vZMBa6dGv3Xxa9ZiQ', chatId: '8599028500' }],
maxLinesInMessage: 5, cooldownMs: 5000,
schedule: { mode: 'daily', value: '07:00' },
});
ok('Create rule: Security Audit (Telegram)', rule2.data.ok);
const rule3 = await req(MONITOR_PORT, 'POST', '/api/monitor/rules', {
id: 'slow-queries', name: 'Slow Query Detection', enabled: true,
sources: ['sample-logs/app.log'],
patterns: ['[Ss]low.*\\d{4,}ms', 'timeout'],
patternLogic: 'any', timeRange: { type: 'all' },
notifications: [], maxLinesInMessage: 15, cooldownMs: 5000,
schedule: { mode: 'hourly', value: '1' },
});
ok('Create rule: Slow Queries', rule3.data.ok);
// List rules
const rules = await req(MONITOR_PORT, 'GET', '/api/monitor/rules');
ok('3 rules created', rules.data.length === 3);
// Trigger each
const t1 = await req(MONITOR_PORT, 'POST', '/api/monitor/trigger/error-check', {});
ok(`Error Detection: ${t1.data.totalMatches || 0} matches`, (t1.data.totalMatches || 0) > 0);
const t2 = await req(MONITOR_PORT, 'POST', '/api/monitor/trigger/security-audit', {});
ok(`Security Audit: ${t2.data.totalMatches || 0} matches (Telegram sent)`, (t2.data.totalMatches || 0) > 0);
const t3 = await req(MONITOR_PORT, 'POST', '/api/monitor/trigger/slow-queries', {});
ok(`Slow Queries: ${t3.data.totalMatches || 0} matches`, (t3.data.totalMatches || 0) >= 0);
// Results
const results = await req(MONITOR_PORT, 'GET', '/api/monitor/results');
ok('Alert results stored', results.data.length >= 2);
// Test notification
const notif = await req(MONITOR_PORT, 'POST', '/api/monitor/test-notification', {
type: 'telegram', botToken: '8116790669:AAE_llOFDFSwDhPx40vZMBa6dGv3Xxa9ZiQ', chatId: '8599028500',
});
ok('Telegram test notification', notif.data.ok);
// Update rule
const upd = await req(MONITOR_PORT, 'PUT', '/api/monitor/rule/error-check', { name: 'Error Detection (updated)' });
ok('Update rule', upd.data.ok);
// Delete rule
const delr = await req(MONITOR_PORT, 'DELETE', '/api/monitor/rule/slow-queries');
ok('Delete rule', delr.data.ok);
// ═══════════════════════════════════════
// RESULTS
// ═══════════════════════════════════════
console.log('\n ══════════════════════════════════════════════');
console.log(` Results: ${pass} passed, ${fail} failed`);
console.log(' ══════════════════════════════════════════════');
console.log('\n Services running:');
console.log(' Collector → http://localhost:8081');
console.log(' Viewer → http://localhost:9090');
console.log(' Monitor → http://localhost:8082');
// ═══════════════════════════════════════
// START DUMMY LOG SENDER
// ═══════════════════════════════════════
console.log('\n Starting dummy log sender...\n');
const dummyLevels = ['DEBUG', 'INFO', 'INFO', 'INFO', 'WARN', 'ERROR', 'FATAL'];
const dummyComps = ['auth', 'db', 'http', 'cache', 'queue', 'api', 'scheduler'];
const dummyStreams = ['web-app', 'auth-service', 'payment-api', 'worker-1'];
const msgs = {
DEBUG: ['Cache hit key:user:42', 'Query 12ms', 'GC pause 8ms', 'Pool: active=3 idle=7'],
INFO: ['GET /api/users 200 (45ms)', 'User login: alice@corp.com', 'Job done: sync-data', 'Health OK', 'Payment processed $29.99'],
WARN: ['Slow query: 2340ms', 'Memory 82%', 'Rate limit 90%', 'Retry 2/3', 'Cert expires 7d'],
ERROR: ['Connection timeout', 'NullPointerException', '500 POST /api/orders', 'Disk write failed', 'Payment declined'],
FATAL: ['DB connection lost!', 'Out of memory!', 'Process crash'],
};
function rnd(arr) { return arr[Math.floor(Math.random() * arr.length)]; }
let sent = 0;
setInterval(() => {
const stream = rnd(dummyStreams);
const level = rnd(dummyLevels);
const comp = rnd(dummyComps);
const msg = rnd(msgs[level] || msgs.INFO);
const line = `${new Date().toISOString()} ${level.padEnd(5)} [${comp}] ${msg}`;
const payload = line;
const r = http.request({
hostname: 'localhost', port: COLLECTOR_PORT,
path: `/collect/${stream}`, method: 'POST',
headers: { 'Content-Type': 'text/plain' },
});
r.on('error', () => {});
r.write(payload);
r.end();
sent++;
if (sent % 20 === 0) process.stdout.write(`\r 📡 Sent ${sent} logs to collector (${dummyStreams.length} streams)`);
}, 300);
// Keep running
console.log(' Sending logs every 300ms to 4 streams. Press Ctrl+C to stop.\n');
}
main().catch(e => { console.error('Fatal:', e.message); process.exit(1); });