Zap 默认配置在高并发下无法跑满 CPU,因未关闭采样、未锁定写入器、未禁用时间格式化;三项任一缺失均使其吞吐退化至标准 log 包水平。

Zap 默认配置在高并发下根本跑不满 CPU,不是它慢,是你没关掉采样、没锁写入器、没禁用时间格式化——这三项漏掉任意一个,吞吐就掉回 log 包水平。
zap.NewProduction() 为什么一开就丢日志
高频请求场景(比如每秒上千次 HTTP 请求)下,zap.NewProduction() 默认开启采样(sampling),连续相同日志会被静默丢弃。你看到的 “没打出来”,大概率是被采样器吃了,不是写失败。
- 检查是否真丢失:加
zap.AddCaller()和唯一字段(如zap.String("req_id", uuid)),避免被判定为重复日志 - 关闭采样:用
zap.WrapCore(func(core zapcore.Core) zapcore.Core { return zapcore.NewSampler(core, time.Second, 100, 100) })替换为无采样器,或直接用zap.New(zapcore.NewCore(...))手动构造 - 别信 “只是开发环境” —— 本地压测时采样照样生效,必须显式关掉
WriteSyncer 不加锁会 panic
裸传 os.File 或 &lumberjack.Logger{} 给 zapcore.AddSync(),多 goroutine 并发写时会触发 “invalid memory address or nil pointer dereference” 或 syscall 错误,因为底层文件句柄非线程安全。
- 必须包一层
zapcore.Lock():zapcore.Lock(zapcore.AddSync(file)) - 别用
os.Stdout/os.Stderr直接做生产输出——它们没有缓冲,且不支持轮转 - 如果用
lumberjack,确保它已设置MaxSize、MaxBackups,否则日志文件会无限增长
JSONEncoder 时间格式化是 GC 杀手
默认 EncodeTime: zapcore.ISO8601TimeEncoder 是安全的,但一旦换成 func(t time.Time, enc zapcore.PrimitiveArrayEncoder) { enc.AppendString(t.Format("2006-01-02 15:04:05")) },每次调用都 new 字符串、触发 GC,QPS 超 2000 就明显卡顿。
初始化并管理阿里云SDK Go 客户端,涵盖 sync.Once 单例、goroutine 安全、endpoint 与 region 配置、VPC 端点及调试。
立即学习“go语言免费学习笔记(深入)”;
- 坚持用
zapcore.ISO8601TimeEncoder,它走预分配路径,不分配内存 - 不要在 encoder config 里设
TimeKey: "time"以外的 key——key 名变长会增加序列化开销 - 如果非要自定义格式,用
zapcore.EpochTimeEncoder(纳秒级整数),比字符串快且零分配
Logger 实例不能在 handler 里反复 new
每次 HTTP 请求都调用 zap.New(...) 或 logger.With(...),会重复创建 encoder、syncer、goroutine,开销远超日志本身,实测比直接用 log.Printf 还慢。
- 全局只初始化一次
*zap.Logger,注入到 handler struct 或 middleware 的闭包变量里 - 通用字段(如
service_name、env)用logger = logger.With(zap.String("service", "api"))预置,而不是每次请求都加 - 请求级字段(如
req_id、ip)用c.With(zap.String("req_id", id)).Info(...),避免污染全局 logger
真正卡点不在 “怎么用 Zap”,而在 “怎么不让它退化成同步阻塞 + 频繁分配”。初始化那几行代码写错,后面所有优化都白搭。

















