recover必须在defer函数中直接调用才有效,仅能捕获同goroutine内未处理的panic;它不是error替代品,仅适用于不可恢复的严重错误,常规错误应返回error。

recover 必须在 defer 中调用才有效
Go 没有传统 try/catch,recover 不是“捕获异常”,而是从 panic 状态中恢复执行——但它只在 defer 函数里调用才起作用。如果写在普通函数体、或者 defer 外层,recover() 永远返回 nil,看起来“没生效”。
常见错误写法:
func badHandler() {
recover() // 这里永远无效
panic("boom")
}
正确姿势是:
- 必须用
defer包裹一个匿名或具名函数 - 该函数内第一件事就是调用
recover(),且不能有其他可能 panic 的操作前置 - 恢复后要显式处理错误值,否则 panic 会继续向上传播
recover 后怎么把 panic 信息发给告警系统
recover() 返回的是 interface{},通常需要断言为 error 或直接格式化为字符串。但注意:不是所有 panic 都是 error 类型(比如 panic(42) 或 panic("string")),所以别硬转 error,用 fmt.Sprint() 更稳妥。
实操建议:
- 用
err := recover()获取值,立即判空:if err == nil { return } - 用
fmt.Sprint(err)转成可读字符串,避免类型断言失败 panic - 带上当前 goroutine 栈信息:
debug.Stack(),方便定位问题源头 - 告警发送建议异步(如丢进 channel 或用
go func(){}()),避免阻塞恢复流程
示例关键片段:
defer func() {
if r := recover(); r != nil {
msg := fmt.Sprint("PANIC: ", r)
stack := string(debug.Stack())
go alert.Send("go-panic", map[string]string{
"message": msg,
"stack": stack,
})
}
}()
HTTP handler 中 recover 容易漏掉的边界情况
Web 服务里常在中间件或顶层 handler 套 recover,但有两个典型漏点:
- goroutine 泄漏:如果 panic 发生在单独启动的 goroutine 里(比如
go http.Serve()之外的手动go doWork()),主 handler 的 defer 根本不覆盖它——每个 goroutine 需要自己配defer+recover - http.Server 的
Shutdown()或Close()期间 panic,不会被常规 handler 的 defer 捕获,得在Server.RegisterOnShutdown()或信号监听里补一层
更安全的做法是:对所有显式 go 启动的逻辑,都封装一层带 recover 的 wrapper:
func safeGo(f func()) {
go func() {
defer func() {
if r := recover(); r != nil {
alert.Send("goroutine-panic", map[string]string{"reason": fmt.Sprint(r)})
}
}()
f()
}()
}
recover 不是兜底方案,别用来掩盖设计缺陷
能用 recover 拦住 panic,不代表该让它发生。比如频繁因空指针、数组越界、类型断言失败而 panic,说明代码缺少前置校验或接口契约不清晰。
真正该用 recover 的场景其实很窄:
- 第三方库内部 panic(你无法改源码,只能兜底)
- 插件/脚本执行环境(用户代码不可信,需隔离崩溃)
- 长期运行服务的“保活”需求(防止一个请求崩掉整个 server)
如果发现 recover 日志里反复出现同一类 panic,优先去修触发点,而不是优化告警格式——那只是把症状当病因。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











