From 86d90c8f3edc5983010065e20d621f4ffb9e80ec Mon Sep 17 00:00:00 2001 From: soufiane Date: Tue, 25 Nov 2025 15:17:56 +0100 Subject: [PATCH] feat: add Prometheus metrics endpoint MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- app/api/metrics/route.ts | 38 ++++++++++++++++++++++++++++++++++++++ package-lock.json | 38 ++++++++++++++++++++++++++++++++++++++ package.json | 1 + 3 files changed, 77 insertions(+) create mode 100644 app/api/metrics/route.ts diff --git a/app/api/metrics/route.ts b/app/api/metrics/route.ts new file mode 100644 index 0000000..ed30948 --- /dev/null +++ b/app/api/metrics/route.ts @@ -0,0 +1,38 @@ +import { NextResponse } from 'next/server'; +import client from 'prom-client'; + +// Create a Registry to register the metrics +const register = new client.Registry(); + +// Add default metrics (CPU, memory, etc.) +client.collectDefaultMetrics({ register }); + +// Custom metrics +const httpRequestsTotal = new client.Counter({ + name: 'http_requests_total', + help: 'Total number of HTTP requests', + labelNames: ['method', 'path', 'status'], + registers: [register], +}); + +const httpRequestDuration = new client.Histogram({ + name: 'http_request_duration_seconds', + help: 'Duration of HTTP requests in seconds', + labelNames: ['method', 'path'], + buckets: [0.1, 0.3, 0.5, 0.7, 1, 3, 5, 7, 10], + registers: [register], +}); + +export async function GET() { + try { + const metrics = await register.metrics(); + return new NextResponse(metrics, { + status: 200, + headers: { + 'Content-Type': register.contentType, + }, + }); + } catch (error) { + return NextResponse.json({ error: 'Failed to get metrics' }, { status: 500 }); + } +} diff --git a/package-lock.json b/package-lock.json index 76c6b41..907dda9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,7 @@ "next": "14.2.4", "next-auth": "^4.24.7", "nodemailer": "^7.0.10", + "prom-client": "^15.1.3", "react": "18.2.0", "react-dom": "18.2.0", "react-hook-form": "^7.66.0", @@ -511,6 +512,15 @@ "node": ">=12.4.0" } }, + "node_modules/@opentelemetry/api": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", + "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", + "license": "Apache-2.0", + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/@panva/hkdf": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/@panva/hkdf/-/hkdf-1.2.1.tgz", @@ -1556,6 +1566,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "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/bootstrap": { "version": "5.3.8", "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.8.tgz", @@ -5128,6 +5144,19 @@ "integrity": "sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew==", "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/prop-types": { "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", @@ -6154,6 +6183,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/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", diff --git a/package.json b/package.json index 96c5bd6..4ff0132 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "next": "14.2.4", "next-auth": "^4.24.7", "nodemailer": "^7.0.10", + "prom-client": "^15.1.3", "react": "18.2.0", "react-dom": "18.2.0", "react-hook-form": "^7.66.0",