Files
webmail/app/api/admin/monitoring/route.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

33 lines
1.4 KiB
TypeScript

import { NextRequest, NextResponse } from 'next/server';
import { requireAdminAuth } from '@/lib/admin/session';
import { logger } from '@/lib/logger';
import { readErrors, summarizeDay, utcDay } from '@/lib/error-reporting/aggregate';
import { shouldCollectErrors } from '@/lib/error-reporting/settings';
import { storedDataUnavailableResponse } from '@/lib/stored-data-errors';
import { metricsEnabled } from '@/lib/metrics';
export const dynamic = 'force-dynamic';
/**
* GET /api/admin/monitoring: what System > Monitoring shows besides the
* config keys (those come from /api/admin/config): whether errors are being
* counted, today's counts and whether the metrics endpoint is on.
*/
export async function GET(request: NextRequest) {
try {
const auth = await requireAdminAuth(request);
if ('error' in auth) return auth.error;
const collecting = await shouldCollectErrors();
const today = utcDay();
const errorsToday = summarizeDay(await readErrors(), today);
const metrics = { enabled: metricsEnabled() };
return NextResponse.json({ collecting, day: today, errorsToday, metrics }, { headers: { 'Cache-Control': 'no-store' } });
} catch (err) {
logger.error('monitoring GET error', { error: err instanceof Error ? err.message : 'unknown' });
const unavailable = storedDataUnavailableResponse(err);
if (unavailable) return unavailable;
return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
}
}