4、日志轮换实战:按大小轮换、按时间轮换、压缩与归档、保留策略

日志轮换,说白了就是给日志文件“计划生育”。

服务器跑久了,日志文件能大到吓人。我见过最夸张的一次,一个没配轮换的 Tomcat 应用,catalina.out 撑到了 80 多 G,直接把磁盘塞爆,服务全挂了。从那以后,我接手任何系统,第一件事就是检查日志轮换配了没。

这一章,咱们把 logrotate 的四个核心能力拆开揉碎:按大小切、按时间切、压缩归档、保留几份。你跟着走一遍,以后配轮换脚本心里就有底了。

核心原则:日志轮换不是备份,是“切割 + 清理”。目的是让日志文件保持可控大小,同时保留最近一段时间或一定数量的历史记录,方便排查问题。

日志轮换核心知识体系 logrotate 引擎 按大小轮换 按时间轮换 压缩与归档 保留策略 size 指令 daily / weekly gzip / tar rotate 指令 maxsize 指令 dateext 指令 delaycompress maxage 指令 四种策略可组合使用,生产环境通常同时配置大小和时间双重触发

4.1 按大小轮换:logrotate 的“体重秤”

按大小轮换,就是给日志文件设一个“体重上限”。超过这个值,logrotate 就把它切掉。

我个人习惯用 size 指令,单位支持 K、M、G。比如 size 100M,表示日志超过 100MB 就轮换。

小技巧:还有一个 maxsize 指令,它和 size 的区别在于:size 是“超过就切”,maxsize 是“在时间轮换周期内,如果超过这个大小,也触发轮换”。说白了,maxsize 是给时间轮换加了一道“保险”。

来看一个典型的按大小轮换配置:

