From 47e325f7f3de8f335677e9afe058dbb3da4cfd6f Mon Sep 17 00:00:00 2001 From: telemetry Date: Tue, 6 Oct 2026 03:15:22 -0400 Subject: [PATCH] feat(metrics): record /api/healthz requests in the API metrics registry Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: telemetry --- changelog.d/added-healthz-request-metrics.md | 1 + src/__tests__/healthz-route.test.ts | 15 +++++++++++++++ src/app/api/healthz/route.ts | 13 +++++++++++++ src/lib/metrics.ts | 2 +- 4 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 changelog.d/added-healthz-request-metrics.md diff --git a/changelog.d/added-healthz-request-metrics.md b/changelog.d/added-healthz-request-metrics.md new file mode 100644 index 0000000..b6ff304 --- /dev/null +++ b/changelog.d/added-healthz-request-metrics.md @@ -0,0 +1 @@ +Record `/api/healthz` request count, status class, and duration in the existing bounded `/api/metrics` registry so readiness failures can be alerted on. diff --git a/src/__tests__/healthz-route.test.ts b/src/__tests__/healthz-route.test.ts index a3ca82f..d3d39ad 100644 --- a/src/__tests__/healthz-route.test.ts +++ b/src/__tests__/healthz-route.test.ts @@ -3,6 +3,7 @@ import fs from "fs"; import { docsContentPath } from "@/app/docs/page-map"; import { GET } from "@/app/api/healthz/route"; import { logger } from "@/lib/logger"; +import { metricsRegistry } from "@/lib/metrics"; /** * Coverage for src/app/api/healthz/route.ts — the readiness probe that @@ -154,4 +155,18 @@ describe("/api/healthz route", () => { error: "EACCES: permission denied", }); }); + + it("records healthz requests by status class in the metrics registry", async () => { + statSyncSpy.mockImplementationOnce(() => { + throw new Error("boom"); + }); + + const res = await GET(); + + expect(res.status).toBe(503); + const body = await metricsRegistry.metrics(); + expect(body).toContain( + 'docs_api_requests_total{route="healthz",method="GET",status_class="5xx"}' + ); + }); }); diff --git a/src/app/api/healthz/route.ts b/src/app/api/healthz/route.ts index affbd02..9df14f7 100644 --- a/src/app/api/healthz/route.ts +++ b/src/app/api/healthz/route.ts @@ -2,6 +2,7 @@ import { NextResponse } from "next/server"; import fs from "fs"; import { docsContentPath } from "../../docs/page-map"; import { logger } from "@/lib/logger"; +import { recordApiRequest } from "@/lib/metrics"; // Readiness check for the docs app. // @@ -15,6 +16,18 @@ import { logger } from "@/lib/logger"; // orchestrator or deploy pipeline can detect that condition before routing // traffic to this instance. export async function GET() { + const startedAt = performance.now(); + const response = check(); + recordApiRequest( + "healthz", + "GET", + response.status, + performance.now() - startedAt + ); + return response; +} + +function check(): NextResponse { try { const stat = fs.statSync(docsContentPath); if (!stat.isDirectory()) { diff --git a/src/lib/metrics.ts b/src/lib/metrics.ts index 3af7c12..37fab63 100644 --- a/src/lib/metrics.ts +++ b/src/lib/metrics.ts @@ -15,7 +15,7 @@ */ import { Counter, Histogram, Registry } from "prom-client"; -export const ApiRoutes = ["search", "docs-image"] as const; +export const ApiRoutes = ["search", "docs-image", "healthz"] as const; export type ApiRoute = (typeof ApiRoutes)[number]; const HTTP_METHODS = new Set([