import os from typing import Optional from paste.core import config class TailRead: """ 文件逆向读取器。 主要针对读取日志文件。当遇到大日志文件时,需要从后向前读取,这样读取的速度更快。 当日志文件动态增加时,再正向读取,此时仅读取差异内容,实现小数据量交互。 """ @classmethod def logReader(cls, log_fn: str = None): """ 取得配置文件设置的日志文件读取器。 :param log_fn: 日志文件名 :return: 默认日志文件读取器 """ if log_fn is None: log_fn = config.get_config('logger.filename') return TailRead(fn=log_fn) def __init__(self, fn: str): self.file_name = fn """ 要读取的文件名。 """ self.file_io = open(self.file_name, 'rb') """ 文件 IO 对象。 """ self.current_position: Optional[int] = None """ 当前读取点位置。 """ _f_size = self.size() if _f_size > 1: # 移动到文件末尾 self.file_io.seek(_f_size - 1) else: self.file_io.seek(0) def size(self): """ 取得文件大小。 :return: 文件大小 """ return os.path.getsize(self.file_name) def readTail(self, lines: int = 100): """ 从文件中,逆向读取 lines 行。读取结束后,将读取定位移动到所有读取到的字节的最后。 :param lines: 读取的行数 :return: 读取到的数据,读取完成点 """ _buffer: bytes = b'' _c_pos = self.file_io.tell() # 从当前位置读取一位,判断是否是回车 # 若是,则增加一行,确保读取足够的行数 _byte = self.file_io.read(1) if _byte == b'\n': lines += 1 # 重新回到原始位置 self.file_io.seek(_c_pos) _r_pos = _c_pos while lines > 0: # 读取一个字节 _byte = self.file_io.read(1) if _byte == b'': # 无数据,退出 break if _byte == b'\n': # 减少行数 lines -= 1 # 逆向前移 _r_pos -= 1 if _r_pos <= 0: # 超出第一位时,退出 break self.file_io.seek(_r_pos) # 加入缓存 _buffer = _byte + _buffer # 扣除首字节回车符号 if _buffer[0:1] == b'\n': _buffer = _buffer[1:] # 第一位已经读取,因此正向移动一位 self.current_position = _c_pos + 1 self.file_io.seek(self.current_position) return _buffer, self.current_position def readLines(self, lines: int = 100, crt_pos: int = None): """ 读取文件数据,默认读取 100 行。当不传入 crt_pos 时逆向读取,传入时正向读取。 具有动态方向,确保第一次是最大量读取,以后每次都是增量读取,减少传递的数据量。 :param lines: 要读取的行数 :param crt_pos: 当前读取位置 :return: """ _buffer: bytes = b'' if crt_pos is None: # 参数 cur_pos 为 None 时,逆向读取 self.file_io.seek(self.size()-1) _buffer, crt_pos = self.readTail(lines) else: # 参数 cur_pos 有值时,正向读取 self.file_io.seek(crt_pos) while lines: _bytes = self.file_io.readline() if _bytes == b'': # 无数据,退出 break else: # 减少行数 lines -= 1 # 加入缓存 _buffer += _bytes crt_pos = self.file_io.tell() self.current_position = crt_pos return _buffer, self.current_position def read(self, lines: int = 200, crt_pos: int = None): """ 读取文件数据。注意:: 1、首次读取时 crt_pos 应为 None 此时逆向读取,返回读取到的数据流和读取点位置。 2、当有 crt_pos 参数时,先检查文件是否发生了变化,若文件变大,则正向读取增量部分,若文件变小则置空。 3、若有 crt_pos 且文件没有发生变化,则返回空字节流,读取位置不变。 :param lines: 要读取的最大行数,默认 200 行 :param crt_pos: 当前读取位置,为 None 时逆向读取,否则正向读取 :return: 读取到的字节流 """ _buffer = b'' if crt_pos is None: # 参数 cur_pos 为 None 时,逆向读取 _buffer, crt_pos = self.readLines(lines, crt_pos=crt_pos) else: # 参数 cur_pos 有值时 # 检查文件是否发生了变化 _log_size = self.size() if _log_size > crt_pos + 1: # 内容增加,继续正向读取 _buffer, crt_pos = self.readLines(lines, crt_pos=crt_pos) elif _log_size < crt_pos: # 内容减少,置空读取位置 # 置空后,再次调用本函数,执行逆向读取 crt_pos = None self.current_position = crt_pos return _buffer, self.current_position