Python 如何实现 tail -f 类功能?

Python实现tail-f需轮询文件大小并记录偏移量,支持inode变化检测以应对日志轮转,或用pyinotify/watchdog监听内核事件;注意编码、休眠间隔、异常退出及超大行处理。

Python 实现 tail -f 类功能,核心是持续读取文件末尾新增内容,类似 Linux 的实时日志跟踪。关键在于:不加载整个文件、不重复读取旧数据、能响应文件追加、支持中断退出。

基础实现:轮询 + 文件指针定位

最直接的方式是打开文件,记录当前文件大小或行数,定期检查是否有新内容写入:

  • file.seek(0, 2) 移动到文件末尾,获取初始位置(即字节偏移)
  • 循环中调用 os.stat(filename).st_size 判断文件是否变长
  • 若变长,用 file.seek() 跳转到上次位置,逐行读取新增部分(注意换行符边界)
  • 每次读完更新记录的偏移量,避免重复输出

更健壮:处理日志轮转(log rotation)

真实场景中,日志文件可能被重命名或清空(如 logrotate)。仅靠文件大小判断会失效,需额外校验:

  • os.stat().st_ino 检查 inode 是否变化——inode 改变说明文件已被替换(如 mv app.log app.log.1 && touch app.log
  • 发现 inode 变化后,重新打开文件,重置偏移量,避免漏读或错读
  • 可选:结合 os.path.getmtime() 辅助判断是否真有更新(防止小文件反复 stat 误触发)

替代方案:使用第三方库 watchdogpyinotify

轮询有延迟且消耗 CPU,Linux 下可用 inotify 监听内核事件,更高效:

  • pyinotify(仅 Linux)监听 IN_MODIFYIN_MOVED_TO 事件,触发时再读新增行
  • watchdog 跨平台,监听 FileModifiedEventFileMovedEvent,适合需要兼容 macOS/Windows 的场景
  • 注意:事件只通知“变了”,仍需自己控制读取逻辑(比如从上次位置读到新末尾),不能完全替代 tail 逻辑

实用建议与注意事项

实际写脚本时要注意几个易错点:

  • 文件编码需显式指定(如 open(... , encoding='utf-8', errors='replace')),避免日志含乱码崩溃
  • time.sleep(0.1) 控制轮询频率,太短伤 CPU,太长延迟高(tail -f 默认约 1s,但可调)
  • 捕获 KeyboardInterrupt(Ctrl+C)优雅退出,关闭文件句柄
  • 对超大行(如单行几 MB 的 JSON 日志)要限制单次读取长度,

    防止内存暴涨