From ba180d7dde7dca81499104688c392315a7f4f032 Mon Sep 17 00:00:00 2001 From: Daniel Date: Fri, 8 May 2026 20:22:14 +0200 Subject: [PATCH] fix metrics route normalization --- src/utils/metrics.js | 7 ++++--- test/metrics.test.js | 19 +++++++++++++++++++ 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/src/utils/metrics.js b/src/utils/metrics.js index 0a3e40c..392c42d 100644 --- a/src/utils/metrics.js +++ b/src/utils/metrics.js @@ -29,11 +29,12 @@ const httpRequestsInProgress = new client.Gauge({ registers: [register] }); -function normalizeRoute(req) { +function normalizeRoute(req, statusCode) { var routePath = req.route && req.route.path; var baseUrl = req.baseUrl || ''; if (Array.isArray(routePath)) return baseUrl + routePath[0]; if (routePath) return baseUrl + routePath; + if (statusCode === 404) return 'unmatched'; var path = req.path || req.url || 'unknown'; return path @@ -45,13 +46,13 @@ function normalizeRoute(req) { function metricsMiddleware(req, res, next) { if (req.path === '/metrics') return next(); - var route = normalizeRoute(req); + var route = req.route ? normalizeRoute(req) : 'pending'; var method = req.method; var endTimer = httpRequestDuration.startTimer({ method, route }); httpRequestsInProgress.inc({ method, route }); res.on('finish', function() { - var finalRoute = normalizeRoute(req); + var finalRoute = normalizeRoute(req, res.statusCode); var labels = { method, route: finalRoute, diff --git a/test/metrics.test.js b/test/metrics.test.js index c39e563..0c4ddb8 100644 --- a/test/metrics.test.js +++ b/test/metrics.test.js @@ -43,6 +43,25 @@ test('metrics middleware uses the first path for Express array routes', async () assert.doesNotMatch(metrics, /route="\/,\/index\.html"/); }); +test('metrics middleware collapses unmatched 404 routes', async () => { + const req = { + method: 'GET', + path: '/wp-content/plugins/example.php', + url: '/wp-content/plugins/example.php?probe=1', + baseUrl: '', + route: null + }; + const res = new EventEmitter(); + res.statusCode = 404; + + await new Promise((resolve) => metricsMiddleware(req, res, resolve)); + res.emit('finish'); + + const metrics = await register.metrics(); + assert.match(metrics, /route="unmatched"/); + assert.doesNotMatch(metrics, /wp-content\/plugins\/example\.php/); +}); + test('metrics handler exposes prometheus content type', async () => { const headers = {}; const res = {