lumberjack不能按天自动切分日志,因其仅依据文件大小或数量轮转,不解析时间;maxage仅为清理策略(删除n天前旧文件),非切割触发器,需手动拼接带日期的filename并在每次write前比对日期、加锁切换文件。

lumberjack 不能按天自动切分日志文件,它只响应大小或数量阈值,不解析时间;所谓“按天轮转”,必须由你控制 Filename 拼接逻辑并主动切换 Writer。
为什么 lumberjack 的 MaxAge 不是“每天切一次”
MaxAge 是清理策略,不是切割触发器。它只在每次写入前检查旧文件的 ModTime(),决定是否删除(比如删掉 7 天前的 app.log.1),但不会导致当天的 app.log 在零点被重命名。
常见错误现象:app.log 从 5 月 21 日下午写到 5 月 22 日上午,文件名没变、内容跨天;或者你设了 MaxAge: 1,结果第二天日志还在往老文件里写,只是旧备份被删了。
-
MaxAge: 0表示禁用基于时间的清理,仅靠MaxBackups控制保留数量 -
LocalTime: true必须开启,否则ModTime()是 UTC,和系统时区对不上,清理可能误判 - 它不关心你写的日志里有没有日期字段,也不解析日志内容——只看文件元信息
如何真正实现“每天一个文件”且不丢日志
核心是:每次 Write() 前判断当前日期是否变化,变了就关旧、开新,并确保整个过程加锁、原子、无竞态。
不要用 time.AfterFunc 或 goroutine 定时轮询——进程重启后定时器丢失,且零点毫秒级漂移会导致漏切。
- 封装一个结构体,缓存当前
Filename和today字符串(如"2026-05-22") - 每次
Write(p []byte)先time.Now().Format("2006-01-02")对比,不一致则调用Close()+ 重建*lumberjack.Logger - 必须用
sync.Mutex包住整个判断 + 关闭 + 打开流程,否则并发写时可能往已关闭的文件句柄写,触发write: bad file descriptor - 新
Filename要带绝对路径,且父目录需提前os.MkdirAll(),lumberjack不创建目录
lumberjack 配置里最容易错的三个参数
MaxSize 单位是字节,不是 MB;MaxBackups 是保留的 .1/.2 文件数;Mode 决定新文件权限——这三个写错,轻则日志写失败,重则运维连文件都读不了。
-
MaxSize: 100是 100 字节,不是 100MB;正确写法是100 * 1024 * 1024 -
MaxBackups: 0表示无限保留,磁盘迟早打满;生产环境建议 ≤ 7 - 默认
Mode: 0600,其他用户无法读取;需显式设Mode: 0644,且注意容器内 umask 可能覆盖它 -
Compress: true会阻塞写入线程调用/bin/gzip,高吞吐下慎用;Docker 镜像里得提前装好 gzip
什么时候该放弃 lumberjack,改用 file-rotatelogs
当你需要“每小时切一次”“保留最近 7 天 + 最近 50 个文件”“切完立刻 gzip”这类组合策略时,lumberjack 就力不从心了——它的设计目标是简单、稳定、低侵入,不是灵活调度。
file-rotatelogs 原生支持时间格式化(%Y%m%d%H)、符号链接(WithLinkName)、异步压缩(需配 WithRotationCallback),但代价是多一层抽象、略高内存占用。
- 若只需“按大小切 + 保留 N 份”,
lumberjack足够,也更轻量 - 若业务日志有强时间语义(如按小时分析流量)、或需与 logrotate 行为对齐,直接上
file-rotatelogs - 别在
file-rotatelogs里手动拼日期字符串——它内部已用time.Now().In(loc).Format()处理时区,传错loc会导致链接指向错误文件
轮转不是配置完就结束的事:Close() 是否被 defer 正确包裹、父目录是否存在、新文件权限是否可读、压缩命令是否在 PATH 中——这些点任何一个出问题,都会让日志静默丢失或运维无法排查。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











