Nest.js中如何在全局日志上下文注入JWT验证后的用户数据?
Nest.js 全局日志自动注入JWT用户数据的通用方案
核心思路
利用Node.js的AsyncLocalStorage实现请求级别的上下文共享,把JWT验证后的用户数据存入上下文,让nestjs-pino在打印任何日志时都能自动从上下文取到用户信息,不用在控制器和服务之间手动传参。
具体实现步骤
1. 封装请求上下文存储
新建request.context.ts文件,用AsyncLocalStorage存储请求相关的用户数据:
import { AsyncLocalStorage } from 'async_hooks'; // 可根据你的JWT用户结构定义更具体的类型 export interface RequestContext { user?: { id: string; username: string; // 其他用户字段 }; } export const requestContext = new AsyncLocalStorage<RequestContext>();
2. 拦截请求并注入用户上下文
编写全局拦截器,在JWT验证完成后将用户数据存入上下文:
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common'; import { Observable } from 'rxjs'; import { requestContext } from './request.context'; @Injectable() export class RequestContextInterceptor implements NestInterceptor { intercept(context: ExecutionContext, next: CallHandler): Observable<any> { const request = context.switchToHttp().getRequest(); // 假设JWT守卫已将用户信息存入request.user const ctxData = { user: request.user }; // 启动上下文,后续整个请求链路均可访问该数据 return requestContext.run(ctxData, () => next.handle()); } }
在AppModule中全局注册这个拦截器:
import { Module } from '@nestjs/common'; import { APP_INTERCEPTOR } from '@nestjs/core'; import { RequestContextInterceptor } from './request.context.interceptor'; @Module({ providers: [ { provide: APP_INTERCEPTOR, useClass: RequestContextInterceptor, }, ], }) export class AppModule {}
3. 配置nestjs-pino从上下文取用户数据
修改nestjs-pino的配置,让customProps从我们的上下文获取用户数据:
import { Module } from '@nestjs/common'; import { LoggerModule } from 'nestjs-pino'; import { requestContext } from './request.context'; @Module({ imports: [ LoggerModule.forRoot({ pinoHttp: { customProps: () => { // 每次生成日志时从上下文读取用户数据 const currentContext = requestContext.getStore(); return { user: currentContext?.user ?? null, }; }, // 你的其他pino配置(如日志格式、级别、输出方式等) transport: { target: 'pino-pretty', }, }, }), ], }) export class AppModule {}
4. 服务中使用pino日志
在服务里注入PinoLogger(而非Nest默认的Logger),直接打印日志即可自动带上用户信息:
import { Injectable } from '@nestjs/common'; import { PinoLogger } from 'nestjs-pino'; @Injectable() export class UserService { constructor(private readonly logger: PinoLogger) { this.logger.setContext(UserService.name); } async getUserInfo(userId: string) { this.logger.info(`查询用户信息,ID: ${userId}`); // 该日志会自动包含user字段,无需手动传递 // 业务逻辑... } }
原理说明
AsyncLocalStorage会在整个请求调用链(控制器→服务→子服务→工具函数)中维护独立的上下文,无论在哪一层打印日志,pino的customProps都能从上下文拿到当前请求的用户数据,彻底解决服务日志中用户字段为空的问题。
内容的提问来源于stack exchange,提问作者Yaroslav-BV
相关产品推荐
相关产品推荐

