Revel 默认不捕获业务 handler 中的 panic,因其未在 handler 外层加 defer/recover,panic 由 net/http 直接处理返回空白 500;需用 Fixed:true 的前置 Interceptor 注册 defer/recover 实现全局恢复,并记录 trace_id 与脱敏日志。

Revel 框架默认不提供开箱即用的全局异常捕获机制,panic 会直接终止请求并返回空白 500 页面;必须手动注册 revel.Interceptor 在 revel.BEFORE 或 revel.AFTER 阶段介入,结合 defer+recover() 实现拦截,且需注意拦截时机不能晚于 handler 执行完成。
为什么 Revel 的 panic 不走自定义错误页
Revel 的请求生命周期中,revel.Run() 启动后,handler 函数在 goroutine 中同步执行。一旦发生 panic,Go 运行时立即中止当前 goroutine,而 Revel 默认未在 handler 外层加 defer/recover —— 它把错误处理交给了上层 HTTP server(如 net/http),最终由 Go 标准库返回空 500 响应。你写的 App.Init() 里注册的中间件、App.Error() 方法或模板,都不会被触发。
关键点在于:App.Error() 只对显式调用 panic() 且发生在 Revel 自身框架逻辑(如路由匹配、参数绑定)中的错误有效,对业务 handler 内部 panic 无效。
如何用 Interceptor 实现全局 recovery
必须在 app/init.go 的 init() 函数中,通过 revel.Interceptors.Insert() 注册一个前置拦截器,在 handler 执行前布设 recover 逻辑。不要用 revel.BEFORE 的默认链,因为它的执行顺序不可控;推荐插入到 revel.BEFORE 链首。
- 拦截器函数需接收
*revel.Controller,并在其中用defer func()包裹c.Result = ...赋值 - 必须调用
c.FinishRequest()或显式写响应体,否则请求挂起 - 避免在 recover 后继续调用
c.RenderError(),它可能再次 panic - 生产环境禁止返回原始
err.Error(),建议统一映射为"internal server error"
示例代码片段:
func RecoveryInterceptor(c *revel.Controller) revel.Result {
defer func() {
if err := recover(); err != nil {
c.Response.Status = "500 Internal Server Error"
c.Response.ContentType = "application/json"
c.Response.Out.Write([]byte(`{"code":500,"msg":"internal server error","data":null}`))
c.FinishRequest()
}
}()
return nil
}
func init() {
revel.Interceptors.Insert(RecoveryInterceptor, revel.BEFORE, &revel.InterceptPoint{Fixed: true})
}
如何让错误提示更友好且可追踪
单纯返回 JSON 错误还不够。Revel 的 c.Request.Id 是请求唯一标识,但默认不透出;需在 recovery 拦截器中将其注入响应,并记录到日志。否则线上出问题,前端只看到“服务器错误”,后端日志却找不到对应请求。
- 在 recover 块中提取
c.Request.Id,拼入错误响应的trace_id字段 - 使用
revel.ERROR.Printf()记录带堆栈的完整错误,但脱敏敏感字段(如密码、token) - 若需区分业务错误与系统 panic,可在 panic 前主动抛出带码的自定义 error(如
revel.NewError(1001, "user not found")),再在 recover 中做类型断言 - 前端拿到
trace_id后,可通过日志系统快速定位原始 panic 堆栈
真正容易被忽略的是:Revel 的拦截器链是线性执行的,revel.Interceptors.Insert() 插入位置决定了是否能捕获所有 panic。如果你在 app/init.go 里注册了多个拦截器,又没显式控制顺序,某个中间件提前 panic 就可能绕过你的 recovery——所以务必用 Fixed: true 强制前置,且只注册一个 recovery 拦截器。


















