Go中panic后需在recover中调用runtime.Stack(buf, false)获取当前goroutine栈信息,buf需预分配足够空间,且必须在recover成功后立即执行,避免日志丢失或上下文失效。

panic 发生后如何获取完整栈信息
Go 的 panic 默认会打印到 stderr 并终止程序,但你无法直接拿到字符串形式的栈迹用于自定义记录。必须在 recover 捕获后,用 debug.PrintStack() 或 runtime.Stack() 主动抓取。
推荐用 runtime.Stack():它返回 []byte,可控性强,还能限制栈长度避免内存暴涨;debug.PrintStack() 直接输出到 os.Stderr,不适合日志集成。
-
runtime.Stack(buf, true)的第二个参数设为true表示捕获所有 goroutine 的栈,生产环境慎用——可能卡住或打爆内存 - 只抓当前 goroutine 时传
false,更安全,也符合多数错误排查需求 - buf 需预先分配足够空间,例如
make([]byte, 1024*1024);太小会截断,太大浪费,建议从 64KB 起调
在 defer + recover 中正确捕获 panic 栈
recover 必须在 defer 函数中直接调用才有效,且不能跨函数间接调用。常见错误是把 recover() 包在另一个函数里,结果始终返回 nil。
栈捕获要放在 recover() 成功之后,否则 runtime.Stack() 拿到的是“恢复后”的栈,不是 panic 点。
- 必须写成
if r := recover(); r != nil { /* 此处调用 runtime.Stack */ } - 不要在 recover 后再起 goroutine 去记录栈——那时 panic 上下文已丢失
- panic 值(
r)本身不带栈,只是原始 error 或任意值,别指望靠它反查调用链
func doWork() {
defer func() {
if r := recover(); r != nil {
buf := make([]byte, 64*1024)
n := runtime.Stack(buf, false)
log.Printf("panic: %v\n%s", r, string(buf[:n]))
}
}()
panic("something went wrong")
}为什么 log.Fatal 和 os.Exit 会让 panic 栈丢失
如果你在 recover 后调用了 log.Fatal 或 os.Exit,进程会立即终止,导致 defer 链中断,之前准备写入的日志可能根本没刷出。更隐蔽的是,某些日志库(如 zap 的 sync 日志器)在进程强退时来不及 flush。
- recover 后应避免任何可能导致进程非正常退出的操作
- 记录完栈后,用
os.Exit(1)是安全的,但务必确保日志已落盘(比如用log.SetOutput(os.Stderr)并确认 stderr 未被重定向缓冲) - 在 HTTP handler 等长生命周期场景中,recover 后通常选择返回错误响应,而不是退出进程
生产环境栈信息记录的取舍点
全量栈(runtime.Stack(buf, true))在高并发 panic 场景下可能引发雪崩:goroutine 数千时,单次调用耗时几十毫秒、内存占用数 MB 很常见。这不是理论风险,而是真实踩过的坑。
- 默认只采集当前 goroutine 栈(
false),够定位 95% 以上问题 - 若需关联上下游,优先加 traceID、requestID 等上下文字段,比堆栈更轻量且可检索
- 对关键服务,可配置 panic 采样率(如每 100 次 panic 记一次全栈),用原子计数器控制
栈信息不是越多越好,能快速锁定 panic 点+上下文变量,就达到了记录目的。过度采集反而掩盖真正需要关注的信号。


















