Watch at Ease看见世界,读懂生活。

科技 · 实用指南

Python 日志轮转丢行?验证 inode 切换与记录并集

只保留一个轮转所有者,对比打开描述符和路径身份,并验证轮转文件与活动文件中的编号记录恰好出现一次。

原创示意图:对比 rename-create 前后的日志路径与打开描述符设备号和 inode。
原创示意图:首条轮转后记录写入后,描述符必须切换到新路径 inode。 Watch at Ease

logrotate 成功退出且出现新的 app.log,并不能证明 Python 进程已停止向旧 inode 写入。WatchedFileHandler 会在类 Unix 系统的每次 emit 前检查设备号和 inode,因此必须从描述符、路径和记录三个层面验证。

只使用一个轮转所有者:外部 rename-create 配合 WatchedFileHandler,或只使用一个内部轮转处理器,不能叠加。围绕边界给记录编号,旧文件与新文件必须组成无缺口、无重复的精确序列。

1. 只选择一个轮转所有者

若 logrotate 会重命名 app.log 并创建新文件,就只给 logger 配置一个 WatchedFileHandler;不要再叠加 RotatingFileHandler 或 TimedRotatingFileHandler。

Windows 或设备号、inode 语义不同的文件系统不能直接套用;必须在真实挂载点和服务账号下实测。

2. 记录描述符和路径身份

轮转前先 flush,分别记录 os.fstat(handler.stream.fileno()) 与 os.stat(log_path) 的设备号和 inode;两者应指向同一对象,同时记录目录权限与新文件所有者。

这能区分旧描述符和不可访问的新路径。CPython 公开问题表明,目标目录消失时重新打开可能失败。

3. 重命名并创建后写入一条

把 app.log 重命名为 app.log.1,在下一次写入前按审核后的所有者和权限创建新的 app.log,并保留轮转文件直到审计结束。

写入一条编号哨兵并 flush;只有新路径 inode 等于处理器描述符 inode,且轮转文件保持旧 inode,才通过这一层。

4. 验证每条编号记录恰好一次

轮转前写 seq-001 至 seq-003,轮转后写 seq-004 至 seq-006,完整读取两个文件。并集必须等于预期集合,每个值只出现一次,且不得出现未知值。

本地 CPython 3.12.14 夹具中,前三条位于旧 inode,后三条位于新 inode;缺失和重复集合为空,轮转后描述符与新路径 inode 一致。

5. 拒绝掩盖边界的捷径

不要把 copytruncate 当成无损等价方案;logrotate 明确警告复制与截断之间可能丢数据。若为兼容性使用,必须单独接受并测试该风险。

不要只比总行数;一条缺失加一条重复仍可得到相同行数,也不要仅凭新路径存在判断成功。

6. 记录结论边界

该测试只证明指定 Python 版本、文件系统、服务账号和配置下观察到的边界,不证明未来轮转、远程文件系统、容器卷或 Windows。

更改 Python、处理器、轮转策略、挂载、权限或账号后都要重测,并监控重新打开异常和描述符路径不匹配。

原创示意图:前三条记录在轮转文件,后三条在新活动文件。
原创示意图:只有编号并集无缺失、无重复时才接受轮转。 Watch at Ease