一 核心概念与策略
- 日志轮转用于避免单个日志文件无限增长,常见策略包括:
- 按大小轮转:当文件达到指定大小(如10 MB)时切割,配合保留数量与保留天数控制磁盘占用。
- 按时间轮转:按天/小时等时间边界切割,便于检索与归档。
- 混合策略:同时限制大小与保留天数,既控容量又便于按时间管理。
- 在 Go 生态中,主流日志库(如 logrus、zap、slog)并不直接内置轮转功能,而是通过可配置的 io.Writer / WriteSyncer / Handler 接入轮转组件(如 lumberjack)实现;也可将日志输出到 stdout/stderr,交由系统工具(如 logrotate)处理。
二 常用实现方式与对比
| 方式 |
适用场景 |
优点 |
局限 |
| lumberjack |
应用内按大小切割、保留 N 天/个、可选压缩 |
接入简单、与主流日志库解耦、生产可用 |
原生以大小为触发;时间边界需额外逻辑 |
| 自定义 Writer/定时器 |
需要按天/小时或事件触发切割 |
策略完全可控、可定制命名与保留策略 |
需处理并发、文件句柄、信号与优雅关闭 |
| 系统 logrotate |
容器/虚拟机/物理机统一运维、遵循系统规范 |
运维统一、与系统日志策略一致、无需改代码 |
依赖外部调度;容器场景需确保日志落盘与信号传递 |
以上三种方式均可落地,选择取决于你对可控性、运维统一性与复杂度的权衡。
三 与主流日志库的集成示例
- 标准库 log + lumberjack(按大小)
- 要点:将 lumberjack.Logger 作为 io.Writer 注入到标准库的 log.SetOutput。
- 示例:
- import (
- “log”
- “gopkg.in/natefinch/lumberjack.v2”
)
- logger := &lumberjack.Logger{
- Filename: “/var/log/myapp.log”,
- MaxSize: 10, // MB
- MaxBackups: 7,
- MaxAge: 30, // days
- Compress: true, // 压缩旧日志
}
- log.SetOutput(logger)
- log.Println(“hello, rotating by size”)
- zap + lumberjack(高性能结构化)
- 要点:用 zapcore.AddSync 包装 lumberjack.Logger,构建 zapcore.Core 时指定该 WriteSyncer。
- 示例:
- import (
- “go.uber.org/zap”
- “go.uber.org/zap/zapcore”
- “gopkg.in/natefinch/lumberjack.v2”
)
- writeSyncer := zapcore.AddSync(&lumberjack.Logger{
- Filename: “/var/log/myapp.log”,
- MaxSize: 10,
- MaxBackups: 7,
- MaxAge: 30,
- Compress: true,
})
- core := zapcore.NewCore(
- zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig()),
- writeSyncer,
- zap.InfoLevel,
)
- logger := zap.New(core, zap.AddCaller())
- defer logger.Sync()
- logger.Info(“hello, zap with rotation”)
- slog + lumberjack(官方结构化日志)
- 要点:slog.Handler 的第一个参数是 io.Writer,可直接传入 lumberjack.Logger。
- 示例:
- import (
- “log/slog”
- “gopkg.in/natefinch/lumberjack.v2”
)
- w := &lumberjack.Logger{
- Filename: “/var/log/myapp.log”,
- MaxSize: 10,
- MaxBackups: 7,
- MaxAge: 30,
- Compress: true,
}
- logger := slog.New(slog.NewJSONHandler(w, nil))
- logger.Info(“hello, slog with rotation”)
- 以上三种方式体现了 Go 日志库通过 io.Writer / WriteSyncer / Handler 与轮转组件解耦的通用模式。
四 高级用法与注意事项
- 按时间切割的两种常见做法
- 定时器触发:启动 time.Ticker,在每天固定时刻调用 lumberjack.Logger.Rotate() 强制轮转;下一次写入自动切到新文件。
- 封装 Writer:在 Write(p []byte) 中检测日期变化(如跨日),满足阈值时先 Rotate() 再写入,实现“每日切割”语义。
- 关键参数与行为
- MaxSize:触发轮转的阈值,单位为MB。
- MaxBackups:最多保留的旧日志数量(按备份序号清理)。
- MaxAge:按天保留的最大期限;通常在新轮转发生时触发过期清理逻辑。
- Compress:是否对旧日志进行 gzip 压缩,开启会消耗 CPU。
- LocalTime:命名与清理时是否使用本地时间(便于阅读与对齐本地策略)。
- 运维与容器场景
- 将日志输出到 stdout/stderr,使用 logrotate 统一管理(如 daily、rotate 7、compress、delaycompress、missingok、notifempty、create 640 root adm),便于集中采集与归档。
- 实践建议
- 在程序退出前调用 Sync()/Flush() 并关闭日志写入器,确保缓冲区落盘与文件句柄正确释放。
- 为日志目录设置合适的权限(如 0755 目录、0644 文件),避免因权限导致写入失败。
- 合理设置 MaxBackups/MaxAge,并监控磁盘使用;压缩建议在 I/O 压力较低的时段或异步进行。