gin默认日志不轮转是因为gin.defaultwriter底层是os.stdout,无文件尺寸感知与自动切割逻辑;lumberjack.logger需直接传入recoverywithwriter或loggerwithwriter,不可包装或混用io.multiwriter,否则轮转失效。

直接用 lumberjack.Logger 配给 Gin 的日志输出或 Recovery 中间件,就能实现按大小自动轮转——但必须传实例本身,不能包装、不能代理、不能用 io.MultiWriter 混合时漏掉它。
为什么 Gin 默认日志不轮转?
Gin 的 gin.DefaultWriter 是个 io.Writer,底层默认是 os.Stdout;它不感知文件尺寸、不重开句柄、不处理并发写入竞争。你用 gin.SetMode(gin.ReleaseMode) 或自己调 gin.DefaultWriter = yourFile,只要没引入轮转逻辑,日志文件就会无限增长。
标准库 log 也一样:它只负责格式化和写入,轮转得你自己监听、判断、关闭旧文件、打开新文件——高并发下极易出现 write on closed file panic。
lumberjack.Logger 不是日志框架,它只是个带轮转能力的 io.WriteCloser,Gin 能接受它,是因为它满足接口要求。
怎么把 lumberjack 接进 Gin 的 Recovery 中间件?
Gin 提供了 gin.RecoveryWithWriter,它接受一个 io.Writer,正好能接 *lumberjack.Logger 实例。
- 别用
os.OpenFile创建的*os.File直接传进去——它不会轮转 - 别在
RecoveryWithWriter里传io.MultiWriter(os.Stdout, yourLumberjack)——如果yourLumberjack是包装过的(比如加了 mutex 或 buffer),Sync()可能被忽略,导致轮转失效或日志堆积 - 确保
Filename是绝对路径(如/var/log/myapp/error.log),相对路径在 systemd 或 docker 启动时工作目录不确定,容易写到错误位置 - 必须设
LocalTime: true,否则轮转文件名里的日期是 UTC,排查时时间对不上
示例:
func CustomRecovery() gin.HandlerFunc {
return gin.RecoveryWithWriter(&lumberjack.Logger{
Filename: "/var/log/myapp/error.log",
MaxSize: 50, // MB
MaxBackups: 7,
MaxAge: 30,
LocalTime: true,
Compress: true,
}, func(c *gin.Context, err interface{}) {
// 自定义错误响应逻辑
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": "internal error"})
})
}
logrus 或 zap 和 lumberjack 混用时最容易错哪?
很多人以为“装了 lumberjack 就万事大吉”,结果日志没进文件、轮转后还在往旧文件写、或者突然丢日志——问题几乎都出在传参方式上。
-
logrus.SetOutput必须传*lumberjack.Logger实例,不是它的io.Writer包装器,也不是io.MultiWriter里的一员 -
zapcore.NewCore构造时,WriteSyncer必须同时实现Write()和Sync(),且都代理到lumberjack.Logger;漏掉Sync()会导致刷盘延迟、轮转滞后,甚至因缓冲区满而卡住写入 -
MaxSize单位是 MB,但实际比较的是字节数(MaxSize * 1024 * 1024),不是“精确到字节就切”,而是在每次写入前检查,所以可能略超(通常几 KB 内) -
MaxBackups: 0表示不保留任何旧文件,轮转时直接删除——生产环境慎用
按日期轮转怎么做?lumberjack 支持吗?
lumberjack 本身**不支持按日期命名新文件**(比如 app-2026-08-11.log),它只支持固定文件名 + 数字后缀(如 app.log.1、app.log.2)。所谓“按日期轮转”,其实是靠外部定时触发 Rotate() 方法。
如果你需要每天一个文件:
- 启动时起一个 goroutine,在每天 0 点调
w.Rotate() -
Rotate()是线程安全的,可并发调用 - 注意:
Rotate()会立即切文件,但不会改变Filename值;所以你得自己拼接日期路径,比如每天生成/var/log/myapp/access-2026-08-11.log,再把lumberjack.Logger.Filename动态更新过去——这已超出lumberjack职责范围,建议用zap配合自定义WriteSyncer或改用go.uber.org/zap/zapcore.LockingWriter+ 定时器
简单说:按大小轮转,lumberjack 开箱即用;按日期轮转,它只提供基础能力,具体调度和文件名管理得你自己补。











