Sentry 上报失败或堆栈不完整,是因为默认 sentry.Recover() 仅捕获 panic 类型且上报位置为 recover 调用点而非 panic 发生行;若 sentry.Init() 未在 main() 开头完成、未启用 AttachStacktrace、未对异步 goroutine 单独加 defer recover,则 panic 无法被捕获或缺失上下文与堆栈。

为什么 Gin 的 panic 会上报失败或堆栈不完整
默认的 sentry.Recover() 只捕获 panic 类型,但不会还原原始堆栈位置——它上报的是 recover 调用点,不是 panic 发生行。更隐蔽的问题是:如果中间件注册前 sentry.Init() 没完成,整个 HTTP 流程的 panic 都不会进 Sentry。
必须同时满足三个条件才真正生效:
-
sentry.Init()在main()最开头调用(早于gin.Default()) - 中间件里启用
AttachStacktrace: true(否则只报panic: interface conversion,无文件行号) - 每个关键 handler 都包裹在 defer recover 里,不能只靠全局中间件——比如异步 goroutine 中的 panic 不会触发中间件
如何写一个带上下文的 Sentry 中间件
单纯 defer sentry.Recover() 只能捕获 panic,但无法带上请求路径、用户 ID、Header 等信息。Sentry 默认不解析 *http.Request,得手动往 scope 塞。
推荐写法(含脱敏和路由标记):
func SentryMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
// 先设置请求级上下文
sentry.ConfigureScope(func(scope *sentry.Scope) {
scope.SetTag("path", c.Request.URL.Path)
scope.SetTag("method", c.Request.Method)
if uid := c.Request.Header.Get("X-User-ID"); uid != "" {
scope.SetTag("user_id", uid) // 注意:不建议塞 token 或密码
}
// 自动附加基础字段(需启用 HTTPIntegration)
scope.AddEventProcessor(sentry.HTTPRequestEventProcessor(c.Request))
})
<pre class="brush:php;toolbar:false;"> defer sentry.Recover()
c.Next()
}
}
注册时注意顺序:r.Use(SentryMiddleware()) 必须在所有其他中间件之前,否则 panic 可能被上游中间件提前吞掉。
普通 error 为什么不能用 CaptureMessage 上报
sentry.CaptureMessage("db timeout") 只发字符串,Sentry 无法聚类——100 次超时会生成 100 条独立 issue。而 sentry.CaptureException(err) 会提取 error 类型、堆栈、cause 链,Sentry 才能按 *net.OpError 归并。
常见误用场景:
- 数据库查询失败:
if err != nil { sentry.CaptureException(err) }✅ - HTTP client 返回非 2xx:
sentry.CaptureException(fmt.Errorf("api failed: %s", resp.Status))✅ - 主动 throw 错误:
sentry.CaptureMessage("invalid input")❌(应包装成 error 并用 CaptureException)
goroutine panic 为什么总漏报
Gin 的中间件只覆盖主 goroutine。像 go func() { ... }() 或定时任务里的 panic,根本不会经过中间件,recover() 也捕不到。
解决办法不是全局加 recover,而是针对性加固:
- 所有显式启动的 goroutine,开头加
defer sentry.Recover() - 第三方库的回调(如 Kafka consumer、WebSocket handler),在其入口函数里手动 defer
- 避免在
init()里起 goroutine——此时 Sentry 还没初始化,panic 会直接 crash
最易忽略的一点:sentry.Flush() 要配超时。网络抖动时,没 flush 的事件会丢,尤其在进程退出前(如 SIGTERM)必须显式 sentry.Flush(5 * time.Second)。











