tangled
alpha
login
or
join now
quilling.dev
/
social-app
7
fork
atom
An ATproto social media client -- with an independent Appview.
7
fork
atom
overview
issues
pulls
pipelines
add metrics middleware to ogcard service
Austin McKinley
5 months ago
0ddd09eb
5211b269
+41
3 changed files
expand all
collapse all
unified
split
bskyogcard
package.json
src
config.ts
index.ts
+3
bskyogcard/package.json
···
14
"@atproto/common": "^0.4.0",
15
"@resvg/resvg-js": "^2.6.2",
16
"express": "^4.19.2",
0
17
"http-terminator": "^3.2.0",
18
"pino": "^9.2.0",
0
19
"react": "^18.3.1",
20
"satori": "^0.10.13",
21
"twemoji": "^14.0.2"
22
},
23
"devDependencies": {
0
24
"@types/node": "^20.14.3",
25
"ts-node": "^10.9.2",
26
"typescript": "^5.4.5"
···
14
"@atproto/common": "^0.4.0",
15
"@resvg/resvg-js": "^2.6.2",
16
"express": "^4.19.2",
17
+
"express-prom-bundle": "^7.0.0",
18
"http-terminator": "^3.2.0",
19
"pino": "^9.2.0",
20
+
"prom-client": "^15.1.3",
21
"react": "^18.3.1",
22
"satori": "^0.10.13",
23
"twemoji": "^14.0.2"
24
},
25
"devDependencies": {
26
+
"@types/express": "^4.17.21",
27
"@types/node": "^20.14.3",
28
"ts-node": "^10.9.2",
29
"typescript": "^5.4.5"
+4
bskyogcard/src/config.ts
···
6
7
export type ServiceConfig = {
8
port: number
0
9
version?: string
10
appviewUrl: string
11
originVerify?: string
···
13
14
export type Environment = {
15
port?: number
0
16
version?: string
17
appviewUrl?: string
18
originVerify?: string
···
21
export const readEnv = (): Environment => {
22
return {
23
port: envInt('CARD_PORT'),
0
24
version: envStr('CARD_VERSION'),
25
appviewUrl: envStr('CARD_APPVIEW_URL'),
26
originVerify: envStr('CARD_ORIGIN_VERIFY'),
···
30
export const envToCfg = (env: Environment): Config => {
31
const serviceCfg: ServiceConfig = {
32
port: env.port ?? 3000,
0
33
version: env.version,
34
appviewUrl: env.appviewUrl ?? 'https://api.bsky.app',
35
originVerify: env.originVerify,
···
6
7
export type ServiceConfig = {
8
port: number
9
+
metricsPort: number
10
version?: string
11
appviewUrl: string
12
originVerify?: string
···
14
15
export type Environment = {
16
port?: number
17
+
metricsPort?: number
18
version?: string
19
appviewUrl?: string
20
originVerify?: string
···
23
export const readEnv = (): Environment => {
24
return {
25
port: envInt('CARD_PORT'),
26
+
metricsPort: envInt('CARD_METRICS_PORT'),
27
version: envStr('CARD_VERSION'),
28
appviewUrl: envStr('CARD_APPVIEW_URL'),
29
originVerify: envStr('CARD_ORIGIN_VERIFY'),
···
33
export const envToCfg = (env: Environment): Config => {
34
const serviceCfg: ServiceConfig = {
35
port: env.port ?? 3000,
36
+
metricsPort: env.metricsPort ?? 3001,
37
version: env.version,
38
appviewUrl: env.appviewUrl ?? 'https://api.bsky.app',
39
originVerify: env.originVerify,
+34
bskyogcard/src/index.ts
···
2
import type http from 'node:http'
3
4
import express from 'express'
0
5
import {createHttpTerminator, type HttpTerminator} from 'http-terminator'
0
6
7
import {type Config} from './config.js'
8
import {AppContext} from './context.js'
···
13
14
export class CardService {
15
public server?: http.Server
0
16
private terminator?: HttpTerminator
0
17
18
constructor(
19
public app: express.Application,
···
24
let app = express()
25
26
const ctx = await AppContext.fromConfig(cfg)
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
27
app = routes(ctx, app)
28
app.use(errorHandler)
29
···
31
}
32
33
async start() {
0
34
this.server = this.app.listen(this.ctx.cfg.service.port)
35
this.server.keepAliveTimeout = 90000
36
this.terminator = createHttpTerminator({server: this.server})
37
await events.once(this.server, 'listening')
0
0
0
0
0
0
0
0
0
0
0
38
}
39
40
async destroy() {
41
this.ctx.abortController.abort()
42
await this.terminator?.terminate()
0
43
}
44
}
···
2
import type http from 'node:http'
3
4
import express from 'express'
5
+
import promBundle from 'express-prom-bundle'
6
import {createHttpTerminator, type HttpTerminator} from 'http-terminator'
7
+
import {register} from 'prom-client'
8
9
import {type Config} from './config.js'
10
import {AppContext} from './context.js'
···
15
16
export class CardService {
17
public server?: http.Server
18
+
public metricsServer?: http.Server
19
private terminator?: HttpTerminator
20
+
private metricsTerminator?: HttpTerminator
21
22
constructor(
23
public app: express.Application,
···
28
let app = express()
29
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
+
48
app = routes(ctx, app)
49
app.use(errorHandler)
50
···
52
}
53
54
async start() {
55
+
// Start main application server
56
this.server = this.app.listen(this.ctx.cfg.service.port)
57
this.server.keepAliveTimeout = 90000
58
this.terminator = createHttpTerminator({server: this.server})
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')
71
}
72
73
async destroy() {
74
this.ctx.abortController.abort()
75
await this.terminator?.terminate()
76
+
await this.metricsTerminator?.terminate()
77
}
78
}