NestJS共享日志模块抽为NPM包后请求触发服务重启报错
问题背景
开发可在多个微服务间复用的NestJS共享日志模块时,日志模块代码直接内置在微服务项目中运行正常,但将代码抽离为独立NPM包引入后功能异常。
异常复现代码
共享NPM包内的日志模块代码:
// my-logger.module.ts import { Module } from '@nestjs/common'; import { ConfigModule, ConfigService } from '@nestjs/config'; import { LoggerModule } from 'nestjs-pino'; @Module({ imports: [ LoggerModule.forRootAsync({ imports: [ConfigModule], useFactory: async (configService: ConfigService) => ({ pinoHttp: { level: process.env.LOG_LEVEL || 'info', redact: configService.get<string[]>('logger.redacted.fields'), prettyPrint: { colorize: false, singleLine: true, levelFirst: false, translateTime: "yyyy-mm-dd'T'HH:MM:ss.l'Z'", messageFormat: '{req.headers.x-correlation-id} [{context}] {msg}', ignore: 'pid,hostname,context,req,res,responseTime', errorLikeObjectKeys: ['err', 'error'], }, }, }), inject: [ConfigService], }), ], controllers: [], providers: [], }) export class MyLoggerModule {}
微服务侧App Module代码:
// app.module.ts import { Module } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; import configuration from '../config/configuration'; import { MyLoggerModule } from '@my-company/my-logger.module'; import { HttpModule } from '@nestjs/axios'; @Module({ imports: [ ConfigModule.forRoot({ load: [configuration] }), MyLoggerModule, HttpModule, ], controllers: [], providers: [], }) export class AppModule {}
异常现象
引入共享NPM包的微服务可正常构建、部署,但每次收到新请求时都会触发服务重启,抛出如下错误:
node[1]: ../src/tcp_wrap.cc:149:static void node::TCPWrap::New(const v8::FunctionCallbackInfo<v8::Value>&): Assertion `args[0]->IsInt32()' failed.
问题根本原因
该问题和ConfigService本身逻辑无关,核心是NPM依赖重复安装导致的Nest模块多实例隔离:
- 抽离为独立NPM包时,如果未将
@nestjs/common、@nestjs/config、nestjs-pino这类框架级依赖声明为peerDependencies,NPM会在共享包的独立node_modules目录下安装一套属于自己的依赖副本,最终整个项目中会存在两份完全独立的Nest运行时实例、两份ConfigModule定义。 - 共享日志模块中引用的
ConfigModule来自共享包自身的依赖副本,和微服务AppModule中通过ConfigModule.forRoot()初始化的ConfigModule不是同一个实例,因此异步工厂中注入的ConfigService没有加载微服务侧自定义的configuration配置,configService.get<string[]>('logger.redacted.fields')返回undefined。 - pino-http收到undefined类型的redact配置后,初始化日志传输层时会传入类型非法的文件描述符参数,最终触发Node.js底层TCPWrap的整数类型断言失败,抛出看到的崩溃错误。
- 代码内置在微服务项目中时,所有代码引用的都是项目根目录node_modules下的同一套依赖,ConfigModule为单例,配置可正常读取,因此不会触发该问题。
解决方案
- 修改共享日志NPM包的
package.json,把所有Nest生态、pino相关的框架级依赖全部移到peerDependencies中,强制使用宿主微服务的依赖实例,避免重复安装:
{ "name": "@my-company/my-logger", "version": "1.0.0", "peerDependencies": { "@nestjs/common": "^9.0.0 || ^10.0.0", "@nestjs/config": "^2.0.0 || ^3.0.0", "nestjs-pino": "^3.0.0", "pino-http": "^8.0.0", "pino-pretty": "^9.0.0 || ^10.0.0" }, "dependencies": {} }
- 清理缓存后重新安装依赖:删除共享包、所有微服务项目的
node_modules目录和lock文件(package-lock.json/yarn.lock/pnpm-lock.yaml),重新执行依赖安装命令,确保整个项目依赖树中只存在一份Nest相关依赖实例。 - 增加配置兜底逻辑,避免配置读取失败时传入非法值:
useFactory: async (configService: ConfigService) => ({ pinoHttp: { level: process.env.LOG_LEVEL || 'info', redact: configService.get<string[]>('logger.redacted.fields') || ['req.headers.authorization', 'req.headers.cookie'], // 其余原有配置保持不变 } })
- (可选优化)将MyLoggerModule标记为全局模块,避免后续微服务引入时重复注册:
import { Global, Module } from '@nestjs/common'; // 其余import保持不变 @Global() @Module({ // 原有模块配置保持不变 exports: [LoggerModule] }) export class MyLoggerModule {}
内容的提问来源于stack exchange,提问作者mh377
相关产品推荐
相关产品推荐

