要让 slog 输出 json,需使用 slog.newjsonhandler 替代 texthandler,并传入 os.stdout 等 writer 及 handleroptions{addsource: false};slog.with 添加的字段默认会出现在 json 中,避免误用 withgroup 导致嵌套;字段名不可直接重映射,建议在日志采集层处理。

怎么让 slog 输出 JSON 而不是默认文本
默认情况下 slog.New 配的 slog.TextHandler 会输出可读性好的多行文本,不是 JSON。要 JSON,得显式换 handler。
- 用
slog.NewJSONHandler替代slog.NewTextHandler,第一个参数是io.Writer(比如os.Stdout) - 注意:Go 1.21 的
slog.NewJSONHandler默认不加换行,日志会挤成一行;如果想每条日志独立成行,得传入带"addSource": true或手动 wrap writer,更稳妥是用io.MultiWriter(os.Stdout, os.Stderr)不解决这事——真正办法是加slog.HandlerOptions{AddSource: false}并确保 writer 自带换行(比如直接用os.Stdout就行,它本身不缓冲) - 别漏掉
context.Background()或实际 context ——slog的With和Info等方法不自动透传 context,但 handler 本身不依赖它;只是如果你之后要集成 trace 或 timeout,得自己把 context key 塞进slog.With
logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
AddSource: false,
}))
logger.Info("user login", "user_id", 123, "ip", "192.168.1.1")
slog.With 加的字段为什么没出现在 JSON 里
常见现象:调用 logger := slog.With("service", "auth") 后,logger.Info("login") 输出的 JSON 里没有 service 字段——其实是有的,但要看 handler 是否开启属性继承,默认是开启的;问题往往出在「复用了底层 handler 但没正确包装」。
-
slog.With返回的是新Logger,它内部持有一个handler+ 一组attrs;JSON handler 会把这组 attrs 合并进每条日志,所以字段一定出现 - 容易踩的坑:写成
slog.New(slog.NewJSONHandler(...)).With(...)是对的;但写成slog.With(...).New(...)就错了——With没有New方法 - 另一个坑:用
slog.WithGroup后字段变成嵌套对象,JSON 里会多一层 map,比如"net": {"ip": "127.0.0.1"};如果后端解析器不支持嵌套,就别用WithGroup
自定义字段名(比如把 time 改成 timestamp)
slog.JSONHandler 不提供字段名重映射接口,它硬编码了 time、level、msg、source 这几个键。想改,只能绕过去。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 不要试图 patch
slog源码或反射修改 handler——Go 标准库不保证内部结构稳定 - 可行做法:写个 wrapper
io.Writer,在写入前用json.Decoder/Encoder解析原始 JSON,改 key,再 encode 回去;但性能差,只适合低频调试日志 - 更现实的选择:接受标准字段名,或在日志收集层(如 Fluentd、Vector)做字段重命名;多数 SaaS 日志平台(Datadog、Sentry)都支持 ingestion-time 字段 alias
- 注意:
level值是字符串("INFO"),不是数字,改名时别误当成 int 处理
生产环境要不要关掉 AddSource
开 AddSource: true 会让每条日志带上 "source": "main.go:23",方便定位,但代价明确。
- 每次记录都触发
runtime.Caller,实测增加约 15%–25% CPU 开销(尤其高频日志场景) - 字段内容依赖 GOPATH 和构建方式;CGO disabled 或 upx 打包后可能显示
???:0 - 如果用
go run或未 strip 的二进制,source 可靠;CI/CD 流水线里建议统一关掉,靠 Sentry 或 OpenTelemetry 的 source map 补充 - 折中方案:开发环境开,生产通过环境变量控制,比如
if os.Getenv("ENV") == "dev" { opts.AddSource = true }
字段名和时间格式这些细节,上线前最好拿真实流量压测一次 handler 性能——slog 很轻量,但 JSON 序列化+IO 写入才是瓶颈所在。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










