如何在NestJS中间件中获取500响应的响应体?
解决方案:在NestJS中间件中获取500状态码的响应体
因为Express/NestJS的响应采用流式输出机制,默认不会将响应体存储到response对象上,所以你需要主动拦截响应发送的核心方法,提前把响应体保存下来,才能在finish事件中获取。
方案一:修改中间件,拦截响应发送方法
重写response.send和response.json方法,在实际发送响应前将响应体挂载到response对象上,后续就能在finish回调中读取:
@Injectable() export class LoggerMiddleware implements NestMiddleware { use(request: Request, response: Response, next: NextFunction): void { const { ip, method, originalUrl } = request; const userAgent = request.get("user-agent") || ""; // 保存原生send方法,并重写 const originalSend = response.send; response.send = function(body) { // 将响应体挂载到response对象(用类型断言绕过TS类型检查) (response as any).body = body; // 调用原生方法发送响应 return originalSend.call(this, body); }; // 额外拦截json方法(如果接口会用response.json返回数据) const originalJson = response.json; response.json = function(body) { (response as any).body = body; return originalJson.call(this, body); }; response.on("finish", () => { const { statusCode } = response; const contentLength = response.get("content-length"); const basicRequestMetaInfo = { method, originalUrl, statusCode, // 其他自定义字段... }; if (this.isErroneousStatusCode(statusCode)) { const additionalRequestMetaInfo = { body: (response as any).body, // 现在可以拿到响应体了 }; this.winstonLoggerErrorLevel.error({ ...basicRequestMetaInfo, ...additionalRequestMetaInfo, }); return; } this.winstonLoggerInfoLevel.info(basicRequestMetaInfo); }); next(); } private isErroneousStatusCode(statusCode: number): boolean { return statusCode >= 500; } }
方案二:使用NestJS拦截器(更推荐)
NestJS的拦截器天生支持在响应发送前后处理数据,比中间件更适配这类场景,代码更简洁:
@Injectable() export class ErrorLoggingInterceptor implements NestInterceptor { intercept(context: ExecutionContext, next: CallHandler): Observable<any> { const request = context.switchToHttp().getRequest(); const { method, originalUrl } = request; return next.handle().pipe( // 正常响应时记录日志 tap((responseBody) => { const response = context.switchToHttp().getResponse(); this.winstonLoggerInfoLevel.info({ method, originalUrl, statusCode: response.statusCode, }); }), // 异常响应时捕获并记录 catchError((error) => { const response = context.switchToHttp().getResponse(); this.winstonLoggerErrorLevel.error({ method, originalUrl, statusCode: error.getStatus ? error.getStatus() : 500, body: error.getResponse ? error.getResponse() : error.message, }); return throwError(() => error); }), ); } }
注册拦截器的方式:
- 在模块的
providers数组中配置全局拦截器 - 用
@UseInterceptors(ErrorLoggingInterceptor)装饰器加到指定控制器/方法上
注意事项
- 若接口返回大文件流,不建议保存响应体,会占用大量内存,仅针对JSON/文本格式的接口响应处理即可
- 可以通过扩展Express的类型定义,避免每次使用
(response as any)的类型断言
内容的提问来源于stack exchange,提问作者DanilMakarov
相关产品推荐
相关产品推荐

