【问题标题】:python: read file continuously, even after it has been logrotatedpython:连续读取文件,即使它已经被logrotated
【发布时间】:2021-08-20 05:35:38
【问题描述】:

我有一个简单的 python 脚本,我在其中连续读取日志文件(与 tail -f 相同)

while True:
    line = f.readline()
    if line:
        print line,
    else:
        time.sleep(0.1)

如何确保日志文件在被 logrotate 轮换后仍然可以读取?

即我需要像tail -F 那样做。

我正在使用python 2.7

【问题讨论】:

标签: python file


【解决方案1】:

只要您只打算在 Unix 上执行此操作,最可靠的方法可能是检查打开的文件是否仍然引用相同的 i-node 作为名称,并在不再是这种情况时重新打开它.您可以在st_ino 字段中从os.statos.fstat 获取文件的i 编号。

它可能看起来像这样:

import os, sys, time

name = "logfile"
current = open(name, "r")
curino = os.fstat(current.fileno()).st_ino
while True:
    while True:
        buf = current.read(1024)
        if buf == "":
            break
        sys.stdout.write(buf)
    try:
        if os.stat(name).st_ino != curino:
            new = open(name, "r")
            current.close()
            current = new
            curino = os.fstat(current.fileno()).st_ino
            continue
    except IOError:
        pass
    time.sleep(1)

我怀疑这在 Windows 上是否有效,但由于您使用的是 tail,我猜这不是问题。 :)

【讨论】:

    【解决方案2】:

    您可以通过跟踪您在文件中的位置并在您想要阅读时重新打开它来做到这一点。当日志文件旋转时,您会注意到文件变小了,并且由于您重新打开,您也可以处理任何取消链接。

    import time
    
    cur = 0
    while True:
        try:
            with open('myfile') as f:
                f.seek(0,2)
                if f.tell() < cur:
                    f.seek(0,0)
                else:
                    f.seek(cur,0)
                for line in f:
                    print line.strip()
                cur = f.tell()
        except IOError, e:
            pass
        time.sleep(1)
    

    此示例隐藏了诸如找不到文件之类的错误,因为我不确定 logrotate 详细信息,例如文件不可用的一小段时间。

    【讨论】:

    • 非常感谢。不过,我想知道是否有更好/更优雅的解决方案。例如,有没有办法检查我的文件描述符 f 是否仍然指向文件 file.log ?我想,在文件被 logrotated 之后,文件描述符仍然会指向旧文件,而不是新的 file.log
    • 就文件操作而言,恕我直言,这是相当优雅的。问题是如果您保持文件打开,它的文件描述符继续指向旋转的文件,而不是新文件。如果不重新检查原始文件名,您将无法判断它是否已过期。另一种方法是使用 inotify,它可以让您随时了解文件操作。它更复杂,但也是一个好方法。
    • 有没有办法把这个函数写成生成器?我想对输出采取行动,以完成各种任务。
    【解决方案3】:

    感谢@tdelaney 和@Dolda2000 的回答,我得到了以下内容。它应该可以在 Linux 和 Windows 上运行,并且还可以处理 logrotate 的 copytruncatecreate 选项(分别复制然后截断大小为 0 并移动然后重新创建文件)。

    file_name = 'my_log_file'
    seek_end = True
    while True:  # handle moved/truncated files by allowing to reopen
        with open(file_name) as f:
            if seek_end:  # reopened files must not seek end
                f.seek(0, 2)
            while True:  # line reading loop
                line = f.readline()
                if not line:
                    try:
                        if f.tell() > os.path.getsize(file_name):
                            # rotation occurred (copytruncate/create)
                            f.close()
                            seek_end = False
                            break
                    except FileNotFoundError:
                        # rotation occurred but new file still not created
                        pass  # wait 1 second and retry
                    time.sleep(1)
                do_stuff_with(line)
    

    使用copytruncate 选项时的一个限制是,如果在时间休眠时将行附加到文件中,并且在唤醒之前发生旋转,最后一行将“丢失”(它们仍将位于现在的“旧”日志文件中,但我看不到“遵循”该文件以完成阅读它的体面方式)。此限制与“移动和创建”create 选项无关,因为f 描述符仍将指向重命名的文件,因此将在关闭并再次打开描述符之前读取最后几行。

    【讨论】:

    • 想详细说明反对意见 :) 吗?我很乐意更新答案。
    【解决方案4】:

    我将@pawamoy 的上述很棒的一个变体变成了一个生成器函数,用于我的日志监控和跟踪需求。

    def tail_file(file):
        """generator function that yields new lines in a file
    
        :param file:File Path as a string
        :type file: str
        :rtype: collections.Iterable
        """
        seek_end = True
        while True:  # handle moved/truncated files by allowing to reopen
            with open(file) as f:
                if seek_end:  # reopened files must not seek end
                    f.seek(0, 2)
                while True:  # line reading loop
                    line = f.readline()
                    if not line:
                        try:
                            if f.tell() > os.path.getsize(file):
                                # rotation occurred (copytruncate/create)
                                f.close()
                                seek_end = False
                                break
                        except FileNotFoundError:
                            # rotation occurred but new file still not created
                            pass  # wait 1 second and retry
                        time.sleep(1)
                    yield line
    

    可以像下面这样轻松使用

    access_logfile = '/var/log/syslog'
    loglines = tail_file(access_logfile)
    for line in loglines:
        print(line)
    

    【讨论】:

      猜你喜欢
      • 2013-11-01
      • 2015-07-21
      • 1970-01-01
      • 2021-02-10
      • 2021-12-23
      • 2022-09-27
      • 1970-01-01
      • 2011-04-17
      • 2020-05-25
      相关资源
      最近更新 更多