golang panic信息捕获
背景
我们的日志接入阿里云sls平台,但是,日志是以json的格式存储在阿里云sls平台上,程序中产生的error,info等日志都可以实现以json的格式打印。但是,golang程序中产生的panic信息本身不是以json的格式输出,这就导致panic信息在阿里云sls平台上不方便检索。
基于上述痛点,我们期望捕获程序的panic信息,并且以json的格式打印,如此,我们就可以方便的实现在阿里云sls平台上检索的目的。
解决方案
核心的思路
通过defer
和recover()
机制捕获panic信息,结合Go的JSON序列化能力,将堆栈信息、错误内容等关键数据封装为结构化JSON格式。
实现步骤
定义日志的结构体
日志结构体定义:
type PanicLog struct {
Timestamp string `json:"@timestamp"`
Level string `json:"level"`
Message string `json:"message"`
Stack string `json:"stack"`
Service string `json:"service"`
}
封装打印日志的方法
func logPanicAsJSON(panicObj interface{}) {
stack := string(debug.Stack()) // 获取完整堆栈
logEntry := PanicLog{
Timestamp: time.Now().Format(time.RFC3339),
Level: "PANIC",
Message: fmt.Sprintf("%v", panicObj),
Stack: stack,
Service: "your-service-name",
}
jsonData, _ := json.Marshal(logEntry)
// 输出到SLS(根据实际日志库选择方式)
log.Println(string(jsonData) )
}
封装方法捕获panic
defer func() {
if r := recover(); r != nil {
logPanicAsJSON(r) // 记录 panic 信息
c.AbortWithStatus(http.StatusInternalServerError)
}
}()
测试案例
package main
import (
"encoding/json"
"fmt"
"log"
"os"
"runtime/debug"
"time"
)
type PanicLog struct {
Timestamp string `json:"@timestamp"`
Level string `json:"level"`
Message string `json:"message"`
Stack string `json:"stack"`
Service string `json:"service"`
}
func main() {
defer func() {
if r := recover(); r != nil {
// 捕获panic信息并转换为JSON
logPanicAsJSON(r)
os.Exit(1)
}
}()
// 业务代码...
testPanic()
time.Sleep(1 * time.Second)
}
func testPanic() {
// nil指针引发panic
var a *int
*a = 1
}
func logPanicAsJSON(panicObj interface{}) {
stack := string(debug.Stack()) // 获取完整堆栈
logEntry := PanicLog{
Timestamp: time.Now().Format(time.RFC3339),
Level: "PANIC",
Message: fmt.Sprintf("%v", panicObj),
Stack: stack,
Service: "your-service-name",
}
jsonData, _ := json.Marshal(logEntry)
// 输出到SLS(根据实际日志库选择方式)
log.Println(string(jsonData))
}
注意事项
在Go语言里,recover()
函数只能捕获当前goroutine内产生的 panic
。
所以,在下面的这个案例中recover不能捕获到panic信息。如果需要捕获到,需要在每个协程中都执行recover的逻辑。
func main() {
defer func() {
if r := recover(); r != nil {
// 捕获panic信息并转换为JSON
logPanicAsJSON(r)
os.Exit(1)
}
}()
// 业务代码...
go func() {
testPanic()
}()
time.Sleep(1 * time.Second)
}
扩展优化
日志结构体中增加traceId信息维度。