add metrics middleware to ogcard service

This commit is contained in:
Austin McKinley
2025-09-22 10:20:05 -07:00
parent 5211b26903
commit 0ddd09eb9b
3 changed files with 41 additions and 0 deletions
+3
View File
@@ -14,13 +14,16 @@
"@atproto/common": "^0.4.0", "@atproto/common": "^0.4.0",
"@resvg/resvg-js": "^2.6.2", "@resvg/resvg-js": "^2.6.2",
"express": "^4.19.2", "express": "^4.19.2",
"express-prom-bundle": "^7.0.0",
"http-terminator": "^3.2.0", "http-terminator": "^3.2.0",
"pino": "^9.2.0", "pino": "^9.2.0",
"prom-client": "^15.1.3",
"react": "^18.3.1", "react": "^18.3.1",
"satori": "^0.10.13", "satori": "^0.10.13",
"twemoji": "^14.0.2" "twemoji": "^14.0.2"
}, },
"devDependencies": { "devDependencies": {
"@types/express": "^4.17.21",
"@types/node": "^20.14.3", "@types/node": "^20.14.3",
"ts-node": "^10.9.2", "ts-node": "^10.9.2",
"typescript": "^5.4.5" "typescript": "^5.4.5"
+4
View File
@@ -6,6 +6,7 @@ export type Config = {
export type ServiceConfig = { export type ServiceConfig = {
port: number port: number
metricsPort: number
version?: string version?: string
appviewUrl: string appviewUrl: string
originVerify?: string originVerify?: string
@@ -13,6 +14,7 @@ export type ServiceConfig = {
export type Environment = { export type Environment = {
port?: number port?: number
metricsPort?: number
version?: string version?: string
appviewUrl?: string appviewUrl?: string
originVerify?: string originVerify?: string
@@ -21,6 +23,7 @@ export type Environment = {
export const readEnv = (): Environment => { export const readEnv = (): Environment => {
return { return {
port: envInt('CARD_PORT'), port: envInt('CARD_PORT'),
metricsPort: envInt('CARD_METRICS_PORT'),
version: envStr('CARD_VERSION'), version: envStr('CARD_VERSION'),
appviewUrl: envStr('CARD_APPVIEW_URL'), appviewUrl: envStr('CARD_APPVIEW_URL'),
originVerify: envStr('CARD_ORIGIN_VERIFY'), originVerify: envStr('CARD_ORIGIN_VERIFY'),
@@ -30,6 +33,7 @@ export const readEnv = (): Environment => {
export const envToCfg = (env: Environment): Config => { export const envToCfg = (env: Environment): Config => {
const serviceCfg: ServiceConfig = { const serviceCfg: ServiceConfig = {
port: env.port ?? 3000, port: env.port ?? 3000,
metricsPort: env.metricsPort ?? 3001,
version: env.version, version: env.version,
appviewUrl: env.appviewUrl ?? 'https://api.bsky.app', appviewUrl: env.appviewUrl ?? 'https://api.bsky.app',
originVerify: env.originVerify, originVerify: env.originVerify,
+34
View File
@@ -2,7 +2,9 @@ import events from 'node:events'
import type http from 'node:http' import type http from 'node:http'
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'
@@ -13,7 +15,9 @@ export * from './logger.js'
export class CardService { export class CardService {
public server?: http.Server public server?: http.Server
public metricsServer?: http.Server
private terminator?: HttpTerminator private terminator?: HttpTerminator
private metricsTerminator?: HttpTerminator
constructor( constructor(
public app: express.Application, public app: express.Application,
@@ -24,6 +28,23 @@ export class CardService {
let app = express() let app = express()
const ctx = await AppContext.fromConfig(cfg) const ctx = await AppContext.fromConfig(cfg)
// Add Prometheus middleware for automatic HTTP instrumentation
const metricsMiddleware = promBundle({
includeMethod: true,
includePath: true,
includeStatusCode: true,
includeUp: true,
promClient: {
collectDefaultMetrics: {
timeout: 5000,
},
},
// Don't expose /metrics on main app - we'll use separate server
autoregister: false,
})
app.use(metricsMiddleware)
app = routes(ctx, app) app = routes(ctx, app)
app.use(errorHandler) app.use(errorHandler)
@@ -31,14 +52,27 @@ export class CardService {
} }
async start() { async start() {
// Start main application server
this.server = this.app.listen(this.ctx.cfg.service.port) this.server = this.app.listen(this.ctx.cfg.service.port)
this.server.keepAliveTimeout = 90000 this.server.keepAliveTimeout = 90000
this.terminator = createHttpTerminator({server: this.server}) this.terminator = createHttpTerminator({server: this.server})
await events.once(this.server, 'listening') await events.once(this.server, 'listening')
// Start separate metrics server
const metricsApp = express()
metricsApp.get('/metrics', (_req, res) => {
res.set('Content-Type', register.contentType)
res.end(register.metrics())
})
this.metricsServer = metricsApp.listen(this.ctx.cfg.service.metricsPort)
this.metricsTerminator = createHttpTerminator({server: this.metricsServer})
await events.once(this.metricsServer, 'listening')
} }
async destroy() { async destroy() {
this.ctx.abortController.abort() this.ctx.abortController.abort()
await this.terminator?.terminate() await this.terminator?.terminate()
await this.metricsTerminator?.terminate()
} }
} }