python 使用 watchdog 实现类似 Linux 中 tail -f 的功能

一、代码实现

import logging
import os
import threading
import timefrom watchdog.events import FileSystemEventHandler
from watchdog.observers import Observerlogger = logging.getLogger(__name__)class LogWatcher(FileSystemEventHandler):def __init__(self, log_file, on_modified_callback=None):""""初始化 LogWatcher 类的实例。参数:- log_file:日志文件的路径- on_modified_callback:可选的回调函数,在文件修改时调用属性:- log_file:日志文件的路径- file_object:日志文件对象- on_modified_callback:文件修改回调函数- last_line:最后一行文本- observer:观察者对象- match_string:需要匹配的字符串- stop_watching:停止监视的标志"""self.log_file = log_fileself.file_object = open(log_file, 'rb')self.on_modified_callback = on_modified_callbackself.last_line = self.get_last_line()  # 初始化时获取最后一行文本self.observer = Observer()self.observer.schedule(self, ".", recursive=False)self.match_string = Noneself.stop_watching = Falsedef start(self):"""启动观察者对象,开始监视文件变化。"""self.observer.start()def stop(self):"""停止观察者对象,结束监视文件变化。"""self.observer.stop()self.observer.join()self.file_object.close()def get_last_line(self):"""获取日志文件的最后一行文本。它通过将文件指针移动到文件末尾,然后逐个字符向前搜索,直到找到换行符为止。返回值:- 最后一行文本,如果文件为空则返回None"""# 将文件指针移动到文件末尾self.file_object.seek(0, os.SEEK_END)# 获取当前文件指针的位置(此时指针在最后一行的末尾)position = self.file_object.tell()try:# 尝试向前移动两个字节new_position = max(position - 2, 0)self.file_object.seek(new_position, os.SEEK_SET)except OSError as e:# 如果发生错误,可能是文件太小,返回Nonereturn None# 逐个字符向前搜索,确保文件指针最终停在当前行的第一个字符处while True:# read(1)读取的是指针位置的下一个字符,每次调用read(1)都会读取一个字符,并将指针向后移动一个字符的位置。char = self.file_object.read(1).decode('utf-8', errors='ignore')if char == '\n':breakif new_position == 0:# 如果已经到达文件开头,跳出循环break# 尝试向前移动一个字节位置,确保不越界到文件开头new_position = max(new_position - 1, 0)# 将文件指针移动到新的位置self.file_object.seek(new_position, os.SEEK_SET)# last_line = self.file_object.readline().decode('utf-8', errors='ignore').strip()last_line = self.file_object.read(position - new_position).decode('utf-8', errors='ignore').strip()# 输出调试信息logger.debug(f'Reading line: {last_line}')return last_linedef on_modified(self, event):"""on_modified方法是FileSystemEventHandler的回调方法,当日志文件发生变化时,都会调用这个方法。参数:- event:文件变化事件对象"""# 注意,这里一个要用绝对路径比较,不能直接使用 event.src_path == self.log_file,# event.src_path == self.log_file 的值为false# if event.src_path == self.log_file:if os.path.abspath(event.src_path) == os.path.abspath(self.log_file):# 在文件发生变化时,实时获取最后一行文本self.last_line = self.get_last_line()# 用户可在外部传入一个回调方法,在文本发生变化时执行该事件if self.on_modified_callback:self.on_modified_callback()# 调用基类的同名方法,以便执行基类的默认行为super(LogWatcher, self).on_modified(event)def tail_last_line_and_match(self, match_string=None, max_match_seconds=10):"""实时监控日志文件的变化,并实时获取最后一行文本。如果匹配到指定的字符串,停止监视。参数:- match_string:需要匹配的字符串"""self.match_string = match_stringself.start()end_time = time.time() + max_match_secondstry:while not self.stop_watching and time.time() <= end_time:if self.match_string and self.match_string in self.last_line:self.stop_watching = Trueexcept KeyboardInterrupt:passself.stop_watching = True  # 停止监视循环def write_logs(log_file):"""在新线程中写入日志"""for i in range(10):with open(log_file, 'a') as file:file.write(f'New log entry {i}\n')time.sleep(1)  # 每秒写入一次日志if __name__ == '__main__':import logginglogging.basicConfig(level=logging.DEBUG)log_file = 'demo.log'# 创建日志文件并写入示例日志with open(log_file, 'w') as file:file.write('This is the first line of the log.\n')file.write('This is the second line of the log.\n')log_watcher = LogWatcher(log_file)# 启动新线程写入日志write_thread = threading.Thread(target=write_logs, args=(log_file,))write_thread.start()# 启动实时监控日志文件变化,并打印最后一行文本,直到匹配到指定字符串或超时才停止监视log_watcher.tail_last_line_and_match(match_string='New log entry 9', max_match_seconds=20)# 等待写入线程结束write_thread.join()

