logrotate 本身不丢数据,但服务进程若未及时切换日志文件,会继续向已重命名的旧文件(如 access.log.1)写入,导致新日志文件为空;必须通过 postrotate 发送 usr1/sighup 等信号,促使服务重新打开新日志文件,否则因 inode 句柄未更新而造成“日志丢失”假象。

logrotate 本身不会丢数据,但服务进程若没及时切换到新日志文件,就会继续往旧文件(已被重命名)里写——你看到的“最新日志丢失”,其实是写进了 access.log.1 这类归档文件里,而 access.log 反而空了。
为什么 postrotate 里发信号是必须步骤
Linux 文件系统靠 inode 记录写入位置。logrotate 只是把 access.log 重命名为 access.log.1,原进程仍持有该 inode 的句柄,所以继续往里面追加内容。只有服务收到信号(如 USR1 或 SIGHUP),主动调用 fopen() 重新打开 access.log,才会切换到新建的、空的 inode 上。
- nginx 必须用
kill -USR1 `cat /var/run/nginx.pid`,不是HUP - systemd 服务(如
rsyslog、auditd)优先用systemctl kill --signal=SIGUSR1 service-name,查文档确认支持的信号 - 老版本或自研服务不支持重载?那就得在
postrotate里用systemctl restart service-name,但会中断服务几秒 - 所有
kill或systemctl命令后面务必加|| true,否则某次失败会导致整个轮转流程中断,后续清理、压缩全跳过
如何验证信号是否真正生效
光看配置没用,得确认服务确实响应了。最直接的方法是检查日志文件的 inode 是否变化:
- 轮转前:
ls -i /var/log/nginx/access.log记下 inode 编号 - 执行一次强制轮转:
sudo logrotate -f /etc/logrotate.d/nginx - 立刻再查:
ls -i /var/log/nginx/access.log—— 如果 inode 号变了,说明服务已重新打开新文件;如果没变,就是信号没发成功或服务没响应 - 同时用
lsof -p $(cat /var/run/nginx.pid) | grep access.log看进程还持有哪些日志文件句柄
size +50M 和 maxsize 100M 的行为差异很关键
这两个参数都按大小触发,但判断逻辑完全不同,直接影响“最后一刻”是否丢数据:
-
maxsize 100M:每次 logrotate 执行时,只要当前access.log大小 ≥ 100MB,就立即轮转——适合流量不可预测的生产环境,能防突发写入撑爆磁盘 -
size +50M:只对比本次与上一次轮转后新增的字节数,即“上次轮转后写了 50MB 就切”。但如果上次轮转失败或被跳过,这个增量可能永远算不准,导致该切不切 - 更稳妥的做法是两者共存:
daily maxsize 100M,任一条件满足即触发,兼顾周期性和突发性 - 注意:
maxsize在 cron 每日执行的上下文中才有效;如果你手动-f强制运行,它也会立刻生效
最容易被忽略的是:logrotate 不保证原子性切换。哪怕信号发了、inode 切了,服务在重开文件和写入第一条日志之间仍有极短空窗——高并发场景下,这几十毫秒可能漏掉几条请求日志。真要零丢失,得用 syslog-ng 或 rsyslog 配合 imfile 模块做缓冲,而不是依赖 logrotate 单点控制。











