如何在Python中拖尾日志文件?


76

我想在不阻塞或锁定的情况下在Python中提供tail -F或类似内容的输出。我在这里找到了一些非常老的代码来执行此操作,但我认为现在必须有更好的方法或库来执行相同的操作。有人知道吗?

理想情况下,我希望tail.getNewData()每次需要更多数据时都可以打电话给我。


1
subprocess.call(["tail", "-F", filename])
Whymarrh 2012年


1
@Avaris答案不是尾随的。
基思(Keith)2012年

Avaris:不。那只是尾巴。我需要tail -F,以便它可以不断为我提供所有新行,或者每次调用一些getData()函数时都可以不断获得它们。
伊莱(Eli)2012年

1
您的假设get_new_data方法(PEP-8名称)是否需要返回自上次调用以来的所有数据,或仅返回当前尾部(可能丢失一些数据)?
Keith 2012年

Answers:


70

非阻塞

如果您使用的是Linux(因为Windows不支持在文件上调用select),则可以将subprocess模块​​与select模块一起使用。

import time
import subprocess
import select

f = subprocess.Popen(['tail','-F',filename],\
        stdout=subprocess.PIPE,stderr=subprocess.PIPE)
p = select.poll()
p.register(f.stdout)

while True:
    if p.poll(1):
        print f.stdout.readline()
    time.sleep(1)

这将轮询输出管道以获取新数据,并在可用时进行打印。通常,time.sleep(1)print f.stdout.readline()将被有用的代码替换。

封锁

您可以使用子流程模块,而无需额外的选择模块调用。

import subprocess
f = subprocess.Popen(['tail','-F',filename],\
        stdout=subprocess.PIPE,stderr=subprocess.PIPE)
while True:
    line = f.stdout.readline()
    print line

当添加新行时,它还会打印新行,但是它将阻塞,直到尾部程序关闭为止,可能使用f.kill()


从技术上讲,f.stdout它是管道,而不是文件(但我相信Windows仍无法select在其上使用)。
nneonneo 2012年

3
在“阻止”解决方案中print line,使用代替sys.stdout.write(line)来照顾打印将插入的多余换行符。
Mayank Jaiswal

line = f.stdout.readline()。strip()也会删除多余的换行符
mork

1
@mork是否应该打印多余的换行符?无论如何,我相信.strip()也会删除可能很重要的空白。
马特(Matt)

1
每秒最多只能读取一行,如果日志每秒增长一行以上,则是一个问题。
丹尼尔·沃尔特里普

42

使用sh模块(pip install sh):

from sh import tail
# runs forever
for line in tail("-f", "/var/log/some_log_file.log", _iter=True):
    print(line)

[更新]

由于带有_iter= True的sh.tail是生成器,因此您可以:

import sh
tail = sh.tail("-f", "/var/log/some_log_file.log", _iter=True)

然后,您可以使用以下方法“ getNewData”:

new_data = tail.next()

请注意,如果尾部缓冲区为空,它将阻塞直到有更多数据为止(根据您的问题,尚不清楚在这种情况下要执行的操作)。

[更新]

如果将-f替换为-F,则此方法有效,但是在Python中它将被锁定。如果可能的话,我对拥有一个我可以调用以获取新数据的函数会更感兴趣。–伊莱

容器生成器将tail调用放置在while True循环内并捕获最终的I / O异常将具有与-F几乎相同的效果。

def tail_F(some_file):
    while True:
        try:
            for line in sh.tail("-f", some_file, _iter=True):
                yield line
        except sh.ErrorReturnCode_1:
            yield None

如果无法访问该文件,则生成器将返回None。但是,如果可以访问该文件,它将一直阻塞直到有新数据。我仍然不清楚在这种情况下您想做什么。

Raymond Hettinger的方法似乎很好:

def tail_F(some_file):
    first_call = True
    while True:
        try:
            with open(some_file) as input:
                if first_call:
                    input.seek(0, 2)
                    first_call = False
                latest_data = input.read()
                while True:
                    if '\n' not in latest_data:
                        latest_data += input.read()
                        if '\n' not in latest_data:
                            yield ''
                            if not os.path.isfile(some_file):
                                break
                            continue
                    latest_lines = latest_data.split('\n')
                    if latest_data[-1] != '\n':
                        latest_data = latest_lines[-1]
                    else:
                        latest_data = input.read()
                    for line in latest_lines[:-1]:
                        yield line + '\n'
        except IOError:
            yield ''

如果无法访问文件或没有新数据,则此生成器将返回''。

[更新]

