recover中间件必须包裹c.next()才能生效,panic仅在c.next()执行期间被拦截;若c.next()写在defer外或遗漏,panic将炸穿请求链导致连接断开、无日志、无响应;常见错误是误以为defer覆盖整个函数体,实际只捕获其后发生的panic。

recover中间件必须包裹c.Next()才能生效
panic只有发生在c.Next()执行期间才会被拦截。如果你把c.Next()写在defer外面,或者漏掉这行,panic就直接炸穿整个请求链,HTTP连接会断开、日志没记录、前端收不到任何响应。
常见错误是以为defer自动覆盖整个函数体——其实它只捕获当前匿名函数内defer之后发生的panic。正确结构必须是:
func Recovery() gin.HandlerFunc {
return func(c *gin.Context) {
defer func() {
if r := recover(); r != nil {
// 记录堆栈、上报监控、返回JSON
c.AbortWithStatusJSON(500, map[string]string{"code": "500", "message": "internal error"})
}
}()
c.Next() // panic必须发生在这里及后续调用中
}
}
-
c.Next()前别做可能panic的操作(比如req.Body解包、强制类型断言) - 如果用了
gin.BasicAuth等前置中间件,确保Recovery注册在它们之后,否则panic发生在其作用域外 -
c.AbortWithStatusJSON()会触发c.Abort(),阻止后续中间件;而http.Error()不走Gin生命周期,慎混用
goroutine内panic必须单独recover
Gin中间件的recover对子goroutine无效。你在handler里起一个go routine,里面panic了,主goroutine的defer完全感知不到。
必须在goroutine内部自己加defer+recover,且recover后要主动记录堆栈并上报监控——不能只log.Printf就完事,否则问题静默丢失。
- 不要写
go func() { ... }()就完事,得套一层go func() { defer recover() { ... }(); ... }() - recover后建议用
debug.PrintStack()或runtime/debug.Stack()获取完整堆栈,别只打印r值 - goroutine里recover完,别再往响应里写东西(
c已不可用),只能打日志、发告警、更新指标
panic不等于业务错误,别用recover吞掉所有panic
recover不是兜底万能胶。Go工程规范里明确:业务逻辑禁止主动panic,只允许在服务启动阶段(如配置加载失败、端口监听失败)使用。
你看到nil pointer dereference、index out of range这类panic,说明代码有bug,该修代码而不是靠recover掩盖。
- 业务错误(如参数校验失败、数据库查不到)应该返回
*AppError或error,由中间件统一转成JSON响应 - recover只处理真正“不可恢复”的意外崩溃,比如第三方库内部panic、反射调用出错
- 如果recover频繁触发,说明代码质量有问题,得查堆栈定位源头,而不是加更多日志
统一响应结构必须和错误类型对齐
前后端约定的ErrorResponse字段设计,直接影响中间件怎么包装错误。如果定义了Code int和Message string,那所有错误路径(包括recover、c.Error()、手动return error)都得映射到同一结构上。
混用http.Error()和c.AbortWithStatusJSON()会导致前端收到两种格式:一种是纯文本,一种是JSON,解析逻辑得写两套。
- 推荐定义
type AppError struct { Code int; Message string },实现Error()方法 - handler里统一返回
error(比如return BadRequest("xxx")),中间件读取c.Errors或通过context传递 - recover捕获的panic也得转成
*AppError再序列化,保持字段语义一致(比如Code: 500对应HTTP状态码,不是业务码)
runtime/debug.Stack()输出,而不是依赖日志里的r字符串。golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











