go标准库log包不支持json日志因其仅提供文本拼接输出,无内置序列化能力;推荐用slog(go 1.21+)或zap实现结构化json日志,避免手动marshal导致的并发、性能及错误处理问题。

为什么默认的 log 包不直接支持 JSON 日志
Go 标准库的 log 包只提供文本格式输出,没有内置 JSON 序列化能力。它把时间、级别、消息拼成字符串写入 io.Writer,无法控制字段结构。想输出 JSON,必须自己构造结构体并序列化,或者换用支持结构化日志的第三方包。
用 log/slog(Go 1.21+)最轻量且原生支持 JSON
slog 是 Go 官方推荐的结构化日志方案,开箱即用 JSON 输出,无需额外依赖。关键在于使用 slog.NewJSONHandler 替换默认 handler:
import "log/slog"
<p>logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
logger.Info("user login", "user_id", 123, "ip", "192.168.1.1")
// 输出:{"time":"2024-05-20T10:30:45.123Z","level":"INFO","msg":"user login","user_id":123,"ip":"192.168.1.1"}</p>
- 字段名必须是字符串字面量(如
"user_id"),不能是变量名;值支持基本类型、error、time.Time等 - 如果需要自定义时间格式或添加全局属性,传入非空
slog.HandlerOptions,比如&slog.HandlerOptions{AddSource: true} - 注意:Go 1.21 才引入
slog,旧版本无法使用
用 zap 在生产环境更可靠,但需手动配置 JSON encoder
zap 是高性能结构化日志库,适合高吞吐场景。它的 zapcore.JSONEncoder 负责序列化,但需显式构建 logger:
初始化并管理阿里云SDK Go 客户端,涵盖 sync.Once 单例、goroutine 安全、endpoint 与 region 配置、VPC 端点及调试。
cfg := zap.NewProductionConfig()
cfg.Encoding = "json"
cfg.OutputPaths = []string{"stdout"}
logger, _ := cfg.Build()
logger.Info("db query failed", zap.String("query", "SELECT * FROM users"), zap.Error(err))
- 默认
zap.NewProductionConfig()已设为 JSON 模式,但务必检查Encoding字段是否为"json"(某些旧版默认是"console") - 避免用
zap.NewExample()或zap.NewDevelopment(),它们默认输出可读文本,不是 JSON - 若需添加 trace_id 等上下文字段,用
logger.With(zap.String("trace_id", tid)),而非在每条日志里重复传
自定义 log + json.Marshal 的坑最多,慎用
有人试图包装标准 log,每次调用前手动 json.Marshal 结构体。这看似简单,实际问题不少:
- 日志时间、调用位置(file:line)无法自动注入,得自己调用
runtime.Caller提取,易出错 - 并发写同一
io.Writer(如os.Stdout)时,多个 goroutine 的 JSON 行可能交错,导致解析失败 - 错误对象(
error)直接传给json.Marshal会变成空对象{},需先转成字符串 - 性能差:每次日志都触发反射和内存分配,比
slog或zap慢数倍
除非受限于极简依赖要求且日志量极低,否则不建议这条路。
真正要注意的是字段命名一致性——比如统一用 user_id 还是 userId,以及 error 字段是否展开为 err_message 和 err_stack。这些细节在日志采集和 ES 查询时影响很大,但 slog 和 zap 都不会帮你做,得从一开始约定好。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










