必须预分配足够大的buf,如64kb起步,检查返回值n是否等于len(buf)以避免截断;all=true会stw且耗时高,慎用于高频路径。

recover 后用 runtime.Stack 捕获堆栈必须预分配足够大的 buf
直接传 nil 给 runtime.Stack 看似省事,但返回的 []byte 可能被后续 GC 回收,导致日志乱码或 panic;更常见的是传太小的缓冲区(比如 make([]byte, 4096)),结果只截出几行 runtime 初始化代码,甚至整个返回长度为 0。
实操建议:
- 当前 goroutine 堆栈:至少
make([]byte, 64*1024)(64KB),深度嵌套场景建议 1MB - 所有 goroutine 堆栈:
make([]byte, 2*1024*1024)(2MB)起步,避免因截断漏掉关键阻塞点 - 务必检查返回值
n是否等于len(buf),相等说明仍可能被截断,应 warn 或降级处理 - 别写
string(runtime.Stack(nil, false))—— 这是线上事故高发写法
runtime.Stack 第二个参数 all=true 的真实代价
设 all=true 不只是“多打点信息”,它会同步遍历全部活跃 goroutine 结构体、拼接文本,触发 STW 片段延长。goroutine 数量超 5000 时,单次调用常耗时 20–80ms,内存峰值飙升数 MB。
使用场景要严格区分:
Colly 是一个用于 Go 语言的快速开源爬取和爬虫框架。它适用于从简单的页面提取到异步爬虫处理大量页面集合,支持请求回调和结构化解析。
- 死锁诊断、SIGQUIT 信号处理、pprof 接口底层 —— 可用,但需确保不在高频路径
- HTTP handler 中 recover 后默认不启用,95% 的 panic 根因靠当前 goroutine 堆栈就能定位
- 若真需要全量,建议在独立 goroutine 中执行,并加 timeout 控制(如
time.AfterFunc(200*time.Millisecond, os.Exit)防卡死)
debug.Stack() 和 runtime.Stack 的关键区别
debug.Stack() 是封装好的快捷函数,内部调用 runtime.Stack 并直接返回 []byte,但它把输出固定写到 os.Stderr,无法接入结构化日志系统;而 runtime.Stack 返回原始字节,可控性高,但需手动转 string 并处理换行。
常见错误:
- 在 defer + recover 中用
debug.PrintStack()→ 日志脱离 log 实例,线上无法检索、无 level、无 traceID - 用
log.Printf("%s", debug.Stack())→ 多余拷贝,且末尾自动带换行,和日志前缀混在一起难解析 - 正确姿势:
buf := make([]byte, 1024*1024); n := runtime.Stack(buf, false); log.Error("panic recovered", "stack", string(buf[:n]))
panic 发生后立即记录栈信息,但别紧接着调用 log.Fatal 或 os.Exit
recover 后如果马上调 log.Fatal 或 os.Exit,日志可能根本没刷出磁盘 —— 尤其用了带缓冲的 writer(如 zap 的 core.NewCore 默认行为)或 stderr 被重定向时。
关键动作顺序不能错:
- 先完成栈捕获与日志写入(确认
log.Sync()或用log.SetOutput(os.Stderr)强制直写) - 再决定是否退出:HTTP server 应返回 500,CLI 工具可
os.Exit(1),但必须确保上一步已落盘 - 避免在 defer 链中嵌套多个 recover —— 后面的 recover 永远捕不到前面已 recover 过的 panic
all 参数的组合风险:一个看似无害的 runtime.Stack(buf, true) 在 goroutine 泄漏未被发现时,可能让 panic 处理本身变成性能瓶颈。golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










