云计算百科
云计算领域专业知识百科平台

Go 结构化日志新宠:`slog` 入门与实战指南(附避坑秘籍)

“日志不是写给机器看的,是写给人类在半夜三点 debug 时救命用的。”

Go 1.21 终于带来了官方结构化日志包 —— log/slog。从此,我们不再需要在 zap、zerolog、logrus 之间反复横跳,也不用担心团队里有人偷偷用 fmt.Println 写日志了(好吧,可能还是会有)。

本文将带你从零上手 slog,并通过几个小例子展示它如何让日志变得更结构化、可查询、可维护——甚至还能帮你保住头发。


🧱 一、slog 的三大核心:Logger、Handler、Record

slog 的设计哲学很清晰:前端 API + 后端实现分离。

  • Logger:你每天打交道的“前台”,调用 Info()、Error() 的地方。
  • Handler:幕后英雄,决定日志长什么样、写到哪(比如 JSON 还是文本?stdout 还是文件?)。
  • Record:中间传话筒,封装了时间、级别、消息、属性等原始数据。

想象一下: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}

✅ 结构化!✅ 可被日志系统解析!✅ 没有乱七八糟的 fmt.Printf!


⚠️ 二、一个致命陷阱:别再用 key, value 了!

很多人图省事这么写:

logger.Warn("权限不足", "user_id", 123, "resource") // ❌ 少了个值!

结果?slog 不会报错,而是默默生成一个 !BADKEY 字段:

{"!BADKEY":"resource", }

😱 想象一下:线上故障,你查日志发现关键字段变成了 !BADKEY,而老板正盯着你……

✅ 正确姿势:用 slog.Attr

logger.Warn("权限不足",
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


🛠️ 三、实战:HTTP 请求自动带上 trace ID

在微服务中,请求链路追踪离不开 correlation_id 或 trace_id。slog 配合社区库 slog-context 能轻松实现。

场景:每个 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 // ❌ 千万别打出来!
}

✅ 解决方案:实现 LogValuer 接口

func (u *User) LogValue() slog.Value {
return slog.GroupValue(
slog.String("id", u.ID),
slog.String("email", u.Email),
// 故意不输出 Password
)
}

现在:

logger.Info("创建用户", slog.Any("user", &user))

输出只有安全字段:

{
"msg": "创建用户",
"user": {
"id": "u123",
"email": "alice@example.com"
}
}

🛡️ 安全是底线,日志也不例外!


📊 五、性能怎么样?能用在生产吗?

slog 的性能确实不如 zerolog 或 zap(大约慢 5~6 倍),但对绝大多数应用完全够用。

Logger时间 (ns/op)分配对象
zerolog 380 1
slog 2480 42

如果你的服务每秒处理 10 万请求,且每条都打日志——那你可能需要 zap。 但如果你只是普通 Web 服务?放心用 slog,省下的心智负担远超那几微秒。


🚀 六、进阶建议

  • 日志级别动态调整:用 slog.LevelVar 实现不停机调 DEBUG。

  • 多输出:用 slog-multi 同时写 stdout + 文件 + 网络。

  • 避免重复 key:用 slog-dedup 处理冲突。


✅ 总结

优势说明
官方支持 无需引入第三方依赖(基础场景)
结构化 JSON 输出天然适配现代日志平台
可扩展 Handler 可插拔,轻松集成
安全可控 LogValuer 防止敏感信息泄露
上下文友好 与 context 深度集成

结论:除非你有极端性能要求,否则 slog 是 Go 应用日志的首选方案。


赞(0)
未经允许不得转载:网硕互联帮助中心 » Go 结构化日志新宠:`slog` 入门与实战指南(附避坑秘籍)
分享到: 更多 (0)

评论 抢沙发

评论前必须登录!