Go slog 完整指南:官方结构化日志库
> 发布时间:2026-03-14 | 标签:Golang, 日志, slog, 后端开发
一、什么是 slog?
log/slog 是 Go 1.21(2023年8月)正式引入的官方结构化日志库。在此之前,Go 标准库的 log 包功能极其简单——不支持日志级别、不支持结构化字段、不支持 JSON 输出,导致社区长期依赖 logrus、zap、zerolog 等第三方库。
slog 的出现填补了这一空白,目标是成为:
- Go 生态的日志标准接口
- 足够性能、足够灵活的生产级实现
- 库作者和应用开发者都能接受的统一抽象层
二、快速上手
基本用法
import "log/slog"
// 直接使用默认 logger(输出到 stderr)
slog.Info("服务启动", "port", 8080)
slog.Warn("内存使用率偏高", "usage", "87%")
slog.Error("数据库连接失败", "err", err, "host", "localhost:5432")
slog.Debug("收到请求", "method", "GET", "path", "/api/users")
创建自定义 Logger
// Text Handler(开发环境,人类可读)
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
// JSON Handler(生产环境,接入日志系统)
logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
// 设置为全局默认
slog.SetDefault(logger)
JSON 输出示例
{"time":"2026-03-14T01:52:00.000Z","level":"INFO","msg":"用户登录","user_id":123,"ip":"192.168.1.1","latency_ms":12}
三、核心特性
3.1 日志级别
slog.Debug("调试信息") // 开发时用,生产通常关闭
slog.Info("普通信息") // 正常运行状态
slog.Warn("警告") // 需要关注但不影响运行
slog.Error("错误", "err", err) // 发生错误
3.2 Handler 配置
opts := &slog.HandlerOptions{
Level: slog.LevelWarn, // 只输出 Warn 及以上
AddSource: true, // 在日志中加入文件名+行号
}
handler := slog.NewJSONHandler(os.Stdout, opts)
logger := slog.New(handler)
3.3 字段预设(With)
// 每次日志自动带上公共字段
logger := slog.With(
"service", "payment",
"version", "2.1.0",
"env", "production",
)
logger.Info("支付成功", "order_id", "ORD-001", "amount", 199.00)
// {"service":"payment","version":"2.1.0","env":"production","msg":"支付成功","order_id":"ORD-001","amount":199}
3.4 Group 分组
logger.WithGroup("db").Info("查询完成",
"table", "users",
"rows", 100,
"duration_ms", 12,
)
// 输出:{"msg":"查询完成","db":{"table":"users","rows":100,"duration_ms":12}}
分组让日志字段层次清晰,便于 Elasticsearch/Loki 等系统解析。
3.5 Context 集成
// 从 context 中自动提取 trace_id 等链路信息
slog.InfoContext(ctx, "处理请求",
"user_id", userID,
"handler", "CreateOrder",
)
结合 OpenTelemetry,可以自动将 trace_id、span_id 注入日志,实现日志与链路追踪的关联。
四、自定义 Handler
slog 的核心扩展点是 Handler 接口:
type Handler interface {
Enabled(context.Context, Level) bool
Handle(context.Context, Record) error
WithAttrs(attrs []Attr) Handler
WithGroup(name string) Handler
}
社区已有大量第三方 Handler:
| Handler | 用途 |
|---|---|
| `slog-multi` | 同时写多个目标(文件+控制台) |
| `slog-loki` | 直接推送到 Grafana Loki |
| `slog-sentry` | Error 自动上报到 Sentry |
| `slog-datadog` | 集成 Datadog APM |
| `tint` | 终端彩色输出,开发友好 |
五、性能表现
slog 的设计目标是 zero-allocation,实测性能远超老牌的 logrus:
BenchmarkZerolog ~ 57 ns/op 0 allocs/op
BenchmarkZap ~ 118 ns/op 0 allocs/op
BenchmarkSlog ~ 197 ns/op 0 allocs/op
BenchmarkLogrus ~ 941 ns/op 15 allocs/op
对绝大多数业务来说,slog 的性能完全够用。极端性能场景(每秒百万级日志)再考虑 zerolog。
六、与第三方库对比
| 特性 | slog | zap | zerolog | logrus |
|---|---|---|---|---|
| 标准库 | ✅ | ❌ | ❌ | ❌ |
| 结构化字段 | ✅ | ✅ | ✅ | ✅ |
| 日志级别 | ✅ | ✅ | ✅ | ✅ |
| JSON 输出 | ✅ | ✅ | ✅ | ✅ |
| 性能 | ⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐ |
| 生态成熟度 | 成长中 | 成熟 | 成熟 | 老牌 |
| Context 支持 | ✅ | 需插件 | ✅ | 有限 |
| Handler 扩展 | ✅ | ✅ | ✅ | ✅ |
七、实际采用情况
普及速度:中等偏上
slog 推出两年多,新项目的采用率在快速上升,但整体仍不及 zap 和 zerolog。原因:
1. 存量项目惰性:大量 Go 项目在 1.21 前就已成熟,换日志库成本不低
2. 第三方库先发优势:zap 10 年积累、zerolog 的极致性能,都有忠实用户
3. 新项目首选:但 Go 1.21+ 的新项目,slog 正在成为首选
最有价值的场景:写库
这是 slog 最被推崇的使用场景。库的作者应该接受 *slog.Logger 作为参数,而不是硬编码某个第三方库:
// 推荐:让调用方决定日志实现
func NewService(logger *slog.Logger) *Service {
return &Service{logger: logger}
}
// 不推荐:绑定了 zap
func NewService(logger *zap.Logger) *Service { ... }
这样调用方可以传入任何实现了 slog 接口的 handler,包括 zap/zerolog 的 slog 适配器。
八、最佳实践
8.1 生产配置模板
package main
import (
"log/slog"
"os"
)
func initLogger(env string) *slog.Logger {
var handler slog.Handler
opts := &slog.HandlerOptions{
AddSource: true,
Level: slog.LevelInfo,
}
if env == "development" {
// 开发环境:彩色文本,方便阅读
opts.Level = slog.LevelDebug
handler = slog.NewTextHandler(os.Stdout, opts)
} else {
// 生产环境:JSON,接入日志平台
handler = slog.NewJSONHandler(os.Stdout, opts)
}
logger := slog.New(handler)
slog.SetDefault(logger) // 设为全局默认
return logger
}
8.2 统一错误日志格式
// 封装统一的错误记录
func logError(logger *slog.Logger, msg string, err error, attrs ...any) {
args := append([]any{"error", err.Error()}, attrs...)
logger.Error(msg, args...)
}
// 使用
logError(logger, "支付失败",
err,
"order_id", orderID,
"user_id", userID,
"amount", amount,
)
8.3 与 Viper 配置集成
// 根据配置文件决定日志级别
levelStr := viper.GetString("log.level")
var level slog.Level
level.UnmarshalText([]byte(levelStr))
opts := &slog.HandlerOptions{Level: level}
九、结论与建议
对于新 Go 项目,推荐直接使用 slog。 理由:
- 零额外依赖,减少
go.mod复杂度 - 官方维护,长期稳定
- 性能足够,覆盖 99% 的业务场景
- 写库时是社区公认的最佳实践
以下情况考虑第三方库:
- 需要极致性能(百万 QPS 日志)→ zerolog
- 团队已有大量 zap 经验 → 继续用 zap
- 需要特殊功能(如采样、动态级别调整)→ zap
slog 的出现不是要取代 zap/zerolog,而是提供一个标准化的最小公倍数。Go 生态的日志碎片化问题,会随着 slog 的普及逐步收敛。
参考资料:Go 官方文档 pkg.go.dev/log/slog | Go 1.21 Release Notes | github.com/golang/example/slog-handler-guide