learning_ai_common_plat/packages/monitoring/src/health.ts

106 lines
2.9 KiB
TypeScript

export interface ServiceTarget {
name: string;
url: string;
path: string;
}
export interface ServiceCheck {
name: string;
url: string;
status: 'healthy' | 'unhealthy' | 'unreachable';
responseTimeMs: number;
details?: Record<string, unknown>;
error?: string;
}
export interface HealthReport {
overall: 'healthy' | 'degraded' | 'down';
timestamp: string;
services: ServiceCheck[];
summary: { healthy: number; unhealthy: number; unreachable: number; total: number };
}
/**
* Default service targets (LysnrAI local stack).
*/
export const DEFAULT_SERVICES: ServiceTarget[] = [
{ name: 'Backend API', url: process.env.BACKEND_URL || 'http://localhost:8000', 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',
},
];
export async function checkService(
svc: ServiceTarget,
opts?: { timeoutMs?: number }
): Promise<ServiceCheck> {
const fullUrl = `${svc.url}${svc.path}`;
const start = performance.now();
try {
const res = await fetch(fullUrl, { signal: AbortSignal.timeout(opts?.timeoutMs ?? 5_000) });
const elapsed = Math.round(performance.now() - start);
if (res.ok) {
let details: Record<string, unknown> | undefined;
try {
details = (await res.json()) as Record<string, unknown>;
} 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),
};
}
}
export async function generateHealthReport(
services: ServiceTarget[] = DEFAULT_SERVICES,
opts?: { timeoutMs?: number }
): Promise<HealthReport> {
const checks = await Promise.all(services.map(svc => checkService(svc, opts)));
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 },
};
}