recover 可在 defer 函数中捕获同 goroutine 的 panic 并恢复执行,但无法跨 goroutine 拦截;必须直接调用,不可嵌套;log.Panic 可被 recover 拦截,log.Fatal 则直接退出进程。

panic 会中断程序,但 recover 可以拦截
Go 没有传统 try-catch,panic 触发后默认终止 goroutine,若在 defer 中调用 recover,可捕获当前 goroutine 的 panic 值并恢复执行。注意:它只对同 goroutine 内、且尚未返回的 panic 有效,跨 goroutine 无法捕获。
常见误用是把 recover 放在普通函数里——它必须出现在 defer 调用的函数中才起作用:
func risky() {
defer func() {
if r := recover(); r != nil {
log.Printf("panic recovered: %v", r)
}
}()
panic("something went wrong")
}
-
recover()必须在defer函数内直接调用,不能包在另一层函数里再调用 - 如果 panic 发生在子 goroutine,主 goroutine 的 defer 不会生效
- recover 后程序继续执行 defer 函数之后的代码,不是 panic 发生点
log.Panic 和 log.Fatal 的区别在于是否触发 runtime.Goexit
log.Panic 底层调用 panic,能被 recover 拦截;log.Fatal 底层调用 os.Exit(1),进程立即退出,无法拦截也无法执行 defer。
- 想统一兜底处理崩溃?用
log.Panic+ 全局 defer recover,而不是log.Fatal - 某些初始化失败(如配置加载)确实该硬退出?用
log.Fatal,避免残留状态 - 第三方库内部用了
log.Fatal?你无法拦截,只能提前校验或改用其非 fatal 版本(如有)
http.Server 的 panic 不会自动被 recover,需手动包装 handler
标准 http.ServeMux 或 http.HandlerFunc 中 panic 会导致整个 server 崩溃(除非启用了 Server.ErrorLog,但仅记录不恢复)。必须显式 wrap handler:
立即学习“go语言免费学习笔记(深入)”;
func recoverHandler(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() {
if err := recover(); err != nil {
log.Printf("HTTP panic: %v", err)
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
}
}()
h.ServeHTTP(w, r)
})
}
- 不要只 wrap 主 handler,中间件链中任一环节 panic 都需覆盖
- 使用
http.Server.Handler字段设置前统一 wrap,比每个路由单独加更可靠 - 注意:recover 后 response 已可能部分写出,
http.Error可能失败,建议先检查w.Header().Get("Content-Type")是否为空再写
runtime/debug.Stack() 是调试 panic 根源的关键辅助
recover 返回的是 panic 参数(比如 panic("boom") 返回字符串),不是堆栈。要获取完整调用链,得在 defer 中调用 runtime/debug.Stack():
defer func() {
if r := recover(); r != nil {
stack := debug.Stack()
log.Printf("panic: %v\nstack: %s", r, stack)
}
}()
- debug.Stack() 返回的是字节切片,记得转
string才好打印 - 生产环境慎用——频繁调用会影响性能,可配合环境变量开关(如
os.Getenv("DEBUG_STACK") == "1") - 若 panic 来自 cgo 或 syscall,stack 可能不完整,需结合
gdb或pprof进一步分析
真正难定位的 panic 往往发生在异步逻辑、定时器回调、或 context cancel 触发的 cleanup 函数里——这些地方容易漏掉 defer 包裹,也最容易忽略 recover 的作用域限制。










