From f60aad2864be9370bbffbf9a2860366f8edcdd2b Mon Sep 17 00:00:00 2001 From: Thomas Richter Date: Tue, 3 Feb 2026 22:05:16 +0100 Subject: [PATCH] feat(08-01): add Prometheus /metrics endpoint with prom-client - Install prom-client library for Prometheus metrics - Create src/lib/server/metrics.ts with default Node.js process metrics - Add /metrics endpoint that returns Prometheus-format text - Exposes CPU, memory, heap, event loop metrics Co-Authored-By: Claude Opus 4.5 --- package-lock.json | 38 +++++++++++++++++++++++++++++++++++ package.json | 1 + src/lib/server/metrics.ts | 7 +++++++ src/routes/metrics/+server.ts | 22 ++++++++++++++++++++ 4 files changed, 68 insertions(+) create mode 100644 src/lib/server/metrics.ts create mode 100644 src/routes/metrics/+server.ts diff --git a/package-lock.json b/package-lock.json index 01cb826..f2a56d3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "better-sqlite3": "^12.6.2", "drizzle-orm": "^0.45.1", "nanoid": "^5.1.6", + "prom-client": "^15.1.3", "sharp": "^0.34.5", "svelecte": "^5.3.0", "svelte-gestures": "^5.2.2", @@ -1626,6 +1627,15 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "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/@playwright/test": { "version": "1.58.1", "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.58.1.tgz", @@ -2638,6 +2648,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", @@ -5059,6 +5075,19 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, + "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/pump": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", @@ -5623,6 +5652,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/tinyglobby": { "version": "0.2.15", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", diff --git a/package.json b/package.json index 6d6f663..656f773 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,7 @@ "better-sqlite3": "^12.6.2", "drizzle-orm": "^0.45.1", "nanoid": "^5.1.6", + "prom-client": "^15.1.3", "sharp": "^0.34.5", "svelecte": "^5.3.0", "svelte-gestures": "^5.2.2", diff --git a/src/lib/server/metrics.ts b/src/lib/server/metrics.ts new file mode 100644 index 0000000..4315544 --- /dev/null +++ b/src/lib/server/metrics.ts @@ -0,0 +1,7 @@ +import { Registry, collectDefaultMetrics } from 'prom-client'; + +// Create a custom registry for metrics +export const registry = new Registry(); + +// Collect default Node.js process metrics (CPU, memory, event loop, etc.) +collectDefaultMetrics({ register: registry }); diff --git a/src/routes/metrics/+server.ts b/src/routes/metrics/+server.ts new file mode 100644 index 0000000..450cf0d --- /dev/null +++ b/src/routes/metrics/+server.ts @@ -0,0 +1,22 @@ +import type { RequestHandler } from './$types'; +import { registry } from '$lib/server/metrics'; + +export const GET: RequestHandler = async () => { + try { + const metrics = await registry.metrics(); + + return new Response(metrics, { + status: 200, + headers: { + 'Content-Type': registry.contentType + } + }); + } catch (error) { + console.error('Metrics collection failed:', error); + + return new Response('Metrics unavailable', { + status: 500, + headers: { 'Content-Type': 'text/plain' } + }); + } +};