Iris 中 panic 不自动转 500 是因默认不 recover,仅显式调用 ctx.StatusCode(500) 才触发 app.OnErrorCode;该回调是状态码钩子,非异常拦截器,需手动传值(如 ctx.Values())实现错误上下文透传。

直接回答:Iris 框架里 500 异常不是靠「捕获 panic」或中间件自动兜底,而是必须显式调用 ctx.StatusCode(500) 触发,再由 app.OnErrorCode(iris.StatusInternalServerError, ...) 统一接管处理逻辑。
为什么 panic 不会自动转成 500 页面?
Iris 默认不 recover 任何 panic —— 这是设计选择,不是 bug。如果你在 handler 里写了除零、空指针解引用或主动 panic("xxx"),服务会直接崩溃(dev 环境)或返回空白/默认 500(prod 环境),但不会进 OnErrorCode 回调。
- 只有你手动设置状态码为 500(比如
ctx.StatusCode(500)或ctx.StopWithStatus(500)),才会触发OnErrorCode注册的函数 - 想让 panic 也走统一错误页?得自己加 recover 中间件,且必须在
app.Use(...)中注册在最外层 - 注意:recover 中间件无法捕获 goroutine 内部的 panic,只对当前请求链有效
app.OnErrorCode(iris.StatusInternalServerError, ...) 的实际行为
这个回调不是“拦截未处理异常”,而是“响应阶段的状态码钩子”。它在 handler 执行完、状态码已确定为 500 后才执行,此时 response body 还没写,你可以完全重写输出内容。
- 它接收的是
iris.Context,可读取ctx.Values()里提前塞的上下文信息(如错误详情、trace ID) - 不能修改请求路径或跳转,但可以调用
ctx.JSON()、ctx.View()、ctx.HTML()等任意响应方法 - 如果 handler 里已经写了部分 body(比如先
ctx.WriteString("ok")再ctx.StatusCode(500)),浏览器可能收到混合内容 —— 所以务必确保 500 是最终响应
常见踩坑:自定义 500 页面不生效
你以为配了 OnErrorCode 就万事大吉?这几个点经常被忽略:
- 没调用
ctx.StatusCode(500),只是 return 或 log 错误 —— 这样状态码仍是 200,根本不会进回调 - 在
OnErrorCode回调里又抛 panic(比如模板渲染失败),导致整个响应中断,浏览器看到空白或连接重置 - 用了
ctx.View("error500.html")但没配置视图引擎,或路径错写成"views/error500.html"(Iris 默认从./views开始找,不需要前缀) - 生产环境启用了 gzip 中间件,但
OnErrorCode里返回纯文本时没设ctx.ContentType("text/plain; charset=utf-8"),浏览器可能解析错乱
推荐做法:把错误上下文传进 OnErrorCode
别只在回调里硬编码文案。利用 ctx.Values() 在业务 handler 里存关键信息,让错误页可读、可追踪:
<pre class="brush:php;toolbar:false;">app.Get("/api/user/{id}", func(ctx iris.Context) {
id := ctx.Params().Get("id")
user, err := findUser(id)
if err != nil {
ctx.Values().Set("error", err.Error())
ctx.Values().Set("path", ctx.Path())
ctx.StatusCode(500)
return
}
ctx.JSON(user)
})
app.OnErrorCode(iris.StatusInternalServerError, func(ctx iris.Context) {
err := ctx.Values().GetString("error")
path := ctx.Values().GetString("path")
ctx.JSON(map[string]interface{}{
"code": 500,
"message": "Internal server error",
"detail": err,
"request_path": path,
"timestamp": time.Now().UTC().Format(time.RFC3339),
})
})
真正难的不是接住 500,而是怎么让每个 500 都带足够信息去定位问题 —— Values() 是唯一可靠的跨 handler 传递通道,别省这一步。


















