import { constants as httpStatus } from 'node:http2'; import { Injectable, NestMiddleware } from '@nestjs/common'; import type { NextFunction, Request, Response } from 'express'; import { randomUUID } from 'node:crypto'; import { RequestContextService } from './request-context.service'; import { StructuredLogger } from './structured-logger.service'; const uuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i; @Injectable() export class RequestLoggingMiddleware implements NestMiddleware { constructor( private readonly requestContext: RequestContextService, private readonly logger: StructuredLogger, ) {} use(request: Request, response: Response, next: NextFunction): void { const incoming = request.header('x-request-id'); const clientRequestId = incoming && uuid.test(incoming) ? incoming : undefined; const existingRequestId = (request as Request & { id?: unknown }).id; const requestId = typeof existingRequestId === 'string' ? existingRequestId : randomUUID(); (request as unknown as { id: string }).id = requestId; const startedAt = performance.now(); response.setHeader('X-Request-ID', requestId); response.on('finish', () => { const routeRequest: unknown = request; const routePath = (routeRequest as { route?: { path?: unknown } }).route ?.path; const route = typeof routePath === 'string' ? `${request.baseUrl}${routePath}` : ''; const context = { method: request.method, route, status: response.statusCode, durationMs: Math.round(performance.now() - startedAt), ...(response.locals.publicErrorCode ? { code: response.locals.publicErrorCode as string } : {}), }; const level = response.statusCode >= httpStatus.HTTP_STATUS_INTERNAL_SERVER_ERROR ? 'error' : response.statusCode === httpStatus.HTTP_STATUS_TOO_MANY_REQUESTS || response.statusCode === httpStatus.HTTP_STATUS_UNAUTHORIZED || response.statusCode === httpStatus.HTTP_STATUS_FORBIDDEN ? 'warn' : 'info'; this.logger.event(level, 'http.request.completed', context); }); this.requestContext.run( { requestId, ...(clientRequestId ? { clientRequestId } : {}), }, next, ); } }