logrotate切割后程序仍往旧inode写,因进程持有原文件描述符未刷新;需通过postrotate发信号(如usr1)或使用copytruncate方案解决。

logrotate 切割后程序继续往旧 inode 写,但文件已被重命名或清空,就会出现写入失败、Permission denied、No space left on device(实际是文件描述符失效)等报错——根本不是权限或磁盘问题,而是日志文件句柄没刷新。
为什么切割后程序还会往“不存在”的文件写?
Linux 进程打开日志文件后,内核给它分配的是一个文件描述符(fd),指向底层 inode。logrotate 默认用 rename 方式移动日志(比如 /var/log/myapp.log → /var/log/myapp.log.1),但进程并不知道这个变化,仍向原 fd 写入——而该 fd 对应的 inode 可能已被清空、删除或权限重置。
- 常见现象:
tail -f /var/log/myapp.log看不到新日志,但ls -li /var/log/myapp.log*显示 inode 已变 - 程序日志里突然出现:
write error: Bad file descriptor或静默丢日志 - 用
lsof -p $PID | grep log能看到进程还持有着已 rename 的旧文件路径(如/var/log/myapp.log.1 (deleted))
copytruncate 是最直接的绕过方案
适用于无法发送信号、无 pid 文件、或自研脚本类程序——它不依赖进程配合,靠复制+清空保底。
Linux 性能分析与调优专家,覆盖 CPU、内存、磁盘 I/O、网络、内核参数、编译优化、容器/K8s。适用场景:系统卡顿/高负载、内存不足/OOM/Swap 高、CPU 异常/iowait 高。
- 在配置中加
copytruncate:logrotate 先cp /var/log/myapp.log /var/log/myapp.log.1,再truncate -s 0 /var/log/myapp.log - 进程完全无感,继续往原路径写,因为文件名和 inode 都没变
- 风险点:复制和清空之间有极短窗口(毫秒级),可能丢失最后一小段日志;不适合超高频写入(如每毫秒写一条)
- 必须搭配
notifempty和missingok,否则空日志或路径错误会导致整个 logrotate 执行中断
postrotate 发送信号才是正解(尤其对 Nginx/Apache/Java 服务)
让程序自己关闭旧 fd、重新 open 新日志文件,彻底避免句柄陈旧问题。
- 典型配置需含
create 644 user group(确保新日志可写) +sharedscripts(防止多文件时重复执行 postrotate) - Nginx 推荐用
nginx -s reopen,比kill -USR1更安全(自动处理权限和路径) - Java 应用若用 Logback,需在
postrotate中发kill -SIGUSR2 $(cat /var/run/myapp.pid)(前提是 Logback 配置了contextListener监听信号) - 如果发信号失败,检查:
/var/run/myapp.pid是否存在、权限是否允许 logrotate 读取、进程是否真由该用户启动(例如 www-data 启动的进程,logrotate 脚本得用su -c切换)
测试配置前务必用 -d 和 -f 组合验证
别等 cron 自动跑,出问题就晚了。
- 先运行
logrotate -d -f /etc/logrotate.d/myapp:只打印动作,不真实改文件,重点看输出里有没有error:行、switching to configuration file是否加载对、rotating pattern是否匹配到目标路径 - 确认无误后,
logrotate -f /etc/logrotate.d/myapp强制执行一次,立刻检查:ls -l /var/log/myapp.log*、tail -n1 /var/log/myapp.log是否有新内容、lsof -p $(cat /var/run/myapp.pid) | grep log是否已指向新文件 - 特别注意:如果用了
create,但新日志属主不是预期用户,大概率是 postrotate 里没切用户,或 create 权限写成了600导致其他用户无法写入
真正容易被忽略的是状态文件更新时机和信号权限链:logrotate 的判断依赖 /var/lib/logrotate/status,而这个文件只在成功轮转后才更新;如果你的 postrotate 脚本因权限失败退出,logrotate 就会认为本次轮转失败,下次 cron 还会重试——但反复失败可能让磁盘悄悄涨满。所以每次上线新配置,都得盯着 status 文件时间戳和实际日志行为是否同步。










