Files
webmail/lib/metrics/http.ts
T
Linus Rath 481c4b6030 feat: serve Prometheus metrics and split health into live and ready
- `/api/metrics` in Prometheus text format, off until `METRICS_TOKEN`
  or `METRICS_TOKEN_FILE` is set (404 before, 401 without the bearer
  token): requests per route group with status class and latency,
  sign-in outcomes, calls to Stalwart, WOPI, translation, the plugin
  proxy and OAuth, browser errors, errors today, process memory,
  event-loop lag, uptime and build info. Labels come from fixed lists.
  Each replica serves its own.
- `/api/health/live` answers while the process runs;
  `/api/health/ready` checks the config, the shared store, writable
  data dirs and Stalwart's session resource. `/api/health` and
  `/api/ready` are unchanged.
- the Docker image gets a HEALTHCHECK on the live endpoint
- adds prom-client, and the OpenTelemetry packages the tracing step
  loads on demand
2026-11-01 02:59:40 +01:00

40 lines
1.5 KiB
TypeScript

import diagnostics from 'node:diagnostics_channel';
import type { IncomingMessage, ServerResponse } from 'node:http';
import { metricsEnabled, observeRequest } from './index';
/**
* Counts every request this process serves, through Node's own HTTP
* diagnostics channels: nothing is wrapped or patched, and the proxy's
* matcher doesn't matter. Installed once at startup when the metrics
* endpoint is on (instrumentation.node.ts).
*/
interface HttpState {
installed: boolean;
started: WeakMap<IncomingMessage, number>;
}
const KEY = Symbol.for('bulwark.metrics.http');
function state(): HttpState {
const g = globalThis as typeof globalThis & { [KEY]?: HttpState };
return (g[KEY] ??= { installed: false, started: new WeakMap() });
}
export function installHttpMetrics(basePath = process.env.NEXT_PUBLIC_BASE_PATH || ''): boolean {
const s = state();
if (s.installed || !metricsEnabled()) return false;
s.installed = true;
diagnostics.subscribe('http.server.request.start', (message) => {
const { request } = message as { request: IncomingMessage };
s.started.set(request, performance.now());
});
diagnostics.subscribe('http.server.response.finish', (message) => {
const { request, response } = message as { request: IncomingMessage; response: ServerResponse };
const start = s.started.get(request);
if (start === undefined) return;
s.started.delete(request);
observeRequest(request.url ?? '/', request.method, response.statusCode, (performance.now() - start) / 1000, basePath);
});
return true;
}