mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 23:01:09 +02:00
refactor(todo): use express middleware for HTTP metrics
Moved HTTP request metrics tracking from NestJS interceptor to Express middleware in main.ts. This ensures ALL requests are tracked, including those rejected by auth guards before reaching the handler. - Remove MetricsInterceptor (wasn't capturing guard exceptions) - Add Express middleware in main.ts for metrics collection - Track all HTTP requests including 401/403/404 responses Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
11411ff0a0
commit
f47bf8edd9
4 changed files with 41 additions and 74 deletions
|
|
@ -1,7 +1,6 @@
|
|||
import { Module } from '@nestjs/common';
|
||||
import { ConfigModule } from '@nestjs/config';
|
||||
import { ScheduleModule } from '@nestjs/schedule';
|
||||
import { APP_INTERCEPTOR } from '@nestjs/core';
|
||||
import { DatabaseModule } from './db/database.module';
|
||||
import { HealthModule } from './health/health.module';
|
||||
import { ProjectModule } from './project/project.module';
|
||||
|
|
@ -10,7 +9,7 @@ import { LabelModule } from './label/label.module';
|
|||
import { ReminderModule } from './reminder/reminder.module';
|
||||
import { KanbanModule } from './kanban/kanban.module';
|
||||
import { NetworkModule } from './network/network.module';
|
||||
import { MetricsModule, MetricsInterceptor } from './metrics';
|
||||
import { MetricsModule } from './metrics';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
|
|
@ -29,11 +28,5 @@ import { MetricsModule, MetricsInterceptor } from './metrics';
|
|||
KanbanModule,
|
||||
NetworkModule,
|
||||
],
|
||||
providers: [
|
||||
{
|
||||
provide: APP_INTERCEPTOR,
|
||||
useClass: MetricsInterceptor,
|
||||
},
|
||||
],
|
||||
})
|
||||
export class AppModule {}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,51 @@
|
|||
import { NestFactory } from '@nestjs/core';
|
||||
import { ValidationPipe, Logger } from '@nestjs/common';
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
import { AppModule } from './app.module';
|
||||
import { MetricsService } from './metrics/metrics.service';
|
||||
|
||||
// Normalize route paths to prevent high cardinality
|
||||
function normalizeRoute(path: string): string {
|
||||
return path
|
||||
.replace(/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/gi, ':id')
|
||||
.replace(/\/\d+/g, '/:id');
|
||||
}
|
||||
|
||||
async function bootstrap() {
|
||||
const logger = new Logger('Bootstrap');
|
||||
const app = await NestFactory.create(AppModule);
|
||||
|
||||
// Get MetricsService for request tracking
|
||||
const metricsService = app.get(MetricsService);
|
||||
|
||||
// Global Express middleware to track ALL HTTP requests
|
||||
// This runs before guards/interceptors, so it catches auth failures etc.
|
||||
app.use((req: Request, res: Response, next: NextFunction) => {
|
||||
// Skip metrics endpoint
|
||||
if (req.path === '/metrics') {
|
||||
return next();
|
||||
}
|
||||
|
||||
const startTime = Date.now();
|
||||
const method = req.method;
|
||||
const route = normalizeRoute(req.path);
|
||||
|
||||
res.once('finish', () => {
|
||||
const duration = (Date.now() - startTime) / 1000;
|
||||
metricsService.httpRequestsTotal.inc({
|
||||
method,
|
||||
route,
|
||||
status: res.statusCode.toString(),
|
||||
});
|
||||
metricsService.httpRequestDuration.observe(
|
||||
{ method, route, status: res.statusCode.toString() },
|
||||
duration
|
||||
);
|
||||
});
|
||||
|
||||
next();
|
||||
});
|
||||
|
||||
// Enable CORS for all platforms
|
||||
app.enableCors({
|
||||
origin: (origin, callback) => {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
export * from './metrics.module';
|
||||
export * from './metrics.service';
|
||||
export * from './metrics.interceptor';
|
||||
export * from './metrics.controller';
|
||||
|
|
|
|||
|
|
@ -1,65 +0,0 @@
|
|||
import {
|
||||
Injectable,
|
||||
NestInterceptor,
|
||||
ExecutionContext,
|
||||
CallHandler,
|
||||
HttpException,
|
||||
HttpStatus,
|
||||
} from '@nestjs/common';
|
||||
import { Observable, tap, catchError, throwError } from 'rxjs';
|
||||
import { Request, Response } from 'express';
|
||||
import { MetricsService } from './metrics.service';
|
||||
|
||||
@Injectable()
|
||||
export class MetricsInterceptor implements NestInterceptor {
|
||||
constructor(private readonly metricsService: MetricsService) {}
|
||||
|
||||
intercept(context: ExecutionContext, next: CallHandler): Observable<unknown> {
|
||||
const httpContext = context.switchToHttp();
|
||||
const request = httpContext.getRequest<Request>();
|
||||
const response = httpContext.getResponse<Response>();
|
||||
|
||||
// Skip metrics endpoint itself
|
||||
if (request.path === '/metrics') {
|
||||
return next.handle();
|
||||
}
|
||||
|
||||
const startTime = Date.now();
|
||||
const method = request.method;
|
||||
// Normalize route (remove IDs to prevent high cardinality)
|
||||
const route = this.normalizeRoute(request.path);
|
||||
|
||||
return next.handle().pipe(
|
||||
tap(() => {
|
||||
this.recordMetrics(method, route, response.statusCode, startTime);
|
||||
}),
|
||||
catchError((error) => {
|
||||
// Extract status code from HttpException or use 500
|
||||
const status =
|
||||
error instanceof HttpException ? error.getStatus() : HttpStatus.INTERNAL_SERVER_ERROR;
|
||||
this.recordMetrics(method, route, status, startTime);
|
||||
return throwError(() => error);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
private recordMetrics(method: string, route: string, status: number, startTime: number): void {
|
||||
const duration = (Date.now() - startTime) / 1000;
|
||||
const statusStr = status.toString();
|
||||
|
||||
this.metricsService.httpRequestsTotal.inc({
|
||||
method,
|
||||
route,
|
||||
status: statusStr,
|
||||
});
|
||||
|
||||
this.metricsService.httpRequestDuration.observe({ method, route, status: statusStr }, duration);
|
||||
}
|
||||
|
||||
private normalizeRoute(path: string): string {
|
||||
// Replace UUIDs and numeric IDs with placeholders
|
||||
return path
|
||||
.replace(/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/gi, ':id')
|
||||
.replace(/\/\d+/g, '/:id');
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue