Revel框架panic导致服务中断却无日志,需立即定位goroutine和代码行:1. 检查handler中裸go调用并替换为safeGo;2. 在init()加defer recover捕获主goroutine panic;3. 全局搜索go关键字,统一用safeGo包装;4. 确认interceptors.go注册revel.InterceptFunc(recoverPanic, revel.PANIC)。

Revel框架运行时Panic导致服务突然中断、接口返回500或直接断连,日志里却找不到panic堆栈,进程还可能意外退出——这种问题必须立刻定位到具体goroutine和代码行。
确认panic是否发生在主请求goroutine
打开HTTP handler函数(如app/controllers/app.go中的Action方法),检查内部是否有显式启动的go语句:go sendNotification()、go logAsync()、time.AfterFunc()、http.Client.Do后续回调等。这些子goroutine里的panic无法被revel默认的recover拦截器捕获。
若存在上述调用,【必须全部替换为safeGo包装】,否则日志永远为空。
启用完整panic堆栈捕获
在app/init.go的init()函数最开头插入:
defer func() {
if r := recover(); r != nil {
log.Panicf("main panic: %v\n%v", r, debug.Stack())
}
}()
这一步能捕获main goroutine、init阶段、以及revel run/test启动失败时的panic,避免因启动异常导致整个应用起不来却无迹可寻。
定位子goroutine panic的三步法
第一步:全局搜索所有go关键字,逐行检查是否裸写go func() {...}()
第二步:将每个匹配项改为safeGo(func() { ... }),其中safeGo定义如下:
func safeGo(fn func()) {
go func() {
defer func() {
if r := recover(); r != nil {
log.Panicf("goroutine panic: %v\n%v", r, debug.Stack())
}
}()
fn()
}()
}
第三步:重启服务,复现问题,此时日志中将明确输出panic发生于哪个go调用、哪一行代码、完整调用栈。
注意:第三方SDK的callback(如数据库驱动的onClose、消息队列的ack回调)也属于子goroutine,同样需要safeGo包裹——不能只查自己写的go。
检查revel拦截器链是否覆盖PANIC阶段
打开app/interceptors.go,确认panic拦截器已注册且顺序正确:
revel.InterceptFunc(recoverPanic, revel.PANIC)
该行必须存在,且不能被注释或条件跳过。如果使用自定义recover逻辑,确保其内部调用了debug.Stack()而非仅打印r值。


















