zap.newproduction() 不能按天切日志,因其内部仅使用 os.stdout 或固定文件 writer,无轮转逻辑;切割需依赖 lumberjack 等下游 io.writesyncer 实现。

为什么 zap.NewProduction() 不能按天切日志
因为 zap.NewProduction() 返回的 logger 内部只用了 os.Stdout 或固定文件 writer,完全不带轮转逻辑。它甚至不知道“天”是什么——Zap 本身只管序列化和写入,切割、命名、清理全靠下游 io.WriteSyncer 实现。你看到的日志堆成一个几 GB 的 app.log,不是 Zap 懒,是它压根没这职责。
用 lumberjack 实现按天切割的关键参数设置
lumberjack 不是“按天切”,而是“按 MaxAge 判断文件是否过期 + 每次写满 MaxSize 触发一次切割”。所谓“按天”,本质是靠 MaxAge: 1 配合 LocalTime: true 实现的近似效果。
Go语言(Golang)1.26.0版本提供 Go 官方 Windows amd64 MSI 安装包下载入口,版本号 1.26.0,可用于旧项目维护、兼容性测试和指定版本开发环境配置。
-
Filename必须是绝对路径(如/var/log/myapp/access.log),相对路径在 systemd daemon 下极易写到/根目录或权限拒绝位置 -
MaxSize设为100(MB)比1024更稳妥:太大导致单文件难排查,太小引发频繁rename和open系统调用 -
MaxAge: 1表示“最多保留 1 天内创建的备份文件”,但前提是当天有日志写入才会生成新文件;若某天零日志,就不会产生access-2026-07-14.log,MaxAge也就无从触发清理 - 务必显式设置
LocalTime: true,否则 lumberjack 用 UTC 时间命名文件,在东八区服务器上会晚 8 小时,导致归档时间错乱(比如凌晨 00:30 写的日志被标为前一日)
如何让 error 日志单独进 error.log,其他级别进 access.log
Zap 不支持“开关式分流”,必须用 zapcore.NewTee() 组合多个 core,并为每个 core 绑定不同 level 过滤器和 writer。
- 定义两个
lumberjack.Logger:一个给access.log(InfoLevel及以上),一个给error.log(ErrorLevel及以上) - 用
zapcore.LevelEnablerFunc精确控制每个 core 接收哪些 level,例如:func(lvl zapcore.Level) bool { return lvl >= zapcore.ErrorLevel } - 不要把
error.log的 writer 塞进accesscore 里,否则 error 日志会重复写两次 - 注意
zapcore.NewTee()是并发安全的,但每个 core 的 writer 必须独立构造,不能复用同一个lumberjack.Logger实例
必须调用 Sync() 才能触发切割判断
Zap 默认不主动 flush,lumberjack 的切割判断只发生在每次 Write() 后的 Sync() 调用中。如果你没显式调用,哪怕日志写满 100MB,也不会切。
- 最简单做法:在 main 函数退出前加
logger.Sync() - 更稳妥做法:用
defer logger.Sync(),或在 HTTP handler 结束时调用(尤其对 long-running 服务) - 如果用了
zapcore.BufferedWriteSyncer,它的FlushInterval会自动触发Sync(),但 lumberjack 本身不感知这个间隔——它只响应你传给它的Sync()调用 - 常见坑:在 goroutine 里打日志后直接 return,忘了
Sync(),结果最后一批日志卡在 buffer 里,切割永远不发生
LocalTime: true 和 Sync() 调用时机——前者让时间戳对得上运维看板,后者让切割真正发生。其余参数再精细,这两点没踩准,按天切就只是个幻觉。golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










