2011-08-23 77 views
1

我有一个过程,它跟stderr聊天很多,我想将这些东西记录到一个文件中。子过程stdout/stderr到有限大小日志文件

foo 2> /tmp/foo.log 

其实我与Python subprocess.Popen启动它,但它也可以被从外壳为这一问题的目的。

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

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

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

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

回答

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)过,但我假设你可能有其他的工作要做,而富子做它的东西。

这里是你可以配置日志(代码应该只执行一次)的一种方法:

import logging, logging.handlers 

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

这将创造多达100K命名foo.log的10个文件(和旋转foo.log.1后,foo.log.2等,其中foo.log是最新的)。您也可以通过在1000000 1给你只是foo.log和foo.log.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一起工作的一个例子。当然,如果需要,您可以自己旋转日志文件。

1

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

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

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

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

玩得开心!