Gin 的 gin.Default() 中 Recovery 中间件不能返回自定义 JSON 错误,因为它仅打印 panic 堆栈并返回固定字符串 {"error": "Internal Server Error"},不调用 c.AbortWithStatusJSON(),也不透传 panic 给自定义逻辑,且会拦截其他中间件中的 recover();生产环境必须禁用默认 Recovery,改用 gin.New() 手动注册能记录堆栈并脱敏返回结构化 JSON 的自定义中间件。

为什么 gin.Default() 的 Recovery 中间件不能返回你想要的 JSON 错误
因为 gin.Recovery() 只做两件事:打印 panic 堆栈到日志 + 返回固定字符串 {"error": "Internal Server Error"}。它不调用 c.AbortWithStatusJSON(),也不检查你定义的错误映射逻辑,更不会把 panic 值透传给你自己的中间件——它在 c.Next() 之后直接“吞掉” panic 并终止流程,后续中间件完全收不到信号。
- 开发时若没关掉默认 recovery,
gin.SetMode(gin.DebugMode)也看不到原始 panic 堆栈,调试靠猜 - 生产环境必须禁用
gin.Default(),改用gin.New()+ 手动注册中间件链 - 默认 recovery 会拦截所有
recover(),你在其他中间件里写的 defer+recover 完全无效
怎么写一个真正能记录堆栈并返回结构化 JSON 的 recovery 中间件
核心动作只有四步:defer 捕获 → 获取堆栈 → 写日志 → 调用 c.AbortWithStatusJSON()。别用 c.JSON() + c.Abort() 组合,否则可能被后续中间件重复写响应头导致 panic。
- 用
debug.Stack()获取完整堆栈,转成string存入日志字段(如log.Printf("[PANIC] %s %s: %v\n%s", method, path, err, stack)) - 生产环境响应体里绝对不要带
stack字段,只返回脱敏后的提示,比如"msg": "服务暂时不可用" - 加一层
if c.Writer.Written() { return }防止中间件链中已写过 header 后再 abort 导致 panic - 中间件必须放在
r.Use()链最前面,否则日志或 auth 中间件可能已写 header
panic 捕获后为什么不能继续执行 c.Next()
recover() 一旦触发,当前 goroutine 的 panic 状态就被清除,但 handler 函数已中断执行——c.Next() 后续逻辑不会自动恢复。如果你在 recover 后没调用 c.AbortWithStatusJSON(),请求会继续往下走,可能重复解析 body、查数据库,甚至再次 panic。
-
c.AbortWithStatusJSON(500, ...)是原子操作:写响应 + 中断中间件链 - 不要在 recover 块里调用
c.Error(),它只是往c.Errors里塞 error,不终止流程 - panic 发生在单 goroutine 内,
recover()安全,但别指望它能“重试”或“回滚”业务逻辑
如何区分 handler 显式 error 和 panic 异常
Gin 不自动处理 return err 这类显式错误,它只对 panic 做 recovery。显式 error 必须靠 c.Error(err) 主动注入上下文,再由下游中间件读取 c.Errors 列表统一处理。
立即学习“go语言免费学习笔记(深入)”;
- handler 里遇到校验失败、DB 查询为空等,应调用
c.Error(fmt.Errorf("xxx")),而不是直接 panic - 自定义错误中间件需遍历
c.Errors,按 error 类型决定状态码和响应结构(如ValidationError返回 400,AuthError返回 401) - panic 通常来自空指针、越界、类型断言失败等运行时错误,无法靠
c.Error()捕获,必须靠 recover
c.Error(),留 recovery 只兜底真正的 runtime panic。


















