go 中 recover 仅能捕获当前 goroutine 的 panic,无法处理子协程、定时任务或 context.cancel 等场景;http 中间件(如 gin recovery)仅作用于 handler goroutine,非全局;子协程必须各自用 defer+recover 处理,且 recover 后应立即返回或清理资源。

Go 没有真正意义上的全局异常处理,recover 只能捕获当前 goroutine 的 panic,且对普通 error 完全无效。 你看到的“全局 recover 中间件”只是 HTTP handler 级别的兜底,对子 goroutine、定时任务、消息消费等场景完全失效。想靠它拦住所有崩溃,会漏掉 80% 的真实问题。
为什么 Gin 的 Recover 中间件不等于全局异常处理
它只在 HTTP 请求生命周期内起作用:从 c.Next() 开始到 handler 返回为止,且仅限该 goroutine。常见盲区包括:
- 你在 handler 里启了个
go func() { ... panic("oops") }()——recover完全捕不到 - DB 查询超时返回的是
context.DeadlineExceeded(一个error),不是 panic,中间件根本不会触发 - panic 发生在 middleware 自身或
c.Next()之前(比如路由匹配阶段),Recover还没执行就被跳过了 - 错误堆栈里没有 traceID 或用户上下文,日志查起来像盲人摸象
如何让 recover 和日志真正联动
关键不是“捕获”,而是“带上上下文再记录”。裸调 recover() 只拿到一个 interface{},必须立刻结合当前 context 补全信息:
初始化并管理阿里云SDK Go 客户端,涵盖 sync.Once 单例、goroutine 安全、endpoint 与 region 配置、VPC 端点及调试。
- 在中间件开头就从
c.Request.Context()提取trace_id、user_id等字段,存到局部变量 -
recover()后立即用log.Printf或结构化日志库(如zap)写入,显式传入 traceID 和堆栈:debug.PrintStack() - 别只打
err.Error()—— panic 值可能是字符串、error接口甚至自定义 struct,用fmt.Sprintf("%+v", r)才能看到完整值 - HTTP 响应体里不要直接塞 panic 值(暴露内部细节),统一转成
{"code": "500", "msg": "Internal Server Error"}
goroutine 内 panic 怎么安全捕获
子协程崩溃无法被外层 recover 拦截,必须每个 goroutine 自己负责:
- 所有
go func()内部必须包一层defer+recover,否则 panic 会直接杀死整个程序 - 用
errgroup.Group替代裸go:它自动等待所有子任务,并聚合 error;即使某个子任务 panic,eg.Wait()也会返回非 nil error - 对长期运行的 goroutine(如消费者),recover 后建议加退避重试逻辑,避免反复 panic 占满 CPU
- 切忌在 defer 里只做
log.Println就完事——要上报监控(如 Prometheus counter)、发告警、甚至写入失败队列
真正难的不是写 recover,而是决定哪些 panic 该被 recover、哪些该让程序立刻退出(比如配置加载失败、证书解析错误)。业务代码里混用 error 和 panic 是最大隐患——前者该检查,后者该杜绝。日志里出现 “recovered from panic: xxx” 不代表稳了,只说明你把本该提前发现的逻辑漏洞,拖到了运行时才爆出来。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