三、Demo验证

运行代码,控制台的输出结果:

DEBUG:__main__:Reading line: This is the second line of the log.
DEBUG:__main__:Reading line: New log entry 0
DEBUG:__main__:Reading line: New log entry 1
DEBUG:__main__:Reading line: New log entry 2
DEBUG:__main__:Reading line: New log entry 3
DEBUG:__main__:Reading line: New log entry 4
DEBUG:__main__:Reading line: New log entry 5
DEBUG:__main__:Reading line: New log entry 6
DEBUG:__main__:Reading line: New log entry 7
DEBUG:__main__:Reading line: New log entry 8
DEBUG:__main__:Reading line: New log entry 9Process finished with exit code 0

欢迎技术交流:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/207709.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

《opencv实用探索·十五》inRange二值化图像

opencv接口如下&#xff1a; void inRange(InputArray src, InputArray lowerb, InputArray upperb, OutputArray dst);函数实现二值化功能&#xff0c;主要是将在两个阈值内的像素值设置为白色&#xff08;255&#xff09;&#xff0c;而不在阈值区间内的像素值设置为黑色&am…

一篇文章带你快速入门 Nuxt.js 服务端渲染

1. Nuxt.js 概述 1.1 我们一起做过的SPA SPA&#xff08;single page web application&#xff09;单页 Web 应用&#xff0c;Web 不再是一张张页面&#xff0c;而是一个整体的应用&#xff0c;一个由路由系统、数据系统、页面&#xff08;组件&#xff09;系统等等&#xff0…

HbuilderX使用Uniapp+Vue3安装uview-plus

如果你是vue2版本想使用uniapp去配置uviewui库可以参考之前的文章 小程序的第三方ui库推荐较多的还是uview的&#xff0c;看起来比较美观&#xff0c;功能也比较完善&#xff0c;下面将提一下Vue3安装uview-plus库的教程 创建项目 安装 首先进入官网 uView-Plus 直接下载并导…

预训练--微调

预训练–微调 一个很简单的道理&#xff0c;如果我们的模型是再ImageNet下训练的&#xff0c;那么这个模型一定是会比较复杂的&#xff0c;意思就是这个模型可以识别到很多种类别的即泛化能力很强&#xff0c;但是如果要它精确的识别是否某种类别&#xff0c;它的表现可能就不…

07-2 Python模块和命名空间

1. 模块 概念&#xff1a;其实就是一个Python文件&#xff0c;正常文件有的变量&#xff0c;函数&#xff0c;类&#xff0c;模块都有 功能:模块可以被其它程序引入&#xff0c;以使用该模块中的函数等功能。 示例&#xff1a;test-module.py调用mymodule.py模块中的now_time…

一篇文章带你快速入门 Vue 核心语法

一篇文章带你快速入门 Vue 核心语法 一、为什么要学习Vue 1.前端必备技能 2.岗位多&#xff0c;绝大互联网公司都在使用Vue 3.提高开发效率 4.高薪必备技能&#xff08;Vue2Vue3&#xff09; 二、什么是Vue 概念&#xff1a;Vue (读音 /vjuː/&#xff0c;类似于 view) …

Mysql 日期函数大全

一、时间函数 &#xff08;一&#xff09;、获取当前时间 1、NOW() 获取当前日期和时间&#xff0c;在程序一开始执行便拿到时间 返回格式 YYYY-MM-DD hh:mm:ss eg&#xff1a; NOW() 得到 2023-12-03 12:20:02 NOW(),SLEEP(2),NOW() 得到 2023-12-03 12:20:02 | 0 | 2023-…

目标检测——OverFeat算法解读

论文&#xff1a;OverFeat: Integrated Recognition, Localization and Detection using Convolutional Networks 作者&#xff1a;Pierre Sermanet, David Eigen, Xiang Zhang, Michael Mathieu, Rob Fergus, Yann LeCun 链接&#xff1a;https://arxiv.org/abs/1312.6229 文章…

SpringAOP专栏二《原理篇》

上一篇SpringAOP专栏一《使用教程篇》-CSDN博客介绍了SpringAop如何使用&#xff0c;这一篇文章就会介绍Spring AOP 的底层实现原理&#xff0c;并通过源代码解析来详细阐述其实现过程。 前言 Spring AOP 的实现原理是基于动态代理和字节码操作的。不了解动态代理和字节码操作…

【C语言】函数递归详解(一)

