lumberjack.maxage不是每天切一次,而是清理策略,仅删除n天前旧文件,不触发日志切割;真正按天切需手动拼接日期文件名并在每次write前加锁比对并切换writer。

lumberjack 本身不支持按天自动切割,所谓“每天一个文件”必须由你控制文件名拼接和 Writer 切换逻辑,否则日志必然跨天。
为什么 lumberjack.MaxAge ≠ 每天切一次
MaxAge 是清理策略,不是切割触发器。它只在每次写入前检查旧文件的 ModTime(),决定是否删除(比如删掉 7 天前的 app.log.1),但不会导致当天的 app.log 在零点被重命名或新建。
- 常见错误现象:
app.log从 6 月 4 日下午写到 6 月 5 日上午,文件名没变、内容跨天 - 设了
MaxAge: 1,结果第二天日志还在往老文件里写,只是旧备份被删了 -
LocalTime: true必须开启,否则ModTime()是 UTC,和本地时区对不上,清理可能误判 - 它完全不解析日志内容里的日期字段,只看文件元信息
如何真正实现“每天一个文件”且不丢日志
核心是:每次 Write(p []byte) 前判断当前日期是否变化,变了就关旧、开新,并确保整个过程加锁、原子、无竞态。
- 不要用
time.AfterFunc或 goroutine 定时轮询——进程重启后定时器丢失,且零点毫秒级漂移会导致漏切 - 封装一个结构体,缓存当前
Filename和today字符串(如"2026-06-04") - 每次
Write先调用time.Now().Format("2006-01-02")对比,不一致则调用Close()+ 重建*lumberjack.Logger - 必须用
sync.Mutex包住整个判断 + 关闭 + 打开流程,否则并发写时可能往已关闭的文件句柄写,触发write: bad file descriptor
lumberjack 配置最容易踩的坑
即使你补全了日期切换逻辑,lumberjack.Logger 自身配置不对也会导致归档错乱或权限问题。
-
MaxSize单位是 MB(不是字节),设10就是 10MB;别写成10 * 1024 * 1024或"10MB" -
MaxBackups应设具体值(比如7),设0表示不限——但磁盘会涨满 -
Mode: 0644必须显式设置,否则新日志文件默认权限是0600,运维查不到 -
Compress: true会阻塞写入,高吞吐场景慎开;压缩失败也不会报错,容易静默丢归档 - 确保进程对
Filename所在目录有写权限,否则切分时静默失败(只留 error log 在 stderr)
更轻量、更可靠的替代思路
如果你不需要 Go 进程内完全掌控切割时机,用 logrotate 是更稳的选择——它不依赖进程状态,支持按天 + 按大小双触发,且归档、压缩、清理全部由系统级工具保障。
- Go 进程只需往 stdout 或固定文件(如
/var/log/myapp/app.log)持续写,不用管轮转 -
logrotate配置里加copytruncate,就能在不重启进程前提下安全切割(先拷贝再清空原文件) - 避免了 Go 层手动切换 Writer 的所有竞态、句柄泄漏、时区漂移问题
- 上线前记得在 systemd service 文件中配
ReadWritePaths=/var/log/myapp,否则切分失败且无提示
真正难的不是“怎么切”,而是“怎么切得既准时又不丢、并发写时不崩、重启后不漏、运维能直接读”。日期字符串拼接+加锁切换是最小侵入方案,但一旦涉及小时级精度、多实例共享日志路径、或容器环境,外部工具反而更值得优先考虑。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











