beego v2 全局异常捕获唯一可控入口是 beego.bconfig.recoverfunc,需在 beego.run() 前劫持并兼容 pageerror 与非 pageerror,优先处理业务中断 panic,统一降级未预期 panic,严禁泄露敏感信息。

Beego v2 没有类似 Gin 的 Next() 或 Spring 的 @RestControllerAdvice,无法靠传统中间件 + defer/recover 实现真正的全局异常捕获——因为它的中间件链不支持在末尾统一注入 recover 逻辑,recover 必须在 panic 发生的同一 goroutine 中执行,而 Beego 的请求处理是在 controller 方法内启动的独立 goroutine 上下文。
beego.BConfig.RecoverFunc 是唯一可控入口
Beego v2 内部确实预留了钩子:beego.BConfig.RecoverFunc,类型为 func(*context.Context, *web.Config)。它会在每个请求 panic 后被调用,但默认值是私有的 defaultRecoverPanic,且该字段在配置加载后即固化,不能直接赋值覆盖(会 panic)。
- 必须在
beego.Run()之前、beego.BConfig初始化完成之后立即劫持,例如在main()开头调用自定义初始化函数 - 不能简单赋值
beego.BConfig.RecoverFunc = myFunc,需先保存原函数再替换,否则丢失默认兜底行为(如 500 页面渲染) - 该函数接收的是
*context.Context,不是*controller.Controller,因此无法直接调用this.JSONResp()等 controller 方法,只能用ctx.Output.*系列 API
自定义 RecoverFunc 必须兼容 PageError 和非 PageError
Beego 的 bresp.PageError 是框架内置的 panic 类型(如 this.Abort("404") 抛出),它携带 Code 和 Message,应优先识别并返回对应状态码;其余 panic 视为未预期错误,需降级处理,避免暴露堆栈。
- 用
if e, ok := err.(bresp.PageError); ok判断是否为已知业务中断点 -
e.Code == 200是特殊情形(Beego 允许用 200 表示成功跳转),此时仍要调用ctx.Output.SetStatus(e.Code),但响应体应走 JSON 格式而非 HTML - 对非
PageError的 panic,建议统一返回500+ 友好提示,并记录完整堆栈到日志(debug.Stack()),但绝不写入响应体 - 务必调用原
oldFunc(若存在),保证 Beego 默认的 HTML 错误页兜底能力不被破坏
controller 内 panic(PageError{}) 是最轻量的业务中断方式
不要在 controller 里写 try/catch 或手动 defer/recover,那会污染每个方法;Beego 鼓励用 panic 主动中断流程,配合 RecoverFunc 统一收口。
- 抛出
panic(&bresp.PageError{Code: 400, Message: "参数校验失败"}),比this.Abort("400")更灵活(可带结构化数据) - 自定义 error 类型需显式实现
bresp.PageError接口(即含Code() int和Message() string方法),才能被RecoverFunc正确识别 - 禁止 panic 原始字符串或
errors.New(),这类 panic 会被当作未知错误,触发 500 + 日志告警,不符合业务语义
优雅降级的关键:不阻塞主线程,不泄漏敏感信息
Beego 的 panic 恢复发生在单个请求 goroutine 内,不影响其他请求;但若你在 RecoverFunc 里做耗时操作(如发邮件、调远程服务),会拖慢当前请求响应,甚至引发超时连锁反应。
- 日志记录必须异步(用 channel + 单独 goroutine 消费),或至少用
log.Printf而非阻塞式文件写入 - 告警通知(如钉钉、企业微信)应设超时和重试上限,失败立即丢弃,不阻塞响应流
- 响应体中永远不返回
debug.Stack()内容,生产环境连err.Error()都要脱敏(如替换数据库密码、路径等) - 若依赖的下游服务不可用,应在 service 层提前判断并 panic 自定义
PageError,而不是让 panic 发生在 DB 驱动层,那样 recovery 时已无业务上下文
真正难的不是写一个能 catch panic 的函数,而是决定哪些 panic 该被转化成 200 响应、哪些该降级为 500、哪些该触发告警——这需要你深入理解每个 PageError.Code 的 HTTP 语义,以及业务场景中“失败”与“异常”的边界。Beego 不替你做这个判断,它只提供钩子。











