Go 中 recover 不能全局拦截错误,仅捕获当前 goroutine 的 panic,对 error 无效;HTTP handler 应返回 error 类型并配合自定义 CodeError 实现统一错误处理。

Go 里 recover 不是全局错误拦截器
recover 只能捕获当前 goroutine 的 panic,对 error 值完全无效。HTTP handler 中写 defer func() { recover() }() 看似“兜底”,实则掩盖了本该显式返回的业务错误(比如参数校验失败应返回 400,不是 500),还容易导致状态码未重置、日志丢失 requestID、甚至 goroutine 泄漏。
常见错误现象:
- 中间件 recover 后没调用
w.WriteHeader(500),响应体写了但状态码仍是 200 - panic 日志里看不到 HTTP 方法、路径、
requestID,无法关联请求上下文 - 把
fmt.Errorf("xxx: %v", err)当作包装——%v切断错误链,errors.Is()失效
HTTP handler 必须返回 error 类型才可统一拦截
真正能做“统一错误响应”的前提是:handler 函数签名返回 error,而不是直接操作 http.ResponseWriter。这样中间件才能在调用后检查返回值,决定怎么渲染、设码、上报。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 定义统一 handler 类型:
type HandlerFunc func(http.ResponseWriter, *http.Request) error - 中间件包装时,用
if err := h(w, r); err != nil { renderError(w, r, err) }替代裸写c.JSON() - 所有业务 handler 显式
return fmt.Errorf("user not found: %w", dbErr),别用panic或log.Fatal -
renderError内部必须从err提取状态码(如通过errors.As(err, &e)断言*AppError),不能硬编码 500
自定义 error 类型必须实现 Unwrap 和 ErrorCode 方法
靠字符串匹配或全局常量查错误码,上线后一包装就失效。要让中间件、日志、监控都能稳定提取信息,*AppError 必须满足两个硬性条件:
- 实现
Unwrap() error—— 否则errors.Is(err, ErrNotFound)无法穿透包装层 - 提供可反射/断言的
ErrorCode() int方法 —— 中间件靠它映射 HTTP 状态码(如 1001 → 404) - 构造必须走工厂函数:
ErrUserNotFound = NewCodeError(1001, "user not found"),禁用裸fmt.Errorf - 下游错误包装用
WrapCodeError(1003, "auth failed", err),保留原始*CodeError结构
示例关键字段:
type CodeError struct {
Code int
Message string
Err error
}
func (e *CodeError) ErrorCode() int { return e.Code }
func (e *CodeError) Unwrap() error { return e.Err }
goroutine 错误无法被中间件捕获,必须显式收集
HTTP handler 里启的子 goroutine(比如发异步通知、写日志、调第三方 API),其 panic 或 error 完全逃逸出中间件作用域。你看到的“全局拦截”对它们无效。
正确做法:
- 并发任务用
errgroup.Group,eg.Wait()返回聚合错误 - 定时任务、消息消费等后台 goroutine,错误必须手动传回主流程(channel / callback / context.Value)
- 不要依赖
recover拦截子协程 panic —— 它只在 defer 中生效,且不跨 goroutine - 所有外部依赖(DB、RPC、HTTP client)返回的错误,应在 DAO 层立刻转成你的
*CodeError,避免上层直面sql.ErrNoRows这类底层类型
最易被忽略的是:错误和日志没绑定请求生命周期。同一个 requestID 必须从入口 middleware 注入 context.Context,并在每层 error 包装和日志输出中透传。否则你永远不知道那个 1001 错误对应的是哪个用户、哪次调用、哪条 SQL。


















