磁盘告警的病例里,日志没做轮转占了绝大多数。一个 Nginx 访问日志每天涨 2G,三十天后就是 60G;一个忘记切割的应用日志,能把 40G 的系统盘直接写满,导致数据库无法写入、服务全部异常。解决这件事的标准答案是 logrotate——几乎所有 Linux 发行版都自带它,由 cron/systemd timer 每天调用一次,按规则把日志切割、压缩、归档、删除。本文讲清 logrotate 的工作机制、配置字段的准确含义(daily/rotate/compress/missingok/notifempty/create 等)、自定义应用日志的完整写法、以及如何验证配置真的生效——毕竟写错了但没报错,是最危险的状态。
一、logrotate 是怎么工作的
logrotate 是"被调用"而非"常驻"的。它的运行方式有两种:
- cron 方式:/etc/cron.daily/logrotate 脚本,由 cron 每天调用(RHEL/CentOS 常见)。
- systemd timer 方式:logrotate.timer + logrotate.service(Ubuntu/Debian 及新版 RHEL 常见),用
systemctl status logrotate.timer查看。
# 确认调度方式 ls -l /etc/cron.daily/logrotate systemctl status logrotate.timer systemctl list-timers | grep logrotate # 主配置文件与目录 cat /etc/logrotate.conf ls /etc/logrotate.d/ # 记录"每个日志上次轮转时间"的状态文件 cat /var/lib/logrotate/logrotate.status # 或老版本路径 cat /var/lib/logrotate.status
主配置文件 /etc/logrotate.conf 里通常有一行 include /etc/logrotate.d,所以自定义日志应该在 /etc/logrotate.d/ 下新建独立文件,而不是去改主配置。这样既清晰,也不会在系统升级时被覆盖。
状态文件很关键:logrotate 判断"今天该不该轮转"依赖它记录的时间戳。如果某个日志从未出现在状态文件里,第一次运行时(在非 -f 模式下)只要满足时间条件就会立即轮转一次。排障时先看这个文件,能确认配置是否被执行过。
二、配置字段逐个解释
一个典型的配置块长这样:
/var/log/nginx/*.log {
daily
rotate 14
compress
delaycompress
missingok
notifempty
sharedscripts
create 0640 nginx adm
postrotate
[ -f /var/run/nginx.pid ] && kill -USR1 $(cat /var/run/nginx.pid)
endscript
}
| 字段 | 含义 | 说明与注意 |
|---|---|---|
| daily / weekly / monthly / yearly | 轮转周期 | 四选一,写多个时只有最后一个生效;daily 表示距上次轮转超过 1 天就切 |
| rotate N | 保留历史文件个数 | rotate 14 保留 14 份,第 15 次轮转时最老的被删除;rotate 0 表示不保留直接删 |
| compress | 压缩历史日志 | 默认用 gzip,生成 .gz;可用 compresscmd 指定其他命令 |
| delaycompress | 延迟一轮再压缩 | 与 compress 配合使用,让刚切出的 .1 文件保持未压缩,因为程序可能还在写它 |
| missingok | 日志文件不存在时不报错 | 不加的话 logrotate 会报错并中断,建议总是加上 |
| notifempty | 空文件不轮转 | 避免产生一堆 0 字节的历史文件 |
| create 0640 nginx adm | 轮转后新建空日志并设定权限属主 | 格式:create 模式 属主 属组;省略参数则沿用原文件属性 |
| copytruncate | 复制后清空原文件,而非改名新建 | 用于不支持信号重开日志的程序;有极小概率丢失复制期间的写入 |
| size 100M | 达到指定大小就轮转 | 与 daily 同时存在时,size 优先级更高(时间条件被忽略) |
| maxsize / minsize | 配合时间周期的大小门槛 | minsize:没到这么大就不轮转(即使时间到了);maxsize:到了就轮转(即使时间没到) |
| dateext | 历史文件用日期而非序号 | 生成 access.log-20240615.gz,便于按日期查找 |
| dateformat | 自定义日期格式 | 如 dateformat -%Y%m%d;注意 %Y 之外的转义 |
| olddir /data/logarchive | 把历史文件移到指定目录 | 需提前创建目录,且与原日志同一文件系统 |
| maxage 30 | 超过 30 天的历史文件删除 | 与 rotate 并存时取更严格者 |
| sharedscripts | 所有日志只执行一次脚本 | 不加的话每个匹配的文件都会跑一遍 postrotate |
| postrotate / endscript | 轮转后执行的命令 | 常用于通知程序重新打开日志文件 |
| su root adm | 以指定用户身份执行轮转 | 日志目录属主非 root 时需要,避免权限错误 |
| errors / mail | 错误报告与邮件通知 | 如 errors root、mail ops@example.com |
两个最容易踩的坑,单独强调:
其一,size 与 daily 的关系。很多资料说"size 优先",准确说法是:如果同时写了 daily 和 size 100M,logrotate 的逻辑是"时间到了 或 大小到了"?——实际是 size 指令会让时间指令失效,只要文件超过 100M 就轮转,不再按天判断。如果你想要"每天轮转,但太小的时候跳过",应该用 daily + minsize 10M;如果想要"每天轮转,但涨太快时提前切",用 daily + maxsize 500M。这三个指令的组合关系是配置正确与否的关键。
其二,create 与 copytruncate 不能同时用。两者是互斥的两套机制:create 模式下 logrotate 把原文件改名(如 access.log → access.log.1),然后新建空 access.log,程序需要重新打开文件句柄(靠 postrotate 发信号);copytruncate 模式下 logrotate 先把内容复制到新文件,再原地清空原文件,程序无感知,不需要发信号。多数现代服务(Nginx、rsyslog、PHP-FPM)都支持信号重开,应优先用 create + postrotate。
三、常见服务的标准配置写法
3.1 Nginx
/var/log/nginx/*.log {
daily
rotate 14
compress
delaycompress
missingok
notifempty
create 0640 nginx adm
sharedscripts
postrotate
if [ -f /var/run/nginx.pid ]; then
kill -USR1 $(cat /var/run/nginx.pid)
fi
endscript
}
USR1 信号对 Nginx 的含义是"重新打开日志文件"。如果用 systemd 管理,也可以写成 /usr/bin/systemctl reload nginx 或 kill -USR1 $(cat /run/nginx.pid)(路径因发行版而异,用 nginx -V 2>&1 | grep pid 确认)。
3.2 自建应用日志(不支持信号重开,用 copytruncate)
/data/app/logs/*.log {
daily
rotate 30
compress
delaycompress
missingok
notifempty
copytruncate
dateext
dateformat -%Y%m%d
maxage 60
su app app
}
3.3 按大小切割的大流量日志
/data/gateway/logs/access.log {
size 500M
rotate 10
compress
delaycompress
missingok
notifempty
copytruncate
dateext
dateformat -%Y%m%d-%H%M%S
}
判断标准:单日日志量超过 500M 时,按天切会产生超大单文件,压缩和检索都困难,改用 size 触发更合理。历史保留量按"磁盘预算 / 单份大小"估算,例如给 /data 留 20G、每份压缩后约 500M,则 rotate 设为 30 左右比较稳妥。
四、验证配置:写完不等于生效
这一步最容易被跳过,也最容易埋雷。logrotate 提供两个调试参数:
# 调试模式:只打印将要做什么,不实际执行(必须先用它) logrotate -d /etc/logrotate.d/nginx # 调试主配置(会 include 所有子配置) logrotate -d /etc/logrotate.conf # 强制立即执行一次(即使时间/大小条件未满足) logrotate -f /etc/logrotate.d/nginx # 强制并显示详细过程 logrotate -vf /etc/logrotate.conf # 只测试某一个配置块,注意要带主配置 logrotate -d /etc/logrotate.conf --state /tmp/test.state
怎么读 logrotate -d 的输出:它会逐条打印 "considering log /var/log/nginx/access.log"、"log does not need rotating"(未满足条件)、"renaming /var/log/nginx/access.log to /var/log/nginx/access.log.1"、"running postrotate script" 等。如果看到 "error: /etc/logrotate.d/xxx:1 unknown option" 说明字段拼错了;如果看到 "parent directory has insecure permissions" 说明日志所在目录权限过松,需要修正目录属主或加 su 指令。
验证的完整流程:
- 写配置 →
logrotate -d 配置文件检查语法与预期动作。 logrotate -f 配置文件强制执行一次。ls -lh 日志目录确认生成了 .1 或带日期的归档文件,且新的空日志已创建。- 检查新日志的权限属主是否与 create 指令一致。
- 触发一条业务日志(如访问一次网站),确认新日志确实在被写入——这一步能验证 postrotate 信号是否生效。
- 查看状态文件确认时间戳已更新:
grep 日志路径 /var/lib/logrotate/logrotate.status。
第 5 步最关键。很多配置切完文件看起来都对,但因为 postrotate 信号没发成功,程序仍持有旧文件句柄,日志继续写进已改名的 access.log.1,导致"日志停更但磁盘还在涨"。检查方法:lsof | grep 日志名 看进程打开的是哪个文件。
五、与其他日志机制的关系
| 日志类型 | 轮转方式 | 配置文件 | 注意事项 |
|---|---|---|---|
| 传统文本日志 | logrotate | /etc/logrotate.d/* | 本文主题,需 postrotate 通知程序 |
| systemd journald | journald 自带配额 | /etc/systemd/journald.conf | SystemMaxUse、MaxRetentionSec;journalctl --vacuum-size 手动清理 |
| rsyslog | logrotate(由 rsyslog 软件包提供配置) | /etc/logrotate.d/rsyslog | postrotate 里用 systemctl kill -s HUP rsyslogd |
| Docker 容器日志 | Docker 自身日志驱动 | /etc/docker/daemon.json | log-opts max-size / max-file,logrotate 对容器内日志无效 |
| 应用内轮转(如 logback) | 应用框架自带 | 应用配置 | 若应用已在轮转,logrotate 要设为不处理,避免双重切割 |
| MySQL 慢日志/错误日志 | logrotate + flush-logs | /etc/logrotate.d/mysql | 需 mysqladmin flush-logs 或发信号 |
判断标准:如果应用框架(Java logback、Python logging.handlers.RotatingFileHandler)已经自带按大小轮转,就不要再给同一批文件配 logrotate,否则会出现 .log.1.1 这种奇怪文件名,且难以预测保留份数。
六、落地步骤清单
- 盘点日志:
du -sh /var/log/* /data/*/logs 2>/dev/null | sort -rh | head -20,列出所有日志目录及日增长量。 - 测量增长率:连续两天记录同一日志大小,算出日均增量,作为设置 size 与 rotate 的依据。
- 确定保留策略:按合规要求(常见 30-180 天)与磁盘预算共同决定 rotate 值。
- 选择机制:程序支持信号重开 → create + postrotate;不支持 → copytruncate。
- 写配置文件:在 /etc/logrotate.d/ 下建独立文件,文件名与业务名一致。
- 调试验证:先
logrotate -d,再logrotate -f,最后确认新日志确实在被写入。 - 确认调度存在:检查 /etc/cron.daily/logrotate 或 logrotate.timer 是否启用。
- 纳入监控:对日志目录大小设置告警(建议 70% 预警),并定期抽检归档文件是否生成。
- 定期复盘:每季度检查一次状态文件与归档数量,确认长期运行下没有悄悄失效。
小结
防止日志占满磁盘,靠的是"机制"而不是"勤快"。logrotate 的配置核心就那十几个字段,真正决定成败的是三个细节:周期与 size/minsize/maxsize 的组合关系要写对、create 与 copytruncate 二选一不可混用、写完必须用 -d 调试、-f 强制验证,并确认新日志确实在被写入。再配合 journald 配额、Docker 日志限制与磁盘告警,就能把"磁盘被日志写满"这类事故从常见故障清单里彻底划掉。
避坑清单
- 写了配置不代表会执行,必须确认 cron.daily 或 logrotate.timer 存在且启用。
- postrotate 里的命令写错不会让轮转失败,只会静默失效,务必用 -d 和触发日志来验证。
- dateext 与 delaycompress 同时用时,延迟压缩的判定基于文件名后缀,配置前先 -d 看一遍预期文件名。
- copytruncate 期间写入可能丢失,对日志完整性要求高的场景优先用信号重开。
- 通配符 *.log 会匹配到已轮转的文件:access.log.1 不匹配 *.log,但 access.log.1.gz 也不匹配,通常安全;不过
*.log*会造成重复轮转,应避免。 - 日志目录权限过松会报 insecure permissions,修正目录属主或在配置里加
su 属主 属组。 - 不要同时用应用内轮转和 logrotate,二选一。
- olddir 必须与源日志在同一文件系统,否则移动会失败(跨设备需复制+删除)。
- rotate 0 不是"不轮转"而是"轮转后立刻删除",想要"永不删除"要设一个很大的 rotate 值并配合 maxage。
企业QQ咨询




