recover返回值是interface{},须先判非nil再断言error;http handler中需提前捕获requestid等上下文,panic仅用于不可预期错误,业务错误应return err。

recover 返回值是 interface{},不能直接断言 error
recover() 返回的是 interface{},不是 error。很多人写 err := recover().(error) 会 panic——因为 recover 捕获的可能是任意类型(字符串、int、自定义 struct),甚至 nil。一旦类型断言失败,程序立刻崩溃,反而把原本可恢复的 panic 变成不可恢复的二次 panic。
正确做法是先判断是否非 nil,再用类型断言或 errors.As 处理:
- 永远先检查
recover()返回值是否为nil:只有非 nil 才代表真发生了 panic - 若期望捕获的是
error,应使用双断言:if r := recover(); r != nil { if err, ok := r.(error); ok { ... } } - 若 panic 是用
panic("msg")或panic(123)触发的,r.(error)一定失败,此时需 fallback 到fmt.Sprint(r)构造错误描述
想用 errors.Is / errors.As 匹配自定义 panic 错误?别这么做
errors.Is 和 errors.As 只对实现了 error 接口的值有效,而 recover 拿到的是原始 panic 值——它可能根本不是 error 类型。比如 panic(fmt.Errorf("db timeout")) 确实返回 error,但 panic("timeout") 返回的是 string,errors.As(r, &e) 必然失败且不报错(返回 false)。
真正可控的做法是:统一用 error 类型 panic,并在 recover 后显式断言:
- 业务中只允许
panic(err),禁止panic("xxx")或panic(404) - 中间件里写:
if r := recover(); r != nil { if err, ok := r.(error); ok { handlePanicError(err) } else { handlePanicValue(fmt.Sprintf("%v", r)) } } - 不要依赖
errors.As对 recover 结果做类型提取——它不适用这个场景
HTTP handler 里 recover 后怎么关联 requestID 和状态码
recover 发生在 defer 函数里,此时 *http.Request 和响应 writer 已经脱离作用域。如果没提前把 requestID、http.Method、http.Request.URL.Path 等上下文存进闭包,recover 日志里就只剩 “panic: xxx”,完全无法定位请求。
关键点在于 defer 要捕获足够上下文:
- 在 handler 开头就提取
requestID := r.Header.Get("X-Request-ID"),并在 defer 中引用它 - 状态码不能硬编码 500:必须从 panic 的 error 中解析(如断言
*AppError并调e.ErrorCode()),否则所有 panic 都变成 500,掩盖真实语义 - 务必调用
w.WriteHeader(code),否则客户端收到 200 + 错误体,前端逻辑全乱 - 日志必须包含
method、path、requestID、panic value四要素,缺一不可
recover 不是错误处理主干,只是最后防线
真正该被 recover 捕获的,只有两类:不可预期的 runtime panic(空指针、切片越界)和极少数手动 panic 的致命流程错误。所有业务错误(参数校验失败、DB 记录不存在、权限不足)都该走 return err 路径,由统一 error middleware 处理。
混淆这两者会导致三个严重后果:
- 错误分类丢失:400 和 500 全混成 500,监控告警失效
- goroutine 泄漏:recover 后没清理资源(如未关闭 http.Response.Body),下次请求复用时出错
- 错误链断裂:用
fmt.Errorf("wrap: %v", err)替代%w,导致errors.Is在上层失效
recover 的唯一合理位置是顶层 HTTP handler 的 defer,且必须配合上下文快照、状态码协商、资源清理三件套——少一个,就不算“正确”。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











