NestJS+AWS Lambda集成Sentry:部分错误未稳定上报问题求助
我基于NestJS创建了一个极简项目,仅包含一个抛出错误的控制器。同时自定义了Sentry拦截器、SentryService,并在模块中完成配置。将项目部署到AWS Lambda并关联API网关后,连续发起10次请求时,日志每次都显示sending error to sentry account,但Sentry后台仅能收到3-9条错误(每次测试数量不同)。本地非Lambda环境下所有错误均能正常上报,尝试添加Sentry.flush(2000)及使用第三方NestJS-Sentry集成模块后问题依旧。推测是Lambda执行结束前Sentry未完成错误发送,请问如何实现稳定上报?
项目代码
控制器代码
@Controller export class TestAppController { @Get() doSmth() { throw new Error('Error just happened') } }
Sentry拦截器代码
@Injectable() export class SentryApiInterceptor implements NestInterceptor { constructor(private sentryService: SentryService) {} intercept(context: ExecutionContext, next: CallHandler): Observable<any> { const request = context.switchToHttp().getRequest(); const span = this.sentryService.getRequestSpan(request, { op: `Route Handler`, }); return next.handle().pipe( catchError((error) => { const skipCapture = error instanceof HttpException && error.getStatus() < 500; if (!skipCapture) { console.log(`sending error to sentry account`); Sentry.captureException(error, span.getTraceContext()); } return throwError(() => error); }), finalize(async () => { span.finish(); }), ); } }
SentryService代码
@Injectable() export class SentryService { get span(): Span { return Sentry.getCurrentHub().getScope().getSpan(); } public getRequestSpan(request: Request, spanContext: SpanContext) { const { method, headers, url } = request; const transaction = Sentry.startTransaction({ name: `Route: ${method} ${url}`, op: 'transaction', }); Sentry.getCurrentHub().configureScope((scope) => { scope.setSpan(transaction); scope.setContext('http', { method, url, headers, }); }); const span = Sentry.getCurrentHub().getScope().getSpan(); span.startChild(spanContext); return span; } startChild(spanContext: SpanContext) { return this.span.startChild(spanContext); } }
模块配置代码
@Module({ providers: [ SentryService, { provide: APP_INTERCEPTOR, useClass: SentryApiInterceptor, }, ], controllers: [TestAppController], }) export class TestAppModule {}
Lambda处理函数代码
let cachedServer: Server; Sentry.init({ dsn: 'my-dsn', debug: true, }); async function bootstrapServer(): Promise<Server> { if (!cachedServer) { const expressApp = express(); const nestApp = await NestFactory.create( TestAppModule, new ExpressAdapter(expressApp), ); nestApp.use(eventContext()); await nestApp.init(); cachedServer = createServer(expressApp, undefined, undefined); } return cachedServer; } export const handler: Handler = async (event: any, context: Context) => { cachedServer = await bootstrapServer(); return proxy(cachedServer, event, context, 'PROMISE').promise; };
核心问题分析
AWS Lambda的执行环境是短暂且可能复用的,Sentry默认采用异步上报机制,Lambda执行结束时会直接冻结进程,导致未完成的上报请求被中断。即使调用Sentry.flush(),如果时机不对或未等待其完成,依然会丢失上报数据。
具体修正步骤
1. 错误捕获后等待上报完成
修改拦截器的catchError逻辑,将回调改为异步函数,在捕获错误并调用captureException后,等待Sentry.flush()完成再抛出错误:
catchError(async (error) => { const skipCapture = error instanceof HttpException && error.getStatus() < 500; if (!skipCapture) { console.log(`sending error to sentry account`); Sentry.captureException(error, span.getTraceContext()); // 设置足够长的超时时间,确保上报完成 await Sentry.flush(5000); } return throwError(() => error); }),
2. 清理并重置Sentry上下文
Lambda会复用执行环境,当前的事务创建逻辑可能导致上下文污染。在拦截器中添加Scope的推送与清理:
intercept(context: ExecutionContext, next: CallHandler): Observable<any> { const request = context.switchToHttp().getRequest(); // 推送新的Scope,隔离当前请求上下文 Sentry.getCurrentHub().pushScope(); const span = this.sentryService.getRequestSpan(request, { op: `Route Handler`, }); return next.handle().pipe( // ... 原有catchError逻辑 finalize(async () => { span.finish(); await Sentry.flush(5000); // 弹出当前Scope,恢复到之前的状态 Sentry.getCurrentHub().popScope(); }), ); }
3. 调整Lambda handler的事件循环配置
在handler中显式设置callbackWaitsForEmptyEventLoop为true,确保Lambda等待所有异步操作完成后再结束:
export const handler: Handler = async (event: any, context: Context) => { context.callbackWaitsForEmptyEventLoop = true; cachedServer = await bootstrapServer(); const result = await proxy(cachedServer, event, context, 'PROMISE').promise; // 额外执行一次flush,兜底确保所有上报完成 await Sentry.flush(5000); return result; };
4. 优化Sentry初始化配置
针对Lambda环境调整初始化参数,开启会话追踪并关闭不必要的调试:
Sentry.init({ dsn: 'my-dsn', debug: process.env.NODE_ENV === 'development', autoSessionTracking: true, tracesSampleRate: 1.0, environment: process.env.NODE_ENV || 'production', });
验证方法
连续发起20次以上请求,检查Sentry后台是否能接收所有错误日志,同时查看Lambda日志确认每次请求都执行了Sentry.flush()操作。
内容的提问来源于stack exchange,提问作者Tarmo

