为什么覆盖sys.stdout不适用于来自multiprocessing.Process的输出?

时间:2018-10-20 22:58:00

标签: python logging

我将标准输出重定向到记录器,现在我使用multiprocessing.Process生成了一个进程。但是,即使将进程stdout重定向到父stdout,它也会忽略sys.stdout覆盖。这是一个示例:

import multiprocessing
import sys
import logging

def worker():
    print('Hello from the multiprocessing')
    sys.stdout.flush()

class LoggerWriter:
    def __init__(self, logger, level):
        self.logger = logger
        self.level = level

    def write(self, message):
        if message != '\n':
            self.logger.log(self.level, "LOGGER: "+message)

    def flush(self):
        pass


if __name__ == '__main__':
    logging.basicConfig(level=logging.INFO, format='%(message)s')
    sys.stdout = LoggerWriter(logging.getLogger(), logging.INFO)
    p = multiprocessing.Process(target=worker)
    print("Hello from main")
    p.start()
    p.join()

我希望它能打印

LOGGER: Hello from main
LOGGER: Hello from the multiprocessing

但是我得到

LOGGER: Hello from main
Hello from the multiprocessing

它完全忽略了sys.stdout ...为什么?第一种情况可以解决吗?

注意:这是在Windows 7上-似乎可能起作用。

2 个答案:

答案 0 :(得分:1)

您使用的是Windows,因此您正在使用spawn方法来启动多处理工作程序。此方法从头开始全新的Python解释器,并在开始工作之前将脚本作为模块导入。

由于您的工作人员是从头开始,而不是从头开始,因此他们不会自动继承主流程执行的设置,包括像stdout包装程序这样的设置,并且由于它们位于{{1} }后卫。他们只有常规的if __name__ == '__main__'

您可能必须安排工作人员设置自己的标准输出包装程序,也许是将包装程序设置放在sys.stdout防护程序之外。

答案 1 :(得分:0)

基于评论和@ user2357112的回答,我最终使用了threading.Thread来处理子进程(通过Queue交换)中的日志,而仍然使用Process进行实际工作。这是为了防止有人需要类似的东西。

基本添加之后:

class LoggedProcess(multiprocessing.Process):

    class LoggerWriter:
        def __init__(self, queue):
            self.queue = queue

        def write(self, message):
            for line in message.rstrip().splitlines():
                self.queue.put(line.rstrip())

        def flush(self):
            pass

    @staticmethod
    def logged_worker(logger_queue, worker, *args, **kwargs):
        import sys
        sys.stdout = sys.stderr = LoggedProcess.LoggerWriter(logger_queue)
        logging.basicConfig(format="%(message)s", level=logging.INFO)
        try:
            worker(*args, **kwargs)
        except:
            pass
        logger_queue.put(None)

    @staticmethod
    def process_logger(process, logger_queue, name):
        while True:
            try:
                if not process.is_alive():
                    raise EOFError()
                msg = logger_queue.get(timeout=1)
                if msg is None:
                    raise EOFError()
                logging.getLogger().log(logging.INFO, f"[PROCESS {process.pid} {name}] {msg}")
            except queue.Empty:
                pass # timeout
            except Exception:
                break # queue closed

    def __init__(self, target, log_name='', args=(), kwargs={}):
        self.logger_queue = multiprocessing.Queue()
        self.log_name = log_name
        super().__init__(target=self.logged_worker, args=(self.logger_queue, target, *args), kwargs=kwargs)


    def start(self):
        super().start()
        logger_t = threading.Thread(target=self.process_logger, args=(self, self.logger_queue, self.log_name))
        logger_t.setDaemon(True)
        logger_t.start()

    def terminate(self):
        super().terminate()
        super().join()
        self.logger_queue.put(None)

我们可以将p = multiprocessing.Process(target=worker)替换为p = LoggedProcess(target=worker)。现在,子进程日志被混合到父进程记录器中,在这种情况下,会导致

LOGGER: Hello from main
[PROCESS 5000 ] Hello from the multiprocessing

我已经添加了一些其他流程信息,但是原始意图仍然可以满足,现在,它可以例如由父流程将其全部放入相同的单个日志文件中,或者需要执行任何其他操作。