实时监控日志需用open('app.log','r')后seek(0,2)定位到文件末尾,避免重复读旧日志;须检测inode变化应对轮转,显式指定encoding防编码错误,并用time.sleep(0.1)轮询readline()防CPU空转。

用 open() 配合 seek(0, 2) 定位到文件末尾
日志文件是追加写入的,实时监控必须从当前末尾开始读,而不是每次从头读——否则会重复刷旧日志、拖慢性能甚至 OOM。直接 open('app.log', 'r') 然后 readline() 会卡在开头,必须手动跳转。
-
seek(0, 2)表示“从文件末尾偏移 0 字节”,即定位到 EOF;seek(0, 0)是开头,seek(0, 1)在部分系统不可靠,别用 - 打开时务必用
'r'模式(不是'rb'),否则文本换行符解析可能错乱;若日志含非 UTF-8 编码(如 GBK),需显式传encoding='gbk' - 注意:如果文件被轮转(logrotate)或清空(
> app.log),seek(0, 2)后的文件描述符仍指向原 inode,会漏掉新内容——这是最常踩的坑
用 while True + readline() 轮询新行
没有操作系统级事件通知时,轮询是最简单可靠的方案。关键不是“要不要轮询”,而是“怎么轮询不伤 CPU、不丢行”。
- 每次调用
file.readline(),有新行就立刻返回;没新行就立即返回空字符串,不会阻塞——所以必须配合time.sleep(0.1)防止空转吃满 CPU - 不要用
readlines()或read(),它们会一次性加载整块内容,遇到大日志或高频写入极易卡住或内存暴涨 - 如果日志行特别长(比如带 base64 的 trace),
readline()仍安全;但要留意单行超 128KB 时某些 Python 版本可能截断(罕见,但生产环境见过)
处理日志轮转:检查 os.stat().st_ino 和文件大小变化
轮转后旧文件被重命名(app.log.1),新日志写入空的 app.log,但你的 file 对象还锁着旧 inode,新内容永远读不到。
- 每轮循环用
os.stat(filepath).st_ino对比当前文件 inode 是否变化;变了说明文件被重建,需关闭旧句柄、重新open() - 仅靠文件大小回退(比如从 10MB 变成 0)不可靠:有些轮转策略是先拷贝再清空,大小可能短暂为 0 但 inode 没变;有些是 rename,inode 不变但名字变
- 补充检查
os.path.exists()防删库跑路:万一运维手抖rm app.log,程序不能崩,应 log 警告并 sleep 后重试
用 select.poll() 替代轮询(Linux/macOS)
想彻底避开 sleep 和 CPU 空转?select.poll() 可让内核通知你“文件可读了”,但只适用于真实文件描述符,且 Windows 不支持。
立即学习“Python免费学习笔记(深入)”;
- 先
fd = file.fileno()拿到底层 fd,再p = select.poll(); p.register(fd, select.POLLIN) - 调用
p.poll(timeout_ms)会阻塞直到有新数据或超时,返回非空列表才readline();超时设 500 即可,比 0.1s sleep 更省资源 - 注意:
POLLIN对普通文件**在 Linux 上始终就绪**(因为文件永远“可读”),所以它只对 FIFO、socket、tty 有效;监控磁盘日志文件时,poll()实际退化为定时唤醒,和 sleep 差不多——别被文档误导










