recover必须在defer中调用才有效,否则返回nil;需在HTTP handler开头defer匿名函数调用recover,捕获panic并记录堆栈、返回500;它仅对当前goroutine的panic有效,无法处理goroutine泄漏、context取消、系统级崩溃等。

recover 必须在 defer 中调用才有效
Go 的 recover 不是全局异常捕获机制,它只在当前 goroutine 的 panic 正在被传播、且尚未退出时生效;如果没用 defer 包裹,recover 会直接返回 nil,什么也拦不住。
常见错误是把 recover 写在普通函数体里,比如:
func handle(req *http.Request) {
if r := recover(); r != nil { /* 永远进不来 */ }
// ...
}正确姿势是:
- 在 HTTP handler 入口或中间件中立即
defer一个匿名函数 - 该匿名函数内调用
recover(),并做日志、返回 500 或 fallback 响应 - 注意:不能在子 goroutine(如
go func(){}())里 defer —— 那个 goroutine panic 了,主 handler 看不到
HTTP handler 中 recover 的典型结构
标准 net/http handler 是最常需要隔离 panic 的场景。每个请求跑在一个独立 goroutine 里,但 panic 会终止整个 goroutine,不处理就会丢请求、无响应。
立即学习“go语言免费学习笔记(深入)”;
推荐写法(带基础错误隔离):
func myHandler(w http.ResponseWriter, r *http.Request) {
defer func() {
if r := recover(); r != nil {
log.Printf("panic in %s %s: %v", r.Method, r.URL.Path, r)
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
}
}()
// 实际业务逻辑,可能触发 panic(如 nil pointer、slice out of bounds)
doSomething(r)
}关键点:
- 必须在 handler 函数开头就
defer,否则中间 panic 时前面的逻辑可能已修改 response header,再调http.Error会 panic("http: multiple response.WriteHeader calls") - 不要试图在
recover后继续执行后续业务代码 —— 栈已损坏,行为不可靠 - 如果用了第三方中间件(如 chi、gin),优先查它们是否内置 recover 中间件(如
chi.Middlewares.Recoverer),避免重复 defer
recover 捕获不到的“异常”类型
recover 只对 panic 有效,对其他常见服务端问题完全无感:
- goroutine 泄漏(忘记 close channel、死循环不退出)→ 不 panic,
recover不起作用 - context 超时或取消 → 返回
ctx.Err(),需主动检查,不是 panic - syscall 级崩溃(如 cgo 调用 segfault)→ Go runtime 可能直接终止进程,
recover无法拦截 - 内存溢出(OOM kill)→ OS 杀进程,Go 层无感知
所以别指望靠 recover 解决所有稳定性问题。它只是防止单个请求 panic 导致整个 handler goroutine 静默失败的兜底手段。
recover 后的日志和可观测性要点
只打印 r 的 %v 输出往往信息不足,比如 panic: runtime error: invalid memory address or nil pointer dereference 没有堆栈,很难定位。
建议增强方式:
- 用
debug.PrintStack()或runtime.Stack(buf, false)获取完整堆栈(注意 buf 大小,避免截断) - 记录请求标识(如
r.Header.Get("X-Request-ID")),方便关联日志 - 区分 panic 类型:对已知可预期 panic(如自定义
panic(errors.New("validation failed")))可降级为 warn 日志;对未知 panic(如 nil pointer)标为 error 并告警 - 避免在 recover 块里调用可能 panic 的函数(比如往已关闭的 channel 发送、操作已释放的 unsafe.Pointer)
真正难的从来不是加 defer 和 recover,而是 panic 发生后,你有没有足够上下文还原现场 —— 这部分得靠日志结构、trace ID、堆栈完整性来补。


















