不可靠,因轮询易卡半行、漏更新且无法感知文件轮转;需结合os.stat()监测inode与size变化,并重置偏移量以应对截断或重建。

用 time.sleep() 轮询读取文件末尾是否可靠
不可靠,但简单场景下够用。关键问题是:频繁调用 os.stat() 或反复 seek() 会浪费 CPU;更严重的是,如果日志写入不带换行(比如程序用 print(..., end='') 或直接 write()),轮询可能卡在半行位置,导致输出错乱或阻塞。
实操建议:
立即学习“Python免费学习笔记(深入)”;
- 每次读取前先
f.seek(0, 2)定位到文件末尾,再尝试f.readline()—— 如果返回空字符串,说明没新行,time.sleep(0.1)后重试 - 必须用
open(..., buffering=1)(行缓冲)或buffering=0(无缓冲)打开文件,否则readline()可能因内部缓冲卡住 - 注意文件被 logrotate 切割的情况:原文件句柄仍有效,但新日志写入新文件,轮询会“失联”——需额外检查
os.path.samefile()
为什么 watchdog 库不适合纯 tail -f 场景
watchdog 监听的是文件系统事件(如 ModifiedEvent),但它不保证事件触发时内容已刷盘,也不提供“从哪一行开始读”的上下文。日志库(如 logging)常批量写入、延迟 flush,导致事件来了却读不到完整行。
实操建议:
立即学习“Python免费学习笔记(深入)”;
- 仅用
watchdog检测文件是否被重命名/重建(例如 logrotate 后的access.log.1→access.log),检测到后关闭旧句柄、重新open()新文件 - 不要依赖
watchdog的ModifiedEvent来触发readline()—— 它和轮询一样要配合主动读取逻辑 - 若用
inotify(Linux)底层,也需注意:IN_MODIFY 事件频发且无内容边界,仍得自己维护读取位置
如何正确处理日志文件被 truncate 或 rotate 的情况
核心是区分“文件内容追加”和“文件被覆盖/重置”。os.stat().st_size 突然变小,或 inode 改变,都是危险信号。
实操建议:
立即学习“Python免费学习笔记(深入)”;
- 记录初始
os.stat().st_ino和st_size;每次循环前重新 stat,若st_ino不同,说明文件被重建(如cp /dev/null access.log或 logrotate) - 若
st_size < current_offset,说明被 truncate,应重置offset = 0并清空已读缓存(避免漏掉开头新内容) - 避免用
file.tell()作为唯一偏移依据:truncate 后tell()仍指向原位置,但该位置已无效
有没有现成的轻量级方案可直接用
有,pytail 太老且不维护;推荐直接用 tailer 库(pip install tailer),它封装了上述所有边界逻辑,且默认处理换行、truncate、rotate。
示例用法:
import tailer
for line in tailer.follow(open('/var/log/syslog')):
print(line.strip())
注意点:
-
tailer.follow()返回生成器,内部已做sleep+stat+seek组合,无需额外线程 - 若需实时性极高(毫秒级),它仍基于轮询,无法替代
inotifywait -m -e modify+ 解析 stdout 的方案 - Windows 上部分日志轮转工具(如
logrotate替代品)可能不触发标准事件,此时仍要 fallback 到 size/inode 检查
真正难的不是“怎么读新行”,而是判断“这行到底算不算新”——inode、size、mtime、内容校验,任何一个维度出偏差,tail 就会丢数据或重复吐。别图省事跳过 inode 检查。


















