Go 高手(23):日志最佳实践——log/slog、结构化日志、日志级别
更新时间:2026-09-01。本文是
languages/go/intermediate/高手层第 23 篇,接 文件系统。Go 1.21 正式引入log/slog标准库,结构化日志成为标准。slog支持 JSON 格式、日志级别、自定义 handler,适合生产环境。不再需要第三方日志库。
本文要回答的问题
log/slog和标准库log有什么区别?为什么需要结构化日志?- 怎么配置日志级别?
Info、Warn、Error怎么用? - 怎么输出 JSON 格式的日志?适合生产环境吗?
- 自定义日志 handler 怎么写?加 trace ID 怎么做?
一、标准库 log 的局限
go
log.Println("something happened")
// 输出:2009/11/10 23:00:00 something happened标准库 log 的问题:
- 只能输出文本,不适合机器解析
- 没有日志级别(Info、Warn、Error)
- 不能结构化输出(key-value 对)
二、log/slog:结构化日志
go
import "log/slog"
// 最简单的用法
slog.Info("server started", "port", 8080, "env", "production")
// 输出:2026/09/01 10:00:00 INFO server started port=8080 env=production
slog.Warn("connection slow", "addr", "192.168.1.1", "latency", "2.3s")
slog.Error("database connection failed", "err", err)slog 默认输出文本格式,带 key-value 对。适合开发环境。
三、JSON 格式:生产环境
go
// 设置 JSON 格式
opts := &slog.HandlerOptions{
Level: slog.LevelInfo, // 日志级别
}
handler := slog.NewJSONHandler(os.Stdout, opts)
logger := slog.New(handler)
logger.Info("server started", "port", 8080)
// 输出:{"time":"2026-09-01T10:00:00Z","level":"INFO","msg":"server started","port":8080}JSON 格式适合生产环境,可以导入日志分析系统(如 ELK、Loki、Datadog)。
四、日志级别
go
opts := slog.HandlerOptions{
Level: slog.LevelInfo, // 只输出 Info 及以上级别
}
handler := slog.NewJSONHandler(os.Stdout, opts)
slog.SetDefault(slog.New(handler))
slog.Debug("debug message") // 不输出,级别低于 Info
slog.Info("info message") // 输出
slog.Warn("warn message") // 输出
slog.Error("error message") // 输出级别从低到高:Debug < Info < Warn < Error。
go
// 开发环境:输出 Debug
// 生产环境:输出 Info 或 Warn五、自定义 handler:加 trace ID
go
type TraceHandler struct {
handler slog.Handler
traceID string
}
func (h *TraceHandler) Enabled(ctx context.Context, level slog.Level) bool {
return h.handler.Enabled(ctx, level)
}
func (h *TraceHandler) Handle(ctx context.Context, record slog.Record) error {
record.AddAttrs(slog.String("trace_id", h.traceID))
return h.handler.Handle(ctx, record)
}
func (h *TraceHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
return &TraceHandler{handler: h.handler.WithAttrs(attrs), traceID: h.traceID}
}
func (h *TraceHandler) WithGroup(name string) slog.Handler {
return &TraceHandler{handler: h.handler.WithGroup(name), traceID: h.traceID}
}用法:
go
base := slog.NewJSONHandler(os.Stdout, nil)
logger := slog.New(&TraceHandler{handler: base, traceID: "abc-123"})
logger.Info("request received")
// 输出:{"time":"...","level":"INFO","msg":"request received","trace_id":"abc-123"}六、slog 的四种方法
go
// 1. 直接传 key-value 对
slog.Info("message", "key1", "value1", "key2", "value2")
// 2. 用 slog.Attr
slog.Info("message", slog.String("key", "value"), slog.Int("count", 42))
// 3. 用 slog.Group 分组
slog.Info("message", slog.Group("request", "method", "GET", "path", "/api"))
// 4. 用 Any 放任意类型
slog.Info("message", slog.Any("data", someStruct))七、常见坑对照
| 坑 | 现象 | 对策 |
|---|---|---|
| 生产环境用文本格式 | 日志系统没法解析 | 用 slog.NewJSONHandler |
| 不设置日志级别 | 所有级别都输出 | 生产环境设 LevelInfo |
| 日志内容包含敏感信息 | 密码、token 写入日志 | 手动过滤敏感字段 |
| 在热路径上频繁调用 Debug | 影响性能 | 先检查 Enabled 再拼接参数 |
相关与延伸
下一篇:多平台编译——GOOS、GOARCH、build tags、条件编译;Go 1.21 的 slog 是标准库,不需要第三方依赖,直接导入;C 语言的日志实践,见 C 日志。
一句话总结
Go 日志最佳实践:Go 1.21 的 log/slog 是标准库,不需要第三方;开发环境用文本格式,生产环境用 slog.NewJSONHandler 输出 JSON;slog.LevelInfo 是生产环境的默认级别,Debug 只在开发用;自定义 handler 可以加 trace ID 等公共字段;结构化日志用 key-value 对,不要用 fmt.Sprintf 拼字符串。