switch metrics to express-prom-metrics
This commit is contained in:
@@ -10,7 +10,8 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@atproto/api": "^0.16.7",
|
"@atproto/api": "^0.16.7",
|
||||||
"@atproto/common": "^0.4.11",
|
"@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",
|
"@types/escape-html": "^1.0.4",
|
||||||
"body-parser": "^1.20.2",
|
"body-parser": "^1.20.2",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
|
|||||||
Vendored
+2
-6
@@ -50,12 +50,8 @@ export class SafelinkClient {
|
|||||||
const end = process.hrtime.bigint()
|
const end = process.hrtime.bigint()
|
||||||
const respTimeMs = Number(end - start) / 1_000_000 // ns to ms :3
|
const respTimeMs = Number(end - start) / 1_000_000 // ns to ms :3
|
||||||
|
|
||||||
this.ctx.metrics
|
this.ctx.safeLinkLookups.labels(status, cached ? 'yes' : 'no').inc()
|
||||||
.getCounter('safeLinkLookups')
|
this.ctx.safeLinkLookupDuration
|
||||||
.labels(status, cached ? 'yes' : 'no')
|
|
||||||
.inc()
|
|
||||||
this.ctx.metrics
|
|
||||||
.getHistogram('safeLinkLookupDuration')
|
|
||||||
.labels(status, cached ? 'yes' : 'no')
|
.labels(status, cached ? 'yes' : 'no')
|
||||||
.observe(respTimeMs)
|
.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 {SafelinkClient} from './cache/safelinkClient.js'
|
||||||
import {type Config} from './config.js'
|
import {type Config} from './config.js'
|
||||||
@@ -9,63 +9,39 @@ export type AppContextOptions = {
|
|||||||
db: Database
|
db: Database
|
||||||
}
|
}
|
||||||
|
|
||||||
type BlinkMetricNames =
|
|
||||||
| 'requestDuration'
|
|
||||||
| 'redirects'
|
|
||||||
| 'shortLinkRequests'
|
|
||||||
| 'safeLinkLookups'
|
|
||||||
| 'safeLinkLookupDuration'
|
|
||||||
|
|
||||||
type BlinkMetricConfig = Record<BlinkMetricNames, MetricConfig>
|
|
||||||
|
|
||||||
export class AppContext {
|
export class AppContext {
|
||||||
cfg: Config
|
cfg: Config
|
||||||
db: Database
|
db: Database
|
||||||
metrics: Metrics<BlinkMetricConfig>
|
|
||||||
safelinkClient: SafelinkClient
|
safelinkClient: SafelinkClient
|
||||||
abortController = new AbortController()
|
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) {
|
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.cfg = this.opts.cfg
|
||||||
this.db = this.opts.db
|
this.db = this.opts.db
|
||||||
this.safelinkClient = new SafelinkClient(this)
|
this.safelinkClient = new SafelinkClient(this)
|
||||||
|
|||||||
+25
-5
@@ -3,7 +3,9 @@ import type http from 'node:http'
|
|||||||
|
|
||||||
import cors from 'cors'
|
import cors from 'cors'
|
||||||
import express from 'express'
|
import express from 'express'
|
||||||
|
import promBundle from 'express-prom-bundle'
|
||||||
import {createHttpTerminator, type HttpTerminator} from 'http-terminator'
|
import {createHttpTerminator, type HttpTerminator} from 'http-terminator'
|
||||||
|
import {register} from 'prom-client'
|
||||||
|
|
||||||
import {type Config} from './config.js'
|
import {type Config} from './config.js'
|
||||||
import {AppContext} from './context.js'
|
import {AppContext} from './context.js'
|
||||||
@@ -31,10 +33,28 @@ export class LinkService {
|
|||||||
app.use(i18n.init)
|
app.use(i18n.init)
|
||||||
|
|
||||||
const ctx = await AppContext.fromConfig(cfg)
|
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)
|
return new LinkService(app, ctx)
|
||||||
}
|
}
|
||||||
@@ -50,8 +70,8 @@ export class LinkService {
|
|||||||
const metricsApp = express()
|
const metricsApp = express()
|
||||||
metricsApp.get('/metrics', async (_req, res) => {
|
metricsApp.get('/metrics', async (_req, res) => {
|
||||||
try {
|
try {
|
||||||
const metrics = await this.ctx.metrics.getRegistry().metrics()
|
const metrics = await register.metrics()
|
||||||
res.set('Content-Type', this.ctx.metrics.getRegistry().contentType)
|
res.set('Content-Type', register.contentType)
|
||||||
res.end(metrics)
|
res.end(metrics)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
res.status(500).end('Error collecting metrics')
|
res.status(500).end('Error collecting metrics')
|
||||||
|
|||||||
@@ -14,10 +14,7 @@ export default function (ctx: AppContext, app: Express) {
|
|||||||
bodyParser.json(),
|
bodyParser.json(),
|
||||||
handler(async (req, res) => {
|
handler(async (req, res) => {
|
||||||
const addMetrics = (statusCode: number) => {
|
const addMetrics = (statusCode: number) => {
|
||||||
ctx.metrics
|
ctx.shortLinkRequests.labels('POST', statusCode.toString()).inc()
|
||||||
.getCounter('shortLinkRequests')
|
|
||||||
.labels('POST', statusCode.toString())
|
|
||||||
.inc()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let path: string
|
let path: string
|
||||||
|
|||||||
@@ -20,10 +20,7 @@ export default function (ctx: AppContext, app: Express) {
|
|||||||
'/redirect',
|
'/redirect',
|
||||||
handler(async (req, res) => {
|
handler(async (req, res) => {
|
||||||
const addMetrics = (ruleStr: string, statusCode: number) => {
|
const addMetrics = (ruleStr: string, statusCode: number) => {
|
||||||
ctx.metrics
|
ctx.redirects.labels(ruleStr, statusCode.toString()).inc()
|
||||||
.getCounter('redirects')
|
|
||||||
.labels(ruleStr, statusCode.toString())
|
|
||||||
.inc()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let link = req.query.u
|
let link = req.query.u
|
||||||
|
|||||||
@@ -11,10 +11,7 @@ export default function (ctx: AppContext, app: Express) {
|
|||||||
'/:linkId',
|
'/:linkId',
|
||||||
handler(async (req, res) => {
|
handler(async (req, res) => {
|
||||||
const addMetrics = (statusCode: number) => {
|
const addMetrics = (statusCode: number) => {
|
||||||
ctx.metrics
|
ctx.shortLinkRequests.labels('GET', statusCode.toString()).inc()
|
||||||
.getCounter('shortLinkRequests')
|
|
||||||
.labels('GET', statusCode.toString())
|
|
||||||
.inc()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const linkId = req.params.linkId
|
const linkId = req.params.linkId
|
||||||
|
|||||||
+130
-515
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user