NestJS 日志关联难题一键破解:用 nestjs-cls 自动追踪全局 Request ID 完整指南
【免费下载链接】nestjs-clsA continuation-local storage (async context) module compatible with NestJS's dependency injection.项目地址: https://gitcode.com/gh_mirrors/ne/nestjs-cls
在 NestJS 项目中,日志分散、无法串联是排障时的头号难题。nestjs-cls是一款基于AsyncLocalStorage的 CLS(异步上下文)模块,它能自动为每个请求生成并追踪全局Request ID,让整条调用链的日志一"键"关联。下面带你用最少代码完成安装与配置,彻底告别日志断片。
为什么 NestJS 日志总是"断片"?
一个请求进来后,通常会穿过 Controller、Interceptor、Guard、Service,甚至跨多个异步调用。这些环节各自console.log出来的日志彼此孤立:
- 看不到"这条日志属于哪个请求"
- 多个用户并发时,日志互相穿插,无法区分
- 想按请求聚合,只能手动把上下文层层透传
手动透传繁琐且容易漏传。而 Node.js 的AsyncLocalStorage恰好能让数据自动跟随整个异步调用链,nestjs-cls就是把它封装成了符合 NestJS 依赖注入规范的模块。
💡 核心思路:在请求入口处生成一个唯一的 Request ID,存进"共享上下文",之后任何位置都能通过
ClsService直接读取,无需参数传递。
nestjs-cls 是什么:让 Request ID 贯穿整个请求
nestjs-cls暴露了一个动态模块ClsModule,并提供可注入的ClsService。你只需在请求生命周期内调用一次cls.run()(通常由中间件自动完成),同一调用链中所有代码就都能用cls.set()/cls.get()读写同一份存储。
它支持的常见场景:
| 场景 | 说明 |
|---|---|
| 🆔 Request ID 追踪 | 为日志关联提供唯一标识(本文主角) |
| 👤 全程记录用户 | 用户信息、登录态贯穿整个请求 |
| 🏢 多租户数据库连接 | 让动态租户连接随处可用 |
| 🔐 权限/角色传播 | 限制资源访问级别 |
| 🧵 数据库事务传播 | 配合 Transactional 插件无缝传递 |
快速安装 nestjs-cls:一条命令搞定
nestjs-cls是标准 NPM 包,支持主流包管理器,任选其一即可:
npm install nestjs-cls # 或 yarn add nestjs-cls # 或 pnpm add nestjs-clsℹ️ 该模块依赖
@nestjs/core和@nestjs/common,请确保你的项目中已安装这两个库。
如果你还想查看或参与源码开发,可以克隆仓库到本地:
git clone https://gitcode.com/gh_mirrors/ne/nestjs-cls三步配置:注册 ClsModule 并挂载中间件
HTTP 请求到达时,中间件是最先执行的环节,因此是初始化上下文的最佳位置。只需三步:
第 1 步:在根模块注册ClsModule,并自动挂载中间件
// app.module.ts import { ClsModule } from 'nestjs-cls'; @Module({ imports: [ ClsModule.forRoot({ global: true, middleware: { mount: true }, // 自动挂载到所有路由 }), ], }) export class AppModule {}第 2 步:打开generateId开关,自动生成 Request ID
这是日志关联的关键。默认 ID 基于Math.random()生成,也可用idGenerator自定义——例如优先读取上游网关传来的X-Request-Id头,没有再本地生成:
ClsModule.forRoot({ middleware: { mount: true, generateId: true, idGenerator: (req) => req.headers['X-Request-Id'] ?? uuid(), }, })第 3 步:在任意位置通过ClsService读取
ID 会被存到上下文的CLS_ID常量中,ClsService提供了getId()快捷方法。比如封装一个自定义日志器:
// my.logger.ts @Injectable() class MyLogger { constructor(private readonly cls: ClsService) {} log(message: string) { console.log(`<${this.cls.getId()}> ${message}`); } }之后无论 Controller、Service 还是 Interceptor 里调用this.logger.log('...'),都会自动带上同一个 Request ID,形如<44c2d8ff-49a6-4244-869f-75a2df11517a> Hello。
🎯 关键点:因为共享了同一份上下文,Service 里不需要声明为 request-scoped,也不需要任何参数传递,即可拿到 ID。
进阶玩法:追踪用户 IP、角色与多租户
Request ID 只是起点。同一套机制可以让任意请求级数据"自动随路"。例如在 Interceptor 里存下用户 IP:
// 在 Interceptor 中 const userIp = context.switchToHttp().getRequest().connection.remoteAddress; this.cls.set('ip', userIp); // 存进上下文 // 在 Service 中直接读取,无需传参 const userIp = this.cls.get('ip');配合类型安全(可为ClsStore定义字段),IDE 还能给出自动补全,进一步降低出错率。
兼容性与安全注意事项
选择上下文入口时,需留意传输协议与安全性差异:
| 入口 | REST | GraphQL | WebSocket | 微服务 |
|---|---|---|---|---|
| ClsMiddleware(推荐 HTTP) | ✔ | ✔ | ✖ | ✖ |
ClsGuard(enterWith) | ✔ | ✔ | ✔ | ✔ |
| ClsInterceptor | ✔ | ✔ | ✔ | ✔ |
- Express / Fastify均通过
ClsMiddleware支持,HTTP 场景首选它。 - WebSocket网关不识别全局绑定,需手动在 Gateway 上挂
ClsInterceptor。 - 安全提示:默认的
run()方式不会跨请求泄漏上下文;ClsGuard使用的enterWith在旧版 Node.js 上存在泄漏风险,务必尽早挂载,且不要在它之前使用依赖ClsService的增强器。(Node.js 24+ 已修复该问题。)
核心文件导读:想深挖源码看这里
- 中间件实现(挂载、生成 ID、存
req):packages/core/src/lib/cls-initializers/cls.middleware.ts - 拦截器与守卫实现:
packages/core/src/lib/cls-initializers/cls.interceptor.ts、cls.guard.ts - 可注入服务与
get/set/getIdAPI:packages/core/src/lib/cls.service.ts CLS_ID等上下文符号定义:packages/core/src/lib/cls.constants.ts
总结清单:5 分钟落地 Request ID 关联
- ✅ 安装:
npm install nestjs-cls - ✅ 注册模块:
ClsModule.forRoot({ global: true, middleware: { mount: true } }) - ✅ 打开开关:
generateId: true(可用idGenerator接上游X-Request-Id) - ✅ 封装日志器:
this.cls.getId()自动带出 ID - ✅ 按传输协议选入口:HTTP 用中间件,WebSocket 用拦截器
完成以上配置后,每个请求的日志都会自动携带同一 Request ID,跨 Controller、Service、日志器全程可追踪——NestJS 的日志关联难题,就此一键破解。
【免费下载链接】nestjs-clsA continuation-local storage (async context) module compatible with NestJS's dependency injection.项目地址: https://gitcode.com/gh_mirrors/ne/nestjs-cls
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考