From 451cd6745599b20146b0e4904500923360c10655 Mon Sep 17 00:00:00 2001 From: soufiane Date: Sat, 6 Dec 2025 18:10:21 +0100 Subject: [PATCH] feat: add HTTP metrics middleware for Prometheus MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- index.js | 4 ++ src/middleware/metrics.js | 108 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 112 insertions(+) create mode 100644 src/middleware/metrics.js diff --git a/index.js b/index.js index 3372f2d4..998786f0 100644 --- a/index.js +++ b/index.js @@ -5,6 +5,7 @@ import helmet from "helmet"; import morgan from "morgan"; import client from "prom-client"; import { pool } from "./db.js"; +import { metricsMiddleware } from "./src/middleware/metrics.js"; dotenv.config(); const app = express(); @@ -19,6 +20,9 @@ app.use(helmet()); app.use(morgan("tiny")); app.use(express.json()); +// Middleware de métriques HTTP (doit être avant les routes) +app.use(metricsMiddleware); + app.get("/health", (req, res) => { res.status(200).json({ status: "ok" }); }); diff --git a/src/middleware/metrics.js b/src/middleware/metrics.js new file mode 100644 index 00000000..f909f541 --- /dev/null +++ b/src/middleware/metrics.js @@ -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;