背景
我們的日志接入阿里云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 mainimport ("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信息并轉換為JSONlogPanicAsJSON(r)os.Exit(1)}}()// 業務代碼...testPanic()time.Sleep(1 * time.Second)
}func testPanic() {// nil指針引發panicvar 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信息并轉換為JSONlogPanicAsJSON(r)os.Exit(1)}}()// 業務代碼...go func() {testPanic()}()time.Sleep(1 * time.Second)
}
擴展優化
日志結構體中增加traceId信息維度。