zap.NewProduction()默认不是异步的,它底层同步写入os.Stderr或裸*os.File,无goroutine、缓冲区或队列,高并发时会阻塞业务;真异步需手动组合Encoder、WriteSyncer和Sampler并调用logger.Sync()确保日志不丢失。

zap.NewProduction() 默认不是异步的,别被名字骗了
它名字带“Production”,但底层仍是同步写入:调用 os.Stderr 或裸 *os.File,没 goroutine、没缓冲区、没队列。压测时 QPS 上去后,pprof 里常看到大量时间卡在 syscall.Syscall 或 runtime.futex——这就是 I/O 阻塞主线程的典型表现。
所谓“高性能”来自零分配编码器和结构化字段直写,不是并发写能力。真要异步,必须手动组装 zapcore.Core,绕过 zap.NewProduction() 的封装。
- 直接传
*os.File给zapcore.NewCore→ 同步写,会卡业务 - 用
lumberjack.Logger替代裸文件 → 解决滚动,但不解决异步 - 只加
go logger.Info(...)→ 是并发打日志,不是异步写入
开发 vs 上线必须用不同配置,混用等于自找麻烦
本地调试时看到一长串紧凑 JSON、字段缩写("m" 代替 "msg")、时间是纳秒整数、没颜色、没换行?你误用了 zap.NewProduction()。它默认启用 JSON 编码 + 字段压缩 + 每秒同模板日志采样(最多 100 条),完全不适合人眼阅读。
上线后磁盘爆满、inode 耗尽、日志涨到几十 GB?大概率把 zap.NewDevelopment() 带上了线。它默认开启 caller 和堆栈展开,单条日志体积可能是生产配置的 3–5 倍。
立即学习“go语言免费学习笔记(深入)”;
- 开发阶段必须用
zap.NewDevelopment():console encoder、颜色、完整字段名、自动换行 - 上线环境优先用
zap.NewProduction():JSON encoder、字段压缩、采样、禁用caller(如需保留得显式加zap.AddCaller()) - K8s 或 systemd 下即使输出到 stdout,也应选
zap.NewProduction()—— fluent-bit 等采集器专为解析 JSON 设计
zap.Stringer() 传 nil 会 panic,zap.String() 才安全
程序启动就 panic,报错 panic: runtime error: invalid memory address or nil pointer dereference,十有八九是把 nil *string 或未初始化结构体指针塞进了 zap.Stringer()。
zap.String() 对 nil 安全:传 nil 会输出空字符串 "";而 zap.Stringer() 会直接调用对象的 String() 方法,如果方法内部访问未初始化字段,立刻崩溃。
- 常见场景:
r.URL.Query().Get("id")返回空字符串"",你误判为nil,转成*string后传给zap.Stringer() - 不确定是否为
nil?统一走zap.String() - 真要用
zap.Stringer()格式化复杂对象,先判空再调用
手动构造异步 Core 必须三件套齐备
Zap 不提供开箱即用的异步 wrapper,真异步必须显式组合 Encoder、WriteSyncer 和 Sampler,缺一不可。
Encoder 必须用 zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig()),禁用自定义 EncodeTime(否则每次 time.Now().Format() 触发 GC);WriteSyncer 不能直接传 *os.File,得套 zapcore.AddSync 再包一层 zapcore.Lock;Sampler 用 zapcore.NewSampler(core, time.Second, 100, 10) 控制高频 error 日志,避免缓冲区打满。
- 别漏掉
logger.Sync():程序退出前必须调用,否则最后一段日志可能丢失 - 写文件时优先用
lumberjack.Logger,它比裸os.OpenFile更健壮 - 采样参数中第三个值(100)是每秒同模板最大日志数,第四个(10)是采样窗口内允许突发次数



















