logrus 无法原生按天切分日志,需结合 lumberjack 并通过每日零点主动调用 rotate() 实现;须设 localtime=true、maxsize=0,配合 ticker 检测日期变更并加锁串行化调用,或自定义 writer 动态生成带日期文件名。

logrus + lumberjack 实现按天切分日志文件
直接用 logrus 本身无法按日期滚动,必须搭配 lumberjack 这个第三方轮转库。它不支持原生时间策略(比如“每天零点新建文件”),而是靠文件大小 + 时间双触发——但你可以通过控制 MaxAge 和 LocalTime 配合外部逻辑逼近“按天切分”的效果。
关键点:lumberjack 的 MaxAge 是“保留多少天的旧日志”,不是“每天新建”。真正实现“零点切分”,得靠重启时重置 logger 或主动调用 Rotate()。
-
lumberjack.Logger的LocalTime: true必须设为true,否则MaxAge和文件名时间戳会出错(默认用 UTC) -
MaxSize建议设为 0(禁用大小切分),只依赖MaxAge和手动Rotate() - 如果应用长期运行,需自己起 goroutine 每日凌晨调用
lumberjack.Rotate(),否则不会自动建新文件
手动触发 Rotate() 实现严格每日零点切分
想让日志文件名带日期(如 app.log.2024-06-15)且每天从零点开始写,就得放弃纯被动轮转,改为主动控制。核心是监听时间到达 00:00:00,然后调用 lumberjack.Rotate()。
注意:Rotate() 不会阻塞,但并发调用可能引发 panic;确保单例 logger 且调用前加锁或用 channel 串行化。
- 用
time.Ticker每分钟检查一次是否到了新一天(比每秒高效,也避免时钟跳变误判) - 判断逻辑用
t.Day() != lastDay,而不是比较字符串格式化时间,避免时区和格式误差 - 第一次初始化 logger 时,文件名不含日期;
Rotate()后生成的归档文件才带日期,当前日志仍叫app.log—— 这是 lumberjack 默认行为,不可改
logrus.SetOutput() 与 io.MultiWriter 的陷阱
别把 lumberjack.Logger 直接传给 logrus.SetOutput() 就完事。它实现了 io.Writer,但内部缓冲和 Close 行为容易导致日志丢失或 panic。
正确做法是封装一层,确保 Write() 调用稳定,并在程序退出前显式调用 lumberjack.Close()。
- 不要在
defer lumberjack.Close()里关,因为 main 函数退出太快,goroutine 可能没刷完缓冲 - 推荐用
signal.Notify捕获os.Interrupt和os.Kill,然后同步关闭 lumberjack - 如果同时写多个输出(比如控制台 + 文件),用
io.MultiWriter包裹,但注意lumberjack.Logger和os.Stdout的 Write 性能差异大,可能拖慢整体日志速度
文件名中嵌入日期的替代方案:自定义 Writer
lumberjack 默认不支持“当前日志文件名含当天日期”,比如想要 app.2024-06-15.log 而不是固定名 app.log。这时得绕过 lumberjack,自己实现 io.Writer。
本质是维护一个带日期的 *os.File 句柄,每天零点 reopen 一次。比 lumberjack 轻量,但要自己处理并发写、文件权限、归档删除。
- 每次 Write 前检查当前日期是否变化,变化则先
file.Close()再os.OpenFile(..., os.O_CREATE|os.O_APPEND|os.O_WRONLY) - 归档逻辑需自行实现:rename 旧文件为
app.2024-06-14.log,再清空或压缩 - 务必加
sync.Mutex锁住文件句柄切换过程,否则多 goroutine 下可能 write 到已 close 的 file
这个方案更贴近“真按天切分”,但稳定性取决于你对文件操作边界的把控——比如磁盘满、权限不足、NFS 挂载延迟等场景都得兜底。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











