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 <noreply@anthropic.com>
This commit is contained in:
Thomas Richter
2026-02-03 22:05:16 +01:00
parent 8c3dc137ca
commit f60aad2864
4 changed files with 68 additions and 0 deletions

38
package-lock.json generated
View File

@@ -12,6 +12,7 @@
"better-sqlite3": "^12.6.2", "better-sqlite3": "^12.6.2",
"drizzle-orm": "^0.45.1", "drizzle-orm": "^0.45.1",
"nanoid": "^5.1.6", "nanoid": "^5.1.6",
"prom-client": "^15.1.3",
"sharp": "^0.34.5", "sharp": "^0.34.5",
"svelecte": "^5.3.0", "svelecte": "^5.3.0",
"svelte-gestures": "^5.2.2", "svelte-gestures": "^5.2.2",
@@ -1626,6 +1627,15 @@
"@jridgewell/sourcemap-codec": "^1.4.14" "@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": { "node_modules/@playwright/test": {
"version": "1.58.1", "version": "1.58.1",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.58.1.tgz", "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.58.1.tgz",
@@ -2638,6 +2648,12 @@
"file-uri-to-path": "1.0.0" "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": { "node_modules/bl": {
"version": "4.1.0", "version": "4.1.0",
"resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz",
@@ -5059,6 +5075,19 @@
"url": "https://github.com/prettier/prettier?sponsor=1" "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": { "node_modules/pump": {
"version": "3.0.3", "version": "3.0.3",
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz",
@@ -5623,6 +5652,15 @@
"node": ">=6" "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": { "node_modules/tinyglobby": {
"version": "0.2.15", "version": "0.2.15",
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",

View File

@@ -39,6 +39,7 @@
"better-sqlite3": "^12.6.2", "better-sqlite3": "^12.6.2",
"drizzle-orm": "^0.45.1", "drizzle-orm": "^0.45.1",
"nanoid": "^5.1.6", "nanoid": "^5.1.6",
"prom-client": "^15.1.3",
"sharp": "^0.34.5", "sharp": "^0.34.5",
"svelecte": "^5.3.0", "svelecte": "^5.3.0",
"svelte-gestures": "^5.2.2", "svelte-gestures": "^5.2.2",

View File

@@ -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 });

View File

@@ -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' }
});
}
};