diff --git a/package-lock.json b/package-lock.json
index 2b96765d..659cd6a6 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -47,6 +47,7 @@
"pg-query-stream": "^4.15.0",
"picocolors": "^1.1.1",
"probe-image-size": "^7.3.0",
+ "prom-client": "^15.1.3",
"reflect-metadata": "^0.2.2",
"tslib": "^2.8.1",
"typeorm": "^0.3.30",
@@ -954,6 +955,15 @@
"url": "https://paulmillr.com/funding/"
}
},
+ "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/@peculiar/asn1-schema": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/@peculiar/asn1-schema/-/asn1-schema-2.7.0.tgz",
@@ -2325,6 +2335,12 @@
"integrity": "sha512-nbE1WxOTTrUWIfsfZ4aHGYu5DOuNkbxGokjV6Z2kxfJK3uaAb8zNK1muzOeipoLHZjInT4Br88BHpzevc681xA==",
"license": "CC0-1.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/bmp-ts": {
"version": "1.0.9",
"resolved": "https://registry.npmjs.org/bmp-ts/-/bmp-ts-1.0.9.tgz",
@@ -5876,6 +5892,19 @@
"stream-parser": "~0.3.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/proxy-addr": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
@@ -6551,6 +6580,15 @@
"node": ">=8"
}
},
+ "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/thirty-two": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/thirty-two/-/thirty-two-1.0.2.tgz",
diff --git a/package.json b/package.json
index dc3fe06a..f20e31d4 100644
--- a/package.json
+++ b/package.json
@@ -115,6 +115,7 @@
"pg-query-stream": "^4.15.0",
"picocolors": "^1.1.1",
"probe-image-size": "^7.3.0",
+ "prom-client": "^15.1.3",
"reflect-metadata": "^0.2.2",
"tslib": "^2.8.1",
"typeorm": "^0.3.30",
diff --git a/src/api/Server.ts b/src/api/Server.ts
index f9f1c708..a1edb986 100644
--- a/src/api/Server.ts
+++ b/src/api/Server.ts
@@ -53,7 +53,6 @@ export class SpacebarServer extends Server {
async start() {
await Monitoring.init();
- Monitoring.attach(this.app);
await initDatabase();
await Config.init();
await initEvent();
diff --git a/src/cdn/Server.ts b/src/cdn/Server.ts
index 8964161d..9327de83 100644
--- a/src/cdn/Server.ts
+++ b/src/cdn/Server.ts
@@ -37,7 +37,6 @@ export class CDNServer extends Server {
async start() {
await Monitoring.init();
- Monitoring.attach(this.app);
await initDatabase();
await Config.init();
diff --git a/src/util/monitoring/Monitoring.ts b/src/util/monitoring/Monitoring.ts
new file mode 100644
index 00000000..3d6d4ed4
--- /dev/null
+++ b/src/util/monitoring/Monitoring.ts
@@ -0,0 +1,38 @@
+/*
+ Spacebar: A FOSS re-implementation and extension of the Discord.com backend.
+ Copyright (C) 2026 Spacebar and Spacebar Contributors
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published
+ by the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+*/
+
+import * as client from "prom-client";
+import { Router } from "express";
+
+export class Monitoring {
+ static isInitialised = false;
+ public static async init() {
+ if (Monitoring.isInitialised) return;
+ console.log("[Monitoring] Initialising prometheus metrics");
+ client.collectDefaultMetrics();
+ Monitoring.isInitialised = true;
+ }
+
+ public static attach(router: Router) {
+ router.get("/metrics", async (req, res) => {
+ res.setHeader("Content-Type", client.register.contentType);
+ const metrics = await client.register.metrics();
+ res.send(metrics);
+ });
+ }
+}
|