Beego 中 main 或 init 的 defer recover 无效,因 panic 发生在独立请求 goroutine 内,而主线程的 recover 无法跨 goroutine 捕获;真正起作用的是 Beego 内置的 recoverPanic(recoverpanic=true),它已自动注入每个请求生命周期中。

Beego 里没有真正意义上的“全局异常捕获”——recover 只作用于当前 goroutine,而每个 HTTP 请求都在独立 goroutine 中执行,main 函数或 init 里的 defer recover 完全无效。
为什么在 main 或 init 里 defer recover 没用
Beego 启动后,每个请求由独立 goroutine 处理,panic 发生在这些子 goroutine 内。main 函数的 defer 只绑定主线程,无法跨 goroutine 捕获。你看到服务没崩,其实是 Beego 默认的 recoverPanic 在起作用(配置项 recoverpanic = true),它已内置在每个请求生命周期中。
- 现象:在
main()里写defer func() { recover() }(),然后在控制器里panic("test"),日志里看不到你的 recover 日志,但响应仍是 500 - 原因:Beego 的
recoverPanic已先于你的逻辑执行并吞掉了 panic - 后果:你以为自己接管了,其实只是和框架默认逻辑重叠,还可能干扰原有日志/监控路径
想自定义 panic 处理,必须在每个控制器方法里显式 defer
要替换 Beego 默认行为(比如加告警、改响应格式、记录 requestID),唯一可靠方式是把 defer recoverPanic() 手动写进每个需要保护的 handler 方法内部,比如 Get()、Post()。
- 不要写成通用函数再调用,
recover()必须在defer中直接调用,不能包在另一层函数里 - 示例写法:
func (c *MainController) Get() { defer func() { if r := recover(); r != nil { logs.Error("PANIC in Get: %v, stack: %s", r, debug.Stack()) // 发送告警、上报监控... c.Ctx.ResponseWriter.WriteHeader(500) c.Ctx.WriteString("Internal server error") } }() // 业务逻辑 panic("simulate crash") } - 注意:Beego 的
Controller是值类型,c.Ctx是指针,所以c.Ctx.ResponseWriter可安全用于写响应
别把 recover 当 error 处理器,常规错误必须 return error
recover 只管 panic,对 error 值完全无感。把参数校验失败、DB 查询为空等业务错误用 panic 抛出,再靠 recover 拦截,是反模式。
- 问题:recover 后状态码常被忽略(仍为 200),requestID 丢失,错误链断裂(
%v包装导致errors.Is()失效) - 正解:定义统一 handler 签名,如
func(http.ResponseWriter, *http.Request) error,中间件检查返回值 - 自定义 error 类型必须实现:
-
Unwrap() error—— 否则errors.Is(err, ErrNotFound)穿透不了 -
ErrorCode() int—— 中间件靠它映射 HTTP 状态码(如 1001 → 404)
-
- 构造错误必须用工厂函数:
ErrUserNotFound = NewCodeError(1001, "user not found"),禁用裸fmt.Errorf
Beego 默认 recoverPanic 的行为和限制
Beego 的 recoverPanic 是开箱即用的兜底,但它只做三件事:打印日志、设 500 状态码、返回简单 HTML 错误页。它不记录 requestID,不关联 traceID,也不触发告警。
- 如果你关掉它(
recoverpanic = false),又没手动加 defer recover,panic 会直接杀掉对应 goroutine,HTTP 连接挂起,客户端超时 - 它不处理 panic 后的资源清理(比如未关闭的文件句柄、未释放的数据库连接),这部分得靠业务代码里的
defer显式保证 - 它的日志输出不含 HTTP 方法、路径、IP,排查时很难定位到具体哪个接口出的问题
真正难的不是“怎么捕获”,而是“捕获之后要不要继续让它走默认流程”。多数场景下,保留 Beego 默认 recover + 补充中间件级 error 统一处理,比硬切 panic 流程更稳。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











