zap.Logger默认不输出JSON是因为NewDevelopment()使用consoleEncoder(彩色文本),仅NewProduction()或手动配置jsonEncoder才输出JSON;结构化日志必须用后者或自定义EncoderConfig。

zap.Logger 为什么默认不输出 JSON?
因为 zap.NewDevelopment() 和 zap.NewProduction() 创建的 logger 默认使用不同编码器:前者用 consoleEncoder(带颜色、易读的文本),后者才用 jsonEncoder。很多人直接调用 zap.NewDevelopment() 后发现字段没进 JSON,其实是根本没走 JSON 编码路径。
要结构化输出,必须显式指定编码器为 jsonEncoder,或直接用 zap.NewProduction() —— 它内部已配置好 jsonEncoder 和合理日志级别(InfoLevel 及以上)。
- 开发环境想看 JSON?别用
NewDevelopment(),改用zap.New(zapcore.NewCore(...))手动组合 -
NewProduction()输出的 JSON 字段名是level、ts、caller、msg,不是severity或timestamp,和某些云平台期望格式不一致时需自定义字段名 - 若用
jsonEncoder但没看到结构体字段被展开,大概率是用了logger.Info("msg", obj)而非logger.Info("msg", zap.Any("obj", obj))
zap.Any()、zap.Object()、zap.String() 这几个函数怎么选?
它们决定字段如何序列化,影响最终 JSON 的嵌套深度和可读性。选错会导致字段丢失、类型错误或日志体积暴增。
-
zap.String("key", "value"):只接受string,安全高效;传非 string 会 panic -
zap.Any("key", value):最常用,自动判断类型,支持 struct、map、slice;但对未导出字段(小写首字母)静默忽略,且嵌套过深时 JSON 可能超长 -
zap.Object("key", zapcore.ObjectMarshaler):需实现MarshalLogObject接口,适合控制复杂对象的序列化逻辑(比如隐藏敏感字段、扁平化嵌套) - 别用
fmt.Sprintf拼接后塞进String()—— 失去结构化意义,搜索和解析都失效
如何让 zap 输出标准 JSON 字段名(如 time → timestamp)?
zap 默认字段名是约定俗成的简短形式(ts 代替 timestamp),但 ELK、Loki 或部分 SaaS 日志服务要求特定 key 名。必须通过 zapcore.EncoderConfig 覆盖默认映射。
立即学习“go语言免费学习笔记(深入)”;
cfg := zap.NewProductionConfig() cfg.Encoding = "json" cfg.EncoderConfig.TimeKey = "timestamp" cfg.EncoderConfig.LevelKey = "severity" cfg.EncoderConfig.NameKey = "logger" logger, _ := cfg.Build()
注意:TimeKey 改了只是改字段名,时间格式仍由 EncodeTime 控制;若要 ISO8601 格式,得额外设置:
cfg.EncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder-
cfg.EncoderConfig.CallerKey设为"caller"是 OK 的,但有些系统要"source",得按需调整 - 修改字段名后,务必检查下游日志采集器(如 filebeat、fluent-bit)的解析规则是否匹配,否则字段会被丢弃
结构化日志里怎么处理 error 类型?
直接传 err 给 zap.Any("err", err) 通常只输出 "error: xxx" 字符串,丢失堆栈和原始类型信息。zap 提供专用封装:zap.Error(err)。
-
zap.Error(err)会自动提取err.Error(),并尝试调用fmt.Printf("%+v", err)获取带行号的堆栈(如果 err 实现了fmt.Formatter) - 对自定义 error 类型,建议同时实现
Unwrap()和Format(),否则zap.Error()可能只打印顶层错误 - 不要用
zap.String("error", err.Error())—— 丢失类型、无法区分 nil error、且堆栈全无 - 如果 error 带业务上下文(如订单 ID、用户 ID),应单独打字段:
logger.Error("pay failed", zap.Error(err), zap.String("order_id", oid))
字段命名一致性容易被忽略:同一服务里要么全用 err,要么全用 error,混用会让日志查询变得麻烦。


















