subprocess stdout / stderr到有限大小的日志文件

时间:2011-08-23 06:47:54

标签: python unix logging file-io subprocess

我有一个对stderr聊天很多的过程,我想把这些东西记录到一个文件中。

foo 2> /tmp/foo.log

实际上我是用python subprocess.Popen启动的,但是出于这个问题的目的,它也可能来自shell。

with open('/tmp/foo.log', 'w') as stderr:
  foo_proc = subprocess.Popen(['foo'], stderr=stderr)

问题是几天后我的日志文件可能非常大,例如> 500 MB。我对所有stderr聊天感兴趣,但只对最近的东西感兴趣。如何将日志文件的大小限制为1 MB?该文件应该有点像循环缓冲区,因为最新的东西将被写入但旧的东西应该从文件中掉出来,因此它永远不会超过给定的大小。

我不确定是否有一种优雅的Unixey方式可以做到这一点,我根本不知道,有某种特殊文件。

只要我不必中断正在运行的进程,具有日志轮换的替代解决方案也足以满足我的需求。

3 个答案:

答案 0 :(得分:3)

您应该能够使用stdlib日志包来执行此操作。您可以执行以下操作,而不是将子进程的输出直接连接到文件:

import logging

logger = logging.getLogger('foo')

def stream_reader(stream):
    while True:
        line = stream.readline()
        logger.debug('%s', line.strip())

这只记录从流中收到的每一行,您可以使用提供日志文件轮换的RotatingFileHandler配置日志记录。然后,您可以安排读取此数据并进行记录。

foo_proc = subprocess.Popen(['foo'], stderr=subprocess.PIPE)

thread = threading.Thread(target=stream_reader, args=(foo_proc.stderr,))
thread.setDaemon(True) # optional 
thread.start()

# do other stuff

thread.join() # await thread termination (optional for daemons)

当然你也可以调用stream_reader(foo_proc.stderr),但我假设你可能还有其他工作要做,而foo子进程可以做它的东西。

以下是配置日志记录的一种方法(应该只执行一次的代码):

import logging, logging.handlers

handler = logging.handlers.RotatingFileHandler('/tmp/foo.log', 'a', 100000, 10)
logging.getLogger().addHandler(handler)
logging.getLogger('foo').setLevel(logging.DEBUG)

这将创建多达10个名为foo.log的100K文件(并在旋转foo.log.1,foo.log.2等之后,其中foo.log是最新的)。你也可以传入1000000,1来给你一个foo.log和foo.log.1,当文件大小超过1000000字节时会发生旋转。

答案 1 :(得分:1)

循环缓冲区的方式很难实现,因为一旦出现问题,你就不得不重写整个文件。

使用logrotate或其他方法将是您的方法。在这种情况下,你只需要这样做:

import subprocess
import signal

def hupsignal(signum, frame):
    global logfile
    logfile.close()
    logfile = open('/tmp/foo.log', 'a')

logfile = open('/tmp/foo.log', 'a')
signal.signal()
foo_proc = subprocess.Popen(['foo'], stderr=subprocess.PIPE)
for chunk in iter(lambda: foo_proc.stderr.read(8192), ''):
    # iterate until EOF occurs
    logfile.write(chunk)
    # or do you want to rotate yourself?
    # Then omit the signal stuff and do it here.
    # if logfile.tell() > MAX_FILE_SIZE:
    #     logfile.close()
    #     logfile = open('/tmp/foo.log', 'a')

这不是一个完整的解决方案;把它想象为伪代码,因为它未经测试,我不确定这个或其他地方的语法。可能需要进行一些修改才能使其正常工作。但你应该明白这一点。

同样,它是如何使其与logrotate一起使用的一个示例。当然,如果需要,您可以自己轮换日志文件。

答案 2 :(得分:1)

您可以使用“打开文件描述”的属性(与“打开文件描述符”不同,但密切相关)。特别是,当前写入位置与打开文件描述相关联,因此共享单个打开文件描述的两个进程可以分别调整写入位置。

因此,在上下文中,原始进程可以保留子进程标准错误的文件描述符,并且当位置达到1 MiB大小时,定期将指针重新定位到文件的开头,从而实现所需的循环缓冲效应。

最大的问题是确定当前消息的写入位置,以便您可以从最旧的材料(位于文件位置前面)读取到最新的材料。覆盖旧旧的新线不太可能完全匹配,因此会有一些碎片。您可能能够跟踪具有已知字符序列的孩子的每一行(比如'XXXXXX'),然后从子重新定位中每次写入以覆盖前一个标记......但这肯定需要控制正在执行的程序跑。如果它不在您的控制之下,或者无法修改,那么该选项就会消失。

另一种方法是定期截断文件(可能在复制之后),并让子进程以附加模式写入(因为文件在追加模式下在父文件中打开)。您可以安排在截断前将材料从文件复制到备用文件,以保留之前的1 MiB数据。你最多可以使用2 MiB,这比500 MiB好很多,如果你实际上空间不足,可以配置尺寸。

玩得开心!