You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.10 19:10:34