openclaw/src/gateway/metrics-http.ts
lluviaoscuradeldoce-design bb97993755 feat(observability): add Prometheus metrics and OpenTelemetry tracing
- Add Prometheus metrics endpoints (GET /metrics)
- Add health checks (GET /health, GET /ready)
- Add OpenTelemetry SDK with automatic HTTP instrumentation
- Expose update metrics for messages, AI calls, and errors
2026-01-29 16:32:05 -05:00

98 lines
2.9 KiB
TypeScript

/**
* HTTP handler for Prometheus metrics endpoint.
*
* Exposes metrics at GET /metrics in Prometheus text format.
*/
import type { IncomingMessage, ServerResponse } from 'node:http';
import { getMetricsText, getMetricsContentType } from '../infra/metrics.js';
/**
* Handle GET /metrics requests for Prometheus scraping.
*/
export async function handleMetricsRequest(
_req: IncomingMessage,
res: ServerResponse,
): Promise<void> {
try {
const metricsText = await getMetricsText();
res.writeHead(200, {
'Content-Type': getMetricsContentType(),
'Cache-Control': 'no-cache, no-store, must-revalidate',
});
res.end(metricsText);
} catch (error) {
res.writeHead(500, { 'Content-Type': 'text/plain' });
res.end('Error collecting metrics');
}
}
/**
* Handle GET /health liveness probe.
* Always returns 200 if the server is responding.
*/
export function handleHealthRequest(
_req: IncomingMessage,
res: ServerResponse,
): void {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({
status: 'ok',
timestamp: new Date().toISOString(),
}));
}
/**
* Readiness check function type.
* Returns true if the service is ready to accept traffic.
*/
export type ReadinessCheck = () => Promise<boolean> | boolean;
/**
* Create a readiness handler with custom checks.
*
* @param checks - Array of check functions that must all pass
*/
export function createReadinessHandler(
checks: ReadinessCheck[] = [],
): (req: IncomingMessage, res: ServerResponse) => Promise<void> {
return async (_req: IncomingMessage, res: ServerResponse): Promise<void> => {
try {
const results = await Promise.all(checks.map((check) => check()));
const isReady = results.every(Boolean);
if (isReady) {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({
status: 'ready',
timestamp: new Date().toISOString(),
}));
} else {
res.writeHead(503, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({
status: 'not_ready',
timestamp: new Date().toISOString(),
}));
}
} catch (error) {
res.writeHead(503, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({
status: 'error',
timestamp: new Date().toISOString(),
}));
}
};
}
/**
* Simple readiness handler that always returns ready.
* Use createReadinessHandler for custom checks.
*/
export async function handleReadyRequest(
req: IncomingMessage,
res: ServerResponse,
): Promise<void> {
const handler = createReadinessHandler([]);
return handler(req, res);
}