lumberjack.v2 的 Compress 参数在旧文件被归档时(如变为 .2 或超 MaxAge)才生效,而非轮转即压;它不压缩当前活跃日志,压缩失败仅 warn 不中断,使用 gzip.BestSpeed 且需避免手写压缩的三大坑。

lumberjack.v2 的 Compress 参数到底什么时候生效
它不是“每次轮转完立刻压缩”,而是等旧文件被归档时才触发。比如你配置了 MaxBackups: 5 和 Compress: true,那么 app.log.1 刚轮转出来是明文;只有当它变成 app.log.2(即新轮转挤掉它一次),或超过 MaxAge 时限后,才会被 gzip 压缩为 app.log.2.gz。
这个延迟压缩的设计是为了避免在高并发写日志时,压缩操作阻塞主 I/O 路径。如果你硬要“轮转即压”,得自己实现 HookRotate 回调,但要注意:lumberjack 的 rotate 是同步的,加压缩逻辑会拖慢所有日志写入。
-
Compress: true只对非当前活跃文件生效(即不压app.log) - 压缩失败时只打
log.Warn,不会 panic,也不会中断后续轮转 - 压缩用的是
gzip.BestSpeed,不是DefaultCompression——后者在吞吐高时容易 CPU 突增
手写 compressLogFile 最容易踩的三个坑
自己用 compress/gzip 写压缩函数看似简单,但生产环境出问题基本都集中在这几个点:
- 忘了显式调用
gz.Close():gzip.Writer必须Close()才能写入尾部 CRC 和长度信息,否则生成的.gz文件用gunzip -t会报unexpected end of file - 在 goroutine 里 defer
gz.Close():defer 绑定的是 goroutine 启动时的变量作用域,若该 goroutine 没跑完就退出,Close()可能根本没执行 - 没检查原文件是否还在被写入:直接
os.Remove或重命名前没确认lumberjack已关闭该文件句柄,会导致日志丢失或 panic
为什么推荐用 logrotatelogs 而不是自己封装
因为边界条件全被兜住了,不是“能不能压”,而是“压哪个、什么时候压、压失败了怎么办”。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
立即学习“go语言免费学习笔记(深入)”;
logrotatelogs 内部用 os.Rename + 文件锁确保重命名无竞态;用 SIGUSR1 实现原子切换;压缩失败自动 fallback 到保留明文状态,绝不停止轮转。你自己写 compressLogFile 很难覆盖这些细节。
- 它默认只对
.1、.2等已归档文件做压缩,不碰正在写的app.log - 压缩失败不中断流程,避免丢日志
- 压缩后文件权限继承源文件,无需额外
chmod(如需自定义,得在PostRotate里手动处理)
和 zap / logrus 配合时的常见错配
别把 lumberjack.Logger 直接塞进 zapcore.NewMultiWriteSyncer 就完事——注意写入器是否线程安全,以及是否混淆了输出目标。
- 控制台输出(
os.Stdout)和文件归档(lumberjack.Logger)必须分开走两个独立 write syncer,不能混进同一个MultiWriteSyncer - 如果同时往
os.Stderr和 lumberjack 写,错误日志会被归档,这通常不符合运维预期 -
Compress: true在zapcore.AddSync(&lumberjack.Logger{...})中才有效;若用io.MultiWriter包裹 lumberjack 实例,压缩逻辑会失效
lumberjack 和 logrotatelogs 都已沉淀成稳定行为,手写反而容易漏掉某一个环节。

















