Skip to content

Commit 8860921

Browse files
committed
feat: prometheus key to access /metrics
1 parent de287f1 commit 8860921

File tree

4 files changed

+72
-4
lines changed

4 files changed

+72
-4
lines changed

.env.example

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,6 @@ AUTH_CLIENT_ID=b4bc4b9a-7162-44c5-bb50-fe935dce1f5a
99

1010
# Loki Host
1111
# LOKI_HOST=http://localhost:3100
12+
13+
# Prometheus Key
14+
PROMETHEUS_KEY=prometheus

src/app.ts

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ import {
1010
RawReplyDefaultExpression,
1111
RawRequestDefaultExpression,
1212
RawServerDefault,
13+
FastifyReply,
14+
FastifyRequest,
15+
RouteOptions,
1316
} from "fastify";
1417
import fastifyMetrics from "fastify-metrics";
1518
import * as path from "path";
@@ -23,6 +26,7 @@ export type AppOptions = {
2326
// MongoDB URI (Optional)
2427
// mongoUri: string;
2528
lokiHost?: string;
29+
prometheusKey?: string;
2630
} & FastifyServerOptions &
2731
Partial<AutoloadPluginOptions> &
2832
AuthPluginOptions;
@@ -52,6 +56,7 @@ const options: AppOptions = {
5256
authDiscoveryURL: getOption("AUTH_DISCOVERY_URL")!,
5357
authClientID: getOption("AUTH_CLIENT_ID")!,
5458
lokiHost: getOption("LOKI_HOST", false),
59+
prometheusKey: getOption("PROMETHEUS_KEY", false),
5560
authSkip: (() => {
5661
const opt = getOption("AUTH_SKIP", false);
5762
if (opt !== undefined) {
@@ -69,7 +74,7 @@ if (options.lokiHost) {
6974
target: "pino-loki",
7075
options: {
7176
batching: true,
72-
interval: 5,
77+
interval: 5, // Logs are sent every 5 seconds, default.
7378
host: options.lokiHost,
7479
labels: { application: packageJson.name },
7580
},
@@ -102,9 +107,26 @@ const app: FastifyPluginAsync<AppOptions> = async (
102107
});
103108

104109
// Register Metrics
110+
const metricsEndpoint: RouteOptions | string | null = opts.prometheusKey
111+
? {
112+
url: "/metrics",
113+
method: "GET",
114+
handler: async () => {}, // Overridden by fastify-metrics
115+
onRequest: async (request: FastifyRequest, reply: FastifyReply) => {
116+
if (
117+
request.headers.authorization !== `Bearer ${opts.prometheusKey}`
118+
) {
119+
reply.code(401).send("Unauthorized");
120+
return reply;
121+
}
122+
},
123+
}
124+
: "/metrics";
125+
105126
await fastify.register(fastifyMetrics.default, {
106-
endpoint: "/metrics",
127+
endpoint: metricsEndpoint,
107128
defaultMetrics: { enabled: true },
129+
clearRegisterOnInit: true,
108130
});
109131

110132
// Register Swagger & Swagger UI & Scalar

test/helper.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,16 @@ async function config(): Promise<AppOptions> {
2525
}
2626

2727
// Automatically build and tear down our instance
28-
async function build(t: TestContext) {
28+
async function build(t: TestContext, options?: Partial<AppOptions>) {
2929
// you can set all the options supported by the fastify CLI command
3030
const argv = [AppPath];
3131

32+
const appOptions = { ...(await config()), ...options };
33+
3234
// fastify-plugin ensures that all decorators
3335
// are exposed for testing purposes, this is
3436
// different from the production setup
35-
const app = await helper.build(argv, await config(), await config());
37+
const app = await helper.build(argv, appOptions, appOptions);
3638

3739
// Tear down our app after we are done
3840
t.after(() => void app.close());

test/routes/metrics.test.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { test } from "node:test";
2+
import * as assert from "node:assert";
3+
import { build } from "../helper.js";
4+
5+
test("metrics route without key", async (t) => {
6+
const app = await build(t);
7+
8+
const response = await app.inject({
9+
url: "/metrics",
10+
});
11+
12+
assert.equal(response.statusCode, 200);
13+
});
14+
15+
test("metrics route with key", async (t) => {
16+
const app = await build(t, { prometheusKey: "secret" });
17+
18+
// Without auth header
19+
const response = await app.inject({
20+
url: "/metrics",
21+
});
22+
assert.equal(response.statusCode, 401);
23+
24+
// With correct auth header
25+
const responseAuth = await app.inject({
26+
url: "/metrics",
27+
headers: {
28+
authorization: "Bearer secret",
29+
},
30+
});
31+
assert.equal(responseAuth.statusCode, 200);
32+
33+
// With incorrect auth header
34+
const responseBadAuth = await app.inject({
35+
url: "/metrics",
36+
headers: {
37+
authorization: "Bearer wrong",
38+
},
39+
});
40+
assert.equal(responseBadAuth.statusCode, 401);
41+
});

0 commit comments

Comments
 (0)