生产环境禁用 zap.NewDevelopment(),应使用 zap.NewProduction() 或手动构建 JSON Core;写文件需搭配 lumberjack.Logger 实现轮转,双写须用 zapcore.NewTee() 而非 NewMultiCore,高频日志须零分配构造字段并显式检查级别。

生产环境别用 zap.NewDevelopment(),它不是“开发版”而是“低性能调试模式”
直接调用 zap.NewDevelopment() 看着舒服——带颜色、行号、堆栈、可读时间,但它默认开启 debug 日志、用字符串拼接、不压缩字段,日志体积大、解析成本高,根本扛不住线上流量。Loki/ELK 这类日志系统也压根不认这种格式。
真正该用的是 zap.NewProduction() 或手动构造 JSON Core:它禁用 debug、用纳秒级时间戳、结构化键值对、写入 stderr,天然适配日志采集链路。但注意:zap.NewProduction() 输出路径固定为 stderr,不能直接写文件,想落盘必须自己构造 zapcore.Core。
- 别碰
zap.NewDevelopmentConfig().Build()—— 它和NewDevelopment()一样,底层仍是 console encoder,只是多一层配置壳 - 开发阶段如需终端可读 + 文件结构化双写,必须用
zapcore.NewTee(),而非zapcore.NewMultiCore() -
zap.NewProduction()默认启用 sampling,高频日志(如请求 trace)可能被静默丢弃,需显式关闭或调大阈值
写文件必须用 lumberjack.Logger 封装,os.OpenFile 不支持轮转
Zap 本身不提供文件轮转能力,仅负责日志格式化与写入 io.Writer;自动归档需依赖 lumberjack.Logger 这类外部 writer 实现切割、压缩与清理。常见错误是以为调用 zap.NewProduction() 就能按天切文件、自动删旧,结果跑一周发现日志全堆在同一个大文件里。
-
Filename必须是绝对路径,相对路径在 daemon 模式下极易写到根目录或不可写位置 -
MaxSize单位是 MB(不是 KB 也不是字节),设成100就是 100MB;太大导致单文件臃肿,太小引发频繁 IO 切割 -
MaxAge: 7配合MaxBackups: 30是较平衡组合:保留最近 7 天内生成的所有备份,但最多存 30 个文件 - 务必设置
LocalTime: true,否则lumberjack用 UTC 时间命名文件,在中国区服务器上会导致归档时间错乱 - 启动前确保日志目录存在且进程有写权限:
os.MkdirAll(filepath.Dir(cfg.FilePath), 0755)
双写控制台 + 文件且格式不同,必须用 zapcore.NewTee()
zapcore.NewMultiCore 是并发写入,但不处理格式差异——它只是把同一份 Field 并发塞给多个 Core。你要的是“同一条日志,控制台人类可读、文件机器可解析”,这必须靠 zapcore.NewTee()。
初始化并管理阿里云SDK Go 客户端,涵盖 sync.Once 单例、goroutine 安全、endpoint 与 region 配置、VPC 端点及调试。
立即学习“go语言免费学习笔记(深入)”;
关键点在于:每个 Core 可独立指定 WriteSyncer(输出目标)、Encoder(格式)、LevelEnabler(级别阈值):
- 控制台 Core:用
zapcore.NewConsoleEncoder(zap.NewDevelopmentEncoderConfig()),LevelEnabler设为DebugLevel - 文件 Core:用
zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig()),LevelEnabler设为InfoLevel以上 - 别忘了给文件写入器加
lumberjack.Logger,否则os.OpenFile不支持轮转,磁盘迟早被打满
高频路径禁用 fmt.Sprintf 和 time.Now().Format(),字段构造必须零分配
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})实现延迟序列化 - 初始化全局
Logger时必须设置的三个关键选项:zap.AddCaller()、zapcore.Lock(zapcore.AddSync(...))、EncodeTime: zapcore.ISO8601TimeEncoder;漏掉任意一项都可能让吞吐跌回标准库log包水平
最易被忽略的点是:采样开关、writer 锁、时间编码器三者必须同时生效,缺一不可;而 lumberjack 的静默失败机制会让轮转问题难以排查——没报错,但日志就是不切割。


















