From d6365787617f2da56dacf1bc830c61879a43307d Mon Sep 17 00:00:00 2001 From: soufiane Date: Tue, 25 Nov 2025 16:24:33 +0100 Subject: [PATCH] fix: use singleton pattern and force nodejs runtime for metrics route MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add runtime = 'nodejs' to ensure Node.js environment - Add dynamic = 'force-dynamic' to prevent static optimization - Use singleton pattern to avoid duplicate metric registration - Add error logging for debugging 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- app/api/metrics/route.ts | 62 ++++++++++++++++++++++++++++------------ 1 file changed, 44 insertions(+), 18 deletions(-) diff --git a/app/api/metrics/route.ts b/app/api/metrics/route.ts index ed30948..a150f48 100644 --- a/app/api/metrics/route.ts +++ b/app/api/metrics/route.ts @@ -1,30 +1,55 @@ import { NextResponse } from 'next/server'; import client from 'prom-client'; -// Create a Registry to register the metrics -const register = new client.Registry(); +// Force Node.js runtime for this route +export const runtime = 'nodejs'; +export const dynamic = 'force-dynamic'; -// Add default metrics (CPU, memory, etc.) -client.collectDefaultMetrics({ register }); +// Singleton pattern to avoid duplicate metric registration +const globalForProm = globalThis as unknown as { + promRegistry: client.Registry | undefined; + metricsInitialized: boolean | undefined; +}; -// Custom metrics -const httpRequestsTotal = new client.Counter({ - name: 'http_requests_total', - help: 'Total number of HTTP requests', - labelNames: ['method', 'path', 'status'], - registers: [register], -}); +function getRegistry(): client.Registry { + if (!globalForProm.promRegistry) { + globalForProm.promRegistry = new client.Registry(); + } + return globalForProm.promRegistry; +} -const httpRequestDuration = new client.Histogram({ - name: 'http_request_duration_seconds', - help: 'Duration of HTTP requests in seconds', - labelNames: ['method', 'path'], - buckets: [0.1, 0.3, 0.5, 0.7, 1, 3, 5, 7, 10], - registers: [register], -}); +function initializeMetrics(register: client.Registry) { + if (globalForProm.metricsInitialized) { + return; + } + + // Add default metrics (CPU, memory, etc.) + client.collectDefaultMetrics({ register }); + + // Custom metrics + new client.Counter({ + name: 'http_requests_total', + help: 'Total number of HTTP requests', + labelNames: ['method', 'path', 'status'], + registers: [register], + }); + + new client.Histogram({ + name: 'http_request_duration_seconds', + help: 'Duration of HTTP requests in seconds', + labelNames: ['method', 'path'], + buckets: [0.1, 0.3, 0.5, 0.7, 1, 3, 5, 7, 10], + registers: [register], + }); + + globalForProm.metricsInitialized = true; +} export async function GET() { try { + const register = getRegistry(); + initializeMetrics(register); + const metrics = await register.metrics(); return new NextResponse(metrics, { status: 200, @@ -33,6 +58,7 @@ export async function GET() { }, }); } catch (error) { + console.error('Metrics error:', error); return NextResponse.json({ error: 'Failed to get metrics' }, { status: 500 }); } }