Python2.7跨平台处理可轮换日志文件并查找指定字符串
处理循环覆盖式日志文件的跨平台读取问题
你的场景是典型的循环滚动日志(主程序会将满容量的data.log重命名为data.log.bak并覆盖旧备份,同时新建data.log继续写入),需要跨Windows/Mac平台的Python2.7脚本并行读取并监控特定字符串。先聊聊你之前两种方法的问题,再给出可靠的解决方案:
为什么之前的方法行不通?
- 基于inode的方案:Windows系统没有真正的inode概念,
os.fstat().st_ino在Windows下返回的是一个模拟值,不具备唯一标识文件的能力,所以跨平台用inode判断文件是否被替换完全不可靠。 - 基于备份文件修改时间的方案:这个逻辑依赖
data.log.bak的存在和修改时间变化,但如果主程序是直接删除data.log再重建(而非重命名),或者第一次滚动还没生成备份文件时,你就无法检测到data.log的替换,会导致漏读日志。
可靠的跨平台解决方案
核心思路是:跨平台获取文件的唯一标识(Windows用文件ID,Mac/Linux用inode),定期对比当前打开的文件标识和磁盘上data.log的标识,如果不一致,说明文件被重命名/删除重建,需要重新打开文件继续读取。同时处理文件不存在的等待逻辑。
实现步骤与代码
首先需要封装跨平台的文件唯一标识获取函数,然后改进日志跟踪逻辑:
import os import sys import time import win32file import msvcrt from win32file import FILE_SHARE_DELETE, FILE_SHARE_READ, FILE_SHARE_WRITE, GENERIC_READ, OPEN_EXISTING class LogMonitor: def __init__(self, filename): self.filename = filename self.bak_filename = "%s.bak" % filename self.stop = False self.found = False def get_file_unique_id(self, file_obj): """跨平台获取文件的唯一标识""" if sys.platform == 'win32': # Windows下获取FileIndex(唯一标识文件) handle = msvcrt.get_osfhandle(file_obj.fileno()) file_info = win32file.GetFileInformationByHandle(handle) # FileIndex由两个部分组成,组合成唯一标识 return (file_info[8], file_info[9]) else: # Mac/Linux下用inode return os.fstat(file_obj.fileno()).st_ino def open_file(self): """安全打开日志文件,支持跨平台共享访问""" if not os.path.isfile(self.filename): return None, None if sys.platform == 'win32': # Windows下必须指定共享权限,避免主程序无法重命名/删除文件 handle = win32file.CreateFile( self.filename, GENERIC_READ, FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, None, OPEN_EXISTING, 0, None ) detached_handle = handle.Detach() fd = msvcrt.open_osfhandle(detached_handle, os.O_RDONLY) file_obj = os.fdopen(fd, 'r') else: # Mac/Linux下直接打开,默认支持共享 file_obj = open(self.filename, 'r') file_id = self.get_file_unique_id(file_obj) return file_obj, file_id def follow(self): current_file = None current_file_id = None last_pos = 0 try: while not self.stop: # 处理文件不存在的情况:等待文件创建 if not os.path.isfile(self.filename): print '[Debug] Log file not found, waiting...' time.sleep(2) continue # 如果还没打开文件,或者文件已被替换,重新打开 if not current_file: current_file, current_file_id = self.open_file() if not current_file: time.sleep(2) continue # 定位到上次读取的位置(如果是新打开的文件,从开头读) current_file.seek(last_pos) print '[Debug] Opened log file' else: # 检查磁盘上的文件是否和当前打开的是同一个 if sys.platform == 'win32': # Windows下重新获取磁盘上文件的FileID temp_handle = win32file.CreateFile( self.filename, GENERIC_READ, FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, None, OPEN_EXISTING, 0, None ) temp_file_info = win32file.GetFileInformationByHandle(temp_handle) temp_file_id = (temp_file_info[8], temp_file_info[9]) temp_handle.Close() else: temp_file_id = os.stat(self.filename).st_ino if temp_file_id != current_file_id: print '[Debug] Detected log file was replaced, reopening...' current_file.close() current_file, current_file_id = self.open_file() last_pos = 0 # 新文件从开头读 current_file.seek(last_pos) continue # 读取日志内容 line = current_file.readline().strip() if line: print '[log]', line if 'error' in line: self.found = True print '[Debug] Found exception' break # 更新上次读取的位置 last_pos = current_file.tell() else: # 没有新内容,等待后重试 print '[Debug] No new lines, sleeping...' time.sleep(2) # 回到上次位置,避免下次读取重复内容 current_file.seek(last_pos) finally: if current_file: current_file.close() print '[Debug] Log monitor stopped'
代码说明
- 跨平台文件唯一标识:
- Windows通过
GetFileInformationByHandle获取FileIndex(由两个整数组成,唯一标识文件) - Mac/Linux直接使用
st_ino(inode号)
- Windows通过
- 共享访问:Windows下打开文件时必须指定
FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,否则主程序无法重命名/删除日志文件,这是你之前代码里可能没注意到的关键点! - 文件替换检测:每次读取到EOF后,都会检查磁盘上的
data.log的唯一标识是否和当前打开的文件一致,如果不一致,说明文件被替换,重新打开并从开头读取新文件。 - 文件不存在处理:如果日志文件被删除,脚本会等待文件重新创建后继续监控。
- 位置跟踪:用
last_pos记录上次读取的位置,避免重复读取已处理的内容。
使用注意事项
- 确保Python2.7安装了
pywin32库(Windows下):pip install pywin32 - 脚本通过线程运行时,注意设置
stop标志来优雅停止监控 - 如果主程序会直接删除
data.log而不是重命名,这个方案也能正确处理,因为会检测到文件标识变化
内容的提问来源于stack exchange,提问作者Sri
相关产品推荐
相关产品推荐

