Go HTTP handler 中 panic 会静默返回 500,必须手动 recover 并用 debug.PrintStack() 或 debug.Stack() 记录完整堆栈;recover 仅作用于当前 goroutine,子 goroutine 需单独处理;panic 仅用于不可恢复错误,业务错误应使用 error。

Go 后端的 panic 不会自动被捕获,HTTP handler 里直接 panic 就静默 500 —— 必须手动加 recover,且只对当前 goroutine 生效。
HTTP handler 中 panic 导致静默失败的典型现象
你写了个 handler,里面不小心访问了 nil map 或越界切片,本地测试没报错,上线后接口偶尔返回空响应或 500,日志里却找不到堆栈 —— 这不是 bug 没触发,是 panic 被吞了。
- Go 的
http.ServeHTTP默认不 recover,panic 后连接直接断开 - 错误信息只输出到 stderr(通常被忽略),
log不会自动记录 - 前端看到的是空 body + 500,无法区分是业务逻辑错还是崩溃
Recovery 中间件必须调用 debug.PrintStack()
只 recover() 并打印 err 值远远不够,它只返回 panic 的 message,比如 "assignment to entry in nil map",根本定位不到哪行代码出的问题。
- 必须搭配
runtime/debug.Stack()或debug.PrintStack()获取完整调用栈 - 注意:
debug.PrintStack()输出到 os.Stderr,生产环境建议用log.Printf("%s", debug.Stack()) - 别在 recover 里 panic 其他东西,否则二次崩溃,堆栈更难读
子 goroutine 的 panic 完全逃逸 Recovery 中间件
你在 handler 里起个 go func() {}() 做异步操作,里面 panic 了?中间件完全捕获不到 —— recover 只作用于当前 goroutine。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
立即学习“go语言免费学习笔记(深入)”;
- 每个独立 goroutine 都得自己包一层
defer+recover - 常见坑:日志异步写入、定时清理、消息队列消费者
- 示例写法:
go func() { defer func() { recover() }(); doAsyncWork() }()
Recovery 不该替代 error 处理
把所有错误都塞进 panic 再 recover,等于把可恢复的业务错误当成崩溃处理 —— 这会让错误流混乱、日志爆炸、监控失真。
-
error用于文件读取失败、参数校验不通过等预期错误 -
panic应限于初始化失败、配置硬编码错误、断言失败等“程序不该继续运行”的场景 - HTTP 状态码要显式设置:
w.WriteHeader(http.StatusNotFound),不能靠 recover 自动推导
真正麻烦的不是写 recovery,而是判断哪些 panic 值得 recover、哪些该让程序立刻挂掉。越靠近边界(如 HTTP 入口)越该 recover,越深入业务层越该用 error 显式传递 —— 这个分界点,比代码本身更难把握。

















