python 模拟 tail -f 需用 open() + seek(0,2) 定位末尾,配合 os.stat() 监控 inode 和大小变化以应对轮转,linux 优先用 inotify,windows 回退轮询,并处理编码、多行日志等边界问题。

Python 中用 open() 配合循环读取文件末尾,再结合 os.stat() 判断文件是否增长,就能可靠模拟 tail -f 行为——但直接 readline() 不停轮询会吃 CPU,必须加 time.sleep() 控制节奏,且得处理文件被轮转(logrotate)的场景。
用 while True + seek() 定位到文件末尾开始监听
核心是先定位到当前文件末尾,之后每次循环只尝试读新行。注意不能一上来就 readline(),否则可能漏掉最后一行未换行的内容:
with open('/var/log/syslog', 'r') as f:
f.seek(0, 2) # 移动到文件末尾
while True:
line = f.readline()
if line:
print(line.rstrip('\n'))
else:
time.sleep(0.1)
-
f.seek(0, 2)是关键,确保从 EOF 开始监听,不是从头读 - 如果日志最后一行没换行符,
readline()会阻塞直到下一行或文件关闭,所以实际中建议用read(1)+ 缓冲拼接更稳妥 - 不要用
for line in f:,它内部缓存行为不可控,且无法响应文件截断或重写
检测文件是否被轮转(inode 变化或文件大小突降)
logrotate 等工具常通过 rename 或 truncate 实现日志轮转,原文件 inode 可能不变但内容清空,也可能新建同名文件(inode 改变)。仅靠文件大小判断不保险,必须同时检查 inode:
stat = os.stat(log_path) ino = stat.st_ino size = stat.st_size # ……循环中再次 stat 对比 ino 和 size
- 若
st_ino改变 → 文件被重建(如mv old new && touch log),需重新open() - 若
st_ino不变但st_size归零 → 被truncate或清空,此时应f.seek(0)重读 - 仅监控
st_size增长不够:轮转后新文件可能比旧文件小,导致误判为“无新日志”
用 select.poll() 替代 sleep 实现更低延迟(Linux only)
在 Linux 上,可以对文件描述符使用 poll() 等待可读事件,避免固定 sleep 的延迟或空转。但注意:普通文件不支持 POLLIN,必须配合 inotify 才行;更现实的做法是用 inotify 库监听文件变更:
- 安装
pip install inotify后,监听IN_MODIFY和IN_MOVE_SELF事件比轮询高效得多 -
IN_MOVE_SELF能捕获文件被 mv 走的瞬间,比单纯比对 inode 更及时 - 但 inotify 不通知“内容追加”,仍需配合
readline()或seek()读取新增部分 - Windows 下无 inotify,只能退回到带 inode 检查的 sleep 轮询
真正稳定的 tail-f 实现,必须同时处理轮转、截断、编码异常(比如日志里混入二进制)、以及多行日志(如 Python traceback)的粘连问题。这些细节不显眼,但线上跑几天后往往就暴露出来。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











