Iris 的 panic 恢复必须在 app.OnAnyErrorCode 中统一处理,不能依赖 handler 内的 defer+recover 或 recover.New() 中间件;该回调需注册在 app.UseRouter() 之后,通过 ctx.Values().Get("error") 安全获取 panic 并防御性响应。

panic 恢复必须在 OnAnyErrorCode 里做,不能靠 defer+recover 放 handler 内
Iris 的 panic 恢复机制是集中式、生命周期绑定的。你在某个 app.Get 的 handler 函数里写 defer func() { recover() }(),只能捕获该函数内部发生的 panic(比如空指针解引用),但对中间件 panic、路由参数解析失败、模板渲染崩溃、甚至依赖注入过程中的 panic 完全无效——这些都会绕过你的局部 recover,直接导致整个请求上下文终止且无兜底响应。
真正能覆盖全部 panic 场景的唯一入口,是 app.OnAnyErrorCode 回调。它在 Iris 内部完成错误封装后触发,此时 panic 已被框架捕获并转为可读错误上下文。
-
app.OnAnyErrorCode必须注册在app.UseRouter()之后,否则不生效 - 回调函数内可通过
ctx.Values().Get("error")取出原始 panic 值,类型为interface{},需用if err, ok := val.(error)或fmt.Sprintf("%v", val)安全转义 - 别在里面调
ctx.Next()—— 请求流程已终结,调了也无意义
为什么 app.Use(recover.New()) 不够用
recover.New() 是 Iris 官方提供的 panic 中间件,但它只做最基础的 recover 和日志记录,不参与错误响应生成。它不会设置状态码、不写响应体、不触发 OnErrorCode 链路。前端收到的仍是空响应或默认 500 页面(尤其在 iris.DevMode 下还会暴露堆栈)。
更关键的是:它和 OnAnyErrorCode 是两条独立路径。你同时用了两者,panic 会先被 recover.New() 拦下并打日志,但后续仍可能因未设状态码或未写响应体,导致 OnAnyErrorCode 无法按预期接管输出逻辑。
- 推荐方案:**只用
OnAnyErrorCode,不用recover.New()** —— 避免逻辑重叠和响应混乱 - 若坚持用
recover.New(),务必确保它注册在所有其他中间件之前(app.Use(recover.New())放最顶上),否则中间件 panic 仍会漏掉 -
recover.New()默认不记录堆栈详情,需手动传入 logger 才能看到完整 panic 位置
OnAnyErrorCode 里怎么安全输出 panic 信息
直接 ctx.JSON(500, map[string]interface{}{"error": err}) 很危险:如果 err 是 nil、func 类型或含不可序列化字段(如 sync.Mutex),JSON 编码会 panic 并再次触发 OnAnyErrorCode,造成死循环。
正确做法是做最小化、防御性封装:
- 先检查
errVal := ctx.Values().Get("error")是否为nil - 用
fmt.Sprintf("%+v", errVal)转字符串,而非直接 JSON 序列化errVal - 若需堆栈,用
debug.PrintStack()写到日志文件,**不要塞进 HTTP 响应体**(生产环境必须隐藏) - 响应体统一用简单结构:
ctx.JSON(500, iris.Map{"code": 500, "message": "Internal Server Error"}),开发环境再加"debug": stackStr
开发与生产环境必须手动区分错误页
Iris 不会根据 GO_ENV 或 iris.IsDevelopment() 自动切换错误行为。你在 OnAnyErrorCode 里写的逻辑,开发和生产跑的是一套。这意味着:开发时想看堆栈,生产时却把完整 panic 信息返回给前端,等于公开服务端细节。
必须显式判断环境:
- 用
ctx.Application().IsDevelopment()判断(注意不是IsDevelo,拼错就永远返回 false) - 开发环境可返回带堆栈的 JSON;生产环境只返回泛化提示,且建议加监控上报(如 Sentry、Prometheus Alert)
- 避免在该回调里做耗时操作:写磁盘日志、远程调用、模板渲染 —— 会阻塞整个错误响应,让客户端等超时
最易忽略的一点:即使你写了完美的 OnAnyErrorCode,只要没调 app.UseRouter() 就注册它,或者把它放在 app.Use() 里当普通中间件用,它就根本不会被调用。顺序和注册方式,比逻辑本身还关键。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











