最稳妥方案是用watchdog库监听目录,结合文件名模式与修改时间动态切换读取目标,并用缓冲区拼接跨readline的多行日志。
用
轮询文件末尾是最简单但要注意 CPU 占用
Python 没有内置的阻塞式“tail -f”等价物,最直接的做法是循环检查文件大小变化,读新内容。核心逻辑就是:记录上次读到的位置 → 等待 → 检查文件是否增长 → 从上次位置读新增行。
常见错误现象:
太小导致频繁唤醒、CPU 占用高;
太大导致日志延迟明显;没处理文件被 logrotate 清空或重命名的情况,导致漏读或报错。
推荐起始值用
,再根据实际吞吐量调整
每次读之前先
获取当前大小,比直接
更可靠
如果日志可能被轮转(比如
→
),仅靠文件大小不够,得加
判断是否还是同一个文件
用
监听文件描述符在 Linux 上更高效但不跨平台
Linux 下可以对普通文件 fd 使用
,配合
事件实现近似 inotify 的效果——不过注意:
普通文件不触发 IN_MODIFY,只有某些特殊文件系统(如 procfs)或内核版本较新时才可能生效
。多数情况下它只对管道、socket、终端有效,对磁盘日志文件基本无效。
所以别被网上某些示例误导:用
去监听日志文件,大概率会一直阻塞或立刻返回无事件。
立即学习
“
Python免费学习笔记(深入)
”;
真正可用的替代方案是用
(需安装
)或
库监听目录变更
的
可捕获文件内容变化,但注意它不保证“内容已刷盘”,可能读到半截行
若坚持用底层机制,
的
+
才能可靠捕获 logrotate 场景
用
库处理轮转和多行日志更稳妥
真实生产环境里,日志文件几乎一定会被轮转、压缩、重命名。这时候只盯着一个文件名读,很容易断档。用
可以监听整个目录,结合文件名模式(如
)和修改时间,主动发现新文件并续读。
Python 3.14.3
微软官方的 Python 扩展,是 VS Code 安装量最高的扩展(209M+)。集成 IntelliSense(通过 Pylance)、调试(通过 Python Debugger)、代码检查、格式化、重构和单元测试等功能。支持 Jupyter Notebook、虚拟环境管理和多 Python 版本切换。
下载
使用场景:你不能假设日志永远写在
,而要接受它可能是
或刚被 rename 出来的
。
初始化时先按时间排序找到最新日志文件,从末尾开始读(用
)
监听
和
,当检测到新文件或旧文件被移走,立即切换读取目标
每行日志可能跨多个
调用(尤其 JSON 日志含换行符),建议用缓冲区拼接,直到遇到完整换行再解析
注意
默认用 inotify,不支持 macOS 或 Windows 的原生事件,跨平台需配
的不同 backend
用
调用系统
是最省事的兜底方案
如果你只需要“把 tail -f 的输出拿进 Python 处理”,而不是非得纯 Python 实现,那直接调用系统命令反而最稳——它天然支持符号链接更新、文件轮转重开、信号中断等 edge case。
性能影响很小,因为只是做一层管道桥接;兼容性上,只要系统有
(Linux/macOS 都自带),就不用操心内核版本或文件系统限制。
启动时加
用
流式读取,避免阻塞
别忘了处理子进程异常退出:比如
被 kill、文件被删导致
Windows 下没有
,可改用
(PowerShell),但需注意编码和换行符差异
真正难的不是“怎么读最后一行”,而是“怎么确认这行确实写完了、没被截断、没被轮转跳过”。轮转时机、缓冲策略、编码边界、权限变化,任何一个环节松动都会导致日志丢失或重复。别迷信某一种方法能通吃所有部署环境。
time.sleep()time.sleep(0.1)sleep(5)time.sleep(0.5)os.stat(filepath).st_sizeseek(0, 2)app.logapp.log.1os.path.getinode()select.poll()select.poll()IN_MODIFYpoll().register(fd, select.POLLIN)inotifypyinotifywatchdogwatchdogFileSystemEventHandler.on_modified()inotifyIN_MOVED_TOIN_CREATEwatchdogwatchdog*.logapp.logapp.log.2024-06-15app.logfile.seek(0, 2)on_createdon_movedreadline()watchdogObserversubprocess.Popentail -ftailstdout=subprocess.PIPE, stderr=subprocess.STDOUT, bufsize=1, universal_newlines=Truefor line in iter(proc.stdout.readline, ''):tailOSError: [Errno 5] Input/output errortailGet-Content -Wait