Iris中间件必须手动recover panic并调用ctx.Stop()阻断流程,先设StatusCode再JSON响应,避免读取Body导致handler异常,日志需包含路径、方法、IP、状态码及完整堆栈。

中间件里 panic 会直接崩服务,必须 recover
Iris 不像某些框架自动 recover panic,一旦 handler 或中间件里发生未捕获 panic(比如 nil pointer dereference、map 写入未初始化的 slice),整个 HTTP server 就会退出。所以异常中间件第一件事是用 recover() 拦住它。
常见错误现象:前端请求突然 502,日志里没报错,app.Listen() 后的进程直接消失了——大概率是某处 panic 没被接住。
- 中间件开头必须包一层
defer func() { if r := recover(); r != nil { ... } }() - 别只打印
r,用fmt.Sprintf("%v", r)+debug.PrintStack()才能看到完整调用栈 - recover 后不能继续
ctx.Next(),得立刻ctx.Stop()阻断后续流程,否则可能重复写响应体导致 panic
ctx.StatusCode() 必须在 ctx.JSON() 前调用
很多人写异常中间件时直接 ctx.JSON(iris.Map{"error": "xxx"}),结果前端收到 200 状态码 + 错误内容,根本分不清成功失败。Iris 的 ctx.JSON() 不会自动设状态码,它只管序列化和 Content-Type。
正确顺序只能是:
- 先
ctx.StatusCode(iris.StatusInternalServerError)(或具体错误码) - 再
ctx.JSON(iris.Map{...}) - 或者更稳妥:封装成
writeError(ctx, code, msg)函数,统一处理
漏掉 StatusCode() 是生产环境最常被忽略的细节,前端 fetch 的 response.ok 一直为 true,排查成本翻倍。
不要在中间件里读取 body 或调用 ReadJSON
中间件执行时机在路由匹配之后、handler 之前,但此时 ctx.Request().Body 已被 Iris 内部缓冲或部分读取。如果中间件里调 ctx.ReadJSON(),会导致 handler 再读时返回空或 io.EOF,业务逻辑全乱。
真正需要解析 body 的场景(比如审计、限流),必须用 ctx.Request().Body 手动读一次、再用 io.NopCloser() 重置回去:
body, _ := io.ReadAll(ctx.Request().Body) ctx.Request().Body = io.NopCloser(bytes.NewBuffer(body)) // 后续 handler 还能正常 ReadJSON
但绝大多数异常中间件根本不需要碰 body——它只该关注 panic、状态码、日志,不该承担数据解析职责。
日志记录要带请求上下文,别只打 panic
光记 panic: interface conversion: interface {} is nil, not string 没用。得把请求路径、方法、IP、时间戳、甚至 trace ID(如果有)一起打出来,否则线上出问题根本定位不到哪次请求触发的。
实操建议:
- 用
ctx.Application().Logger().Errorf()而不是log.Printf(),确保日志格式统一 - 必带字段:
"path=%s method=%s ip=%s status=%d", ctx.Path(), ctx.Method(), ctx.RemoteAddr(), ctx.GetStatusCode() - panic 日志末尾追加
stack=%s,用debug.Stack()获取
复杂点在于:recover 拿到的 r 是 interface{},直接转 string 可能 panic;必须先判断类型,再安全转字符串——这点连很多开源中间件都漏了。











