TypeOrm自定义日志器获取JWT时遇switchToHttp未定义报错
解决TypeORM日志器中获取用户标识的问题
核心问题分析
你遇到的TypeError: Cannot read property 'switchToHttp' of undefined是因为ExecutionContext不会自动注入到TypeORM的Logger类中。ExecutionContext是Nest专门为拦截器、守卫、过滤器等组件提供的上下文对象,TypeORM的Logger实例无法直接获取到它,所以你声明的ctt属性始终是undefined。
要在TypeORM日志中添加用户标识,正确的做法是使用AsyncLocalStorage来在请求上下文之间传递用户ID——TypeORM的查询通常属于某个请求的异步调用链,AsyncLocalStorage可以保存请求级别的上下文数据,让日志器能直接读取到当前请求的用户信息。
实现步骤
1. 创建请求上下文存储
用Node.js的AsyncLocalStorage实现请求级别的上下文存储:
// src/common/request-context.ts import { AsyncLocalStorage } from 'async_hooks'; export interface RequestContext { userId?: string; } export const requestContextStorage = new AsyncLocalStorage<RequestContext>();
2. 编写拦截器存入用户ID
创建Nest拦截器,在请求进入时提取JWT中的用户ID,存入上下文存储:
// src/common/request-context.interceptor.ts import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common'; import { Observable } from 'rxjs'; import { requestContextStorage } from './request-context'; import { Request } from 'express'; @Injectable() export class RequestContextInterceptor implements NestInterceptor { intercept(context: ExecutionContext, next: CallHandler): Observable<any> { const request = context.switchToHttp().getRequest<Request>(); // 这里根据你的JWT解析逻辑调整,假设用户信息存在request.user中 const userId = request.user?.id; return requestContextStorage.run({ userId }, () => next.handle()); } }
3. 全局注册拦截器
在AppModule中全局注册这个拦截器,确保所有请求都能触发上下文存储:
// src/app.module.ts import { Module } from '@nestjs/common'; import { APP_INTERCEPTOR } from '@nestjs/core'; import { RequestContextInterceptor } from './common/request-context.interceptor'; @Module({ providers: [ { provide: APP_INTERCEPTOR, useClass: RequestContextInterceptor, }, ], }) export class AppModule {}
4. 修改CustomLogger获取用户ID
更新你的TypeORM Logger,从上下文存储中获取用户ID并添加到日志:
/* eslint-disable prettier/prettier */ import { Logger, QueryRunner } from 'typeorm'; import { createLogger, Logger as winstonLogger, transports, format } from 'winston'; import { Format } from 'logform'; import { requestContextStorage } from './common/request-context'; export class CustomLogger implements Logger { private readonly queryLogger: winstonLogger; private readonly winstonFormat: Format; constructor(){ this.winstonFormat= format.printf(({message, userId})=> { return `${new Date().toISOString()} [userId: ${userId ?? 'unknown'}] - ${message}` }); const options = (filename: string) => ({ transports: [new transports.Console({level:'debug'})], format: format.combine( format.timestamp(), this.winstonFormat ) }); this.queryLogger = createLogger(options('query.log')) } logQuery(query: string, parameters?: any[], _queryRunner?: QueryRunner) { const context = requestContextStorage.getStore(); const userId = context?.userId; this.queryLogger.log({ level: 'debug', message: `Query: ${query}, Parameters: ${JSON.stringify(parameters)}`, timestamp: Date.now(), label: 'query', userId: userId ?? 'unknown' }) } logQueryError(error: string | Error, query: string, parameters?: any[], queryRunner?: QueryRunner) { const context = requestContextStorage.getStore(); const userId = context?.userId; this.queryLogger.error({ level: 'error', message: `Query Error: ${error.toString()}, Query: ${query}, Parameters: ${JSON.stringify(parameters)}`, timestamp: Date.now(), label: 'query-error', userId: userId ?? 'unknown' }) } logQuerySlow(time: number, query: string, parameters?: any[], queryRunner?: QueryRunner) { const context = requestContextStorage.getStore(); const userId = context?.userId; this.queryLogger.warn({ level: 'warn', message: `Slow Query (${time}ms): ${query}, Parameters: ${JSON.stringify(parameters)}`, timestamp: Date.now(), label: 'slow-query', userId: userId ?? 'unknown' }) } logSchemaBuild(message: string, queryRunner?: QueryRunner) { this.queryLogger.info({ level: 'info', message, timestamp: Date.now(), label: 'schema-build', userId: 'system' }) } logMigration(message: string, queryRunner?: QueryRunner) { this.queryLogger.info({ level: 'info', message, timestamp: Date.now(), label: 'migration', userId: 'system' }) } log(level: 'log' | 'info' | 'warn', message: any, queryRunner?: QueryRunner) { const context = requestContextStorage.getStore(); const userId = context?.userId ?? 'unknown'; this.queryLogger.log({ level: level === 'log' ? 'info' : level, message, timestamp: Date.now(), label: 'general', userId }) } }
5. 配置TypeORM使用CustomLogger
在你的TypeORM配置中指定使用这个自定义日志器:
// src/config/typeorm.config.ts import { TypeOrmModuleOptions } from '@nestjs/typeorm'; import { CustomLogger } from './common/custom.logger'; export const typeOrmConfig: TypeOrmModuleOptions = { // 你的数据库连接配置... logger: new CustomLogger(), };
注意事项
- 确保你的JWT认证模块已经正确配置,用户信息能正确解析到
request.user中,根据实际项目调整拦截器里的userId提取逻辑。 - 对于非请求触发的数据库操作(比如定时任务、启动时的Schema构建),userId会显示为
unknown或者你可以手动设置为system标识系统操作。 - AsyncLocalStorage是Node.js v12.17.0+支持的API,确保你的Node版本符合要求。
内容的提问来源于stack exchange,提问作者Siddhinath
相关产品推荐
相关产品推荐

