必须在 recover 后立即调用 debug.Stack() 获取完整堆栈,否则日志仅剩无上下文的 panic 信息;debug.Stack() 返回 []byte 可转 string 写入日志,禁用 debug.PrintStack();避免 runtime.Caller(1) 错误定位,禁用 runtime.Stack(buf, true) 生产环境。

panic 默认只输出最顶层的几行调用,根本不够定位问题——必须在 recover 里主动抓取完整堆栈,否则日志里只剩 panic: index out of range 这种无上下文信息。
recover 后立刻调用 debug.Stack() 才能拿到原始 panic 点的栈
Go 的 recover() 只返回 panic 参数(比如 errors.New("xxx")),不带任何位置信息。堆栈必须额外采集,且必须在 recover() 后**立即执行**,否则后续 defer 可能污染栈帧。
-
debug.Stack()返回[]byte,可直接转成string写入日志,推荐用于生产环境 - 别用
debug.PrintStack()直接输出到os.Stderr:服务若重定向了 stderr(如 systemd、容器日志驱动),你可能完全看不到输出 - 错误写法:
if r := recover(); r != nil { log.Printf("panic: %v", r); file, line, _ := runtime.Caller(1) }—— 此时Caller(1)指向的是recover调用行,不是 panic 发生点
用 runtime.Stack(buf, false) 替代 debug.Stack() 的场景
两者都返回当前 goroutine 的完整调用栈,但行为有关键差异:
-
debug.Stack()是封装好的快捷方式,内部调用的就是runtime.Stack(buf, false) - 手动用
runtime.Stack时,buf太小会导致截断;建议起手就分配make([]byte, 64*1024),避免漏掉深层调用 - 第二个参数传
true会 dump 所有 goroutine,开销极大,仅限调试死锁或 goroutine 泄漏,**生产环境严禁使用** - 如果需要在 panic 恢复后还保留 goroutine ID 和状态字段(比如区分是哪个 handler 崩的),
runtime.Stack(buf, false)输出格式更接近原生 panic 日志
Gin 等 Web 框架中 panic 堆栈常被裁剪,得在 handler 顶层加 defer
Gin 默认的 Recovery() 中间件虽调用了 debug.PrintStack(),但实际运行中常因日志重定向、中间件嵌套或 panic 发生在底层库(如 json.Unmarshal)而丢失关键帧。
立即学习“go语言免费学习笔记(深入)”;
- 在每个 handler 函数开头加
defer func() { if p := recover(); p != nil { log.Printf("[PANIC] %s %s: %v\n%s", c.Request.Method, c.FullPath(), p, string(debug.Stack())) } }() - 不要依赖框架默认 recovery;尤其当 panic 来自第三方库时,堆栈往往止步于
runtime.callN或encoding/json内部,看不到你自己的代码行号 - 配合
req_id注入,在 panic 日志里带上请求标识,方便关联上下游日志
真正容易被忽略的是:debug.Stack() 在内联函数里可能丢帧——如果 defer 所在函数被编译器内联(常见于简单 wrapper),上层调用信息就没了。加 //go:noinline 注释强制不内联,才能确保栈里出现你期望的业务函数名和行号。



















