logrus + lumberjack 是主流选择,因 logrus 无轮转能力而 lumberjack 提供轻量、内建的按大小/时间切割、备份保留与压缩功能,且不依赖外部工具;需注意 writer 初始化时机、复用、目录预创建及关闭 flush,避免日志丢失。

logrus + lumberjack 组合为什么是主流选择
因为 logrus 本身不支持日志轮转,而 lumberjack 是专为 Go 日志切割设计的轻量级 Writer,两者搭配能用最少代码实现按大小/时间自动切割、保留旧日志、压缩归档等生产必需功能。它不依赖系统 cron 或外部工具,所有逻辑在进程内完成。
常见错误是直接用 os.OpenFile 写文件再自己判断大小——这容易丢日志、并发写冲突,且无法优雅处理 SIGTERM 时的 flush。
-
lumberjack的MaxSize单位是 MB(不是字节),默认 100,设太小会导致频繁 rename -
MaxBackups控制保留几个历史文件,设为 0 表示不限制,但磁盘空间会持续增长 -
MaxAge是天数,仅对“最后修改时间”生效,和日志内容里的时间戳无关
初始化 logrus 并绑定 lumberjack.Writer 的标准写法
关键不是“怎么引入”,而是 Writer 初始化时机和复用方式:必须在 logger 创建前就准备好 lumberjack.Logger 实例,并确保整个应用生命周期只用同一个实例——否则切割逻辑失效,多个 Writer 会各自生成编号重复的 backup 文件。
import (
"github.com/sirupsen/logrus"
"gopkg.in/natefinch/lumberjack.v2"
)
func newLogger() *logrus.Logger {
logger := logrus.New()
logger.SetOutput(&lumberjack.Logger{
Filename: "./logs/app.log",
MaxSize: 50, // MB
MaxBackups: 7,
MaxAge: 28, // days
Compress: true,
})
logger.SetFormatter(&logrus.JSONFormatter{})
return logger
}
注意:Filenames 路径需提前创建父目录(os.MkdirAll("./logs", 0755)),否则首次写日志时会 panic 报 open ./logs/app.log: no such file or directory。
避免日志丢失的三个硬性约束
Go 的 logrus 默认异步刷盘,但 lumberjack 的切割动作是同步阻塞的。如果没处理好关闭流程,进程退出时未 flush 的缓冲日志会直接丢失。
- 必须在 main 函数退出前调用
logger.Writer().Close()(前提是把 Writer 提出来保存) - 不能用
logrus.SetOutput多次覆盖,每次覆盖都会丢掉前一个 Writer 的状态 - 若使用
logrus.WithFields构造子 logger,其输出目标仍继承自 root logger,无需额外配置 Writer
典型漏点:测试时用 defer logger.Close() 放在 init 函数里——init 执行完就 close 了,后续日志全写不到文件。
按时间切割需要自己补逻辑
lumberjack 原生只支持按大小和保留天数,不支持“每天一个文件”这种命名规则(如 app-2024-06-15.log)。强行用 MaxAge=1 不能保证每天零点切,它只是删掉超过 1 天的旧文件。
真要按日期分割,有两个可行路径:
- 改用
file-rotatelogs库,它支持%Y-%m-%d格式化,但要注意其RotateDaily本质是定时检查,非精确触发 - 保持
lumberjack,在业务代码中每到整点或每日首次写日志时,手动调用logger.Writer().Rotate()——但得自己维护时间判断和并发安全
多数服务其实不需要严格按日切,用 MaxSize + MaxAge 组合已足够覆盖运维需求。强行追求日期命名反而增加监控解析成本。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