倒数第二个答案绕到文件顶部,似乎每当数据用完时。–伊莱

我认为第二个命令将在尾部处理结束时输出最后十行,-f每当有一个I / O错误时它就会输出。tail --follow --retry对于大多数我可以在类Unix环境中想到的情况,其行为与这个情况相距不远。

也许,如果您更新问题以解释您的真正目标(想要模仿尾巴-重试的原因),将会得到更好的答案。

最后一个答案实际上并没有尾巴,而只是读取运行时可用的内容。–伊莱

当然,默认情况下,tail将显示最后10行...您可以使用file.seek将文件指针定位在文件的末尾,作为练习,我将为读者留出适当的实现。

恕我直言,file.read()方法比基于子流程的解决方案优雅得多。


如果将-f替换为-F,则此方法有效,但是在Python中它将被锁定。如果可能的话,我对拥有一个我可以调用以获取新数据的函数会更感兴趣。
伊莱(Eli)2012年

我认为容器生成器将tail调用置于while True循环内并捕获最终的I / O异常将具有与-F相同的效果。
Paulo Scardine 2012年

倒数第二个答案绕到文件顶部,似乎每当数据用完时。最后一个答案实际上并没有尾巴,而只是读取运行时可用的内容。
伊莱(Eli)2012年

@Eli:seek(0,2)将文件指针移到文件末尾。
Paulo Scardine 2012年

1
只是好奇:对您而言,哪种file.read()方法看起来更优雅?tail正确处理显示文件的最后10行(即使行很大),永久读取新行,在新行到达时唤醒(以平台相关的方式)以及在需要时打开新文件的方法。简而言之,该实用程序针对其用途进行了精心设计-重新实现它似乎并不那么优雅。(不过,我会承认该sh模块非常漂亮。)
nneonneo 2012年

24

实际上,文件的唯一可移植方式tail -f似乎是从文件读取并sleepread返回0时重试(在a之后)tail。各种平台上的实用程序都使用特定kqueue于平台的技巧(例如,在BSD上)来永久有效地尾部文件不需要sleep

因此,tail -f仅使用Python实现良好的效果可能不是一个好主意,因为您将不得不使用最少公分母的实现(而不求助于特定于平台的黑客)。使用简单subprocess的打开tail -f和循环访问单独线程中的行的方法,您可以轻松地tail在Python中实现非阻塞操作。

示例实现:

import threading, Queue, subprocess
tailq = Queue.Queue(maxsize=10) # buffer at most 100 lines

def tail_forever(fn):
    p = subprocess.Popen(["tail", "-f", fn], stdout=subprocess.PIPE)
    while 1:
        line = p.stdout.readline()
        tailq.put(line)
        if not line:
            break

threading.Thread(target=tail_forever, args=(fn,)).start()

print tailq.get() # blocks
print tailq.get_nowait() # throws Queue.Empty if there are no lines to read

4
如果OP的主要关注点不是摆脱对外部命令(尾部)的依赖,那么他应该遵循Unix的传统,即编写日志处理器应用程序以从stdin中读取内容并将其管道化tail -F。我看不到为什么增加线程,队列和子进程的复杂性会带来比传统方法更多的优势。
Paulo Scardine 2012年

他什么时候说他在写日志处理器?
nneonneo 2012年

11
英语不是我的母语,但我想可以从问题标题中推断出它(我如何在Python中尾随日志文件?)。
Paulo Scardine 2012年

13

因此,这来得太晚了,但是我又遇到了同样的问题,现在有了更好的解决方案。只需使用pygtail即可

