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

NestJS:异常抛出时如何执行Interceptor中的事件追踪

解决方案:异常场景下触发NestJS拦截器的事件追踪

这是NestJS里很典型的拦截器与全局异常过滤器执行顺序问题——当异常抛出时,全局过滤器会优先捕获并直接返回响应,跳过了拦截器中tap这类仅在流成功完成时才执行的逻辑。下面给你几个可行的解决思路,按推荐程度排序:

方案1:用RxJS的catchError + tap统一处理成功/失败追踪

修改你的EventsTrackerInterceptor,在流中加入catchError操作符来捕获异常,先记录错误追踪信息,再重新抛出异常让全局过滤器处理;同时保留tap处理正常成功的情况。这样无论成功还是失败,追踪逻辑都会被触发。

示例代码:

// events-tracker.interceptor.ts
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
import { Observable, throwError } from 'rxjs';
import { tap, catchError } from 'rxjs/operators';
import { EventTrackerService } from './event-tracker.service';

@Injectable()
export class EventsTrackerInterceptor implements NestInterceptor {
  constructor(private readonly eventTrackerService: EventTrackerService) {}

  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    // 提前获取类名和handler名称
    const handlerName = context.getHandler().name;
    const className = context.getClass().name;

    return next.handle().pipe(
      // 正常响应时记录成功事件
      tap((response) => {
        this.eventTrackerService.track({
          className,
          handlerName,
          status: 'success',
          responseData: response,
          timestamp: new Date(),
        });
      }),
      // 捕获异常时记录错误事件,再重新抛出给全局过滤器
      catchError((error) => {
        this.eventTrackerService.track({
          className,
          handlerName,
          status: 'error',
          errorMessage: error.message,
          timestamp: new Date(),
        });
        // 必须重新抛出异常,否则全局过滤器无法捕获
        return throwError(() => error);
      }),
    );
  }
}

这个方案的优势是:

  • 成功和失败的追踪逻辑都集中在拦截器里,便于统一维护
  • 完全不影响原有全局异常过滤器的功能
  • 能拿到完整的响应/错误数据

方案2:在全局异常过滤器中补充追踪逻辑

如果不想修改拦截器,也可以直接在AllExceptionsFilter中注入EventTrackerService,在处理异常前先记录追踪信息。

示例代码:

// all-exception.filter.ts
import { Catch, ExceptionFilter, ArgumentsHost, HttpException, HttpStatus } from '@nestjs/common';
import { EventTrackerService } from './event-tracker.service';

@Catch()
export class AllExceptionsFilter implements ExceptionFilter {
  constructor(private readonly eventTrackerService: EventTrackerService) {}

  catch(exception: unknown, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const request = ctx.getRequest();
    
    // 获取类名和handler名称
    const handlerName = host.getHandler().name;
    const className = host.getClass().name;

    // 记录异常追踪
    this.eventTrackerService.track({
      className,
      handlerName,
      status: 'error',
      errorMessage: exception instanceof Error ? exception.message : 'Unknown error',
      path: request.url,
      timestamp: new Date(),
    });

    // 原有标准化错误响应逻辑
    const status = exception instanceof HttpException 
      ? exception.getStatus() 
      : HttpStatus.INTERNAL_SERVER_ERROR;
    
    ctx.getResponse().status(status).json({
      statusCode: status,
      timestamp: new Date().toISOString(),
      path: request.url,
      message: exception instanceof Error ? exception.message : 'Internal server error',
    });
  }
}

这个方案的注意点:

  • 需要确保EventTrackerService在过滤器所在模块中可注入
  • 成功追踪在拦截器,失败追踪在过滤器,逻辑分散,后续维护成本略高
  • 如果有局部异常过滤器,需要重复添加追踪逻辑

方案3:用finalize操作符统一收尾

如果你不需要区分成功/失败的详细数据,只想记录“调用完成”事件,可以用RxJS的finalize操作符——它会在流无论成功还是错误终止时都执行。

示例代码:

// events-tracker.interceptor.ts
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
import { Observable } from 'rxjs';
import { finalize } from 'rxjs/operators';
import { EventTrackerService } from './event-tracker.service';

@Injectable()
export class EventsTrackerInterceptor implements NestInterceptor {
  constructor(private readonly eventTrackerService: EventTrackerService) {}

  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    const handlerName = context.getHandler().name;
    const className = context.getClass().name;
    const response = context.switchToHttp().getResponse();

    return next.handle().pipe(
      finalize(() => {
        // 通过响应状态码判断成功/失败
        const status = response.statusCode;
        this.eventTrackerService.track({
          className,
          handlerName,
          status: status >= 200 && status < 300 ? 'success' : 'error',
          timestamp: new Date(),
        });
      }),
    );
  }
}

这个方案的局限性是:无法直接拿到响应体或错误详情,适合只需要记录调用动作的场景。


内容的提问来源于stack exchange,提问作者Neeraj

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.06 22:57:33