生产环境必须用 zap.newproduction() 或自定义 json encoder,禁用 debug、纳秒时间戳、结构化输出;需用 lumberjack 轮转文件并确保目录权限;双写控制台与文件须用 zapcore.newtee() 组合不同 core。

生产环境别用 NewDevelopment(),它会拖慢服务还难解析
开发时用 zap.NewDevelopment() 看着舒服——带颜色、行号、堆栈、可读时间,但它默认开启 debug 日志、用字符串拼接、不压缩字段,日志体积大、解析成本高,根本扛不住线上流量。Loki/ELK 这类日志系统也压根不认这种格式。
真正该用的是 zap.NewProduction() 或手动配置的 JSON encoder:它禁用 debug、用纳秒级时间戳、结构化键值对、写入 stderr,天然适配日志采集链路。
-
zap.NewProduction()是开箱即用的底线配置,适合快速上线,但输出路径固定为stderr,不能直接写文件 - 想写文件?必须自己构造
zapcore.Core,搭配lumberjack.Logger做切割,不能只靠os.OpenFile - 时间戳默认是 Unix 纳秒(
"ts":1706518203.525694),如果运维要求 ISO8601 格式,得重写EncoderConfig.EncodeTime
怎么同时输出到控制台和文件,且格式不同?用 zapcore.NewTee() 搭配多 Core
调试时既要终端实时看,又要留完整日志文件,但控制台要可读、文件要结构化——Zap 不提供“一键双写”,得靠 zapcore.NewTee() 合并多个 Core。
关键点在于:每个 Core 可独立指定 WriteSyncer(输出目标)、Encoder(格式)、LevelEnabler(级别阈值)。
- 控制台用
zapcore.NewConsoleEncoder(zap.NewDevelopmentEncoderConfig()),人类友好 - 文件用
zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig()),机器友好 - 级别可以错开:控制台设
DebugLevel,文件只收InfoLevel以上,避免日志爆炸 - 别忘了给文件写入器加
lumberjack.Logger,否则os.OpenFile不支持轮转,磁盘迟早被打满
lumberjack 配置不生效?检查 MaxSize 单位和权限
很多人配了 MaxSize: 100,结果日志文件还是涨到几个 GB —— 因为 lumberjack.Logger.MaxSize 单位是 MB,不是 KB 也不是字节。设成 100 就是 100MB,不是 100KB。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
另一个高频问题是权限不足导致切割失败,日志卡在原文件不动,错误还不报(lumberjack 默认静默失败)。
- 启动前确保日志目录存在且进程有写权限:
os.MkdirAll(filepath.Dir(cfg.FilePath), 0755) -
MaxBackups和MaxAge要配合用:只设MaxBackups会积压旧文件;只设MaxAge可能因时间未到一直不删 -
Compress: true会启用 gzip,但注意:老版本lumberjack(github.com/natefinch/lumberjack v2.2.1+incompatible 更稳
动态改日志级别?必须用 AtomicLevel,别硬 reload logger
线上出问题想临时开 debug 日志?别重启服务,也别重新 Build() 一个新 Logger——那样会导致旧日志句柄泄漏、goroutine 泄漏,甚至 panic。
Zap 提供了线程安全的 zap.AtomicLevel,它是唯一被设计用于运行时调整级别的机制。
- 初始化时用
level := zap.NewAtomicLevelAt(zap.InfoLevel),然后传给所有Core - 暴露 HTTP 接口或监听信号时,直接调
level.SetLevel(zap.DebugLevel),立刻生效 - 别把
AtomicLevel存成全局变量后到处改——它本身是线程安全的,但封装层如果加了锁或缓存,反而引入竞争 - 注意:
zap.NewProduction()返回的 logger 用的是内部AtomicLevel,但你无法拿到句柄,所以必须自己构造才能动态调级
最容易被忽略的一点:无论怎么配置,defer logger.Sync() 必须放在 main() 函数退出前,否则最后一段缓冲日志大概率丢掉——尤其在 os.Exit() 或 panic 场景下,Sync() 不执行,日志就没了。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










