recover必须在defer中调用才有效,且仅对当前goroutine的panic生效;子goroutine panic需各自recover;recover不是业务错误处理替代品,仅用于捕获不可预期崩溃。

recover 必须在 defer 中调用才有效
Go 的 recover 不是全局异常捕获机制,它只在当前 goroutine 的 panic 正在被传播、且尚未退出函数时生效。如果没用 defer 包裹,recover 会立刻返回 nil,什么也抓不到。
常见错误是写成这样:
func handler(w http.ResponseWriter, r *http.Request) {
if r := recover(); r != nil { // ❌ 永远为 nil
log.Printf("panic: %v", r)
}
panic("oops")
}
正确写法必须把 recover 放进 defer 函数里:
func handler(w http.ResponseWriter, r *http.Request) {
defer func() {
if r := recover(); r != nil {
log.Printf("panic in handler: %v", r)
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
}
}()
panic("oops") // ✅ 这里触发,defer 中的 recover 才能捕获
}
-
defer要紧挨着函数开头写,避免中间逻辑提前 return 或 panic 导致 defer 未注册 - 不要在 defer 里直接调用
recover()外部函数——必须是匿名函数或闭包,否则执行时机不对 - 多个 defer 会按后进先出顺序执行,但
recover只对当前 panic 有效,嵌套 panic 需要多层 defer 配合
HTTP handler 中 recover 后不能继续写响应体
一旦 http.ResponseWriter 已经写入 header(比如调用了 w.WriteHeader() 或首次调用 w.Write()),后续再 panic 并 recover,也无法“撤回”已发送的 header。此时强行写响应体可能触发 http: multiple response.WriteHeader calls 错误。
立即学习“go语言免费学习笔记(深入)”;
所以 recover 后的处理必须保守:
- 检查
w.Header().Get("Content-Type") == ""判断是否已写 header - 优先用
http.Error(),它内部会检测是否已写 header,未写则补 500 状态码和默认 header - 避免在 recover 后调用
w.WriteHeader(500)+w.Write([]byte(...))组合,容易双写 header - 如果 handler 内部已调用过
w.Write()(比如流式响应场景),recover 后只能记录日志,不能再操作w
recover 无法捕获子 goroutine 的 panic
HTTP handler 启动的子 goroutine(比如异步日志、后台任务)发生 panic,主 goroutine 的 defer+recover 完全无感知。这类 panic 会直接终止子 goroutine,并打印 stack trace 到 stderr,但不会影响当前请求响应。
OpenClaw 基础设施心跳监控与自动恢复:部署/卸载心跳守护进程,配置主机监控(Gateway、磁盘、内存、CPU、进程),设置飞书告警,自动重启故障Gateway。适用关键词:心跳监控、heartbeat daemon、服务器告警、自动重启、健康检查、auto recovery。
典型踩坑场景:
func handler(w http.ResponseWriter, r *http.Request) {
defer func() {
if r := recover(); r != nil {
log.Printf("caught: %v", r) // ✅ 主 goroutine panic 可捕获
}
}()
go func() {
panic("sub-goroutine panic") // ❌ 这里 panic 不会被上面的 recover 捕获
}()
}
解决办法只有两种:
- 在每个子 goroutine 内部单独加
defer+recover,并做好错误上报(如发到 channel、记录 metrics) - 改用
errgroup.Group等结构统一管理子任务,配合上下文取消和错误聚合,不依赖 panic 做流程控制 - 根本上避免在子 goroutine 中 panic —— 把错误转为
error返回,由父 goroutine 统一判断
recover 不是错误处理替代品,别滥用
用 recover 拦截本该用 if err != nil 处理的业务错误(比如数据库查不到记录、参数校验失败),会让代码难以测试、堆栈丢失、监控失真。它只适合应对「不可预期的程序崩溃」,比如空指针解引用、切片越界、类型断言失败等。
真实服务中常见误用:
- 把
json.Unmarshal失败包装成 panic 再 recover —— 应该直接检查err - 在中间件里 blanket recover 所有 panic,却不记录 panic 发生位置(缺少
debug.PrintStack()) - recover 后忽略 panic 原因,只返回通用错误,导致线上问题无法定位
建议每次 recover 都至少做三件事:记录完整 stack trace、打告警 metric、返回 5xx 响应。否则等于把崩溃藏起来,等量变引发质变。
真正难的不是写 recover,而是判断哪个 panic 是可恢复的、哪个该让进程挂掉重启 —— 比如内存溢出后的 panic,recover 了也救不活服务。

















