switch metrics to express-prom-metrics

This commit is contained in:
Austin McKinley
2025-09-29 18:48:01 -07:00
parent ab539776d0
commit db7d6299f0
8 changed files with 189 additions and 590 deletions
+2 -1
View File
@@ -10,7 +10,8 @@
"dependencies": {
"@atproto/api": "^0.16.7",
"@atproto/common": "^0.4.11",
"@haileyok/ts-util": "^1.3.4",
"express-prom-bundle": "^7.0.0",
"prom-client": "^15.1.3",
"@types/escape-html": "^1.0.4",
"body-parser": "^1.20.2",
"cors": "^2.8.5",
+2 -6
View File
@@ -50,12 +50,8 @@ export class SafelinkClient {
const end = process.hrtime.bigint()
const respTimeMs = Number(end - start) / 1_000_000 // ns to ms :3
this.ctx.metrics
.getCounter('safeLinkLookups')
.labels(status, cached ? 'yes' : 'no')
.inc()
this.ctx.metrics
.getHistogram('safeLinkLookupDuration')
this.ctx.safeLinkLookups.labels(status, cached ? 'yes' : 'no').inc()
this.ctx.safeLinkLookupDuration
.labels(status, cached ? 'yes' : 'no')
.observe(respTimeMs)
}
+27 -51
View File
@@ -1,4 +1,4 @@
import {type MetricConfig, Metrics} from '@haileyok/ts-util'
import {Counter, Histogram} from 'prom-client'
import {SafelinkClient} from './cache/safelinkClient.js'
import {type Config} from './config.js'
@@ -9,63 +9,39 @@ export type AppContextOptions = {
db: Database
}
type BlinkMetricNames =
| 'requestDuration'
| 'redirects'
| 'shortLinkRequests'
| 'safeLinkLookups'
| 'safeLinkLookupDuration'
type BlinkMetricConfig = Record<BlinkMetricNames, MetricConfig>
export class AppContext {
cfg: Config
db: Database
metrics: Metrics<BlinkMetricConfig>
safelinkClient: SafelinkClient
abortController = new AbortController()
// Custom business metrics
shortLinkRequests = new Counter({
name: 'blink_short_link_requests_total',
help: 'Number of short link requests handled',
labelNames: ['method', 'status_code'],
})
redirects = new Counter({
name: 'blink_redirects_total',
help: 'Number of link redirects handled',
labelNames: ['safelink_rule', 'status_code'],
})
safeLinkLookups = new Counter({
name: 'blink_safe_link_lookups_total',
help: 'Number of safelink lookups handled',
labelNames: ['status', 'cached'],
})
safeLinkLookupDuration = new Histogram({
name: 'blink_safe_link_lookup_duration_milliseconds',
help: 'Safelink lookup duration in milliseconds',
labelNames: ['status', 'cached'],
buckets: [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10],
})
constructor(private opts: AppContextOptions) {
const metricsConfig: BlinkMetricConfig = {
requestDuration: {
type: 'histogram',
name: 'request_duration_millis',
help: 'Request duration in millis',
labelNames: ['path', 'method', 'code'],
buckets: [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10],
},
redirects: {
type: 'counter',
name: 'redirects',
help: 'Number of link redirects handled',
labelNames: ['safelink_rule', 'code'],
},
shortLinkRequests: {
type: 'counter',
name: 'shortlink_requests',
help: 'Number of shortlink requests handled',
labelNames: ['method', 'code'],
},
safeLinkLookups: {
type: 'counter',
name: 'safelink_lookups',
help: 'Number of safelink lookups handled',
labelNames: ['status', 'cached'],
},
safeLinkLookupDuration: {
type: 'histogram',
name: 'safelink_lookup_duration_millis',
help: 'Request duration in millis',
labelNames: ['status', 'cached'],
buckets: [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10],
},
}
this.metrics = new Metrics(metricsConfig, {
prefix: 'blink_',
collectDefaultMetrics: true,
})
this.cfg = this.opts.cfg
this.db = this.opts.db
this.safelinkClient = new SafelinkClient(this)
+25 -5
View File
@@ -3,7 +3,9 @@ import type http from 'node:http'
import cors from 'cors'
import express from 'express'
import promBundle from 'express-prom-bundle'
import {createHttpTerminator, type HttpTerminator} from 'http-terminator'
import {register} from 'prom-client'
import {type Config} from './config.js'
import {AppContext} from './context.js'
@@ -31,10 +33,28 @@ export class LinkService {
app.use(i18n.init)
const ctx = await AppContext.fromConfig(cfg)
app = routes(ctx, app)
app.use(errorHandler)
ctx.metrics.registerExpressMetrics(app)
// Add Prometheus middleware for automatic HTTP instrumentation
const metricsMiddleware = promBundle({
includeMethod: true,
includePath: true,
includeStatusCode: true,
includeUp: true,
promClient: {
collectDefaultMetrics: {},
},
autoregister: false,
normalizePath: req => {
if (req.route) {
return req.route.path
}
return '<unmatched>'
},
})
app.use(metricsMiddleware)
routes(ctx, app)
app.use(errorHandler)
return new LinkService(app, ctx)
}
@@ -50,8 +70,8 @@ export class LinkService {
const metricsApp = express()
metricsApp.get('/metrics', async (_req, res) => {
try {
const metrics = await this.ctx.metrics.getRegistry().metrics()
res.set('Content-Type', this.ctx.metrics.getRegistry().contentType)
const metrics = await register.metrics()
res.set('Content-Type', register.contentType)
res.end(metrics)
} catch (error) {
res.status(500).end('Error collecting metrics')
+1 -4
View File
@@ -14,10 +14,7 @@ export default function (ctx: AppContext, app: Express) {
bodyParser.json(),
handler(async (req, res) => {
const addMetrics = (statusCode: number) => {
ctx.metrics
.getCounter('shortLinkRequests')
.labels('POST', statusCode.toString())
.inc()
ctx.shortLinkRequests.labels('POST', statusCode.toString()).inc()
}
let path: string
+1 -4
View File
@@ -20,10 +20,7 @@ export default function (ctx: AppContext, app: Express) {
'/redirect',
handler(async (req, res) => {
const addMetrics = (ruleStr: string, statusCode: number) => {
ctx.metrics
.getCounter('redirects')
.labels(ruleStr, statusCode.toString())
.inc()
ctx.redirects.labels(ruleStr, statusCode.toString()).inc()
}
let link = req.query.u
+1 -4
View File
@@ -11,10 +11,7 @@ export default function (ctx: AppContext, app: Express) {
'/:linkId',
handler(async (req, res) => {
const addMetrics = (statusCode: number) => {
ctx.metrics
.getCounter('shortLinkRequests')
.labels('GET', statusCode.toString())
.inc()
ctx.shortLinkRequests.labels('GET', statusCode.toString()).inc()
}
const linkId = req.params.linkId
+130 -515
View File
File diff suppressed because it is too large Load Diff