/** * Monitoring & Health Check — aggregates health from all services. * * Standalone script that polls each service's /health endpoint and * reports combined status. Can be run as a cron job, GitHub Action, * or standalone HTTP endpoint. * * Usage: * npx tsx services/monitoring/health-check.ts # one-shot check * npx tsx services/monitoring/health-check.ts --serve # HTTP server on :4004 * * Environment: * BACKEND_URL (default: http://localhost:8000) * GROWTH_SERVICE_URL (default: http://localhost:4001) * BILLING_SERVICE_URL (default: http://localhost:4002) * PLATFORM_SERVICE_URL (default: http://localhost:4003) * ADMIN_DASHBOARD_URL (default: http://localhost:3001) * USER_DASHBOARD_URL (default: http://localhost:3002) */ export {}; interface ServiceCheck { name: string; url: string; status: 'healthy' | 'unhealthy' | 'unreachable'; responseTimeMs: number; details?: Record; error?: string; } interface HealthReport { overall: 'healthy' | 'degraded' | 'down'; timestamp: string; services: ServiceCheck[]; summary: { healthy: number; unhealthy: number; unreachable: number; total: number }; } const SERVICES = [ { name: 'Backend API', url: process.env.BACKEND_URL || 'http://localhost:8000', path: '/health' }, { name: 'Growth Service', url: process.env.GROWTH_SERVICE_URL || 'http://localhost:4001', path: '/health', }, { name: 'Billing Service', url: process.env.BILLING_SERVICE_URL || 'http://localhost:4002', path: '/health', }, { name: 'Platform Service', url: process.env.PLATFORM_SERVICE_URL || 'http://localhost:4003', path: '/health', }, { name: 'Admin Dashboard', url: process.env.ADMIN_DASHBOARD_URL || 'http://localhost:3001', path: '/api/health', }, { name: 'User Dashboard', url: process.env.USER_DASHBOARD_URL || 'http://localhost:3002', path: '/api/health', }, ]; async function checkService(svc: { name: string; url: string; path: string; }): Promise { const fullUrl = `${svc.url}${svc.path}`; const start = performance.now(); try { const res = await fetch(fullUrl, { signal: AbortSignal.timeout(5_000) }); const elapsed = Math.round(performance.now() - start); if (res.ok) { let details: Record | undefined; try { details = (await res.json()) as Record; } catch { /* ignore */ } return { name: svc.name, url: svc.url, status: 'healthy', responseTimeMs: elapsed, details }; } return { name: svc.name, url: svc.url, status: 'unhealthy', responseTimeMs: elapsed, error: `HTTP ${res.status}`, }; } catch (err) { const elapsed = Math.round(performance.now() - start); return { name: svc.name, url: svc.url, status: 'unreachable', responseTimeMs: elapsed, error: String(err), }; } } async function generateReport(): Promise { const checks = await Promise.all(SERVICES.map(checkService)); const healthy = checks.filter(c => c.status === 'healthy').length; const unhealthy = checks.filter(c => c.status === 'unhealthy').length; const unreachable = checks.filter(c => c.status === 'unreachable').length; let overall: HealthReport['overall'] = 'healthy'; if (unreachable === checks.length) overall = 'down'; else if (unhealthy > 0 || unreachable > 0) overall = 'degraded'; return { overall, timestamp: new Date().toISOString(), services: checks, summary: { healthy, unhealthy, unreachable, total: checks.length }, }; } // ── CLI / HTTP server mode ── const args = process.argv.slice(2); if (args.includes('--serve')) { // Run as HTTP server for continuous monitoring const { createServer } = await import('http'); const PORT = Number(process.env.MONITOR_PORT || 4004); const server = createServer(async (_req, res) => { const report = await generateReport(); res.writeHead(report.overall === 'healthy' ? 200 : 503, { 'Content-Type': 'application/json' }); res.end(JSON.stringify(report, null, 2)); }); server.listen(PORT, () => { console.log(`🩺 Monitoring dashboard running on http://localhost:${PORT}`); console.log(` Checking ${SERVICES.length} services every request`); }); } else { // One-shot check const report = await generateReport(); const icon = { healthy: '✅', degraded: '⚠️', down: '❌' }; console.log(`\n${icon[report.overall]} Overall: ${report.overall.toUpperCase()}\n`); for (const svc of report.services) { const sIcon = { healthy: '✅', unhealthy: '⚠️', unreachable: '❌' }; console.log( ` ${sIcon[svc.status]} ${svc.name.padEnd(20)} ${svc.responseTimeMs}ms${svc.error ? ` — ${svc.error}` : ''}` ); } console.log(`\nHealthy: ${report.summary.healthy}/${report.summary.total}`); process.exit(report.overall === 'healthy' ? 0 : 1); }