- Add /api/metrics endpoint for Prometheus scraping - Add /api/track endpoint for metrics tracking - Add metrics library with HTTP request counters - Add middleware for request tracking - Add instrumentation for Next.js 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
110 lines
3.2 KiB
TypeScript
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();
|