Golang如何在崩溃前存储日志
Golang如何在崩溃前存储日志 你好,
我正在开发一个应用程序,需要妥善处理错误以避免程序崩溃。为了帮助调试和解决可能出现的问题,我希望将发生的任何错误的堆栈跟踪存储到日志文件中。我尝试在 main 函数中添加一个 defer 函数来获取堆栈跟踪,但没有成功。我包含了一个示例代码片段,该代码由于未处理的 panic 导致了堆栈溢出错误。如何修改此代码才能将错误的堆栈跟踪存储到日志文件中?
package main
import (
"io"
"runtime"
"github.com/rs/zerolog"
"gopkg.in/natefinch/lumberjack.v2"
)
func main() {
ljLogger := &lumberjack.Logger{
Filename: "/var/log/myapp.log",
MaxSize: 10, // 每个日志文件的最大大小(MB)
MaxBackups: 5, // 保留的旧日志文件最大数量
MaxAge: 7, // 保留日志文件的最大天数
}
// 创建一个同时写入文件和缓冲区的多路写入器
mw := io.MultiWriter(ljLogger)
logger := zerolog.New(mw).With().Timestamp().Logger()
defer func() {
if r := recover(); r != nil {
// 记录错误信息和堆栈跟踪
stackTrace := make([]byte, 4096)
length := runtime.Stack(stackTrace, false)
logger.Error().Interface("panic_value", r).Bytes("stacktrace", stackTrace[:length]).Msg("Unhandled panic occurred")
}
}()
abcd()
}
func abcd() {
defer func() {
if r := recover(); r != nil {
abcd()
}
}()
panic("something went wrong")
}
更多关于Golang如何在崩溃前存储日志的实战教程也可以访问 https://www.itying.com/category-94-b0.html
我的实际问题:
我使用 Golang 的 Web 应用程序创建了一个 MSI 安装包。但这个应用程序因未知错误而崩溃,我无法看到或重现该问题。我知道这是由于未处理的错误导致的。但我找不到导致问题的确切位置。为了修复或处理该错误,我需要查看堆栈跟踪。
在上面的代码中,我递归调用了 abcd() 来使程序崩溃。
func main() {
fmt.Println("hello world")
}
更多关于Golang如何在崩溃前存储日志的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
正如我所说,从第一个错误处理程序中递归调用 abcd() 只会导致栈溢出,这是无法恢复的。
我稍微简化了你的示例代码,链接的 Playground 对我来说可以正常工作,并将堆栈跟踪打印到终端,同时也打印了长度(我添加这个是为了验证它确实是自定义处理程序的行为,而不是默认行为)。

