beego v2 的 recoverfunc 必须在 beego.run() 前通过封装函数显式替换,因其字段私有不可直赋;自定义函数需类型断言 bresp.pageerror 并交还原函数处理其他 panic;子 goroutine 需单独 safego 包裹 defer recover;recover() 必须直接出现在 defer 函数体内。

Beego v2 的 RecoverFunc 配置项必须显式替换
Beego v2 默认启用 panic 恢复(recoverpanic = true),但它的 RecoverFunc 字段是私有且不可直接赋值的——你不能直接写 beego.BConfig.RecoverFunc = myFunc,因为该字段未导出。必须通过函数包装方式“偷换”引用,否则自定义逻辑永远不会执行。
实操要点:
- 在
main()初始化阶段调用一次封装函数(如HandleRecover(beego.BConfig)),保存原始RecoverFunc并替换成你的版本 - 新函数里先做类型断言:若
recover()返回的是bresp.PageError,说明是业务主动抛出的可控错误,可按需返回 200 + JSON;否则交还给原函数处理(避免破坏默认 500 页面逻辑) - 别在
RecoverFunc里直接log.Fatal或os.Exit,这会让整个服务进程退出
控制器方法内 defer recover 无法替代 RecoverFunc
你在某个 Get() 方法里加 defer func() { recover() }(),只能捕获该方法内部、同一 goroutine 中发生的 panic;它对 Beego 自动注入的中间件 panic、模板渲染 panic、甚至同个请求中启动的子 goroutine panic 都无效。这不是遗漏,而是设计限制——Beego 的请求生命周期由框架调度,不是纯用户代码栈。
所以不要试图在每个控制器里手写 recover,那既重复又漏报。真正要覆盖全局行为,唯一入口就是 RecoverFunc 替换。
子 goroutine panic 必须单独加 defer recover
Beego 的 RecoverFunc 只作用于主请求 goroutine。如果你在控制器里起了裸 go doAsync(),而 doAsync 内部 panic,这个 panic 不会被 RecoverFunc 捕获,也不会打日志,更不会返回 HTTP 响应——它就静默消失了。
必须手动兜底:
- 所有显式
go启动的函数,都得包一层带recover()的闭包 - 推荐封装成
safeGo(func())工具函数,统一记录堆栈(用debug.Stack(),不是只打r) - 检查你用的第三方库(比如 Redis 客户端、日志 SDK)是否内部起 goroutine —— 它们也可能逃逸
recover() 必须在 defer 函数体内直接调用
这是 Go 运行时硬性规则:recover() 只有在 defer 的匿名函数体中**直接出现**才有效。任何间接调用都会失效,比如:
-
r := recover; r()→ ❌ 失效 -
defer myRecoverWrapper(),而myRecoverWrapper内部调用recover()→ ❌ 失效 -
defer func() { if x > 0 { recover() } }()→ ✅ 有效,只要它在 defer 函数体内
Beego 的 RecoverFunc 是被框架在 defer 中调用的,所以你自己的函数里只需正常写 if r := recover(); r != nil { ... } 即可,不用再套 defer。
最易忽略的一点:Beego v2 的 RecoverFunc 替换必须在 beego.Run() 之前完成,且仅能设置一次;如果项目里有多处初始化逻辑(比如插件、模块加载),得确保替换发生在所有依赖初始化之后、Run 之前,否则可能被覆盖回默认值。











