Buffalo默认recovery中间件会吞掉panic导致日志无原始堆栈,因其将panic转为泛化500响应且不输出标准日志;子goroutine panic需手动在每个go调用前加defer recover,或封装SafeGo统一处理。

Buffalo 默认 recovery 中间件会吞掉 panic,导致日志里看不到原始错误堆栈
为什么 recover 在 Buffalo 里经常失效
Buffalo 的 app.Serve() 启动流程中内置了 recovery 中间件,它会自动捕获 panic 并返回 500 页面——这本身是好事,但问题在于:
- 它把原始 panic 信息转成泛化的 HTTP 错误,不输出到标准日志(比如
log.Printf或zap) - 如果你在自定义中间件或 handler 里手动写了
defer recover(),它可能被 Buffalo 的 recovery 拦在前面,根本没机会执行 - 子 goroutine 中的 panic(如异步任务、定时器回调)完全不会被主请求链路的 recovery 捕获
绕过默认 recovery,手动加 recover 日志
想看到完整 panic 堆栈,必须跳过 Buffalo 的自动 recovery。做法是:禁用它的 recovery 中间件,并在最外层 handler 包一层自己的 recover。
Buffalo框架 1.0.1 版本源码包下载,适合需要错误处理改进、依赖更新、render.Download 注释和 request logger 调整的 v1 项目。
- 启动前调用
app.Use(RecoveryMiddleware)替换默认中间件,或直接在app.Build()后调用app.Middleware.Skip(recovery.Middleware)(需 importgithub.com/gobuffalo/buffalo/middleware/recovery) - 自己写一个中间件,在
next.ServeHTTP()外包defer+recover,并用log.Printf("%+v", err)或结构化日志库输出完整堆栈 - 注意:
recover()必须在 panic 发生的同一 goroutine 内执行,所以这个中间件要放在最顶层(比如app.Use()第一个注册)
func PanicLogger(next buffalo.Handler) buffalo.Handler {
return func(c buffalo.Context) error {
defer func() {
if r := recover(); r != nil {
log.Printf("PANIC in request %s %s: %+v", c.Request().Method, c.Request().URL.Path, r)
debug.PrintStack() // 输出完整堆栈
}
}()
return next(c)
}
}
// 然后 app.Use(PanicLogger)
子 goroutine 的 panic 怎么记录
Buffalo 的中间件模型只覆盖 HTTP 请求生命周期,对 go func() {...}() 里的 panic 完全无感。这类 panic 只会打印到 stderr,且不带上下文。
- 所有显式起的 goroutine,都必须自己加
defer recover(),不能依赖框架 - 推荐封装一个安全启动函数:
SafeGo(func() { ... }),内部自动包 recover 并打日志 - 如果用了
buffalo.Task或后台 job,确保每个 task 函数体开头就 defer recover,否则 panic 会直接 kill 进程
开发阶段怎么快速定位 panic 来源
别依赖日志——开发时最有效的方式是让 panic 直接暴露出来:
- 改用
go run main.go启动,而不是buffalo dev;前者不加载 Buffalo 的 recovery,panic 会直接打出 stack trace 到终端 - 加编译参数
-gcflags="-l"避免内联,让 stack trace 更准确 - 在
main.go的app.Start()前加全局debug.SetTraceback("all"),增强 goroutine panic 的可读性
真正难缠的是那些没打日志、又没崩进程的 panic——它们往往藏在中间件、插件或第三方库的 goroutine 里,得靠 SafeGo 和全局 panic hook 主动兜底。

















