
本文介绍如何在 go 中精准捕获 panic 时的堆栈信息(而非简单重定向 stderr),实现对 panic 输出的结构化获取与定制化处理,避免干扰正常日志流。
Go 默认在发生 panic 时会将所有 goroutine 的堆栈跟踪(stack trace)打印到 os.Stderr 并终止程序。但实际工程中,我们往往需要:
- 仅提取 panic 相关的原始堆栈(不含其他 goroutine 的冗余信息);
- 将 panic 内容发送至监控系统、写入结构化日志或触发告警;
- 保持 stderr 对其他错误日志(如 log.Printf 或第三方 logger 输出)的可用性。
此时,简单重定向 os.Stderr 不仅难以区分 panic 输出与其他日志,还可能破坏并发日志写入的安全性。更可靠的方式是在 panic 发生前主动捕获堆栈——借助 recover() + runtime/debug.Stack()(推荐)或 runtime.Stack()。
✅ 推荐做法:使用 runtime/debug.Stack() 获取当前 goroutine 的 panic 堆栈(简洁、安全、无需参数):
package main
import (
"log"
"runtime/debug"
)
func main() {
defer func() {
if r := recover(); r != nil {
// 捕获 panic 时的堆栈(仅当前 goroutine,格式化好)
stack := debug.Stack()
log.Printf("PANIC CAUGHT: %v\nSTACK:\n%s", r, stack)
// ✅ 此处可进一步:上报 Sentry、写入 ELK、触发 webhook 等
// sendToMonitoring("panic", r, string(stack))
}
}()
panic("something went wrong")
}⚠️ 注意事项:
- debug.Stack() 返回的是当前 goroutine 的完整堆栈(含 panic 调用链),语义清晰、开销可控,是官方推荐的 panic 堆栈捕获方式;
- runtime.Stack(buf []byte, all bool) 更底层,需手动管理缓冲区,且 all=true 会返回所有 goroutine 的堆栈(通常冗余,且可能因 goroutine 数量多而截断或 OOM);不建议用于 panic 捕获场景;
- recover() 必须在 deferred 函数中直接调用,且仅对同一 goroutine 的 panic 有效;
- 若需全局 panic 捕获(如主 goroutine 外的子 goroutine),需为每个 goroutine 显式添加 defer/recover —— Go 不支持跨 goroutine 的 panic 传播捕获。
? 进阶提示:结合 runtime.Caller() 可定位 panic 触发位置;搭配 errors.WithStack(如 github.com/pkg/errors)可增强错误上下文,但注意 debug.Stack() 已包含完整调用链,多数场景无需额外封装。
总结:要精准、干净地获取 panic 输出,请始终优先使用 defer + recover + debug.Stack() 组合。它不侵入标准输出流、不干扰日志系统,且符合 Go 的错误处理哲学——将 panic 视为需显式处理的异常控制流,而非不可控的崩溃事件。






