我有一个对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方式可以做到这一点,我根本不知道,有某种特殊文件。
只要我不必中断正在运行的进程,具有日志轮换的替代解决方案也足以满足我的需求。
答案 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好很多,如果你实际上空间不足,可以配置尺寸。
玩得开心!