最直接方式是调用 runtime.stack(nil, false),它自动分配缓冲区并捕获当前 goroutine 栈;需转为字符串查看,且保留 dwarf 信息才能显示函数名和行号。

用 runtime.Stack 捕获当前 goroutine 的完整调用栈
最直接的方式是调用 runtime.Stack,它返回当前 goroutine 的栈帧快照(字节切片),需手动转成字符串查看。注意它默认只捕获本 goroutine,且不包含符号信息(如函数名行号)除非编译时带 -gcflags="-l" 关闭内联,或用 go build -ldflags="-s -w" 以外的调试构建。
常见错误是直接打印返回值而没做类型转换:
buf := make([]byte, 1024*1024)
n := runtime.Stack(buf, false) // false 表示只抓当前 goroutine
fmt.Printf("%s", buf[:n])
关键点:
-
runtime.Stack第二个参数为true时会尝试 dump 所有 goroutine,但可能阻塞并不可靠,生产环境慎用 - 缓冲区太小会截断,建议至少 1MB;也可用
nil让运行时自动分配:runtime.Stack(nil, false) - 输出含地址偏移,要看到可读函数名和行号,必须保留 DWARF 信息(即不用
-ldflags="-s -w")
用 debug.PrintStack 快速打印并终止当前 goroutine
debug.PrintStack 是封装好的快捷方式,等价于 fmt.Println(string(runtime.Stack(nil, false))),适合调试时快速插入、不改逻辑流。但它会把栈输出到 os.Stderr,不能重定向到日志文件或结构化字段。
典型误用场景:
- 在 HTTP handler 中频繁调用 —— 它不 panic,但大量输出会冲垮 stderr,且无上下文标识
- 期望它返回字符串用于日志记录 —— 实际上它只打印,返回值是
void - 在 defer 中无条件调用 —— 可能掩盖真实 panic,干扰错误归因
更稳妥的做法是结合 recover 使用:
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
defer func() {
if r := recover(); r != nil {
log.Printf("panic: %v\n%s", r, debug.Stack()) // 注意是 debug.Stack(),不是 PrintStack
}
}()
用 runtime/debug.Stack 获取字符串形式的栈并参与日志处理
runtime/debug.Stack(注意包路径)才是返回 []byte 的函数,常被误写成 debug.PrintStack 或漏掉 runtime/ 前缀。它是日志集成的主力:可拼接上下文、写入结构化日志、采样控制。
性能与兼容性提醒:
- 每次调用都会触发一次栈遍历,开销不小(尤其 goroutine 多、栈深时),不要在 hot path 频繁调用
- Go 1.16+ 默认启用
GOEXPERIMENT=fieldtrack等优化,但栈采集本身不受影响 - 交叉编译(如 macOS 编译 Linux 二进制)时,行号信息仍有效,前提是源码路径未被 strip
一个轻量日志封装示例:
func logStack(msg string) {
stack := runtime/debug.Stack()
log.Printf("%s\n%s", msg, strings.TrimSpace(string(stack)))
}
panic 时自动打印完整堆栈的可靠做法
单纯依赖 log.Panic 或顶层 panic 不够——默认只显示 panic value,不带栈;而 log.Fatal 会直接退出,无法捕获。
真正可靠的方案是设置全局 panic hook:
- 用
http.Server.ErrorLog拦截 HTTP server 内部 panic(仅限 net/http) - 在 main 函数开头调用
signal.Notify捕获SIGQUIT,然后手动触发debug.Stack输出 - 最通用的是替换
os.Stdout/os.Stderr为自定义 writer,但侵入性强
最容易被忽略的一点:Go 的 panic 栈默认**不展开被 recover 的 goroutine 的其他栈帧**。也就是说,如果 A goroutine panic 后被 B goroutine 的 defer recover,debug.Stack() 在 B 中调用,看到的仍是 B 的栈,不是 A 的。要查原始 panic 点,必须在 A 中 recover 或依赖 runtime.SetPanicHandler(Go 1.19+)。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










