如何基于Nest框架实现捕获调度器、消费者等非HTTP进程异常的Exception Filter?
嗨,我来帮你梳理下在Nest里处理调度器(比如@nestjs/schedule的定时任务)、消费者(比如MQ消息消费者)这类非HTTP场景异常的思路,完全贴合Nest的设计风格哦~
Nest内置的Exception Filter主要是为HTTP/WebSocket/RPC这类有请求响应链路的场景设计的,但对于后台跑的调度任务、MQ消费者这类“无请求上下文”的进程,我们可以通过自定义全局异常过滤器+上下文识别的方式来统一捕获处理,避免每个任务都写重复的try/catch。
一、创建全局通用的非HTTP异常过滤器
首先我们实现一个不依赖HTTP上下文的全局过滤器,它能捕获所有类型的异常,然后根据不同的场景(调度器/消费者/其他)做针对性处理:
import { ExceptionFilter, Catch, ArgumentsHost, Logger } from '@nestjs/common'; import { Reflector } from '@nestjs/core'; import { TASK_METADATA_KEY } from './track-task.decorator'; // 后面会用到这个装饰器 @Catch() // 捕获所有异常类型 export class GlobalNonHttpExceptionFilter implements ExceptionFilter { private readonly logger = new Logger(GlobalNonHttpExceptionFilter.name); constructor(private readonly reflector: Reflector) {} catch(exception: unknown, host: ArgumentsHost) { // 先识别当前的上下文类型(调度器/消费者/其他) const ctxType = host.getType(); const handler = host.getHandler(); // 1. 针对调度器任务:获取任务标识(如果有) const taskName = this.reflector.get<string>(TASK_METADATA_KEY, handler); if (taskName) { this.logger.error(`[调度器任务] ${taskName} 执行失败`, this.getErrorStack(exception)); // 这里可以扩展:比如发送告警通知、记录错误到监控平台、触发任务重试等 return; } // 2. 针对MQ消费者:可以根据上下文获取消息信息 if (ctxType === 'microservice') { const message = host.getArgByIndex(0); this.logger.error(`[MQ消费者] 处理消息失败,消息ID: ${message?.messageId}`, this.getErrorStack(exception)); // 这里可以扩展:比如nack消息、将消息转入死信队列等 return; } // 3. 其他非HTTP场景的兜底处理 this.logger.error(`[非HTTP进程] 捕获到异常`, this.getErrorStack(exception)); } // 辅助方法:格式化异常栈信息 private getErrorStack(exception: unknown): string | undefined { return exception instanceof Error ? exception.stack : undefined; } }
二、给调度器任务加标识(可选但推荐)
为了在过滤器里精准识别是哪个任务抛出的异常,我们可以自定义一个装饰器来标记任务名称:
import { SetMetadata } from '@nestjs/common'; export const TASK_METADATA_KEY = 'taskName'; export const TrackTask = (taskName: string) => SetMetadata(TASK_METADATA_KEY, taskName);
然后在你的定时任务上使用这个装饰器:
import { Cron } from '@nestjs/schedule'; import { TrackTask } from './track-task.decorator'; @Cron('0 * * * *') // 每小时执行一次 @TrackTask('小时级数据同步任务') async syncHourlyData() { // 这里是任务逻辑,可能抛出异常 throw new Error('数据源连接超时'); }
三、注册全局过滤器
要让这个过滤器生效,我们需要在Nest的容器里注册它,有两种方式:
方式1:全局注册(覆盖所有模块)
在main.ts里添加:
import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import { GlobalNonHttpExceptionFilter } from './global-non-http-exception.filter'; import { Reflector } from '@nestjs/core'; async function bootstrap() { const app = await NestFactory.create(AppModule); // 注册全局过滤器,需要注入Reflector依赖 app.useGlobalFilters(new GlobalNonHttpExceptionFilter(app.get(Reflector))); await app.listen(3000); } bootstrap();
方式2:模块内注册(仅对当前模块生效)
如果只希望过滤器作用于调度器模块或消费者模块,可以在对应模块的providers里注册:
import { Module } from '@nestjs/common'; import { ScheduleModule } from '@nestjs/schedule'; import { GlobalNonHttpExceptionFilter } from './global-non-http-exception.filter'; import { APP_FILTER } from '@nestjs/core'; import { Reflector } from '@nestjs/core'; @Module({ imports: [ScheduleModule.forRoot()], providers: [ Reflector, { provide: APP_FILTER, useClass: GlobalNonHttpExceptionFilter, }, ], }) export class SchedulerModule {}
四、针对MQ消费者的额外提示
如果你用的是@nestjs/microservices提供的MQ客户端(比如RabbitMQ、Kafka),还可以结合Microservice专属的Exception Filter,只需要在过滤器的@Catch()里指定异常类型,或者在catch方法里通过host.getType()判断是microservice上下文,然后处理消息的ack/nack逻辑,完全和上面的全局过滤器兼容。
这样处理下来,既符合Nest“统一异常管理”的设计理念,又能完美覆盖调度器、消费者这类后台进程的异常场景,不用再担心任务悄悄失败却找不到日志啦~
备注:内容来源于stack exchange,提问作者Eren Yusuf Duran

