生产环境必须用zap.NewProduction(),它禁用caller和debug、启用紧凑JSON与纳秒时间戳,并内置采样;手动构造JSON Core仅在需字段名或时间格式对齐时使用。

直接上生产环境别用 zap.NewDevelopment(),它不是“开发版”,而是“调试专用低性能模式”——字段展开、颜色编码、行号堆栈全开,日志体积大、CPU 高、ELK 解析失败率高。真要高性能,必须用 zap.NewProduction() 或手动构造 JSON Core。
怎么初始化一个真正能上生产的 zap.Logger
生产环境的起点不是“怎么配得花哨”,而是“怎么避免退化”。zap.NewProduction() 是唯一安全的默认入口,它自动禁用 caller、禁用 debug、启用紧凑 JSON、使用纳秒时间戳,并内置采样(可关)。你不需要自己写 encoder config,除非有字段名或时间格式的硬性对齐要求。
- 别碰
zap.NewDevelopmentConfig().Build()—— 它和NewDevelopment()一样,底层仍是 console encoder,只是多一层配置壳 - 如果必须写文件,不要直接
os.OpenFile后塞给zapcore.AddSync;要用lumberjack.Logger封装,并确保MaxSize单位是 MB(比如100= 100MB) -
OutputPaths设成[]string{"/var/log/app.json"}前,务必os.MkdirAll("/var/log", 0755),否则静默失败 - 想加 trace_id?用
logger.With(zap.String("trace_id", tid)),别改全局 logger,也别每次 HTTP handler 都 new 一个——复用子 logger 实例更省
为什么 logger.Info("msg", zap.String("k", v)) 比 logger.Info(fmt.Sprintf("msg k=%v", v)) 快 5 倍
因为前者字段构造零分配、无反射、可内联;后者每次调用都触发 fmt.Sprintf 分配字符串 + interface{} 装箱 + Zap 再拷贝一次。哪怕日志级别设为 ErrorLevel,fmt.Sprintf 依然执行——它在 logger 判断是否输出前就完成了。
- 高频路径(如中间件、DB 查询封装)禁用
fmt.Sprintf、time.Now().String()、json.Marshal()等预计算 - 不确定要不要记的日志,显式用
logger.Check(zap.DebugLevel, "msg").Write(zap.Int("id", id)),避免字段构造开销 - 结构体别传指针:
zap.Any("user", &u)可能引发竞态;应传值或用zap.Object("user", userEncoder{&u})实现延迟序列化
双写控制台 + 文件且格式不同,为什么不能只用 NewMultiCore
zapcore.NewMultiCore 是并发写入,但不处理格式差异——它只是把同一份 Field 并发塞给多个 Core。你要的是“同一条日志,控制台人类可读、文件机器可解析”,这必须靠 zapcore.NewTee()。
立即学习“go语言免费学习笔记(深入)”;
- 控制台 Core:用
zapcore.NewConsoleEncoder(zap.NewDevelopmentEncoderConfig()),LevelEnabler设为DebugLevel - 文件 Core:用
zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig()),LevelEnabler设为InfoLevel - 两个 Core 的
WriteSyncer必须各自独立封装(比如文件用lumberjack+zapcore.AddSync) - 别漏掉
defer logger.Sync(),否则进程崩溃时最后几条日志大概率丢失
最易被忽略的点是:字段命名不统一(比如一个地方写 user_id,另一个写 userID),后续查日志时连 jq '.user_id' 都匹配不到。上线前用 go test -bench=. -benchmem 看 allocs/op,超过 0.5 就说明某处偷偷触发了字符串拼接或反射。



















