Go 結(jié)構(gòu)化日志slog入門與實戰(zhàn)指南 附避坑秘籍
“日志不是寫給機器看的,是寫給人類在半夜三點 debug 時救命用的。”
Go 1.21 終于帶來了官方結(jié)構(gòu)化日志包 —— log/slog。從此,我們不再需要在 zap、zerolog、logrus 之間反復(fù)橫跳,也不用擔心團隊里有人偷偷用 fmt.Println 寫日志了(好吧,可能還是會有)。
本文將帶你從零上手 slog,并通過幾個小例子展示它如何讓日志變得更結(jié)構(gòu)化、可查詢、可維護——甚至還能幫你保住頭發(fā)。
?? 一、slog 的三大核心:Logger、Handler、Record
slog 的設(shè)計哲學(xué)很清晰:前端 API + 后端實現(xiàn)分離。
Logger:你每天打交道的“前臺”,調(diào)用Info()、Error()的地方。Handler:幕后英雄,決定日志長什么樣、寫到哪(比如 JSON 還是文本?stdout 還是文件?)。Record:中間傳話筒,封裝了時間、級別、消息、屬性等原始數(shù)據(jù)。
想象一下:
Logger是你點外賣的 App,Handler是后廚,Record就是你點的“宮保雞丁 + 微辣 + 不要花生”。
最簡示例
package main
import (
"log/slog"
"os"
)
func main() {
logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
logger.Info("用戶登錄成功", "user_id", 123)
}輸出:
{"time":"2026-01-28T10:00:00Z","level":"INFO","msg":"用戶登錄成功","user_id":123}
? 結(jié)構(gòu)化!? 可被日志系統(tǒng)解析!? 沒有亂七八糟的 fmt.Printf!
?? 二、一個致命陷阱:別再用key, value了!
很多人圖省事這么寫:
logger.Warn("權(quán)限不足", "user_id", 123, "resource") // ? 少了個值!
結(jié)果?slog 不會報錯,而是默默生成一個 !BADKEY 字段:
{"!BADKEY":"resource", ...}
?? 想象一下:線上故障,你查日志發(fā)現(xiàn)關(guān)鍵字段變成了 !BADKEY,而老板正盯著你……
? 正確姿勢:用slog.Attr
logger.Warn("權(quán)限不足",
slog.Int("user_id", 123),
slog.String("resource", "/api/admin"),
)
這樣,編譯器就能幫你抓 bug,而不是等到凌晨三點才暴露問題。
?? 小貼士:配合
golangci-lint+sloglint插件,強制全項目使用slog.Attr,徹底杜絕!BADKEY!
# .golangci.yml
linters:
enable:
- sloglint
settings:
sloglint:
attr-only: true??? 三、實戰(zhàn):HTTP 請求自動帶上 trace ID
在微服務(wù)中,請求鏈路追蹤離不開 correlation_id 或 trace_id。slog 配合社區(qū)庫 slog-context 能輕松實現(xiàn)。
場景:每個 HTTP 請求自動生成唯一 ID,并自動注入日志
package main
import (
"log/slog"
"net/http"
"os"
"github.com/google/uuid"
slogctx "github.com/veqryn/slog-context"
)
func requestID(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
cid := r.Header.Get("X-Correlation-ID")
if cid == "" {
cid = uuid.New().String()
}
ctx := slogctx.Prepend(r.Context(), slog.String("cid", cid))
r = r.WithContext(ctx)
w.Header().Set("X-Correlation-ID", cid)
next.ServeHTTP(w, r)
})
}
func hello(w http.ResponseWriter, r *http.Request) {
slog.InfoContext(r.Context(), "處理請求")
w.Write([]byte("Hello, slog!"))
}
func main() {
handler := slogctx.NewHandler(slog.NewJSONHandler(os.Stdout, nil), nil)
slog.SetDefault(slog.New(handler))
mux := http.NewServeMux()
mux.HandleFunc("/", hello)
http.ListenAndServe(":8080", requestID(mux))
}訪問 curl http://localhost:8080,你會看到:
{
"time": "...",
"level": "INFO",
"msg": "處理請求",
"cid": "a1b2c3d4-...",
...
}? 所有日志自動帶上 cid!
? 無需手動傳遞!
? 故障排查時一鍵過濾整個請求鏈路!
?? 四、敏感信息防護:用LogValuer控制輸出
直接打日志可能泄露密碼、身份證等敏感信息:
type User struct {
ID string
Email string
Password string // ? 千萬別打出來!
}
? 解決方案:實現(xiàn)LogValuer接口
func (u *User) LogValue() slog.Value {
return slog.GroupValue(
slog.String("id", u.ID),
slog.String("email", u.Email),
// 故意不輸出 Password
)
}
現(xiàn)在:
logger.Info("創(chuàng)建用戶", slog.Any("user", &user))
輸出只有安全字段:
{
"msg": "創(chuàng)建用戶",
"user": {
"id": "u123",
"email": "alice@example.com"
}
}
??? 安全是底線,日志也不例外!
?? 五、性能怎么樣?能用在生產(chǎn)嗎?
slog 的性能確實不如 zerolog 或 zap(大約慢 5~6 倍),但對絕大多數(shù)應(yīng)用完全夠用。
| Logger | 時間 (ns/op) | 分配對象 |
|---|---|---|
| zerolog | 380 | 1 |
| slog | 2480 | 42 |
如果你的服務(wù)每秒處理 10 萬請求,且每條都打日志——那你可能需要
zap。
但如果你只是普通 Web 服務(wù)?放心用slog,省下的心智負擔遠超那幾微秒。
?? 六、進階建議
- 日志級別動態(tài)調(diào)整:用
slog.LevelVar實現(xiàn)不停機調(diào)DEBUG。 - 多輸出:用 slog-multi 同時寫 stdout + 文件 + 網(wǎng)絡(luò)。
- 避免重復(fù) key:用 slog-dedup 處理沖突。
? 總結(jié)
| 優(yōu)勢 | 說明 |
|---|---|
| 官方支持 | 無需引入第三方依賴(基礎(chǔ)場景) |
| 結(jié)構(gòu)化 | JSON 輸出天然適配現(xiàn)代日志平臺 |
| 可擴展 | Handler 可插拔,輕松集成 |
| 安全可控 | LogValuer 防止敏感信息泄露 |
| 上下文友好 | 與 context 深度集成 |
結(jié)論:除非你有極端性能要求,否則
slog是 Go 應(yīng)用日志的首選方案。
到此這篇關(guān)于Go 結(jié)構(gòu)化日志slog入門與實戰(zhàn)指南 附避坑秘籍的文章就介紹到這了,更多相關(guān)go 結(jié)構(gòu)化日志slog內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
GoLang并發(fā)機制探究goroutine原理詳細講解
goroutine是Go語言提供的語言級別的輕量級線程,在我們需要使用并發(fā)時,我們只需要通過 go 關(guān)鍵字來開啟 goroutine 即可。這篇文章主要介紹了GoLang并發(fā)機制goroutine原理,感興趣的可以了解一下2022-12-12
淺析Go語言中的緩沖區(qū)及其在fmt包中的應(yīng)用
這篇文章主要為大家詳細介紹了Go語言中的緩沖區(qū)及其在fmt包中的應(yīng)用的相關(guān)知識,文中的示例代碼講解詳細,感興趣的小伙伴可以了解一下2024-01-01

