diff --git a/apps/api/v2/src/app.module.ts b/apps/api/v2/src/app.module.ts index 2c84b82f37..7b42702be6 100644 --- a/apps/api/v2/src/app.module.ts +++ b/apps/api/v2/src/app.module.ts @@ -3,6 +3,8 @@ import { AppLoggerMiddleware } from "@/middleware/app.logger.middleware"; import { RewriterMiddleware } from "@/middleware/app.rewrites.middleware"; import { JsonBodyMiddleware } from "@/middleware/body/json.body.middleware"; import { RawBodyMiddleware } from "@/middleware/body/raw.body.middleware"; +import { ResponseInterceptor } from "@/middleware/request-ids/request-id.interceptor"; +import { RequestIdMiddleware } from "@/middleware/request-ids/request-id.middleware"; import { AuthModule } from "@/modules/auth/auth.module"; import { EndpointsModule } from "@/modules/endpoints.module"; import { JwtModule } from "@/modules/jwt/jwt.module"; @@ -11,7 +13,7 @@ import { RedisModule } from "@/modules/redis/redis.module"; import { RedisService } from "@/modules/redis/redis.service"; import { MiddlewareConsumer, Module, NestModule, RequestMethod } from "@nestjs/common"; import { ConfigModule } from "@nestjs/config"; -import { RouterModule } from "@nestjs/core"; +import { APP_INTERCEPTOR, RouterModule } from "@nestjs/core"; import { seconds, ThrottlerModule } from "@nestjs/throttler"; import { ThrottlerStorageRedisService } from "nestjs-throttler-storage-redis"; @@ -52,6 +54,12 @@ import { AppController } from "./app.controller"; RouterModule.register([{ path: "/v2", module: EndpointsModule }]), ], controllers: [AppController], + providers: [ + { + provide: APP_INTERCEPTOR, + useClass: ResponseInterceptor, + }, + ], }) export class AppModule implements NestModule { configure(consumer: MiddlewareConsumer): void { @@ -63,6 +71,8 @@ export class AppModule implements NestModule { }) .apply(JsonBodyMiddleware) .forRoutes("*") + .apply(RequestIdMiddleware) + .forRoutes("*") .apply(AppLoggerMiddleware) .forRoutes("*") .apply(RewriterMiddleware) diff --git a/apps/api/v2/src/filters/http-exception.filter.ts b/apps/api/v2/src/filters/http-exception.filter.ts index c47e1ff936..ef3eefd4df 100644 --- a/apps/api/v2/src/filters/http-exception.filter.ts +++ b/apps/api/v2/src/filters/http-exception.filter.ts @@ -13,13 +13,17 @@ export class HttpExceptionFilter implements ExceptionFilter { const response = ctx.getResponse(); const request = ctx.getRequest(); const statusCode = exception.getStatus(); + const requestId = request.headers["X-Request-Id"]; + this.logger.error(`Http Exception Filter: ${exception?.message}`, { exception, body: request.body, headers: request.headers, url: request.url, method: request.method, + requestId, }); + response.status(statusCode).json({ status: ERROR_STATUS, timestamp: new Date().toISOString(), diff --git a/apps/api/v2/src/filters/prisma-exception.filter.ts b/apps/api/v2/src/filters/prisma-exception.filter.ts index 4490a0de8c..9bd1f82d10 100644 --- a/apps/api/v2/src/filters/prisma-exception.filter.ts +++ b/apps/api/v2/src/filters/prisma-exception.filter.ts @@ -33,12 +33,15 @@ export class PrismaExceptionFilter implements ExceptionFilter { const ctx = host.switchToHttp(); const response = ctx.getResponse(); const request = ctx.getRequest(); + const requestId = request.headers["X-Request-Id"]; + this.logger.error(`PrismaError: ${error.message}`, { error, body: request.body, headers: request.headers, url: request.url, method: request.method, + requestId, }); response.status(HttpStatus.INTERNAL_SERVER_ERROR).json({ status: ERROR_STATUS, diff --git a/apps/api/v2/src/filters/sentry-exception.filter.ts b/apps/api/v2/src/filters/sentry-exception.filter.ts index 479525ecf4..996d9faa6d 100644 --- a/apps/api/v2/src/filters/sentry-exception.filter.ts +++ b/apps/api/v2/src/filters/sentry-exception.filter.ts @@ -14,6 +14,7 @@ export class SentryFilter extends BaseExceptionFilter { const ctx = host.switchToHttp(); const response = ctx.getResponse(); const request = ctx.getRequest(); + const requestId = request.headers["X-Request-Id"]; this.logger.error(`Sentry Exception Filter: ${exception?.message}`, { exception, @@ -21,6 +22,7 @@ export class SentryFilter extends BaseExceptionFilter { headers: request.headers, url: request.url, method: request.method, + requestId, }); // capture if client has been init diff --git a/apps/api/v2/src/filters/trpc-exception.filter.ts b/apps/api/v2/src/filters/trpc-exception.filter.ts index e65e94af16..47dbcd00b4 100644 --- a/apps/api/v2/src/filters/trpc-exception.filter.ts +++ b/apps/api/v2/src/filters/trpc-exception.filter.ts @@ -41,12 +41,15 @@ export class TRPCExceptionFilter implements ExceptionFilter { break; } + const requestId = request.headers["X-Request-Id"]; + this.logger.error(`TRPC Exception Filter: ${exception?.message}`, { exception, body: request.body, headers: request.headers, url: request.url, method: request.method, + requestId, }); response.status(statusCode).json({ diff --git a/apps/api/v2/src/filters/zod-exception.filter.ts b/apps/api/v2/src/filters/zod-exception.filter.ts index eb962ff5fa..7a61b5c71b 100644 --- a/apps/api/v2/src/filters/zod-exception.filter.ts +++ b/apps/api/v2/src/filters/zod-exception.filter.ts @@ -14,6 +14,7 @@ export class ZodExceptionFilter implements ExceptionFilter { const ctx = host.switchToHttp(); const response = ctx.getResponse(); const request = ctx.getRequest(); + const requestId = request.headers["X-Request-Id"]; this.logger.error(`ZodError: ${error.message}`, { error, @@ -21,6 +22,7 @@ export class ZodExceptionFilter implements ExceptionFilter { headers: request.headers, url: request.url, method: request.method, + requestId, }); response.status(HttpStatus.BAD_REQUEST).json({ diff --git a/apps/api/v2/src/middleware/request-ids/request-id.interceptor.ts b/apps/api/v2/src/middleware/request-ids/request-id.interceptor.ts new file mode 100644 index 0000000000..b49d75d456 --- /dev/null +++ b/apps/api/v2/src/middleware/request-ids/request-id.interceptor.ts @@ -0,0 +1,16 @@ +import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from "@nestjs/common"; +import { Request, Response } from "express"; + +@Injectable() +export class ResponseInterceptor implements NestInterceptor { + intercept(context: ExecutionContext, next: CallHandler) { + const ctx = context.switchToHttp(); + const request = ctx.getRequest(); + const response = ctx.getResponse(); + + const requestId = request.headers["X-Request-Id"] ?? "unknown-request-id"; + response.setHeader("X-Request-Id", requestId.toString()); + + return next.handle(); + } +} diff --git a/apps/api/v2/src/middleware/request-ids/request-id.middleware.ts b/apps/api/v2/src/middleware/request-ids/request-id.middleware.ts new file mode 100644 index 0000000000..2902dd8fb2 --- /dev/null +++ b/apps/api/v2/src/middleware/request-ids/request-id.middleware.ts @@ -0,0 +1,11 @@ +import { Injectable, NestMiddleware } from "@nestjs/common"; +import { Request, Response, NextFunction } from "express"; +import { v4 as uuid } from "uuid"; + +@Injectable() +export class RequestIdMiddleware implements NestMiddleware { + use(req: Request, res: Response, next: NextFunction) { + req.headers["X-Request-Id"] = uuid(); + next(); + } +}