#!/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); });