Files
paste-framework/paste/util/tail_read.py
T
2026-06-02 16:26:10 +08:00

165 lines
5.4 KiB
Python

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