panic时日志未输出是因为程序退出前缓冲未刷新,应在defer中recover并调用debug.stack()和logger.sync()确保堆栈和日志落盘。

panic发生时日志没输出,因为程序直接退出了
Go 的 panic 会立即终止当前 goroutine 的执行,如果没被 recover 捕获,整个程序会打印默认堆栈后退出——此时你用 log.Printf 或 zap.Info 写的日志很可能根本没刷到磁盘或控制台。根本原因不是日志库不行,而是标准输出缓冲未强制刷新、进程已终止。
- 确保 panic 前最后一行日志能落盘:在 defer 中调用
log.Output或logger.Sync()(如 zap) - 不要依赖 panic 后的普通语句执行,它们大概率不会运行
- 避免在
defer里只写log.Println("got panic")—— 它不包含堆栈,且可能因 panic 已开始而丢失
用 recover + runtime/debug.Stack() 获取完整堆栈
想记录 panic 的真实位置和调用链,必须在 defer 中调用 recover(),再手动抓取堆栈。Go 标准库的 runtime/debug.Stack() 是最轻量可靠的方式,它返回的是当前 goroutine 的完整 panic 堆栈字节切片,比 debug.PrintStack() 更可控(后者直接写 stderr)。
- 必须在 panic 发生的同一 goroutine 中 recover,main 函数或 http handler 里加 defer 才有效
-
debug.Stack()返回[]byte,记得用string(...)转换后再传给日志函数 - 别用
fmt.Sprintf("%+v", err)替代——它不显示 goroutine 切换和 runtime 调用帧
func main() {
defer func() {
if r := recover(); r != nil {
buf := debug.Stack()
log.Printf("PANIC: %v\n%s", r, string(buf))
}
}()
panic("something went wrong")
}
在 HTTP 服务中统一捕获 panic 并记录日志
HTTP handler 里 panic 会导致连接中断、500 都不返回,还漏掉日志。最佳实践是写一个中间件,在每个 handler 外层包一层 recover defer。
- 不要在每个 handler 里重复写 recover,用
http.HandlerFunc包装器统一处理 - 记录时带上请求路径、方法、客户端 IP(从
*http.Request提取),方便定位问题上下文 - recover 后应返回 500 状态码并写错误响应体,否则前端会卡住或收到空响应
- zap 用户注意:调用
logger.Sync()强制刷盘,尤其在 defer 中
zap/zapcore 记录 panic 日志时的 sync 和 level 陷阱
用 zap 记录 panic,光调 logger.Error() 不够。zap 默认异步写日志,panic 发生后程序退出,后台 goroutine 可能来不及把日志写出去;另外 panic 属于严重故障,但误设 level 为 InfoLevel 就会过滤掉。
- 务必在 recover defer 末尾显式调用
logger.Sync(),哪怕只调一次 - 使用
logger.WithOptions(zap.AddCaller())开启调用方信息,否则日志里只显示 recover 函数的位置 - panic 日志建议用
Error或自定义Paniclevel(需配置zappcore.LevelEnablerFunc) - 若用
zap.NewDevelopmentConfig(),它默认启用 stacktrace,但仅对DPanic生效;Error需手动传zap.String("stack", string(buf))
logger.Sync() 调用时机和 debug.Stack() 的 goroutine 绑定范围——panic 在子 goroutine 发生,而 recover 写在 main goroutine,就完全捕不到。golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