目录 1.什么是递归&#xff1a; 1.1递归的思想&#xff1a; 1.2递归的限制条件&#xff1a; 2.递归举例&#xff1a; 2.1举例1&#xff1a;求n的阶乘&#xff1a; 2.1.1 分析和代码实现&#xff1a; 2.1.2图示递归过程&#xff1a; 2.2举例2&#xff1a;顺序打印一个整数的…

机器学习---集成学习的初步理解

1. 集成学习 集成学习(ensemble learning)是现在非常火爆的机器学习方法。它本身不是一个单独的机器学 习算法&#xff0c;而是通过构建并结合多个机器学习器来完成学习任务。也就是我们常说的“博采众长”。集 成学习可以用于分类问题集成&#xff0c;回归问题集成&#xff…

多线程并发Ping脚本

1. 前言 最近需要ping地址&#xff0c;还是挺多的&#xff0c;就使用python搞一个ping脚本&#xff0c;记录一下&#xff0c;以免丢失了。 2. 脚本介绍 首先检查是否存在True.txt或False.txt文件&#xff0c;并在用户确认后进行删除&#xff0c;然后从IP.txt的文件中读取IP地…

CSS——sticky定位

1. 大白话解释sticky定位 粘性定位通俗来说&#xff0c;它就是相对定位relative和固定定位fixed的结合体&#xff0c;它的触发过程分为三个阶段 在最近可滚动容器没有触发滑动之前&#xff0c;sticky盒子的表现为相对定位relative【第一阶段】&#xff0c; 但当最近可滚动容…

【MATLAB】tvfEMD信号分解+FFT+HHT组合算法

有意向获取代码&#xff0c;请转文末观看代码获取方式~也可转原文链接获取~ 1 基本定义 TVFEMDFFTHHT组合算法是一种结合了总体变分模态分解&#xff08;TVFEMD&#xff09;、傅里叶变换&#xff08;FFT&#xff09;和希尔伯特-黄变换&#xff08;HHT&#xff09;的信号分解方…

电子学会C/C++编程等级考试2021年06月(五级)真题解析

C/C++等级考试(1~8级)全部真题・点这里 第1题:数字变换 给定一个包含5个数字(0-9)的字符串,例如 “02943”,请将“12345”变换到它。 你可以采取3种操作进行变换 1. 交换相邻的两个数字 2. 将一个数字加1。如果加1后大于9,则变为0 3. 将一个数字加倍。如果加倍后大于…

Python configparser 模块:优雅处理配置文件的得力工具

更多资料获取 &#x1f4da; 个人网站&#xff1a;ipengtao.com 配置文件在软件开发中扮演着重要的角色&#xff0c;而Python中的 configparser 模块提供了一种优雅而灵活的方式来处理各种配置需求。本文将深入介绍 configparser 模块的各个方面&#xff0c;通过丰富的示例代码…

嵌入式杂记 - MDK的Code, RO-data , RW-data, ZI-data意思

嵌入式杂记 - Keil的Code, RO-data , RW-data, ZI-data意思 MDK中的数据分类MCU中的内部存储分布MDK中数据类型存储Code代码段例子 RO-data 只读数据段例子 RW-data 可读写数据段例子 ZI-data 清零数据段例子 在嵌入式开发中&#xff0c;我们经常都会使用一些IDE&#xff0c;例…

Hadoop学习笔记(HDP)-Part.17 安装Spark2

目录 Part.01 关于HDP Part.02 核心组件原理 Part.03 资源规划 Part.04 基础环境配置 Part.05 Yum源配置 Part.06 安装OracleJDK Part.07 安装MySQL Part.08 部署Ambari集群 Part.09 安装OpenLDAP Part.10 创建集群 Part.11 安装Kerberos Part.12 安装HDFS Part.13 安装Ranger …

Web前端 ---- 【Vue】Vuex的使用(辅助函数、模块化开发)

目录 前言 Vuex是什么 Vuex的配置 安装vuex 配置vuex文件 Vuex核心对象 actions mutations getters state Vuex在vue中的使用 辅助函数 Vuex模块化开发 前言 本文介绍一种新的用于组件传值的插件 —— vuex Vuex是什么 Vuex 是一个专为 Vue.js 应用程序开发的状态…

【ArcGIS Pro微课1000例】0053:基于SQL Server创建与启用地理数据库

之前的文章有讲述基于SQL Server创建企业级地理数据库,本文讲述在SQL Server中创建常规的关心数据库,然后在ArcGIS Pro中将其启用,转换为企业级地理数据库。 1. 在SQL Server中创建数据库** 打开SQL Server 2019,连接到数据库服务器。 展开数据库连接,在数据库上右键→新…