NestJS集成Sentry时控制器无法接收数据的问题排查
问题:NestJS接收Sentry Envelope请求时数据为空
我正在开发一个用Sentry做错误追踪的项目,错误数据要发送到本地搭建的简化版Sentry服务器,核心需求是正确处理Sentry请求并获取错误数据用于分析。
在NestJS里写了个控制器处理/api/:projectId/envelope请求,触发错误(比如调用myUndefinedFunction(1))后,Sentry本该向服务器发数据,但控制器里的envelopeData是空的。DSN里的projectId是4,请求能到服务器,但Sentry的请求头没传过来,envelopeData也没有错误信息。
Sentry初始化代码
const Sentry = require('@sentry/node'); Sentry.init({ dsn: 'http://fde473b41d8547c092d69d8eceb2b9bd@127.0.0.1:3000/4', }); myUndefinedFunction(1);
NestJS控制器代码
import { Controller, Post, Param, Body, HttpException, HttpStatus, } from '@nestjs/common'; @Controller('api/:projectId') export class EnvelopeController { @Post('/envelope') async handleEnvelope( @Param('projectId') projectId: string, @Body() envelopeData: any, ) { try { console.log('Received request for projectId:', projectId); console.log('Envelope Data:', JSON.stringify(envelopeData, null, 2)); return { status: 'success', message: 'Envelope processed' }; } catch (error) { console.error('Error during processing:', error); throw new HttpException('BAD REQUEST', HttpStatus.BAD_REQUEST); } } }
日志中间件代码
import { Injectable, NestMiddleware } from '@nestjs/common'; import { Request, Response, NextFunction } from 'express'; @Injectable() export class LoggerMiddleware implements NestMiddleware { use(req: Request, res: Response, next: NextFunction) { console.log('Request Headers:', req.headers); console.log('Request Body:', req.body); console.log('Request Path:', req.path); if (req.body instanceof Buffer) { console.log('Request Body (as string):', req.body.toString('utf-8')); } next(); } }
当前控制台输出
Request Headers: { host: '127.0.0.1:3000', connection: 'keep-alive', 'transfer-encoding': 'chunked' } Request Body: {} Request Path: / Get endpoint for projectId: 4 Envelope Data: {}
疑问
- 为什么请求能通过中间件,日志也显示了请求头,但Sentry的请求头没送达,
envelopeData为空? - 怎么调试并修复这个问题,让控制器能接收到错误数据?
已采取措施
- 确认Sentry已经用正确的DSN完成初始化
- 添加了日志中间件追踪请求头和请求体
预期控制台输出
{ exception: { values: [ [Object] ] }, event_id: 'd5816d366c61412eb92e298638cb308b', platform: 'node', contexts: { trace: { trace_id: '89b29266023447e2b9b781b51e226b54', span_id: 'bbfc1f0e90ac8c7e' }, runtime: { name: 'node', version: 'v20.12.1' }, app: { app_start_time: '2024-10-09T01:38:45.264Z', app_memory: 76492800 }, os: { kernel_version: '6.8.0-45-generic', name: 'Ubuntu Linux', version: '22.04' }, device: { boot_time: '2024-10-08T22:14:04.737Z', arch: 'x64', memory_size: 16478691328, free_memory: 6089416704, processor_count: 12, cpu_description: '11th Gen Intel(R) Core(TM) i5-11400H @ 2.70GHz', processor_frequency: 1622 }, // 其他ErrorEvent字段 } }
解决方案
问题原因分析
- 请求体解析不兼容:Sentry发送的Envelope请求是分块编码的二进制格式,而NestJS默认的body解析器只处理
application/json和x-www-form-urlencoded,无法解析这类请求,导致req.body为空。 - 路由匹配异常:日志显示请求路径为
/,但控制器监听的是/api/:projectId/envelope,说明Sentry实际发送的请求路径与预期不符,存在路由匹配错误。
修复步骤
1. 手动解析原始请求体
安装raw-body依赖后,修改控制器直接读取请求流并解析:
npm install raw-body
修改控制器代码:
import { Controller, Post, Param, Req, HttpException, HttpStatus, } from '@nestjs/common'; import * as rawBody from 'raw-body'; @Controller('api/:projectId') export class EnvelopeController { @Post('/envelope') async handleEnvelope( @Param('projectId') projectId: string, @Req() req: any, ) { try { console.log('Received request for projectId:', projectId); // 读取原始二进制请求体 const envelopeBuffer = await rawBody(req); const envelopeStr = envelopeBuffer.toString('utf-8'); console.log('Raw Envelope Data:', envelopeStr); // Sentry Envelope是多行JSON格式,按行解析 const lines = envelopeStr.split('\n').filter(line => line.trim()); const envelopeHeader = JSON.parse(lines[0]); const eventPayload = lines.length > 1 ? JSON.parse(lines[1]) : null; console.log('Parsed Header:', envelopeHeader); console.log('Parsed Event:', eventPayload); return { status: 'success', message: 'Envelope processed' }; } catch (error) { console.error('Processing Error:', error); throw new HttpException('BAD REQUEST', HttpStatus.BAD_REQUEST); } } }
2. 配置NestJS跳过默认解析
在主模块中针对/envelope路由禁用默认body解析,避免干扰原始请求:
import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common'; import { EnvelopeController } from './envelope.controller'; import { LoggerMiddleware } from './logger.middleware'; import { json, urlencoded } from 'express'; @Module({ controllers: [EnvelopeController], }) export class AppModule implements NestModule { configure(consumer: MiddlewareConsumer) { // 先应用日志中间件 consumer.apply(LoggerMiddleware).forRoutes('api/:projectId/envelope'); // 全局路由配置:非envelope路由用默认解析,envelope路由跳过 consumer .apply((req, res, next) => { if (req.path.includes('/envelope')) { next(); } else { json()(req, res, next); urlencoded({ extended: true })(req, res, next); } }) .forRoutes('*'); } }
3. 验证Sentry请求路径与头信息
开启Sentry调试模式,确认请求的URL和头信息是否正确:
const Sentry = require('@sentry/node'); Sentry.init({ dsn: 'http://fde473b41d8547c092d69d8eceb2b9bd@127.0.0.1:3000/4', debug: true, // 打印请求详情 }); myUndefinedFunction(1);
如果头信息缺失,检查CORS配置是否允许所有头:
import { Module } from '@nestjs/common'; import { EnvelopeController } from './envelope.controller'; import { CorsOptions, CorsMiddleware } from '@nestjs/common'; @Module({ controllers: [EnvelopeController], }) export class AppModule implements NestModule { configure(consumer: MiddlewareConsumer) { const corsOptions: CorsOptions = { origin: '*', allowedHeaders: '*', exposedHeaders: '*', }; consumer.apply(CorsMiddleware(corsOptions)).forRoutes('*'); } }
调试技巧
- 用
curl手动模拟请求验证控制器:
curl -X POST http://127.0.0.1:3000/api/4/envelope \ -H "X-Sentry-Auth: Sentry sentry_version=7, sentry_key=fde473b41d8547c092d69d8eceb2b9bd" \ -d '{"event_id": "test-id"}'
- 查看Sentry客户端调试日志,确认请求是否成功发送,以及请求的URL、头信息是否符合预期。
内容的提问来源于stack exchange,提问作者Yobachu
相关产品推荐
相关产品推荐

