NestJS中如何在Sequelize日志函数注入服务依赖并关联请求UUID记录查询日志
解决NestJS中Sequelize查询日志关联请求UUID的问题
要实现把Sequelize的查询日志关联到每个请求的UUID并写入Logs表,核心难点是在全局的Sequelize日志回调中获取当前请求的上下文(也就是UUID)。因为Sequelize的日志函数是全局配置的,默认无法直接拿到请求级别的数据,这里我们可以用Node.js的AsyncLocalStorage来实现请求上下文的传递,具体步骤如下:
1. 实现请求上下文存储服务
首先创建一个服务,用AsyncLocalStorage在异步操作链中共享当前请求的UUID,确保并发请求之间不会串号:
// src/common/services/request-context.service.ts import { Injectable } from '@nestjs/common'; import { AsyncLocalStorage } from 'async_hooks'; @Injectable() export class RequestContextService { private readonly als = new AsyncLocalStorage<string>(); // 设置当前请求的UUID到上下文 setRequestId(requestId: string): void { this.als.run(requestId, () => {}); } // 获取当前请求的UUID getRequestId(): string | undefined { return this.als.getStore(); } }
2. 更新LoggingInterceptor,注入上下文服务
修改你的拦截器,在生成UUID后把它存入上下文,同时初始化日志记录时给queries字段设默认空数组:
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common'; import { Observable } from 'rxjs'; import { tap, finalize } from 'rxjs/operators'; import { randomUUID } from 'crypto'; import { Log } from './logs/log.model'; import { RequestContextService } from './common/services/request-context.service'; @Injectable() export class LoggingInterceptor implements NestInterceptor { constructor(private readonly requestContextService: RequestContextService) {} intercept(context: ExecutionContext, next: CallHandler): Observable<any> { const [req] = context.getArgs(); const requestId = randomUUID(); // 将UUID存入异步上下文,后续Sequelize的日志回调可以拿到它 this.requestContextService.setRequestId(requestId); // 初始化日志记录,queries默认空数组 Log.create({ id: requestId, request: req.body, queries: [] }).catch(err => console.error('初始化日志失败:', err)); return next.handle().pipe( // 响应返回时更新日志的response字段 tap((responseData) => { Log.update( { response: responseData }, { where: { id: requestId } } ).catch(err => console.error('更新响应日志失败:', err)); }), // 请求结束后销毁上下文,避免内存泄漏 finalize(() => { this.requestContextService.als.exit(); }) ); } }
3. 用SequelizeModule.forRootAsync注入上下文服务,关联查询日志
原来的SequelizeModule.forRoot是同步的,无法注入服务,所以改用forRootAsync来获取RequestContextService实例,在日志回调中拿到当前请求的UUID,然后把查询信息追加到对应日志的queries字段:
// app.module.ts import { Module } from '@nestjs/common'; import { SequelizeModule } from '@nestjs/sequelize'; import { ConfigService } from '@nestjs/config'; import { RequestContextService } from './common/services/request-context.service'; import { Log } from './logs/log.model'; import { CatsModule } from './cats/cats.module'; import { LoggingModule } from './logs/logging.module'; @Module({ imports: [ CatsModule, SequelizeModule.forRootAsync({ inject: [RequestContextService, ConfigService], useFactory: (requestContext: RequestContextService, config: ConfigService) => ({ ...config.get('db'), logging: (query, parameters) => { const currentRequestId = requestContext.getRequestId(); if (!currentRequestId) return; // 根据你的数据库类型调整追加数组的方式: // PostgreSQL用array_append,MySQL用JSON_ARRAY_APPEND Log.update( { queries: { [Sequelize.Op.concat]: [ Sequelize.col('queries'), [ { query, parameters, timestamp: new Date().toISOString() } ] ] } }, { where: { id: currentRequestId } } ).catch(err => console.error('更新查询日志失败:', err)); }, logQueryParameters: true }) }), LoggingModule ], providers: [RequestContextService], exports: [RequestContextService] }) export class AppModule {}
补充:Log模型的queries字段定义
确保你的Log模型中queries字段是JSON类型,默认值为空数组:
// src/logs/log.model.ts import { Column, Model, PrimaryKey, DataType } from '@nestjs/sequelize'; export class Log extends Model { @PrimaryKey @Column(DataType.UUID) id: string; @Column(DataType.JSON) request: any; @Column(DataType.JSON) response: any; @Column({ type: DataType.JSON, defaultValue: [] }) queries: Array<{ query: string; parameters: any[]; timestamp: string }>; }
关键注意事项
- 错误处理:所有日志相关的数据库操作都要加
catch,绝对不能因为日志写入失败影响主业务流程 - 性能优化:如果你的应用请求量很大,频繁更新
Logs表可能会带来性能瓶颈,可以考虑用消息队列(比如BullMQ)异步批量写入日志 - 数据库兼容性:不同数据库的数组/JSON操作语法不同,比如PostgreSQL用
array_append,MySQL用JSON_ARRAY_APPEND,要根据你的数据库调整Log.update中的queries更新逻辑 - 上下文隔离:
AsyncLocalStorage会自动隔离不同请求的上下文,不用担心并发请求中UUID串号的问题
内容的提问来源于stack exchange,提问作者Bilal AL Massry
相关产品推荐
相关产品推荐

