返回 AiToEarn
http-metrics.interceptor.ts
根目录 / project / aitoearn-backend / libs / common / src / interceptors / http-metrics.interceptor.ts
1 import type { CallHandler, ExecutionContext, NestInterceptor } from '@nestjs/common'
2 import type { Request, Response } from 'express'
3 import { Injectable } from '@nestjs/common'
4 import { Counter, Gauge, Histogram } from 'prom-client'
5 import { tap } from 'rxjs'
6
7 const EXCLUDED_PATHS = new Set(['/metrics', '/health'])
8
9 const httpRequestDuration = new Histogram({
10 name: 'http_request_duration_seconds',
11 help: 'Duration of HTTP requests in seconds',
12 labelNames: ['method', 'route', 'status_code'] as const,
13 buckets: [0.003, 0.03, 0.1, 0.3, 1.5, 10],
14 })
15
16 const httpRequestTotal = new Counter({
17 name: 'http_requests_total',
18 help: 'Total number of HTTP requests',
19 labelNames: ['method', 'route', 'status_code'] as const,
20 })
21
22 const httpRequestsInFlight = new Gauge({
23 name: 'http_requests_in_flight',
24 help: 'Number of HTTP requests currently being processed',
25 labelNames: ['method'] as const,
26 })
27
28 @Injectable()
29 export class HttpMetricsInterceptor implements NestInterceptor {
30 intercept(context: ExecutionContext, next: CallHandler) {
31 if (context.getType() !== 'http')
32 return next.handle()
33
34 const req = context.switchToHttp().getRequest<Request>()
35 if (EXCLUDED_PATHS.has(req.path))
36 return next.handle()
37
38 const res = context.switchToHttp().getResponse<Response>()
39 const route = req.route
40 ? this.normalizeRoute(`${req.baseUrl}${req.route.path}`)
41 : this.getRouteFromMetadata(context)
42
43 const end = httpRequestDuration.startTimer()
44 httpRequestsInFlight.inc({ method: req.method })
45
46 const recordMetrics = () => {
47 const statusCode = String(res.statusCode)
48 const labels = { method: req.method, route, status_code: statusCode }
49
50 end(labels)
51 httpRequestTotal.inc(labels)
52 httpRequestsInFlight.dec({ method: req.method })
53 }
54
55 return next.handle().pipe(
56 tap({ next: recordMetrics, error: recordMetrics }),
57 )
58 }
59
60 private normalizeRoute(raw: string): string {
61 return raw.replace(/\/+/g, '/').replace(/\/$/, '') || '/'
62 }
63
64 private getRouteFromMetadata(context: ExecutionContext): string {
65 const controllerPath = Reflect.getMetadata('path', context.getClass()) || ''
66 const handlerPath = Reflect.getMetadata('path', context.getHandler()) || ''
67 const prefix = Array.isArray(controllerPath) ? controllerPath[0] : controllerPath
68 const handler = Array.isArray(handlerPath) ? handlerPath[0] : handlerPath
69 return this.normalizeRoute(`/${prefix}/${handler}`)
70 }
71 }
72
72 lines TYPESCRIPT