Pygtail读取尚未读取的日志文件行。它甚至可以处理已轮换的日志文件。基于logcheck的logtail2(http://logcheck.org


请注意,它的行为不像尾巴,但可能有用,具体取决于您想做什么。
Haroldo_OK

12

将Ijaz Ahmad Khan的答案改编为仅在完全编写它们时才产生屈服线(行以换行符char结尾)提供了不依赖外部的pythonic解决方案:

def follow(file) -> Iterator[str]:
    """ Yield each line from a file as they are written. """
    line = ''
    while True:
        tmp = file.readline()
        if tmp is not None:
            line += tmp
            if line.endswith("\n"):
                yield line
                line = ''
        else:
            time.sleep(0.1)


if __name__ == '__main__':
    for line in follow(open("test.txt", 'r')):
        print(line, end='')

11

理想情况下,我会有诸如tail.getNewData()之类的东西,每次需要更多数据时都可以调用

我们已经有一个,它非常好。只要您想获取更多数据, 只需调用f.read()。它将开始读取上一个读取中断的位置,并将读取数据流的结尾:

f = open('somefile.log')
p = 0
while True:
    f.seek(p)
    latest_data = f.read()
    p = f.tell()
    if latest_data:
        print latest_data
        print str(p).center(10).center(80, '=')

要逐行读取,请使用f.readline()。有时,正在读取的文件将以部分读取的行结尾。使用f.tell()查找当前文件位置并使用f.seek()将文件指针移回不完整行的开头来处理这种情况。有关工作代码,请参见此ActiveState配方


1
关键是我想跟踪文件。如果我打开文件,则f.read()只会运行到文件运行结束为止。在那之后,它将不会读取任何新添加的内容。
伊莱(Eli)2012年

1
我在发布之前对其进行了测试。我只是做了:blah = open('some_file',r)而1:sleep(1)print blah.read()并尝试写入文件。没运气。
伊莱(Eli)2012年

1
@Eli:那么您应该在Windows中。这是您的问题中缺少的重要信息。
Paulo Scardine 2012年

10
@Paulo:答案中缺少重要信息。如果未指定操作系统,则将构建通常可以运行的程序,或者至少构建适用于* nix的程序。您从不假定使用Windows。
伊莱(Eli)2012年

为什么永远不要假设Windows?python比nix更接近Windows,例如:UTF-16 vs UTF-8
Jasen

8

所有使用tail -f的答案都不是pythonic。

这是pythonic的方法:(不使用任何外部工具或库)

def follow(thefile):
     while True:
        line = thefile.readline()
        if not line or not line.endswith('\n'):
            time.sleep(0.1)
            continue
        yield line



if __name__ == '__main__':
    logfile = open("run/foo/access-log","r")
    loglines = follow(logfile)
    for line in loglines:
        print(line, end='')

1
如果将日志文件追加到2个系统调用中,则这种“跟随”文件的方式有时会返回该行的2部分,而不是全行本身
Ferrybig,

我已发布答案以解决@Ferrybig指出的错误:stackoverflow.com/a/54263201/431087
Isaac Turner

考虑另一个python程序正在使用writer写入此文件,当writer停止写入时,有什么方法可以通过编程方式停止此操作?
codelord

是的,您可以使用类似锁的机械机制在写入之前获取该锁,并在完成后释放它
Ijaz Ahmad Khan

6

您可以使用“ tailer”库:https : //pypi.python.org/pypi/tailer/

它具有获取最后几行的选项:

# Get the last 3 lines of the file
tailer.tail(open('test.txt'), 3)
# ['Line 9', 'Line 10', 'Line 11']

它也可以跟随一个文件:

# Follow the file as it grows
for line in tailer.follow(open('test.txt')):
    print line

如果有人想要像尾巴一样的行为,那似乎是一个不错的选择。


1
follow()删除/重新创建后,它不是同一文件,所以对我不起作用:/
Jose Alban

1
@JoseAlban监视文件的删除/创建不是图书馆的责任,make-all-the-things-work-by-themselves而是使用pypi模块
Pavel Vlasov

3

另一个选择是该tailhead库提供Python版本的tailhead以及可在您自己的模块中使用的实用程序和API。

最初基于tailer模块,其主要优点是能够按路径跟踪文件,即它可以处理重新创建文件时的情况。此外,它还针对各种极端情况修复了一些错误。


1

Python是“含电池”-它有一个不错的解决方案:https : //pypi.python.org/pypi/pygtail

读取尚未读取的日志文件行。记住上次完成的位置,并从那里继续。

import sys
from pygtail import Pygtail

for line in Pygtail("some.log"):
    sys.stdout.write(line)

14
必须安装软件包才能获得功能与“包括电池”完全相反。
bfontaine

好吧,幸运的是,并非默认安装了所有软件包。但是,您无需使用子进程来编写(调试和维护)任何棘手的代码,因为业力更高的答案表明。
Peter M.-代表莫妮卡

@Eli-是的,答案中提到了pygtail,但没有示例说明它的易用性。顺便说一句,我赞成你的回答,所以请不要太沮丧:-)
彼得·

1
在Pygtail示例中如何使用--full-lines选项
G.ONE


0

如果您使用的是Linux,则可以通过以下方式在python中实现非阻塞实现。

import subprocess
subprocess.call('xterm -title log -hold -e \"tail -f filename\"&', shell=True, executable='/bin/csh')
print "Done"

1
在Linux上,运行X并安装了csh。那是很多不必要的依赖!
kmarsh
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.