zap 默认不是 json 格式,因其使用 zapcore.consoleencoder 输出人类可读键值对;需显式配置 zapcore.newjsonencoder 并设置 iso8601timeencoder 等参数才输出标准 json。

为什么默认 Zap 日志不是 JSON 格式
Zap 默认使用 zapcore.ConsoleEncoder,它输出的是人类可读的键值对(带颜色、缩进、时间格式化),不是标准 JSON。直接看到日志里有换行、空格、单引号,docker logs 或 ELK 收集时会解析失败或字段丢失。
- 根本原因:没显式指定编码器,Zap 不会自动“猜”你要 JSON
- 常见错误现象:
{"level":"info","ts":171...}看起来像 JSON,但实际是 ConsoleEncoder 模拟的伪 JSON(比如字符串值不加双引号、时间字段是 float64) - 关键判断:只要没调用
zapcore.NewJSONEncoder并传给zapcore.NewCore,就不是真 JSON
怎样配置 Zap 输出标准 JSON 日志
核心是替换 encoder,并确保所有字段符合 JSON 规范(字符串双引号、布尔小写、null 表示空值)。别依赖 zap.NewProductionConfig() —— 它默认仍是 ConsoleEncoder。
- 必须手动构造
zapcore.EncoderConfig,设置EncodeTime为zapcore.ISO8601TimeEncoder(否则时间字段是 Unix 时间戳数字,不兼容多数日志平台) - 必须用
zapcore.NewJSONEncoder包裹该 config,再传给zapcore.NewCore - 示例关键片段:
encoderCfg := zapcore.EncoderConfig{
TimeKey: "ts",
LevelKey: "level",
NameKey: "logger",
CallerKey: "caller",
MessageKey: "msg",
StacktraceKey: "stacktrace",
EncodeLevel: zapcore.LowercaseLevelEncoder,
EncodeTime: zapcore.ISO8601TimeEncoder, // 关键!不是 UnixNano
EncodeDuration: zapcore.SecondsDurationEncoder,
EncodeCaller: zapcore.ShortCallerEncoder,
}
core := zapcore.NewCore(
zapcore.NewJSONEncoder(encoderCfg),
zapcore.AddSync(os.Stdout),
zapcore.InfoLevel,
)
容器环境里容易漏掉的三个细节
Docker/Kubernetes 下跑 Go 服务,光配对 encoder 还不够。日志得能被容器运行时和采集 agent 正确识别和转发。
-
os.Stdout必须保持未缓冲(Zap 默认已处理),但如果你套了bufio.Writer或重定向到文件再 cat 出来,JSON 行会被合并或截断 - 避免在
encoderCfg中设EncodeLevel为自定义函数返回非小写字符串(如"INFO"),部分日志系统(如 Loki)只认"info"/"error" - K8s Pod 日志路径是
/dev/pts/0或 stdout/stderr 字符设备,不要用os.OpenFile写磁盘路径 —— 容器里可能没权限,且日志采集器(fluentd/filebeat)只监听 stdout
结构化字段怎么加才不破坏 JSON 格式
用 logger.Info("user login", zap.String("user_id", "u123"), zap.Bool("success", true)) 是安全的;但手拼字符串或用 fmt.Sprintf 塞进 msg 会污染结构化能力。
立即学习“go语言免费学习笔记(深入)”;
- 永远别在
msg参数里塞 key-value(如logger.Info("user_id=u123 success=true")),这样字段就锁死在字符串里,没法做聚合查询 - 敏感字段(如密码、token)不要用
zap.String直接打,要么过滤(用zap.String("token", "[redacted]")),要么用zap.ByteString+ 自定义 encoder 做脱敏 - 嵌套结构想打成 JSON 对象?Zap 不原生支持 map/object 字段。得用
zap.Any("meta", map[string]interface{}{"ip": "1.2.3.4", "ua": "..."} ),但注意:如果 value 是 nil 或含不可序列化类型(如 channel、func),会 panic
JSON 结构化真正的难点不在配置 encoder,而在整个团队写日志时能否坚持「字段即字段」——而不是把结构化逻辑推给日志收集端去 parse 字符串。










