WatchedFileHandler通过定期检查文件mtime判断外部轮转,仅在mtime变化且原文件句柄失效时重开;默认每秒stat一次baseFilename,不监听目录或内容追加,常见触发场景为mv+touch或logrotate copytruncate。

WatchedFileHandler 的自动重载机制依赖于文件 mtime 变化
logging.handlers.WatchedFileHandler 并不监听文件内容变更,而是通过定期检查日志文件的 mtime(最后修改时间)来判断是否被外部轮转。一旦发现 mtime 变更(比如 logrotate 重命名或覆盖原文件),它会在下一次 emit() 时尝试重新打开文件句柄。
- 默认每 1 秒检查一次 —— 这个间隔不可配置,硬编码在
watched_file_handler.py中(CPython 3.9+) - 只对当前打开的
self.baseFilename做 stat 检查,不监控目录或通配符 - 若外部操作只是追加(如
echo "x" >> app.log),mtime会变,但 handler 不会重开 —— 它只在mtime变化且当前文件句柄已失效(如被 unlink)时才触发 reopen;单纯追加不会导致句柄失效,因此通常不重载 - 常见有效触发场景:日志被
mv app.log app.log.1+touch app.log,或logrotate的copytruncate模式外加写入
为什么有时看起来“没重载”?常见失效原因
不是 handler 失效,而是外部操作未满足它的 reopen 条件:
- 你用
echo "msg" > app.log覆盖文件 →mtime变,但原 fd 仍可写(Linux 允许),handler 不认为需要 reopen - 你用
truncate -s 0 app.log→mtime变,fd 仍有效,同样不 reopen - 你在容器中运行,挂载卷导致
stat返回缓存值(如某些 NFS 或 Docker bind mount 配置),mtime看似没变 - Python 进程以非 root 用户运行,但外部轮转脚本用 root 创建新文件 → 新文件权限为
rw-r--r--,而进程无写权限,reopen 失败且静默吞掉异常(仅在logging.raiseExceptions=True时抛出)
如何验证 WatchedFileHandler 是否正常工作
最直接的方式是模拟 logrotate 行为并观察 handler 是否新建文件:
# 终端 1:启动 Python 日志写入
python3 -c "
import logging
from logging.handlers import WatchedFileHandler
h = WatchedFileHandler('test.log')
h.setFormatter(logging.Formatter('%(message)s'))
l = logging.getLogger()
l.setLevel(logging.INFO)
l.addHandler(h)
for i in range(5): l.info(f'line {i}'); __import__('time').sleep(1)
"终端 2:在日志运行中执行
mv test.log test.log.bak && touch test.log
几秒后,test.log 应开始接收新日志(旧日志留在 .bak)。若没发生,检查:ls -l test.log 权限、strace -e trace=openat,stat python3 ... 是否有 stat 调用、以及 logging.raiseExceptions = True 是否开启以便捕获 reopen 异常。
替代方案:需要强触发重载时该怎么做
如果业务要求「任意外部修改都立即响应」,WatchedFileHandler 不够用,得自己控制:
- 改用
RotatingFileHandler+ 外部信号(如SIGHUP)手动调用doRollover() - 子类化
WatchedFileHandler,重写emit(),在每次写前强制os.stat()并比对 inode(绕过 mtime 陷阱) - 完全放弃自动检测,由运维通过
kill -SIGHUP $(pidof python)触发自定义 reload 逻辑 —— 这是最可控的方式
注意:WatchedFileHandler 的设计初衷是配合标准轮转工具(如 logrotate),不是通用文件变更监听器。依赖它做精细控制,容易掉进 stat 语义和平台行为差异的坑里。










