必须在顶层 defer 中调用 recover() 并用 runtime/debug.Stack() 获取堆栈,写入 *os.File;子 goroutine 需各自 recover,且写入时应使用 os.O_SYNC 或 f.Sync() 确保落盘。

panic 时如何捕获并写入日志文件
Go 程序遇到未处理的 panic 会直接终止,并把堆栈打印到标准错误(stderr)。默认不写文件,也不经过你配置的日志库。想存进日志文件,必须主动拦截 recover 并手动格式化堆栈。
关键点:不能只靠 log.SetOutput 或重定向 os.Stderr —— 那只能捕获 panic 后 stdout/stderr 的输出,但 Go 运行时在 panic 退出前会绕过这些,直接调用底层 write 系统调用输出堆栈,不受 Go 层日志设置影响。
- 必须在主 goroutine 的顶层 defer 中调用
recover() - 用
debug.PrintStack()或runtime/debug.Stack()获取完整堆栈字符串(推荐后者,可控且可捕获) - 把结果写入打开的
*os.File,而非依赖全局日志器(避免 panic 中再触发日志器内部 panic)
用 runtime/debug.Stack() 安全获取堆栈
debug.PrintStack() 直接输出到 os.Stderr,无法捕获;而 runtime/debug.Stack() 返回 []byte,你可以自由处理。这是唯一可靠方式。
注意:该函数在非 panic 场景下也能用,但在 panic 恢复后调用才真正有意义。它包含 goroutine 列表和每个 goroutine 的完整调用帧,比简单 fmt.Sprintf("%+v", err) 全面得多。
立即学习“go语言免费学习笔记(深入)”;
- 返回值是原始字节,需用
string(stackBytes)转为字符串 - 堆栈可能很长,写入文件前建议加时间戳和分隔线,方便定位
- 不要在 recover 后继续执行业务逻辑——除非你明确知道状态是干净的;通常应记录后直接
os.Exit(1)
defer func() {
if r := recover(); r != nil {
f, _ := os.OpenFile("crash.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
defer f.Close()
t := time.Now().Format("2006-01-02 15:04:05")
stack := string(debug.Stack())
fmt.Fprintf(f, "\n[%s] PANIC RECOVERED: %v\n%s\n", t, r, stack)
os.Exit(1)
}
}()
为什么 log.Fatal 不适合替代 panic 日志
log.Fatal 是主动退出,会触发 log 包的输出流程,但它不会产生运行时堆栈(即没有 goroutine 列表、没有各帧源码行号),只有你显式传入的 error 字符串或 fmt 格式内容。
-
log.Fatal("something went wrong")→ 只有这一行文字 -
log.Fatal(err)→ 仅 error 实现的Error()方法返回值,无调用链 - 真正崩溃现场需要的是
runtime/debug.Stack()提供的完整上下文
所以,不要指望用 log.Fatal 替代 panic 日志——它解决的是“主动终止”,不是“崩溃诊断”。
goroutine 泄漏导致 panic 日志丢失的风险
如果 panic 发生在非主 goroutine(比如 http handler 或 timer goroutine),而主 goroutine 已经退出,整个进程会立刻终止,defer 来不及执行,日志就丢了。
这意味着:仅靠主 goroutine 的 defer recover 是不够的。你需要确保所有可能 panic 的 goroutine 都有自己的 recover 机制,或者统一用中间件/包装器兜底(如 http.Server 的 Handler 包装)。
- HTTP 服务中,应在每个 handler 内部 defer recover,或使用公共 wrapper
- 定时任务(
time.Ticker)启动的 goroutine 必须自己 recover - 写日志文件时用
os.O_SYNC或调用f.Sync(),防止进程退出时缓冲区未刷盘
最常被忽略的是:以为加了主 goroutine 的 recover 就万事大吉,结果 panic 出现在子 goroutine,crash.log 里空空如也。


















