生产环境必须用zap.NewProduction()或手动配置JSON编码器,禁用console encoder、caller和debug日志;开发才用NewDevelopment()。因NewProduction默认JSON+采样+纳秒时间戳+字段缩写,适配ELK/Loki;NewDevelopment用彩色console encoder,体积大、无结构化字段,线上会导致磁盘暴涨、解析失败。

生产环境必须用 zap.NewProduction() 或手动构建 zap.Config,禁用 console encoder、caller、debug 日志;开发调试才用 zap.NewDevelopment()。别把两者混着配,也别在上线后还用 development 模式。
为什么 zap.NewProduction() 和 zap.NewDevelopment() 不能互换用
zap.NewProduction() 默认启用 JSON 编码、采样、纳秒时间戳、字段缩写(如 "msg" → "m"),输出到 stderr,适合日志采集器(如 fluent-bit)消费;zap.NewDevelopment() 用彩色 console encoder,带行号、堆栈、可读时间格式,但体积大、无结构化字段名对齐,Loki/ELK 解析不了。
- 线上误用
NewDevelopment():日志文件暴涨,磁盘几小时打满,且字段无法被 Kibana 过滤 - 本地误用
NewProduction():终端输出全是压缩 JSON,没换行没颜色,查问题像在解谜 - 二者采样策略不同:
NewProduction()默认开启每秒同模板限流(100 条),NewDevelopment()不采样——高频请求下后者可能直接压垮 I/O
如何手动配置 JSON 结构化输出(绕过 NewProduction 的固定路径)
想写文件、改字段名、调时间格式,就不能只靠 NewProduction()。必须自己构造 zap.Config,关键项不能漏:
-
Encoding: "json"是结构化的硬性前提,缺省是"console",不设就白配 -
EncoderConfig.TimeKey和LevelKey要和你的日志平台 schema 对齐(比如 Loki 要"ts",有些内部系统要"timestamp") -
EncodeTime: zapcore.ISO8601TimeEncoder才能输出"2026-05-23T16:00:00Z",默认是纳秒浮点数1716479999.123456 -
OutputPaths设成[]string{"/var/log/app.json"}前,确保目录存在且进程有写权限,否则静默失败
双写控制台 + 文件时,为什么不能直接 os.OpenFile 后塞进 OutputPaths
OutputPaths 只接受字符串路径,底层会为每个路径新建 os.File 并封装为 WriteSyncer,但它不支持轮转、压缩、自动删旧——单靠它写文件,早晚磁盘爆掉。
立即学习“go语言免费学习笔记(深入)”;
- 正确做法是用
lumberjack.Logger做轮转,再套一层zapcore.AddSync转成WriteSyncer - 同时输出到控制台和文件?得用
zapcore.NewTee()合并两个 Core:一个配consoleEncoder(开发看),一个配jsonEncoder(机器收) -
lumberjack.MaxSize单位是 MB,不是 KB 或字节;设100就是 100MB,不是 100KB - 忘了
defer logger.Sync()?缓冲区日志可能永远不落盘,进程崩溃就丢最后几条
高频日志场景下,哪些配置会悄悄拖慢性能
只要 allocs/op > 1,说明某处已退化。用 go test -bench=. -benchmem 验证最准:
-
EncoderConfig.EncodeCaller: zapcore.ShortCallerEncoder看似轻量,但每次调用都触发 runtime.Caller,CPU 开销翻倍,生产必须关 - 自定义
EncodeLevel函数里用了fmt.Sprintf?等于主动放弃零分配,回到 logrus 水平 -
OutputPaths写多个目标(如["stdout", "/var/log/app.json"])会让 zap 启动 goroutine + channel,小流量时锁竞争反成瓶颈 - 开了
Development: true(哪怕只是 config 里写了)?ns/op 高出 2–3 倍,不是 allocs 问题,是 caller 开销实打实的
最易被忽略的是:字段名不统一("user_id" 和 "userID" 并存)、错误不用 zap.Error(err) 而用 zap.String("err", err.Error())、全局 logger 被 With() 覆盖却没更新变量——这些问题不会报错,只会让日志查起来像大海捞针。



















