zap.newproduction() 不支持日志切割,因其仅写入 os.stderr 且无轮转逻辑;必须用 lumberjack.logger 替换 writer 才能实现自动切分、压缩与清理。

zap.NewProduction() 为什么切不了日志
因为 zap.NewProduction() 返回的 logger 内部用的是 os.Stderr 作为 writer,且没做任何轮转逻辑——它只负责把结构化日志写进一个固定输出流。你看到的日志堆在单个文件里,不是 bug,是设计使然:Zap 的核心职责是「序列化 + 写入」,不碰文件管理。
常见错误现象:app.log 一周涨到 8GB,ls -la 发现只有 1 个文件,tail -f app.log 滚动飞快但 never 切。
- 别指望
zap.NewDevelopment()或zap.NewProduction()自带切割能力,它们连io.Writer都不暴露给你换 - 不要试图用
os.Rename()或定时mv脚本强行归档——Zap 还在往原 fd 写,会导致日志丢失或写错位置 - 生产环境直接用
zap.NewProduction()接文件路径?不行,它根本不接受路径参数
必须用 lumberjack.Logger 替换默认 writer
真正起作用的是 lumberjack.Logger,它实现了 io.WriteSyncer,能响应 Write() 和 Sync(),并在满额时自动重命名、压缩、清理旧文件。Zap 只需把它当普通 writer 用。
实操关键点:
-
Filename必须是绝对路径,比如"/var/log/myapp/access.log";相对路径在 systemd service 或 docker 中常写到/下导致权限失败 -
MaxSize单位是 MB(不是字节),设成100比1024更稳:太大难排查,太小频繁 syscall 影响吞吐 -
MaxAge: 7是指“最老备份文件存活不超过 7 天”,不是“每天生成一个”——如果某天零日志,就不会产生新文件,MaxAge不触发删除 - 务必加
LocalTime: true,否则文件名用 UTC 时间,在中国服务器上会比本地时间晚 8 小时,归档逻辑错乱
Sync() 调用时机决定切割是否生效
Zap 默认不主动调用底层 writer 的 Sync(),而 lumberjack 正是靠 Sync() 判断是否该切——所以你不显式 sync,它永远不切。
初始化并管理阿里云SDK Go 客户端,涵盖 sync.Once 单例、goroutine 安全、endpoint 与 region 配置、VPC 端点及调试。
两种可靠做法:
- 用
zap.AddSync(&lumberjack.Logger{...})包一层,它内部做了WriteSyncer封装,保证每次写都带Sync() - 自己封装
WriteSyncer实现,在Write()后立刻Sync(),但注意并发安全——lumberjack本身已线程安全 - 千万别依赖
defer logger.Sync()做切割:那是进程退出前 flush,对运行中切割无效
归档行为完全由 lumberjack 控制,Zap 不感知
压缩、删旧、重命名这些动作,全是 lumberjack.Logger 自己在 Sync() 里完成的。Zap 就是个“倒数据的管道工”,连文件名都看不到。
这意味着:
-
MaxBackups: 30是lumberjack自己删文件,不是 Zap 触发的 hook - 你想加归档后上传 S3?得在
lumberjack的Rotate()后手动监听文件系统事件,Zap 提供不了钩子 - 如果程序崩溃没来得及
Sync(),最后几 KB 日志可能卡在 buffer 里,lumberjack也来不及切——这是设计取舍,不是缺陷
真正容易被忽略的,是 lumberjack 的 Compress: true 会调用系统 gzip 命令,若容器里没装 gzip 或 PATH 不对,压缩会静默失败,只留未压缩备份。上线前务必验证。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










