logrus + rotatelogs 是 gin 集成 logrus 时最稳妥的按时间轮转方案,支持多实例并发写入,需在 init 或 main 开头配置 setoutput,避免与 gin.logger() 冲突,并正确设置 withrotationtime、withlinkname 和 withmaxage 等参数。

logrus + rotatelogs 实现按时间轮转
直接用 rotatelogs 替换默认输出,是 Gin 集成 Logrus 时最稳妥的日志回滚方案。它不依赖文件锁或进程信号,靠时间戳生成新文件,天然支持多实例并发写入(比如部署多个 pod)。
常见错误是把 WithRotationTime 设得太小(如 1 * time.Minute),导致每分钟新建一个文件,但没配 WithMaxAge 或 WithRotationCount,磁盘很快被撑爆。
-
WithRotationTime(24 * time.Hour)表示每天切一次,文件名带%Y%m%d;设成1 * time.Hour就是每小时切 -
WithLinkName("logs/latest.log")会创建软链接指向最新日志,方便运维脚本读取,但注意路径必须可写且父目录已存在 -
WithMaxAge(7 * 24 * time.Hour)和WithRotationCount(7)二选一:前者按保留天数清理,后者按文件个数清理;混用会导致行为不可预期
logrus.SetOutput() 必须在初始化阶段调用
如果在中间件里、或者 c.Next() 之后才调用 logrus.SetOutput(),日志会漏写——因为 Logrus 全局 logger 在首次调用 Info() 等方法时就已绑定默认输出(os.Stderr),后续再改 SetOutput 不影响已缓存的 handler。
正确做法是在 main() 开头或 init() 函数里完成全部配置:
func init() {
writer, _ := rotatelogs.New(
"logs/app-%Y%m%d.log",
rotatelogs.WithLinkName("logs/latest.log"),
rotatelogs.WithRotationTime(24*time.Hour),
rotatelogs.WithMaxAge(7*24*time.Hour),
)
logrus.SetOutput(writer)
logrus.SetFormatter(&logrus.TextFormatter{FullTimestamp: true})
logrus.SetLevel(logrus.InfoLevel)
}
别在中间件里 new 一个 logrus.Logger 再 set output——那只是局部实例,Gin 默认 logger 和你自定义的中间件 logger 不是一回事。
gin.Logger() 中间件和 logrus 冲突怎么办
如果你用了 router.Use(gin.Logger()),它会用自己的 io.Writer 输出到控制台,和你配置的 logrus.SetOutput() 完全无关。两者并存会导致日志重复、格式错乱、甚至 panic(比如 gin.Logger() 往已关闭的文件句柄写)。
解决方式只有两个:
- 彻底弃用
gin.Logger(),自己写中间件,用logrus.WithFields()记录请求信息 - 保留
gin.Logger()仅用于开发调试(输出到os.Stdout),生产环境用logrus单独接管,且确保router.Use()里不同时挂两个日志中间件
典型错误是既调 gin.Default()(自带 Logger()),又在 main() 里配 logrus.SetOutput(),结果线上日志一半进文件、一半刷屏,还互相干扰。
rotatelogs 软链接失效或权限拒绝
WithLinkName 创建软链接失败,90% 是因为目标目录不存在或无写权限。例如 logs/latest.log 的父目录 logs/ 没提前 os.MkdirAll("logs", 0755),就会静默失败,日志照常写但链接不生效。
另一个坑是容器环境:某些镜像(如 alpine)默认不支持软链接,或挂载卷时设置了 noexec,导致 rotatelogs 创建链接失败后 fallback 到直接写文件,但文件名不含时间戳,轮转逻辑就崩了。
验证方式很简单:
- 启动服务后,立刻
ls -l logs/latest.log,看是不是软链接,目标文件是否存在 - 手动
touch logs/test,确认目录可写 - 查
rotatelogs的 error 日志——它其实会把链接失败打到stderr,但很多人没留意
真正麻烦的是跨平台兼容性:Windows 不支持软链接,WithLinkName 会静默忽略,得换用 lumberjack 这类更保守的轮转方案。











