分别记录stdout和stderr时截断的输出日志文件

时间:2019-09-27 16:00:38

标签: python python-3.x logging subprocess stdout

我在上下文管理器中设置了一个子进程命令,该命令通过我自己的记录器将stdout和stderr传递给单独的文件。这是此处给出答案的变体: https://stackoverflow.com/a/4838875/4844311

我的代码如下:

import logging
import subprocess

with StreamLogger(logging.DEBUG, my_out_logger) as out:
    with StreamLogger(logging.ERROR, my_err_logger) as err:
        p = subprocess.Popen(cmd, shell=False, stdout=out, stderr=err)
        p.communicate()
        p.wait()

其中my_out_loggermy_err_logger是记录对象的句柄,这些句柄记录到文件等。

StreamLogger代码类似于上面链接中给出的代码:

import io
import os
import threading
import select
import time

class StreamLogger(io.IOBase):
    def __init__(self, level, logger):
        self.logger = logger
        self.level = level
        self.pipe = os.pipe()
        self.thread = threading.Thread(target=self._flusher)
        self.thread.start()

    def _flusher(self):
        self._run = True
        buf = b''
        while self._run:
            for fh in select.select([self.pipe[0]], [], [], 0)[0]:
                buf += os.read(fh, 1024)
                while b'\n' in buf:
                    data, buf = buf.split(b'\n', 1)
                    self.write(data.decode())
            time.sleep(0.01)
        self._run = None

    def write(self, data):
        return self.logger.log(self.level, data)

    def fileno(self):
        return self.pipe[1]

    def close(self):
        if self._run:
            self._run = False
            while self._run is not None:
                time.sleep(0.01)
            os.close(self.pipe[0])
            os.close(self.pipe[1])

我的代码与上面链接中答案提供的代码之间的唯一显着区别是,我的代码将日志记录消息发送到记录器,该记录器根据其句柄进行重定向,而不是直接按照链接中的代码进行记录

这段代码在大多数情况下都可以正常工作。

但是我注意到,每隔一段时间就有一个被截断的输出日志文件。似乎在写入所有标准输出内容之前,已关闭由FileHandler中的my_out_logger写入的输出文件。

我不确定为什么会这样或在哪里修复代码。现在,我刚刚在time.sleep(0.3)p.communicate()之间添加了p.wait()语句,这减少了被截断文件的频率,但这似乎是一个丑陋的解决方案。

我宁愿了解出了什么问题并正确解决。我欢迎任何建议或见识。

1 个答案:

答案 0 :(得分:0)

我想我终于明白了。 这里的问题是StreamLogger代码无法显式检查以确保stdout已完全写入。一旦运行subprocess的主线程收到returncode,它就会退出上下文管理器,并调用__exit__()的{​​{1}}方法,该方法继承自StreamLogger (源代码here)。然后调用IOBase,将close()属性更改为self._run。这将使正在轮询管道的线程停止循环,无论管道中仍在经过什么。

这对于大多数输出​​到False的中小型命令来说效果很好,在stdout返回和写入输出之间没有延迟时间。但就我而言,我正在通过returncode运行程序,其中大量文本写入了subprocess。因此,在主线程告诉子线程停止轮询管道之前,有一种争分夺秒的尝试来清理管道。

此处的两个关键变量是从管道读取的缓冲区的大小以及轮询管道的频率。通过将stdout中的缓冲区大小增加到4096,并在os.read()方法的time.sleep()循环中删除了while,解决了我的问题。这样可以最大化从管道读取的数据量,在我的情况下,可以确保在日志记录循环停止之前完全记录了输出。