You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Python2.7跨平台处理可轮换日志文件并查找指定字符串

处理循环覆盖式日志文件的跨平台读取问题

你的场景是典型的循环滚动日志(主程序会将满容量的data.log重命名为data.log.bak并覆盖旧备份,同时新建data.log继续写入),需要跨Windows/Mac平台的Python2.7脚本并行读取并监控特定字符串。先聊聊你之前两种方法的问题,再给出可靠的解决方案:

为什么之前的方法行不通?

  1. 基于inode的方案:Windows系统没有真正的inode概念,os.fstat().st_ino在Windows下返回的是一个模拟值,不具备唯一标识文件的能力,所以跨平台用inode判断文件是否被替换完全不可靠。
  2. 基于备份文件修改时间的方案:这个逻辑依赖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'

代码说明

  1. 跨平台文件唯一标识:
    • Windows通过GetFileInformationByHandle获取FileIndex(由两个整数组成,唯一标识文件)
    • Mac/Linux直接使用st_ino(inode号)
  2. 共享访问:Windows下打开文件时必须指定FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,否则主程序无法重命名/删除日志文件,这是你之前代码里可能没注意到的关键点!
  3. 文件替换检测:每次读取到EOF后,都会检查磁盘上的data.log的唯一标识是否和当前打开的文件一致,如果不一致,说明文件被替换,重新打开并从开头读取新文件。
  4. 文件不存在处理:如果日志文件被删除,脚本会等待文件重新创建后继续监控。
  5. 位置跟踪:用last_pos记录上次读取的位置,避免重复读取已处理的内容。

使用注意事项

  • 确保Python2.7安装了pywin32库(Windows下):pip install pywin32
  • 脚本通过线程运行时,注意设置stop标志来优雅停止监控
  • 如果主程序会直接删除data.log而不是重命名,这个方案也能正确处理,因为会检测到文件标识变化

内容的提问来源于stack exchange,提问作者Sri

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.27 07:34:17