recover中间件必须包裹c.next()才能生效,panic需发生在c.next()执行期间;goroutine内panic需单独recover,且recover后应记录堆栈并上报监控。

Go HTTP 服务中 recover 中间件必须包裹 c.Next()
中间件里 recover 生效的前提是 panic 发生在 c.Next() 执行期间。如果把 c.Next() 放在 defer 外面、或漏写,panic 就不会被拦截,请求直接崩溃。常见错误是误以为 defer 自动覆盖整个函数体——其实它只覆盖 defer 所在的匿名函数作用域。
正确结构必须是:
func Recovery() gin.HandlerFunc {
return func(c *gin.Context) {
defer func() {
if r := recover(); r != nil {
// 记录日志、返回响应
c.JSON(500, map[string]string{"error": "internal server error"})
}
}()
c.Next() // panic 必须发生在这里及以下调用链中
}
}
- 不要在
c.Next()前做任何可能 panic 的操作(如解包空指针、强制类型断言) - 如果用了
gin.BasicAuth或其他前置中间件,确保 Recovery 在它们之后注册,否则 panic 可能发生在 Recovery 作用域外 - Gin 内置的
gin.Recovery()默认只打印日志,不返回 JSON,需自行封装
http.Error 和 c.AbortWithStatusJSON 的状态码行为差异
用 http.Error(w, msg, code) 会直接写响应并结束 HTTP 流程,但 Gin 的 c.AbortWithStatusJSON(code, data) 还会触发 c.Abort(),阻止后续中间件执行。两者都终止当前请求,但语义和可扩展性不同。
- 标准库方式适合裸
net/http项目,简单直接;Gin 方式更利于统一响应结构(比如始终带code、message字段) - 若你已定义了全局响应结构体
rsp.FailMsg(),就别混用http.Error,否则前端要处理两种错误格式 - 注意:调用
c.AbortWithStatusJSON后,c.Next()不再执行,但 defer 仍会运行 —— 所以 recover 中的日志记录不会丢
panic 不等于业务错误,recover 不该吞掉所有 panic
Go 的 panic 是程序级异常,代表不可恢复的逻辑错误(如 nil 指针解引用、切片越界、map 写入未初始化)。它不是业务校验失败(如参数非法、资源不存在),后者应走 error 返回路径。
- 用
panic("user_id is empty")是反模式;应该return nil, errors.New("user_id is required") - 真正该 recover 的 panic 是那些你无法提前防御的底层崩溃,比如模板渲染时的
reflect.Value.Interface()panic、第三方库内部 panic - recover 后建议加堆栈:用
debug.Stack()而非仅fmt.Sprint(r),否则无法定位 panic 出处 - 线上环境 recover 到 panic 后,务必上报监控(如 Sentry、Prometheus),不能只打日志
goroutine 中的 panic 无法被 HTTP 中间件捕获
HTTP 请求处理器里启的 goroutine(比如异步发消息、定时清理)一旦 panic,主线程不受影响,但 panic 会被直接丢弃,无日志、无告警、无堆栈 —— 这是最容易被忽略的盲区。
- 每个 goroutine 入口必须自己加
defer/recover,例如:go func() { defer func(){...}(); doWork() }() - 不要依赖全局 init + defer 设置的 recover,它只对 main goroutine 有效
- 用
golang.org/x/sync/errgroup替代裸 go 启动,它支持统一错误收集和 cancel 传播 - 如果用了 worker pool,pool 的每个 worker loop 也得包一层 recover
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











