the-tip-top-frontend/lib/metrics.ts
soufiane 3e36284146 feat: add Prometheus HTTP metrics for frontend
- Add metrics middleware for request tracking
- Add /api/metrics endpoint
- Add /api/track endpoint for async tracking

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-26 13:42:03 +01:00

110 lines
3.2 KiB
TypeScript

import client from 'prom-client';
// Singleton pattern pour éviter les duplications
const globalForProm = globalThis as unknown as {
promRegistry: client.Registry | undefined;
httpRequestsTotal: client.Counter<string> | undefined;
httpRequestDuration: client.Histogram<string> | undefined;
httpErrorsTotal: client.Counter<string> | 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<string> {
ensureMetricsInitialized();
return globalForProm.httpRequestsTotal!;
}
export function getHttpRequestDuration(): client.Histogram<string> {
ensureMetricsInitialized();
return globalForProm.httpRequestDuration!;
}
export function getHttpErrorsTotal(): client.Counter<string> {
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();