NestJS LoggingMiddleware日志重复记录问题排查与解决
NestJS日志中间件重复输出问题分析与解决
场景与代码实现
尝试基于NestMiddleware实现日志中间件,核心代码如下:
LoggingMiddleware 实现
import { Injectable, NestMiddleware } from '@nestjs/common'; import { NextFunction, Request, Response } from 'express'; import { getReasonPhrase, StatusCodes } from 'http-status-codes'; import { AppLogger } from '../app-logger'; import { CommonLoggingHttpFunctions } from '../http/common-logging-http-functions'; import { HeadersInfoTreatment } from '../http/headers-info-treatment'; @Injectable() export class LoggingMiddleware implements NestMiddleware { constructor(private logger: AppLogger) {} public use(request: Request, response: Response, next: NextFunction): void { this.logHttpRequest(request); response.on('close', () => { const httpStatusCode = response.statusCode ?? StatusCodes.INTERNAL_SERVER_ERROR; if (httpStatusCode < StatusCodes.BAD_REQUEST) { this.logHttpResponse(response); } else { this.logHttpErrorResponse(response); } }); next(); } private logHttpRequest(request: Request): void { const httpVerb = request.method.toUpperCase(); this.logger.log(`${httpVerb} ${request.url}`, { 'http-msg': { headers: HeadersInfoTreatment.maskSensitiveInfo(request.headers), method: httpVerb, body: request.body }, }); } private logHttpResponse(response: Response): void { const httpVerb = response.req.method?.toUpperCase(); const statusCode = response.statusCode ?? StatusCodes.INTERNAL_SERVER_ERROR; const reasonPhrase = CommonLoggingHttpFunctions.isHttpStatusCode(response.statusCode) ? getReasonPhrase(response.statusCode) : ''; this.logger.log(`${response.statusCode} ${reasonPhrase} ${httpVerb} ${response.req.url}`, { 'http-msg': { headers: HeadersInfoTreatment.maskSensitiveInfo(response.getHeaders()), method: httpVerb, body: response, statusCode }, }); } private logHttpErrorResponse(response: Response): void { const httpVerb = response.req.method?.toUpperCase(); const statusCode = response.statusCode ?? StatusCodes.INTERNAL_SERVER_ERROR; const reasonPhrase = CommonLoggingHttpFunctions.isHttpStatusCode(statusCode) ? getReasonPhrase(statusCode) : ''; this.logger.error(`${statusCode} ${reasonPhrase} ${httpVerb} ${response.req.url}`, { 'http-msg': { headers: HeadersInfoTreatment.maskSensitiveInfo(response.getHeaders()), method: httpVerb, body: response, statusCode }, }); } }
LoggingModule 实现
import { DynamicModule, Global, MiddlewareConsumer, Module } from '@nestjs/common'; import { APP_INTERCEPTOR } from '@nestjs/core'; import { CommonLoggingHttpFunctions, configureLogger, LoggingInterceptor } from '..'; import { AppLogger } from './app-logger'; import { CONFIG_OPTIONS, LoggingOptions } from './model'; import { LoggingMiddleware } from './middleware/logging.middleware'; @Global() @Module({}) export class LoggingModule { public static register(options: LoggingOptions): DynamicModule { configureLogger(options); return { module: LoggingModule, providers: [ { provide: CONFIG_OPTIONS, useValue: { logPayloads: true, ...options }, }, AppLogger, { provide: APP_INTERCEPTOR, useClass: LoggingInterceptor, }, ], exports: [AppLogger], }; } public configure(consumer: MiddlewareConsumer) { consumer.apply(LoggingMiddleware).exclude(`(.*)${CommonLoggingHttpFunctions.excludeEndpoint}(.*)`).forRoutes('*'); } }
问题现象
- 保留
LoggingModule中的configure方法时,请求与响应日志均重复输出 - 移除
configure方法后,应用无任何日志输出(已确认AppModule无重复导入)
问题原因
核心冲突在于**LoggingInterceptor和LoggingMiddleware同时在处理日志记录**:
LoggingModule的register方法中注册了全局的APP_INTERCEPTOR(即LoggingInterceptor),它会拦截所有请求和响应并生成日志- 同时
configure方法又全局应用了LoggingMiddleware,同样会处理请求和响应日志 - 两者功能重叠,导致每一次请求/响应都会被记录两次
- 移除
configure方法后,LoggingMiddleware未被注册,若LoggingInterceptor未正确实现日志逻辑(或日志配置未生效),就会出现无日志输出的情况
解决办法
方案1:仅使用LoggingMiddleware(移除Interceptor)
修改LoggingModule的register方法,去掉全局Interceptor的注册,保留configure方法让Middleware生效:
public static register(options: LoggingOptions): DynamicModule { configureLogger(options); return { module: LoggingModule, providers: [ { provide: CONFIG_OPTIONS, useValue: { logPayloads: true, ...options }, }, AppLogger, // 移除全局Interceptor注册 // { // provide: APP_INTERCEPTOR, // useClass: LoggingInterceptor, // }, ], exports: [AppLogger], }; }
这样只有LoggingMiddleware负责日志记录,不会出现重复输出。
方案2:仅使用LoggingInterceptor(移除Middleware)
如果倾向于用Interceptor处理日志,移除LoggingModule中的configure方法,同时确保LoggingInterceptor完整实现了请求和响应的日志逻辑:
- 在
LoggingInterceptor的intercept方法中,记录请求信息 - 通过RxJS的
tap操作符监听响应流,记录响应/错误日志 - 检查
configureLogger方法是否正确初始化日志配置,确保AppLogger能正常输出内容
额外检查点
- 确认
LoggingInterceptor的逻辑是否覆盖了请求和响应的日志输出,没有遗漏 - 验证
AppLogger的实例是否正确注入,日志级别等配置是否生效
内容的提问来源于stack exchange,提问作者Nikasha Von carstein
相关产品推荐
相关产品推荐

