switch metrics to express-prom-metrics
This commit is contained in:
@@ -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",
|
||||
|
||||
Vendored
+2
-6
@@ -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
@@ -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
@@ -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')
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user