Go Playground - Go 编程语言
不过,与其预初始化一个长度为 4k 的缓冲区,我可能更倾向于使用一个长度为 0、容量为 4k 的缓冲区(make([]byte, 0, 4096))。
此外,通过在输出中添加 r,你可以包含原始错误消息,正如你在 Go Playground - Go 编程语言 中看到的那样。
我认为最可靠的处理方式是通过你的监控程序/初始化系统/系统日志记录器,来收集并将你所有程序的输出写入一个集中化的日志文件。
你这里的问题是:
你在 abcd() 中发生了 panic,而它会试图通过调用 abcd() 来 recover() 自身,这将导致无限递归,最终耗尽你的栈空间并产生一个你无法 recover 的错误,因为 Go 运行时在栈溢出时会强制终止你的程序,而不给你从中恢复的机会。
在我看来,一个记录 panic 的集中化处理程序是可以接受的,尽管它不应该依赖于日志记录器(因为可能正是它崩溃了),而只应该依赖于“原始”操作,并且它也不应该依赖除标准输出/错误流之外的任何东西,因为这些可能已经不可用了。你永远不知道是什么崩溃了。
除了这个用于获取一些日志的集中化 panic 处理程序之外,你真的完全不应该使用 recover()!处理你得到的任何 error 返回值,如果你调用的某个函数在不返回错误的情况下使用了 panic,那就修改它进行正确的错误处理,或者向上游提交一个 bug 报告!
func main() {
fmt.Println("hello world")
}
在Go中捕获panic并记录堆栈跟踪的正确方法如下:
package main
import (
"io"
"os"
"runtime/debug"
"time"
"github.com/rs/zerolog"
"gopkg.in/natefinch/lumberjack.v2"
)
func main() {
// 设置日志轮转
ljLogger := &lumberjack.Logger{
Filename: "/var/log/myapp.log",
MaxSize: 10,
MaxBackups: 5,
MaxAge: 7,
Compress: true,
}
// 创建多路写入器(同时输出到文件和终端)
mw := io.MultiWriter(ljLogger, os.Stdout)
// 创建zerolog实例
logger := zerolog.New(mw).
With().
Timestamp().
Logger()
// 设置全局panic处理
defer handlePanic(&logger)
// 你的应用逻辑
abcd()
}
func handlePanic(logger *zerolog.Logger) {
if r := recover(); r != nil {
// 获取完整的堆栈跟踪
stack := debug.Stack()
// 记录panic信息
logger.Error().
Time("panic_time", time.Now()).
Interface("panic_value", r).
Str("stacktrace", string(stack)).
Msg("Unhandled panic occurred")
// 可以选择重新panic或优雅退出
panic(r) // 重新抛出panic以保持原始行为
}
}
func abcd() {
defer func() {
if r := recover(); r != nil {
// 这里不应该递归调用,会导致堆栈溢出
// 应该记录错误并退出或重新panic
panic(r)
}
}()
panic("something went wrong")
}
更健壮的实现可以使用自定义的panic处理器:
package main
import (
"fmt"
"io"
"os"
"runtime/debug"
"time"
"github.com/rs/zerolog"
"gopkg.in/natefinch/lumberjack.v2"
)
type PanicHandler struct {
logger zerolog.Logger
}
func NewPanicHandler(logger zerolog.Logger) *PanicHandler {
return &PanicHandler{logger: logger}
}
func (ph *PanicHandler) HandlePanic() {
if r := recover(); r != nil {
// 获取堆栈信息
stack := debug.Stack()
// 记录详细信息
ph.logger.Error().
Time("timestamp", time.Now()).
Str("panic", fmt.Sprintf("%v", r)).
Str("goroutine_stack", string(stack)).
Msg("PANIC RECOVERED")
// 输出到stderr以便立即查看
fmt.Fprintf(os.Stderr, "PANIC: %v\n%s\n", r, stack)
// 根据需求选择退出或继续
os.Exit(1)
}
}
func (ph *PanicHandler) Run(f func()) {
defer ph.HandlePanic()
f()
}
func main() {
// 配置日志
ljLogger := &lumberjack.Logger{
Filename: "/var/log/myapp.log",
MaxSize: 10,
MaxBackups: 5,
MaxAge: 7,
}
mw := io.MultiWriter(ljLogger, os.Stdout)
logger := zerolog.New(mw).
With().
Timestamp().
Logger()
// 创建panic处理器
handler := NewPanicHandler(logger)
// 运行应用
handler.Run(func() {
abcd()
})
}
func abcd() {
// 正常的业务逻辑
panic("simulated panic")
}
对于HTTP服务器,可以这样处理:
package main
import (
"net/http"
"runtime/debug"
"github.com/rs/zerolog"
"gopkg.in/natefinch/lumberjack.v2"
)
func panicMiddleware(logger zerolog.Logger, next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() {
if err := recover(); err != nil {
stack := debug.Stack()
logger.Error().
Interface("panic", err).
Str("stack", string(stack)).
Str("url", r.URL.String()).
Str("method", r.Method).
Msg("HTTP panic recovered")
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
}
}()
next.ServeHTTP(w, r)
})
}
func main() {
// 日志配置
ljLogger := &lumberjack.Logger{
Filename: "/var/log/myapp.log",
MaxSize: 10,
MaxBackups: 5,
MaxAge: 7,
}
logger := zerolog.New(ljLogger).With().Timestamp().Logger()
// 创建带panic恢复的handler
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
panic("HTTP handler panic")
})
handler := panicMiddleware(logger, mux)
http.ListenAndServe(":8080", handler)
}
关键点:
- 使用
debug.Stack()获取完整堆栈跟踪 - 在defer函数中处理panic
- 避免在panic处理中递归调用导致堆栈溢出
- 确保日志在panic后能正确刷新到文件
- 对于不同的应用类型(CLI、HTTP服务等)采用适当的panic处理策略

