import client from 'prom-client'; // Singleton pattern pour éviter les duplications const globalForProm = globalThis as unknown as { promRegistry: client.Registry | undefined; httpRequestsTotal: client.Counter | undefined; httpRequestDuration: client.Histogram | undefined; httpErrorsTotal: client.Counter | undefined; metricsInitialized: boolean | undefined; }; // Registry singleton export function getRegistry(): client.Registry { if (!globalForProm.promRegistry) { globalForProm.promRegistry = new client.Registry(); } return globalForProm.promRegistry; } // Initialiser les métriques une seule fois function ensureMetricsInitialized() { if (globalForProm.metricsInitialized) { return; } const register = getRegistry(); // Métriques par défaut (CPU, mémoire, event loop, etc.) client.collectDefaultMetrics({ register }); // Compteur de requêtes HTTP totales globalForProm.httpRequestsTotal = new client.Counter({ name: 'http_requests_total', help: 'Total number of HTTP requests', labelNames: ['method', 'path', 'status_code'], registers: [register], }); // Histogramme de durée des requêtes globalForProm.httpRequestDuration = new client.Histogram({ name: 'http_request_duration_seconds', help: 'Duration of HTTP requests in seconds', labelNames: ['method', 'path', 'status_code'], buckets: [0.01, 0.05, 0.1, 0.3, 0.5, 0.7, 1, 2, 5, 10], registers: [register], }); // Compteur d'erreurs HTTP globalForProm.httpErrorsTotal = new client.Counter({ name: 'http_errors_total', help: 'Total number of HTTP errors (4xx and 5xx)', labelNames: ['method', 'path', 'status_code'], registers: [register], }); globalForProm.metricsInitialized = true; } // Getters pour les métriques export function getHttpRequestsTotal(): client.Counter { ensureMetricsInitialized(); return globalForProm.httpRequestsTotal!; } export function getHttpRequestDuration(): client.Histogram { ensureMetricsInitialized(); return globalForProm.httpRequestDuration!; } export function getHttpErrorsTotal(): client.Counter { ensureMetricsInitialized(); return globalForProm.httpErrorsTotal!; } // Fonction helper pour normaliser les paths (éviter haute cardinalité) export function normalizePath(path: string): string { return path // Remplacer les UUIDs .replace(/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/gi, ':id') // Remplacer les IDs numériques .replace(/\/\d+/g, '/:id') // Remplacer les codes de ticket .replace(/\/[A-Z0-9]{6,}/g, '/:code'); } // Fonction pour enregistrer une requête HTTP export function recordHttpRequest( method: string, path: string, statusCode: number, durationMs: number ): void { const normalizedPath = normalizePath(path); const labels = { method, path: normalizedPath, status_code: statusCode.toString(), }; getHttpRequestsTotal().inc(labels); getHttpRequestDuration().observe(labels, durationMs / 1000); if (statusCode >= 400) { getHttpErrorsTotal().inc(labels); } } // Initialiser les métriques au chargement du module ensureMetricsInitialized();