Bluesky app fork with some witchin' additions 💫

add metrics middleware to ogcard service

Changed files
+41
bskyogcard
+3
bskyogcard/package.json
··· 14 14 "@atproto/common": "^0.4.0", 15 15 "@resvg/resvg-js": "^2.6.2", 16 16 "express": "^4.19.2", 17 + "express-prom-bundle": "^7.0.0", 17 18 "http-terminator": "^3.2.0", 18 19 "pino": "^9.2.0", 20 + "prom-client": "^15.1.3", 19 21 "react": "^18.3.1", 20 22 "satori": "^0.10.13", 21 23 "twemoji": "^14.0.2" 22 24 }, 23 25 "devDependencies": { 26 + "@types/express": "^4.17.21", 24 27 "@types/node": "^20.14.3", 25 28 "ts-node": "^10.9.2", 26 29 "typescript": "^5.4.5"
+4
bskyogcard/src/config.ts
··· 6 6 7 7 export type ServiceConfig = { 8 8 port: number 9 + metricsPort: number 9 10 version?: string 10 11 appviewUrl: string 11 12 originVerify?: string ··· 13 14 14 15 export type Environment = { 15 16 port?: number 17 + metricsPort?: number 16 18 version?: string 17 19 appviewUrl?: string 18 20 originVerify?: string ··· 21 23 export const readEnv = (): Environment => { 22 24 return { 23 25 port: envInt('CARD_PORT'), 26 + metricsPort: envInt('CARD_METRICS_PORT'), 24 27 version: envStr('CARD_VERSION'), 25 28 appviewUrl: envStr('CARD_APPVIEW_URL'), 26 29 originVerify: envStr('CARD_ORIGIN_VERIFY'), ··· 30 33 export const envToCfg = (env: Environment): Config => { 31 34 const serviceCfg: ServiceConfig = { 32 35 port: env.port ?? 3000, 36 + metricsPort: env.metricsPort ?? 3001, 33 37 version: env.version, 34 38 appviewUrl: env.appviewUrl ?? 'https://api.bsky.app', 35 39 originVerify: env.originVerify,
+34
bskyogcard/src/index.ts
··· 2 2 import type http from 'node:http' 3 3 4 4 import express from 'express' 5 + import promBundle from 'express-prom-bundle' 5 6 import {createHttpTerminator, type HttpTerminator} from 'http-terminator' 7 + import {register} from 'prom-client' 6 8 7 9 import {type Config} from './config.js' 8 10 import {AppContext} from './context.js' ··· 13 15 14 16 export class CardService { 15 17 public server?: http.Server 18 + public metricsServer?: http.Server 16 19 private terminator?: HttpTerminator 20 + private metricsTerminator?: HttpTerminator 17 21 18 22 constructor( 19 23 public app: express.Application, ··· 24 28 let app = express() 25 29 26 30 const ctx = await AppContext.fromConfig(cfg) 31 + 32 + // Add Prometheus middleware for automatic HTTP instrumentation 33 + const metricsMiddleware = promBundle({ 34 + includeMethod: true, 35 + includePath: true, 36 + includeStatusCode: true, 37 + includeUp: true, 38 + promClient: { 39 + collectDefaultMetrics: { 40 + timeout: 5000, 41 + }, 42 + }, 43 + // Don't expose /metrics on main app - we'll use separate server 44 + autoregister: false, 45 + }) 46 + app.use(metricsMiddleware) 47 + 27 48 app = routes(ctx, app) 28 49 app.use(errorHandler) 29 50 ··· 31 52 } 32 53 33 54 async start() { 55 + // Start main application server 34 56 this.server = this.app.listen(this.ctx.cfg.service.port) 35 57 this.server.keepAliveTimeout = 90000 36 58 this.terminator = createHttpTerminator({server: this.server}) 37 59 await events.once(this.server, 'listening') 60 + 61 + // Start separate metrics server 62 + const metricsApp = express() 63 + metricsApp.get('/metrics', (_req, res) => { 64 + res.set('Content-Type', register.contentType) 65 + res.end(register.metrics()) 66 + }) 67 + 68 + this.metricsServer = metricsApp.listen(this.ctx.cfg.service.metricsPort) 69 + this.metricsTerminator = createHttpTerminator({server: this.metricsServer}) 70 + await events.once(this.metricsServer, 'listening') 38 71 } 39 72 40 73 async destroy() { 41 74 this.ctx.abortController.abort() 42 75 await this.terminator?.terminate() 76 + await this.metricsTerminator?.terminate() 43 77 } 44 78 }