Revel panic日志为空需分三步排查:1.确认interceptors.go中revel.InterceptFunc(recoverPanic, revel.PANIC)已注册且recoverPanic调用debug.Stack();2.全局搜索裸go并替换为带defer recover和debug.Stack()的safeGo;3.在init()开头加defer recover捕获启动期panic。

Revel panic 日志为空?先确认 recover 拦截器是否生效
Revel 默认注册了 revel.InterceptFunc(recoverPanic, revel.PANIC),但这个拦截器只对主请求 goroutine 有效。一旦你在 controller 方法里写了裸 go sendAsyncLog() 或用了 time.AfterFunc(),panic 就会逃逸出拦截范围,日志里完全看不到堆栈。
检查 app/interceptors.go 是否存在该注册行,且未被注释或条件跳过;若使用自定义 recoverPanic 函数,务必确保内部调用的是 debug.Stack(),而非仅 fmt.Println(r) —— 后者只打印 panic 值,不带调用链。
子 goroutine panic 怎么定位?全局搜 go + 统一替换为 safeGo
裸 go 是 Revel 项目中最隐蔽的 panic 黑洞。它不仅出现在你写的 handler 里,还藏在第三方 SDK 的回调中:数据库 Rows.Close() 后的清理、消息队列 Ack() 回调、HTTP client 的 Transport.RoundTrip 后处理等,都可能在子 goroutine 中 panic。
- 用编辑器全局搜索
go(注意末尾空格,避免匹配到goto或golang) - 逐行检查每个匹配项:是否启动了无超时、无 cancel、无等待的 goroutine
- 全部替换为
safeGo(func() { ... }),其中safeGo必须自带defer recover和debug.Stack()
启动阶段 panic 找不到日志?在 init() 里加顶层 defer recover
Revel 应用启动失败(比如 revel run 报错退出、revel test 卡住)却无任何 panic 输出,大概率是 init() 或 main() 阶段 panic 了——这些不在请求生命周期内,revel 的拦截器根本覆盖不到。
在 app/init.go 的 init() 函数最开头插入:
defer func() {
if r := recover(); r != nil {
log.Panicf("main panic: %v\n%v", r, debug.Stack())
}
}()
这能捕获初始化阶段、配置加载、依赖注入等环节的所有 panic,避免“服务起不来却不知为何”。
堆栈里只有 runtime 函数?检查构建是否启用了调试信息
线上环境跑出的 panic 堆栈如果全是 runtime.gopark、runtime.chansend 这类底层函数,没有你的源码文件名和行号,说明 Go 编译时优化掉了调试符号——不是 Revel 的问题,而是构建配置导致的。
确保编译时未加 -ldflags="-s -w";生产部署若需裁剪体积,应改用 -gcflags="all=-l"(禁用内联)配合保留符号表,而非直接 strip。
另外,Revel 的 app.conf 若设了 app.debug = false,部分 debug 信息(如完整 controller 调用路径)会被隐式关闭,建议在排查期临时开启。

















