logrus 默认不支持按天分割日志,因其本身无文件轮转能力,仅负责日志写入;必须结合 lumberjack 等第三方库,通过设置 localtime=true、maxage=1 天并配合手动 rotate() 或定时检测日期变更,才能实现每日零点切分。

为什么 Logrus 默认不支持按天分割日志?
Logrus 本身是无文件切割能力的,logrus.FileHook 或直接写文件时,它只管写,不管轮转。你看到日志文件一天天变大,不是它“忘了切”,而是它压根没这功能。想按天分文件,必须自己控制文件句柄切换时机——比如每天零点新建一个 logrus.Logger 实例,或复用同一实例但动态更新其输出 io.Writer。
用 lumberjack + Logrus 实现零配置按天切分
最省事、也最常用的做法是用 lumberjack.Logger 做底层写入器,它支持基于时间或大小的轮转。虽然名字叫 “按大小” 主导,但只要把 MaxAge 设为 1 天、LocalTime 开启,并配合 RotateDaily(需手动触发)或更稳妥的定时检查机制,就能达成“每日一文件”。实际项目中推荐以下组合:
-
lumberjack.Logger的Filename设为固定路径,如"logs/app.log"(注意目录需存在) -
MaxAge: 24 * time.Hour,确保旧文件超时后被清理 -
LocalTime: true,避免 UTC 时间导致切分错乱 -
Compress: false(除非你真需要 gzip,否则关掉,减少 IO 和权限问题)
然后把这个 lumberjack.Logger 作为 logrus.SetOutput 的目标,Gin 的中间件里用 logrus.WithFields 补充请求上下文即可。
Gin 中间件如何注入 Logrus 日志且不丢上下文?
Gin 的 gin.Context 是请求生命周期绑定的,不能直接塞进全局 logrus.Logger。常见错误是每个 handler 里都调 logrus.WithField("trace_id", c.GetString("trace_id")),既重复又易漏。正确做法是:在中间件里把 logrus.Entry 绑定到 c 上:
func Logger() gin.HandlerFunc {
return func(c *gin.Context) {
entry := logrus.WithFields(logrus.Fields{
"ip": c.ClientIP(),
"method": c.Request.Method,
"path": c.Request.URL.Path,
"agent": c.GetHeader("User-Agent"),
})
c.Set("logger", entry) // 注意:不是 SetLogger,那是 Gin 内置的 debug logger
c.Next()
}
}
后续 handler 里通过 c.MustGet("logger").(*logrus.Entry) 拿到它,再追加字段或打日志。这样字段不会跨请求污染,也不依赖全局变量。
按天切分后,怎么查昨天的日志文件?
文件名不会自动带日期,lumberjack 默认生成的是 app.log.2024-05-22 这样的格式(取决于系统时区和 LocalTime 设置),但前提是 MaxBackups 足够大、且程序持续运行。容易忽略的点有三个:
- 如果服务每天重启,
lumberjack可能无法识别“该切了”,因为没机会在零点执行rotate;建议加个启动时的checkRotate()手动触发 - 日志路径权限不对,比如
logs/目录不存在或不可写,会导致静默失败——lumberjack不报错,只是写不进去 - 用
logrus.SetLevel(logrus.DebugLevel)但没配lumberjack的MaxSize,可能单日文件过大却没触发轮转,反而掩盖了按天切分效果
真正可靠的按天切分,不是靠“等它自动切”,而是靠“每天固定时间 close + reopen”,但这需要额外 goroutine 和信号协调——多数业务用 lumberjack 加合理参数就够了,别过早复杂化。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











