recover必须在defer中调用才有效,否则立即返回nil;它仅对当前goroutine中正在传播的panic生效,无法捕获runtime.Goexit或os.Exit,且不能修复状态或继续执行逻辑。

recover 必须在 defer 中调用才有效
Go 的 recover 不是全局异常捕获机制,它只在当前 goroutine 的 panic 正在被传播、且尚未退出函数时生效。如果没用 defer 包裹,recover() 会立刻返回 nil,什么也捞不到。
常见错误是写成这样:
func handleRequest() {
if r := recover(); r != nil { // 永远不会触发
log.Println("caught:", r)
}
panic("something went wrong")
}
正确姿势是:
- 把
recover()放在defer函数里 - 确保该
defer在 panic 发生前已注册(即在可能 panic 的代码之前) - 不要跨 goroutine 使用:新起的 goroutine 需要自己配
defer + recover
HTTP handler 中用 recover 做降级响应
典型场景是避免一个请求 panic 导致整个服务挂掉。但要注意:recover 只能阻止当前 handler 返回 panic,不能“修复”状态或继续执行后续逻辑 —— 它只是让函数能正常返回。
立即学习“go语言免费学习笔记(深入)”;
示例:
OpenClaw 基础设施心跳监控与自动恢复:部署/卸载心跳守护进程,配置主机监控(Gateway、磁盘、内存、CPU、进程),设置飞书告警,自动重启故障Gateway。适用关键词:心跳监控、heartbeat daemon、服务器告警、自动重启、健康检查、auto recovery。
func myHandler(w http.ResponseWriter, r *http.Request) {
defer func() {
if err := recover(); err != nil {
http.Error(w, "service temporarily unavailable", http.StatusServiceUnavailable)
log.Printf("panic in handler: %v", err)
}
}()
// 可能 panic 的业务逻辑,比如 map 并发读写、空指针解引用等
riskyOperation()
}
关键点:
- 降级响应码建议用
http.StatusServiceUnavailable或http.StatusInternalServerError,别用 200 - 日志必须记录原始 panic(
err是 interface{},可直接传给fmt.Printf或log.Printf) - 不要在 recover 后尝试“恢复业务逻辑”,比如重试或补数据 —— 状态可能已损坏
recover 无法捕获 runtime.Goexit 或 os.Exit
recover 对两类退出完全无效:一是 runtime.Goexit()(主动终止当前 goroutine),二是 os.Exit()(进程级退出)。这两者不经过 defer 链,recover 根本没机会运行。
容易踩坑的地方:
- 第三方库内部调用了
os.Exit(1)(比如某些 CLI 工具库),你在 handler 里加了 recover 也拦不住 - 用
log.Fatal会间接调用os.Exit,同样绕过 recover - goroutine 泄漏问题不会被 recover 暴露,得靠 pprof 或 trace 查
优雅降级 ≠ 仅靠 recover 就够了
recover 只解决“不崩溃”,不解决“降级后是否可用”。比如数据库连接失败导致 panic,recover 能让接口返回 503,但用户真正需要的是返回缓存数据或默认值 —— 这得靠业务层判断和兜底逻辑。
更实际的做法是:
- 把易 panic 的操作包装成带错误返回的函数(如
json.Unmarshal而非json.NewDecoder().Decode()直接 panic) - 对关键依赖(DB、RPC)设置超时和 circuit breaker,提前失败,而不是等 panic
- recover 作为最后防线,只处理意料之外的 panic(如空指针、数组越界),不替代错误处理流程
真正难的不是加 defer,而是区分哪些 panic 是可预期的、哪些是 bug,以及降级后用户看到的到底是“不可用”还是“有损可用”。

