/var/log/myapp/*.log {
    size 100M
    rotate 5
    compress
    missingok
    notifempty
    copytruncate
}

这里我解释几个关键点:

  • size 100M:日志文件超过 100MB 就触发轮换。
  • rotate 5:保留最近 5 份历史日志。
  • compress:轮换后自动用 gzip 压缩。
  • copytruncate:这个指令很实用。它先复制日志内容到新文件,然后清空原文件。这样应用程序不需要重启,因为文件句柄没变。我建议在 Nginx、Tomcat 这类不自动 reopen 日志的应用上一定要加这个。

注意:copytruncate 有一个小坑——在复制和截断之间,可能会有少量日志丢失。不过对于大多数场景,这点丢失可以接受。如果你要求零丢失,那就用 create 指令配合应用自身的日志重载信号。

4.2 按时间轮换:每日/每周的“闹钟”

按时间轮换,就是定好时间点,到点就切。最常用的是 daily 和 weekly。

你想想看,为什么需要按时间轮换?

  • 排查方便:出问题时,你直接说“看昨天的日志”,而不是“看第 7 个轮换文件”。
  • 规律性强:每天凌晨切一次,日志文件大小基本稳定,磁盘占用可预测。
  • 配合监控:很多监控系统按天拉取日志,时间轮换天然对齐。

每日轮换的经典配置:

/var/log/nginx/access.log {
    daily
    rotate 30
    compress
    delaycompress
    dateext
    dateformat -%Y%m%d
    missingok
    notifempty
    postrotate
        /usr/sbin/nginx -s reopen
    endscript
}

这里有几个我特别想强调的点:

  • delaycompress:延迟压缩。轮换当天不压缩,第二天再压缩。为什么?因为运维同学早上排查问题时,可能还需要看昨天的日志,压缩了还得解压,麻烦。我踩过这个坑,后来就一直用 delaycompress。
  • dateext:用日期作为轮换文件的后缀,而不是默认的数字序号。比如 access.log-20250410,一眼就知道是哪天的日志。
  • postrotate:轮换后执行脚本。这里给 Nginx 发 reopen 信号,让它重新打开日志文件句柄。如果不做这一步,Nginx 会继续往旧文件里写。

每周轮换类似,把 daily 换成 weekly 就行。我个人习惯在日志量不大的系统上用 weekly,比如一些内部管理工具。

4.3 压缩与归档:gzip 和 tar 的取舍

日志轮换后不压缩,那磁盘还是扛不住。logrotate 默认用 gzip 压缩,效果不错。

压缩方式 压缩比 速度 适用场景
gzip(默认) 中等(约 70-80%) 快 日常日志轮换,最常用
bzip2 高(约 80-90%) 慢 归档日志,不常查看
xz 极高(约 90-95%) 很慢 长期冷存储
tar(无压缩) 无 极快 需要保留原始格式,或后续统一压缩

logrotate 默认用 gzip,你如果想换,用 compresscmd 指定:

/var/log/myapp/*.log {
    weekly
    rotate 12
    compress
    compresscmd /usr/bin/bzip2
    compressext .bz2
}

至于 tar 归档,logrotate 本身不直接支持 tar 打包。我一般这样处理:

  • 先用 logrotate 做轮换和 gzip 压缩。
  • 再用一个 cron 脚本,把超过 7 天的压缩日志打包成 tar 归档,然后删除原文件。

举个例子:

#!/bin/bash
# 每周日打包上周的日志
cd /var/log/myapp
find . -name "*.gz" -mtime +7 -exec tar -rvf weekly_archive.tar {} \;
find . -name "*.gz" -mtime +7 -delete

避坑指南:我曾经在压缩日志时,忘记考虑压缩后的文件名变化。logrotate 默认把 access.log.1 压缩成 access.log.1.gz,但如果你用了 dateext,文件名是 access.log-20250410.gz。写脚本处理时,一定要确认命名规则。

4.4 保留策略:留多少份才够?

保留策略,就是决定“历史日志留多久”。logrotate 用 rotate 指令控制保留份数。

比如 rotate 7,表示保留最近 7 份轮换后的日志。第 8 份生成时,最老的那份会被删除。

除了份数,还有时间维度的保留:

  • rotate N:保留 N 份轮换文件。
  • maxage DAYS:保留最近 N 天的日志,超过天数的直接删。

举个例子,我想保留最近 30 天的日志,但每天可能轮换多次(按大小触发),用 rotate 不好算。这时候用 maxage 更合适:

/var/log/myapp/*.log {
    size 50M
    maxage 30
    compress
    missingok
    notifempty
    copytruncate
}

这样,不管一天轮换多少次,超过 30 天的日志都会被清理。

我的建议:生产环境,按时间轮换时,rotate 值设为“保留天数 + 1”。比如保留 7 天,就设 rotate 7。按大小轮换时,根据磁盘空间和单文件大小估算。比如磁盘有 10G 空间,单文件 100M,那 rotate 100 以内都安全。留点余量,别算太满。

4.5 综合实战:一个完整的轮换脚本

最后,咱们把上面这些知识点串起来,写一个生产级别的轮换配置。这个配置我用了好几年,在几十台服务器上跑过,没出过问题。

/var/log/webapp/*.log {
    # 同时按大小和时间触发
    daily
    maxsize 200M
    
    # 保留最近 30 份
    rotate 30
    
    # 压缩配置
    compress
    delaycompress
    
    # 文件名用日期
    dateext
    dateformat -%Y%m%d
    extension .log
    
    # 安全选项
    missingok
    notifempty
    copytruncate
    
    # 轮换后执行
    postrotate
        # 给 Java 应用发信号,让它重新打开日志
        kill -USR1 $(cat /var/run/webapp.pid 2>/dev/null) 2>/dev/null || true
    endscript
}

这个配置做到了:

  • 每天轮换一次,但如果某天日志量暴增超过 200M,也会触发轮换。
  • 保留 30 份,也就是大约一个月的日志。
  • 当天日志不压缩,方便排查;第二天自动压缩。
  • 文件名带日期,一眼可读。
  • 轮换后通知应用重新打开日志句柄。

最后提醒一句:配好轮换后,一定要手动跑一次 logrotate -d /etc/logrotate.d/yourconfig 做调试。 -d 是 debug 模式,只打印执行计划,不实际执行。我见过太多人配完直接上生产,结果轮换没生效,日志把磁盘撑爆了。别问我怎么知道的。