All files / src/common/interceptors metrics.interceptor.ts

95% Statements 19/20
71.42% Branches 5/7
100% Functions 3/3
94.44% Lines 17/18

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 5113x 13x   13x           13x         13x             13x                   298x       298x 298x 298x 298x 298x   298x   241x 241x 241x 241x        
import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common';
import { Histogram, Counter, register } from 'prom-client';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';
 
/**
 * Collects basic HTTP metrics for Prometheus because numbers are sexy 🍆📈
 */
@Injectable()
export class MetricsInterceptor implements NestInterceptor {
  private readonly requestCounter: Counter<string>;
  private readonly durationHistogram: Histogram<string>;
 
  constructor() {
    this.requestCounter = (register.getSingleMetric("http_requests_total") as Counter<string>) ?? new Counter({
      name: "http_requests_total",
      help: "Total number of HTTP requests",
      labelNames: ["method", "route", "status"] as const,
      registers: [register],
    });
 
    this.durationHistogram = (register.getSingleMetric("http_request_duration_seconds") as Histogram<string>) ?? new Histogram({
      name: "http_request_duration_seconds",
      help: "Request duration in seconds",
      buckets: [0.05, 0.1, 0.2, 0.5, 1, 3, 5, 10],
      labelNames: ["method", "route", "status"] as const,
      registers: [register],
    });
  }
 
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    Iif (context.getType() !== 'http') {
      // Skip non-HTTP (e.g., GraphQL resolver). Feel free to extend later.
      return next.handle();
    }
    const httpCtx = context.switchToHttp();
    const request = httpCtx.getRequest();
    const method = request.method;
    const route = request.route?.path || request.url; // crude but works
    const start = Date.now();
 
    return next.handle().pipe(
      tap(() => {
        const status = httpCtx.getResponse().statusCode;
        const labels = { method, route, status: String(status) } as const;
        this.requestCounter.inc(labels);
        this.durationHistogram.observe(labels, (Date.now() - start) / 1000);
      }),
    );
  }
}