生产环境必须用 NewProduction,因其采用紧凑的 jsonEncoder 降低序列化开销;NewDevelopment 使用 consoleEncoder,适合调试但内存开销大,切勿直接用于生产。

用 NewProduction 还是 NewDevelopment?看环境别硬套
开发阶段用 NewDevelopment,生产环境必须切到 NewProduction —— 不是因为后者“更高级”,而是因为两者底层编码器和写入行为完全不同:NewDevelopment 用人类可读的 consoleEncoder,带颜色、换行、字段展开,但会分配更多内存;NewProduction 默认用紧凑的 jsonEncoder,无颜色、无换行、字段扁平,序列化开销低得多。
常见错误是本地调试完直接上生产,忘了改初始化方式,结果日志体积暴涨、CPU 占用异常升高。尤其在高频请求路径(比如 API 入口、DB 查询封装)里,这种差异会被放大数倍。
- 开发时想保留堆栈信息?加
zap.AddStacktrace(zap.ErrorLevel),别依赖NewDevelopment的默认行为 - 生产环境要查日志?确保日志输出能被
jq或 ELK 正确解析:字段名不能含空格或特殊字符,推荐全小写+下划线(如user_id而非userId) - 如果必须在生产用 console 格式(比如容器 stdout 调试),手动构造
Core,用jsonEncoder+os.Stdout,别碰NewDevelopment
Info 和 Debug 日志混用,性能差距能到 3 倍以上
不是所有 logger.Info 调用都安全。Zap 的 Info 方法本身很快,但如果你传了未预计算的值(比如 time.Now().String()、fmt.Sprintf 拼接、结构体转 JSON),这些操作会在每次调用时执行,且无法被日志级别过滤掉 —— 即使你把日志级别设为 Error,这些表达式依然会跑。
真正高性能的做法是:只传原始值,让 Zap 在确定要写日志时才做格式化。例如:
// ❌ 错误:每次调用都执行 fmt.Sprintf,不管日志是否启用
logger.Info("slow query", zap.String("sql", fmt.Sprintf("SELECT * FROM users WHERE id = %d", id)))
<p>// ✅ 正确:只传 id,Zap 内部按需格式化
logger.Info("slow query", zap.Int("id", id), zap.String("table", "users"))
-
Debug级别默认关闭,但它的字段构造器(如zap.String)仍会执行 —— 所以高频率位置(如循环体内、中间件)慎用logger.Debug - 不确定要不要记的日志,先用
logger.Check(zap.DebugLevel, "msg").Write(...)显式判断,避免构造字段的开销 - 避免在日志里传指针或闭包,Zap 不会深拷贝,可能引发竞态或内存泄漏
结构化字段怎么选?别滥用 Any,优先用类型明确的构造器
zap.Any 看起来方便,但它会触发反射,性能比 zap.String、zap.Int 等慢 2–5 倍,而且字段类型丢失后,日志分析工具(如 Loki、Datadog)无法做数值聚合或范围查询。
真实业务中高频字段应有明确类型:
- 用户 ID、订单号 →
zap.String("user_id", userID)(即使它是数字字符串) - HTTP 状态码、重试次数 →
zap.Int("status_code", code) - 耗时、超时阈值 →
zap.Duration("latency", time.Since(start))(自动转成纳秒整数,可直接做 P99 统计) - 布尔状态(是否登录、是否缓存命中)→
zap.Bool("cached", hit)
唯一适合 zap.Any 的场景是调试临时 dump 复杂结构(如 map[string]interface{}),上线前必须删掉或替换。
日志没刷到磁盘?Sync 不是摆设,但别乱 defer
进程退出前不调 logger.Sync(),缓冲区里的日志就丢了 —— 这在 Kubernetes Pod 重启、服务热更新时特别致命。但很多人写成 defer logger.Sync(),导致每条请求都 sync 一次,I/O 成瓶颈。
正确做法是分层控制:
- 短生命周期命令行工具:在
main结尾调一次logger.Sync() - 长运行 HTTP 服务:注册 OS 信号(
SIGTERM),收到信号时 sync 并优雅退出 - 高频异步任务(如消息消费):每 N 条日志或每 M 秒主动
Sync,而不是等 GC 或进程结束
还有一个隐藏坑:zap.NewProduction 默认用 os.Stderr,而某些容器环境会把 stderr 缓冲策略设为行缓冲甚至全缓冲,导致日志延迟几秒才出现 —— 可通过 zap.WriteSyncer 包一层 os.Stderr 并设置 SetOutput 强制 flush,或直接用 lumberjack 写文件规避。



















