From 90c310396a196e49f13cdd65f118fd7014d529cf Mon Sep 17 00:00:00 2001 From: fchinembiri Date: Tue, 28 Jul 2026 10:11:33 +0200 Subject: [PATCH] feat(metrics): expose Prometheus metrics endpoint on /metrics and /api/metrics across all nextgen servers --- apps/nextgen/server/package-lock.json | 38 +++++++++++++++++++++++++++ apps/nextgen/server/package.json | 1 + apps/nextgen/server/src/index.js | 33 +++++++++++++++++++++++ k8s/nextgen/manual-deployments.yaml | 4 +-- 4 files changed, 74 insertions(+), 2 deletions(-) diff --git a/apps/nextgen/server/package-lock.json b/apps/nextgen/server/package-lock.json index bdfc787..8f940e8 100644 --- a/apps/nextgen/server/package-lock.json +++ b/apps/nextgen/server/package-lock.json @@ -15,12 +15,22 @@ "express": "^4.18.2", "jsonwebtoken": "^9.0.2", "multer": "^1.4.5-lts.1", + "prom-client": "^15.1.3", "uuid": "^9.0.1" }, "devDependencies": { "nodemon": "^3.0.3" } }, + "node_modules/@opentelemetry/api": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.1.tgz", + "integrity": "sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q==", + "license": "Apache-2.0", + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/@supabase/auth-js": { "version": "2.107.0", "resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.107.0.tgz", @@ -216,6 +226,12 @@ "file-uri-to-path": "1.0.0" } }, + "node_modules/bintrees": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bintrees/-/bintrees-1.0.2.tgz", + "integrity": "sha512-VOMgTMwjAaUG580SXn3LacVgjurrbMme7ZZNYGSSV7mmtY6QQRh0Eg3pwIcntQ77DErK1L0NxkbetjcoXzVwKw==", + "license": "MIT" + }, "node_modules/bl": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", @@ -1451,6 +1467,19 @@ "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", "license": "MIT" }, + "node_modules/prom-client": { + "version": "15.1.3", + "resolved": "https://registry.npmjs.org/prom-client/-/prom-client-15.1.3.tgz", + "integrity": "sha512-6ZiOBfCywsD4k1BN9IX0uZhF+tJkV8q8llP64G5Hajs4JOeVLPCwpPVcpXy3BwYiUGgyJzsJJQeOIv7+hDSq8g==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api": "^1.4.0", + "tdigest": "^0.1.1" + }, + "engines": { + "node": "^16 || ^18 || >=20" + } + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -1884,6 +1913,15 @@ "node": ">= 6" } }, + "node_modules/tdigest": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/tdigest/-/tdigest-0.1.2.tgz", + "integrity": "sha512-+G0LLgjjo9BZX2MfdvPfH+MKLCrxlXSYec5DaPYP1fe6Iyhf0/fSmJ0bFiZ1F8BT6cGXl2LpltQptzjXKWEkKA==", + "license": "MIT", + "dependencies": { + "bintrees": "1.0.2" + } + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", diff --git a/apps/nextgen/server/package.json b/apps/nextgen/server/package.json index 6bc0d0f..3ac98ef 100644 --- a/apps/nextgen/server/package.json +++ b/apps/nextgen/server/package.json @@ -16,6 +16,7 @@ "express": "^4.18.2", "jsonwebtoken": "^9.0.2", "multer": "^1.4.5-lts.1", + "prom-client": "^15.1.3", "uuid": "^9.0.1" }, "devDependencies": { diff --git a/apps/nextgen/server/src/index.js b/apps/nextgen/server/src/index.js index d9185e9..79178a3 100644 --- a/apps/nextgen/server/src/index.js +++ b/apps/nextgen/server/src/index.js @@ -38,6 +38,39 @@ getSyncEngine(); // Middleware app.use(cors()); + +// Prometheus Metrics Setup +const client = require('prom-client'); +const register = new client.Registry(); +client.collectDefaultMetrics({ register }); + +const httpRequestDurationSeconds = new client.Histogram({ + name: 'http_request_duration_seconds', + help: 'Duration of HTTP requests in seconds', + labelNames: ['method', 'route', 'code'], + buckets: [0.05, 0.1, 0.3, 0.5, 1, 2, 5] +}); +register.registerMetric(httpRequestDurationSeconds); + +app.use((req, res, next) => { + const start = Date.now(); + res.on('finish', () => { + const duration = (Date.now() - start) / 1000; + httpRequestDurationSeconds + .labels(req.method, req.route ? req.route.path : req.path, res.statusCode) + .observe(duration); + }); + next(); +}); + +app.get(['/metrics', '/api/metrics'], async (req, res) => { + try { + res.set('Content-Type', register.contentType); + res.end(await register.metrics()); + } catch (err) { + res.status(500).end(err); + } +}); app.use(express.json()); app.use('/uploads', express.static(path.join(__dirname, '../uploads'))); diff --git a/k8s/nextgen/manual-deployments.yaml b/k8s/nextgen/manual-deployments.yaml index 0013692..d393686 100644 --- a/k8s/nextgen/manual-deployments.yaml +++ b/k8s/nextgen/manual-deployments.yaml @@ -98,7 +98,7 @@ spec: - name: http protocol: TCP port: 80 - targetPort: http + targetPort: 3001 --- apiVersion: networking.k8s.io/v1 kind: Ingress @@ -140,7 +140,7 @@ spec: - name: http protocol: TCP port: 80 - targetPort: http + targetPort: 3001 --- apiVersion: networking.k8s.io/v1 kind: Ingress