logrotatelogs比手写压缩更可靠,因其已完备处理轮转边界条件:文件重命名竞态、sigusr1原子切换、压缩失败fallback;而手写compresslogfile易并发删错文件或panic致原文件丢失。

logrotatelogs 为什么比手写压缩逻辑更可靠
因为 logrotatelogs 已经把日志轮转的边界条件全兜住了:文件重命名时的竞态、SIGUSR1 信号触发的原子切换、压缩失败时的 fallback 机制。你自己用 compress/gzip 写一个 compressLogFile 函数,很容易在并发写日志时删错文件,或者压缩中途 panic 导致原文件丢失。
它默认使用 gzip 压缩,但关键不是“怎么压”,而是“什么时候压、压哪个、压完怎么清理”。比如轮转时生成 app.log.1,你得确保此时 app.log 已完全 close,且 app.log.1 不再被任何 goroutine 写入——logrotatelogs 内部用 os.Rename + 文件锁做了这件事,而手写代码常直接 os.Remove 原文件,风险极高。
- 它支持按大小(
MaxSize)、时间(MaxAge)、备份数(MaxBackups)三重策略组合判断是否该轮转 -
Compress: true会自动对.1、.2等旧文件生成.gz,且只压缩非当前活跃文件 - 压缩失败时不会中断轮转流程,旧文件保留未压缩状态,避免丢日志
lumberjack.v2 的 Compress 参数实际生效条件
Compress: true 不是“每次轮转都立即压缩”,而是:只有当旧日志文件满足 MaxAge 或 MaxBackups 规则被归档时,才会触发 gzip 压缩。也就是说,刚轮转出来的 app.log.1 是明文,等它变成 app.log.2 或超过 30 天后,才被压缩为 app.log.2.gz。
这个设计是为了避免在高频写入场景下,压缩操作阻塞 I/O。如果你希望“轮转即压缩”,得自己 hook Rotate 方法,但要注意:lumberjack 的 rotate 是同步阻塞的,加压缩逻辑会拖慢主日志写入路径。
- 压缩使用
gzip.BestSpeed级别,不设gzip.DefaultCompression—— 因为默认级别在高吞吐下 CPU 占用突增 - 压缩失败时会 log.Warn,但不会 panic,也不会影响后续轮转
- 压缩后的文件权限继承源文件,不会额外 chmod;若需自定义(如 0600),得在
PostRotate回调里手动处理
用 compress/gzip 手动压缩时最容易漏掉的 Close 调用
这是生产环境最常踩的坑:gzip.Writer 必须显式调用 Close(),否则生成的 .gz 文件无法解压,gunzip -t 报 unexpected end of file。原因在于 gzip 尾部的 CRC 校验和原始长度信息,只在 Close() 时写入。
常见错误写法:defer gz.Close() 放在函数开头,看似稳妥,但如果压缩过程 panic,defer 确实会执行;但更隐蔽的问题是——如果压缩逻辑被封装进 goroutine,defer 在 goroutine 返回时才触发,而主流程可能已继续执行并尝试读取未完成的 .gz 文件。
- 正确姿势:压缩写入完成后,立刻调用
gz.Close(),再调用dstFile.Close() - 不要依赖
defer处理关键资源释放,尤其涉及文件落盘顺序时 - 压缩前先
os.Stat检查源文件是否存在且非空,避免对空文件压缩产生无效 .gz
归档目录结构设计要避开时间戳歧义
用 time.Now().Format("2006-01-02") 生成归档子目录名很常见,但要注意:如果日志轮转发生在 UTC+0 和 UTC+8 交界时刻(比如北京时间 00:00),而你的服务器时区没设好,可能把昨天的日志塞进今天的目录,或反之。
更稳妥的方式是用日志文件自身的 mtime 判断归属日期,而不是用当前时间。例如遍历 /var/log/myapp/ 下所有 *.log,对每个文件调用 os.Stat,提取 ModTime().In(loc).Date() 得到本地日期,再 mkdir 并 mv。
- 绝对不要用
time.Now().Unix()命名归档文件,秒级时间戳在高并发下极易冲突 - 归档路径建议包含服务名+日期+序列号,如
myapp-2026-06-15-001.gz,避免同一天多个压缩任务覆盖 - 归档前先
os.Rename到临时路径,再os.Rename到目标归档路径,保证原子性
Close() 调用时机、时区处理、文件锁竞争——在持续运行数月的服务里,都会变成凌晨三点的告警。golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











