Go語言結(jié)構(gòu)化日志slog的用法解析
前言
go 1.21.0 版本引入了一個新的包 log/slog,該包提供了結(jié)構(gòu)化日志的功能。相比于普通的日志,結(jié)構(gòu)化日志更受歡迎,因為它具有更高的可讀性,并且在處理、分析和搜索等方面具有顯著的優(yōu)勢。
接下來讓我們深入探討 log/slog 包的使用,準備好了嗎?準備一杯你最喜歡的咖啡或茶,隨著本文一探究竟吧。
slog 包
slog 包提供了結(jié)構(gòu)化日志,其中的日志記錄包含了 消息、嚴重級別 以及 各種其他屬性,這些屬性以 鍵值對 的形式表示。
slog 包的主要功能如下所示:
- 結(jié)構(gòu)化日志
- 日志嚴重級別
- 日志自定義處理
- 日志分組
初體驗
// github.com/chenmingyong0423/blog/blob/master/tutorial-code/slog/demo1/main.go
package main
import (
"context"
"log/slog"
)
func main() {
slog.Info("slog msg", "greeting", "hello slog")
// 攜帶 context 上下文
slog.InfoContext(context.Background(), "slog msg with context", "greeting", "hello slog")
}在上述示例中,我們直接通過調(diào)用包函數(shù) slog.Info 去輸出一條 info 等級的日志。在該函數(shù)內(nèi)部,會使用默認提供的一個 Logger 實例去執(zhí)行日志輸出的操作。除此之外,我們還能使用 slog.InfoContext 攜帶上下文進行日志輸出。
除了 Info() 和 InfoContext() 函數(shù),還有 Debug()、Warn() 和 Error() 等輸出不同級別日志的函數(shù)。
運行上面這段程序,會得到以下輸出:
2023/10/08 21:08:08 INFO slog msg greeting="hello slog"
2023/10/08 21:08:08 INFO slog msg with context greeting="hello slog"
Logger 的創(chuàng)建
默認情況下,使用 slog 包函數(shù)輸出日志,僅僅是普通的文本格式,若想實現(xiàn) JSON 或者 key=value 的格式輸出,需要使用 slog.New() 函數(shù)創(chuàng)建 Logger 實例,使用該函數(shù)時需要傳入一個 slog.Handler 的實現(xiàn),slog 包提供兩個實現(xiàn):TextHandler 和 JsonHandler。
TextHandler 處理器
TextHandler 是一個日志記錄處理器,它將記錄以一系列鍵值對的形式寫入到一個 io.Writer 中。每個鍵值對都以 key=value 的形式表示,并且它們之間用空格分隔。
// github.com/chenmingyong0423/blog/blob/master/tutorial-code/slog/demo2/main.go
package main
import (
"context"
"log/slog"
"os"
)
func main() {
textLogger := slog.New(slog.NewTextHandler(os.Stdout, nil))
textLogger.InfoContext(context.Background(), "TextHandler", "姓名", "陳明勇")
}在上述示例中,我們通過 slog.NewTextHandler 函數(shù)創(chuàng)建一個日志處理器,第一個參數(shù) os.Stdout 表示將日志輸出到控制臺,然后將處理器作為參數(shù),傳遞到 slog.New 函數(shù)里創(chuàng)建一個 Logger 實例,通過該實例可以執(zhí)行日志輸出的相關(guān)操作。
程序運行結(jié)果如下所示:
time=2023-10-08T21:09:03.912+08:00 level=INFO msg=TextHandler 姓名=陳明勇
JsonHandler 處理器
JsonHandler 是一個日志處理器, 它將記錄以 json 的形式寫入到一個 io.Writer 中。
// github.com/chenmingyong0423/blog/blob/master/tutorial-code/slog/demo3/main.go
package main
import (
"context"
"log/slog"
"os"
)
func main() {
jsonLogger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
jsonLogger.InfoContext(context.Background(), "JsonHandler", "姓名", "陳明勇")
}在上述示例中,我們通過 slog.NewJsonHandler 函數(shù)創(chuàng)建一個 json 日志處理器,第一個參數(shù) os.Stdout 表示將日志輸出到控制臺,然后將處理器作為參數(shù)傳遞到 slog.New 函數(shù)里創(chuàng)建一個 Logger 實例,通過該實例可以執(zhí)行日志輸出的相關(guān)操作。
程序運行結(jié)果如下所示:
{"time":"2023-10-08T21:09:22.614686104+08:00","level":"INFO","msg":"JsonHandler","姓名":"陳明勇"}
全局的 Logger 實例
slog 有一個默認的 Logger 實例,如果我們想要獲取默認的 Logger 實例,可以參考以下代碼:
logger := slog.Default()
在前面的示例中,我們一直使用創(chuàng)建的一個 Logger 實例去輸出日志。然而,如果我們不想每次都需要通過特定的 Logger 實例來記錄日志,而是希望能夠全局操作,我們可以使用 slog.SetDefault 函數(shù)來設置并替換默認的 Logger 實例。這種方式可以使日志記錄更加方便和靈活。
// github.com/chenmingyong0423/blog/blob/master/tutorial-code/slog/demo4/main.go
package main
import (
"context"
"log/slog"
"os"
)
func main() {
jsonLogger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
slog.SetDefault(jsonLogger)
slog.InfoContext(context.Background(), "JsonHandler", "姓名", "陳明勇") //{"time":"2023-10-08T21:11:22.41760604+08:00","level":"INFO","msg":"JsonHandler","姓名":"陳明勇"}
}Group 分組
分組指的給日志記錄相關(guān)聯(lián)的屬性(鍵值對)進行分組,通過示例感受一下:
// github.com/chenmingyong0423/blog/blob/master/tutorial-code/slog/demo5/main.go
package main
import (
"context"
"log/slog"
"os"
)
func main() {
jsonLogger := slog.New(slog.NewJSONHandler(os.Stdout, nil)).WithGroup("information")
jsonLogger.InfoContext(context.Background(), "json-log", slog.String("name", "chenmingyong"), slog.Int("phone", 1234567890))
textLogger := slog.New(slog.NewTextHandler(os.Stdout, nil)).WithGroup("information")
textLogger.InfoContext(context.Background(), "json-log", slog.String("name", "chenmingyong"), slog.Int("phone", 1234567890))
}運行這段程序的結(jié)果如下所示:
{"time":"2023-10-08T21:12:23.124255258+08:00","level":"INFO","msg":"json-log","information":{"name":"chenmingyong","phone":1234567890}}
time=2023-10-08T21:12:23.127+08:00 level=INFO msg=json-log information.name=chenmingyong information.phone=1234567890
根據(jù)運行結(jié)果可知,如果是對具有 JsonHandler 處理器的 Logger 實例進行分組操作,輸出日志時,組名 group name 將作為 key,value 則是所有鍵值對組成的一個 json 對象。
如果是對具有 TextHandler 處理器的 Logger 實例進行分組操作,組名 group name 將與所有鍵值對的鍵進行組合,最終以 groupName.key=value 的形式展示。
LogAttrs 高效輸出日志
如果需要頻繁輸出日志,相比之前的例子,我們使用 slog.LogAttrs 函數(shù)結(jié)合 slog.Attr 類型的去輸出日志會更 高效,因為減少了類型解析的過程。
// github.com/chenmingyong0423/blog/blob/master/tutorial-code/slog/demo6/main.go
package main
import (
"context"
"log/slog"
"os"
)
func main() {
jsonLogger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
jsonLogger.LogAttrs(context.Background(), slog.LevelInfo, "高效輸出日志", slog.String("姓名", "陳明勇"), slog.Int("聯(lián)系方式", 12345678901))
}在上面的示例中,我們使用了 LogAttrs 方法去輸出一條日志,該方法的簽名為:func (l *Logger) LogAttrs(ctx context.Context, level Level, msg string, attrs ...Attr)。
結(jié)合方法簽名我們可以知道,第一個參數(shù)為 context.Context 上下文類型,第二個參數(shù)為 Level 類型,即 slog 包里面的日志嚴重級別類型,第三個參數(shù)為 Attr 鍵值對類型。
在使用其他方法如 Info 輸出日志時,內(nèi)部會將鍵值對轉(zhuǎn)成 Attr 類型,而使用 LogAttrs 方法,我們直接指定了 Attr 類型,減少了轉(zhuǎn)換的過程,因此會更 高效。
With 設置統(tǒng)一的屬性
如果每條日志都包含相同的一個鍵值對,我們可以考慮設置一個統(tǒng)一的屬性。
// github.com/chenmingyong0423/blog/blob/master/tutorial-code/slog/demo7/main.go
package main
import (
"context"
"log/slog"
"os"
)
func main() {
jsonLogger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
logger := jsonLogger.With("systemID", "s1")
logger.LogAttrs(context.Background(), slog.LevelInfo, "json-log", slog.String("k1", "v1"))
logger.LogAttrs(context.Background(), slog.LevelInfo, "json-log", slog.String("k2", "v2"))
}我們可以使用 With 方法添加一個或多個固定屬性,并返回一個新的 Logger 實例,后面通過新實例輸出的日志都會包含 被添加的固定屬性,從而 避免 每條輸出的日志語句添加 相同 的鍵值對。
運行這段程序的結(jié)果如下所示:
{"time":"2023-10-08T21:19:51.338328238+08:00","level":"INFO","msg":"json-log","systemID":"s1","k1":"v1"}
{"time":"2023-10-08T21:19:51.338604943+08:00","level":"INFO","msg":"json-log","systemID":"s1","k2":"v2"}
HandlerOptions 日志處理器的配置選項
細心的讀者也許能發(fā)現(xiàn),在前面的示例中,無論是 NewJSONHandler,還是 NewTextHandler,第二個參數(shù)都被設置為 nil,這是為了使用默認的配置。
這個參數(shù)的類型為 *HandlerOptions,通過它,我們可以配置是否顯示日志語句的源代碼位置信息、最低的日志輸出級別以及鍵值對屬性的重寫操作。
// github.com/chenmingyong0423/blog/blob/master/tutorial-code/slog/demo8/main.go
package main
import (
"context"
"log/slog"
"os"
"time"
)
func main() {
jsonLogger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
AddSource: true,
Level: slog.LevelError,
ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr {
if a.Key == slog.TimeKey {
if t, ok := a.Value.Any().(time.Time); ok {
a.Value = slog.StringValue(t.Format(time.DateTime))
}
}
return a
},
}))
jsonLogger.InfoContext(context.Background(), "json-log", slog.String("姓名", "陳明勇"))
jsonLogger.ErrorContext(context.Background(), "json-log", slog.String("姓名", "陳明勇"))
}在上述示例中,我們創(chuàng)建了一個具有 JsonHanlder 處理器的 Logger 實例。在創(chuàng)建 JsonHanlder 時,通過 HandlerOptions 參數(shù)指定了以下配置:
- 輸出日志語句的源代碼配置
Source信息 - 設置最低日志等級為
Error - 將
key為"time"的屬性值的格式重寫為"2006-01-02 15:04:05"的形式。
運行這段程序得到的結(jié)果如下所示:
{"time":"2023-10-08 21:21:31","level":"ERROR","source":{"function":"main.main","file":"D:/goproject/src/gocode/play/main.go","line":24},"msg":"json-log"
,"姓名":"陳明勇"}
輸出結(jié)果與預期相同,INFO 等級的日志沒有被輸出、輸出 Source 信息以及重寫 key 為 "time" 的屬性值。
自定義 key-value 對中 value 的值
在前面的一個案例中,我們有通過 HandlerOptions 配置來修改 key-value 對中 value 的值,除此之外,slog 包還支持使用另一種方式進行修改。
// github.com/chenmingyong0423/blog/blob/master/tutorial-code/slog/demo9/main.go
package main
import (
"context"
"log/slog"
)
type Password string
func (Password) LogValue() slog.Value {
return slog.StringValue("REDACTED_PASSWORD")
}
func main() {
slog.LogAttrs(context.Background(), slog.LevelInfo, "敏感數(shù)據(jù)", slog.Any("password", Password("1234567890")))
}在上述案例中,我們通過實現(xiàn) slog.LogValuer 接口(為某個類型添加 LogValue() slog.Value 方法),將 key-value 對中 value 的值進行重寫。日志輸出時,value 的值將會被 LogValue 方法的返回值所覆蓋。
運行這段程序輸出的結(jié)果如下所示:
2023/10/08 21:37:11 INFO 敏感數(shù)據(jù) password=REDACTED_PASSWORD
輸出結(jié)果與預期結(jié)果相同,password 的 value 已經(jīng)被修改。
到此這篇關(guān)于Go語言結(jié)構(gòu)化日志slog的用法解析的文章就介紹到這了,更多相關(guān)go slog內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
go gin中間件關(guān)于 c.next()、c.abort()和return的使用小結(jié)
中間件的執(zhí)行順序是按照注冊順序執(zhí)行的,中間件可以通過 c.abort() + retrurn 來中止當前中間件,后續(xù)中間件和處理器的處理流程,?這篇文章給大家介紹go gin中間件關(guān)于 c.next()、c.abort()和return的使用小結(jié),感興趣的朋友跟隨小編一起看看吧2024-03-03
Go內(nèi)存分配之結(jié)構(gòu)體優(yōu)化技巧
這篇文章主要為大家詳細介紹了Go語言內(nèi)存分配之結(jié)構(gòu)體優(yōu)化技巧的相關(guān)知識,文中的示例代碼講解詳細,感興趣的小伙伴可以跟隨小編一起學習一下2023-11-11

