最稳妥方案是 logrus.SetOutput(&lumberjack.Logger{}),必须直接传实例而非包装 Writer;MaxSize 单位为字节(如 1010241024),非 MB;需设 Mode=0644、LocalTime=true,禁用手动 Rotate 并保证全局单例。

logrus 配合 lumberjack 实现按大小轮转最稳妥
直接用标准库 log 做大小轮转非常麻烦,得自己监听文件尺寸、重开文件句柄、处理并发写入竞争——不推荐。生产环境普遍用 logrus + lumberjack 组合,后者专为日志轮转设计,支持按大小、按时间、保留份数等,且线程安全。
常见错误是只引入 logrus 就开始调 SetOutput,结果轮转根本没生效。关键在:必须把 lumberjack.Logger 实例传给 logrus.SetOutput,而不是它的 Writer 或随便封装的 io.WriteCloser。
-
lumberjack.Logger本身实现了io.Write和io.Closer,可直接赋值 - 轮转触发条件是单个日志文件达到
MaxSize(单位 MB),不是总大小 -
MaxBackups控制保留几个旧文件,MaxAge是额外按天清理,两者独立生效
import (
"github.com/sirupsen/logrus"
"gopkg.in/natefinch/lumberjack.v2"
)
<p>func setupLogger() {
writer := &lumberjack.Logger{
Filename: "app.log",
MaxSize: 10, // MB
MaxBackups: 5,
MaxAge: 28, // days
Compress: true,
}
logrus.SetOutput(writer)
}zap 要启用轮转得手动包装 WriteSyncer
zap 默认不带轮转能力,官方明确说“轮转由使用者负责”。想用它实现按大小轮转,得自己构造一个符合 zapcore.WriteSyncer 接口的类型,内部委托给 lumberjack.Logger。
容易踩的坑是忽略 Sync() 方法的实现——lumberjack.Logger.Sync() 会刷盘并检查是否需要轮转,如果包装时漏掉或直接返回 nil,会导致日志堆积不落盘,或轮转延迟甚至失效。
立即学习“go语言免费学习笔记(深入)”;
初始化并管理阿里云SDK Go 客户端,涵盖 sync.Once 单例、goroutine 安全、endpoint 与 region 配置、VPC 端点及调试。
- 必须同时实现
Write()和Sync(),且都代理到lumberjack.Logger - 不要用
os.Stdout或临时bytes.Buffer测试,它们没有轮转逻辑 -
zap.NewProductionConfig()默认禁用Development模式,但轮转与该配置无关
自定义轮转要注意文件句柄泄漏和竞态
真要手写轮转(比如嵌入式场景不能引入第三方),核心逻辑就三步:检查当前文件大小 → 达标则关闭旧文件、重命名、新建 → 写入前确保句柄有效。但实际难点在细节:
- 多个 goroutine 同时写日志时,
stat和open之间存在时间窗口,可能两个协程都判断需轮转,导致重复 rename 或 open 失败 - Linux 下删除正在写的文件,inode 不立即释放,旧日志内容仍占磁盘空间,直到所有句柄关闭——所以轮转后务必
Close()旧文件 -
os.Rename在不同文件系统间可能失败,应改用io.Copy+os.Remove组合,但性能差
Windows 下 lumberjack 的 MaxSize 单位仍是字节,不是 KB
文档没明说,但源码里 lumberjack.Logger 所有尺寸判断都用 int64 字节单位,MaxSize: 10 表示 10 字节,不是 10MB。这个反直觉点在 Windows 上尤其容易出错,因为路径分隔符、权限模型不同,加上测试时日志量小,可能跑几天才发现日志疯狂切分。
正确写法永远显式乘上单位:MaxSize: 10 * 1024 * 1024。别依赖注释或记忆,直接算清楚。
另外,Windows 的文件锁机制比 Linux 更严格,lumberjack 在 rename 时若遇到“被其他进程占用”,会静默重试,默认最多 100 次,每次 100ms——这意味着轮转卡住最长可能 10 秒,期间新日志会暂存内存 buffer,极端情况撑爆内存。

















