feat: add HTTP metrics middleware for Prometheus

- Add src/middleware/metrics.js with http_requests_total, http_errors_total, etc.
- Import and use metricsMiddleware in index.js

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
soufiane 2025-12-06 18:10:21 +01:00
parent bb0aa2fa44
commit 451cd67455
2 changed files with 112 additions and 0 deletions

View File

@ -5,6 +5,7 @@ import helmet from "helmet";
import morgan from "morgan"; import morgan from "morgan";
import client from "prom-client"; import client from "prom-client";
import { pool } from "./db.js"; import { pool } from "./db.js";
import { metricsMiddleware } from "./src/middleware/metrics.js";
dotenv.config(); dotenv.config();
const app = express(); const app = express();
@ -19,6 +20,9 @@ app.use(helmet());
app.use(morgan("tiny")); app.use(morgan("tiny"));
app.use(express.json()); app.use(express.json());
// Middleware de métriques HTTP (doit être avant les routes)
app.use(metricsMiddleware);
app.get("/health", (req, res) => { app.get("/health", (req, res) => {
res.status(200).json({ status: "ok" }); res.status(200).json({ status: "ok" });
}); });

108
src/middleware/metrics.js Normal file
View File

@ -0,0 +1,108 @@
import client from "prom-client";
// Compteur de requêtes HTTP totales
const httpRequestsTotal = new client.Counter({
name: "http_requests_total",
help: "Total number of HTTP requests",
labelNames: ["method", "route", "status_code"],
});
// Histogramme de durée des requêtes
const httpRequestDuration = new client.Histogram({
name: "http_request_duration_seconds",
help: "Duration of HTTP requests in seconds",
labelNames: ["method", "route", "status_code"],
buckets: [0.001, 0.005, 0.015, 0.05, 0.1, 0.2, 0.3, 0.4, 0.5, 1, 2, 5],
});
// Compteur de requêtes en cours
const httpRequestsInProgress = new client.Gauge({
name: "http_requests_in_progress",
help: "Number of HTTP requests currently being processed",
labelNames: ["method"],
});
// Compteur d'erreurs HTTP
const httpErrorsTotal = new client.Counter({
name: "http_errors_total",
help: "Total number of HTTP errors (4xx and 5xx)",
labelNames: ["method", "route", "status_code"],
});
// Taille des réponses
const httpResponseSize = new client.Histogram({
name: "http_response_size_bytes",
help: "Size of HTTP responses in bytes",
labelNames: ["method", "route"],
buckets: [100, 500, 1000, 5000, 10000, 50000, 100000, 500000],
});
// Fonction pour normaliser les routes (éviter la cardinalité élevée)
const normalizeRoute = (req) => {
// Si la route est définie par Express, l'utiliser
if (req.route && req.route.path) {
return req.baseUrl + req.route.path;
}
// Sinon, normaliser le path en remplaçant les IDs par des placeholders
let path = req.path;
// Remplacer les UUIDs
path = path.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 nombres (IDs numériques)
path = path.replace(/\/\d+/g, "/:id");
// Remplacer les codes de ticket (format spécifique)
path = path.replace(/\/[A-Z0-9]{6,}/g, "/:code");
return path;
};
// Middleware de métriques
export const metricsMiddleware = (req, res, next) => {
// Ignorer les endpoints de métriques et health check
if (req.path === "/metrics" || req.path === "/health") {
return next();
}
const startTime = Date.now();
// Incrémenter les requêtes en cours
httpRequestsInProgress.inc({ method: req.method });
// Capturer la fin de la réponse
res.on("finish", () => {
const duration = (Date.now() - startTime) / 1000; // En secondes
const route = normalizeRoute(req);
const statusCode = res.statusCode.toString();
const labels = {
method: req.method,
route: route,
status_code: statusCode,
};
// Enregistrer les métriques
httpRequestsTotal.inc(labels);
httpRequestDuration.observe(labels, duration);
httpRequestsInProgress.dec({ method: req.method });
// Compter les erreurs
if (res.statusCode >= 400) {
httpErrorsTotal.inc(labels);
}
// Taille de la réponse (si disponible)
const contentLength = res.get("Content-Length");
if (contentLength) {
httpResponseSize.observe(
{ method: req.method, route: route },
parseInt(contentLength, 10)
);
}
});
next();
};
export default metricsMiddleware;