使用gevent将子进程stdout和stderr重定向到标准记录器

时间:2015-10-21 10:14:40

标签: python windows logging subprocess gevent

我打算用gevent 1.0.1在Python 2.7.10中编写一个子进程管理器。这似乎是相关的,所以我还要注意到我正在使用桌面x64 Windows 10。

子进程管理器的核心功能之一是将子进程的输出重定向到旋转文件。我假设使用标准RotatingFileLogger会使这很容易实现。通过gevent可以实现非阻塞I / O.

我的第一步是写一个概念证明来展示这种方法,但我正在努力学习我认为是基本的gevent概念。

当我运行PoC时,日志文件显得很好,并且在进程运行时它们会被填充,但是当进程结束时,一切都会停止。我想有些事情会陷入僵局。

我编写了以下gevent.Greenlet子类LogPipe,它将从stdoutstderr中提取并将其提供给标准记录器:

class LogPipe(gevent.Greenlet):
    """
    inspiration: http://codereview.stackexchange.com/questions/6567/redirecting-subprocesses-output-stdout-and-stderr-to-the-logging-module/17959#17959
    """
    def __init__(self, logger, level):
        gevent.Greenlet.__init__(self)

        self._logger = logger
        self._level = level

        self._fd_read, self._fd_write = os.pipe()
        self._pipe_reader = os.fdopen(self._fd_read)

    def fileno(self):
        return self._fd_write

    def _run(self):
        for line in iter(self._pipe_reader.readline, b''):
            self._logger.log(self._level, line.strip())
        self._pipe_reader.close()

    def close(self):
        os.close(self._fd_write)

    def __str__(self):
        return "LogPipe for logger {} ({})".format(self._logger.name, gevent.Greenlet.__str__(self))

这里是SubprocessManager,也是gevent.Greenlet的子类,底部是测试用例:

from __future__ import (absolute_import, division,
                        print_function, unicode_literals)
from builtins import *

import os
import logging
import logging.handlers
import gevent
from gevent.subprocess import Popen

class SubprocessManager(gevent.Greenlet):
    def __init__(self, name, command, log_dir=""):
        gevent.Greenlet.__init__(self)

        self._name = name
        self._args = command

        log_dir = log_dir if os.path.exists(log_dir) else os.getcwd()

        formatter = logging.Formatter('%(message)s')

        self._stdout_logger = logging.getLogger("{}.stdout".format(self._name))
        self._stdout_logger.setLevel(logging.INFO)
        stdout_log_file = os.path.join(log_dir, self._stdout_logger.name)
        stdout_handler = logging.handlers.RotatingFileHandler(stdout_log_file, maxBytes=4096, backupCount=5)
        stdout_handler.setFormatter(formatter)
        self._stdout_logger.addHandler(stdout_handler)

        self._stderr_logger = logging.getLogger("{}.stderr".format(self._name))
        self._stderr_logger.setLevel(logging.INFO)
        stderr_log_file = os.path.join(log_dir, self._stderr_logger.name)
        stderr_handler = logging.handlers.RotatingFileHandler(stderr_log_file, maxBytes=4096, backupCount=5)
        stderr_handler.setFormatter(formatter)
        self._stderr_logger.addHandler(stderr_handler)

    def _run(self):
        stdout_pipe = logutil.LogPipe(self._stdout_logger, logging.INFO)
        stdout_pipe.start()
        stderr_pipe = logutil.LogPipe(self._stderr_logger, logging.INFO)
        stderr_pipe.start()
        try:
            sp = Popen(self._args, stdout=stdout_pipe, stderr=stderr_pipe, bufsize=1)
            return_code = sp.wait()
        finally:
            stdout_pipe.close()
            stderr_pipe.close()
        return return_code


if __name__ == "__main__":
    m = SubprocessManager("test", "test.bat")
    m.start()
    gevent.wait()

test.bat

echo "starting"
ping 127.0.0.1 -n 5 -w 1000 > NUL
echo "bla blasdfasdf"
ping 127.0.0.1 -n 5 -w 1000 > NUL
echo "bla blasdfas   df"
ping 127.0.0.1 -n 5 -w 1000 > NUL
echo "done!"

requirements.txt

future
gevent==1.0.1

所以我的问题是,为什么这段代码会死锁?当我使用调试器时,似乎就像readline中对LogPipe的最终调用永远不会返回。

0 个答案:

没有答案