【问题标题】:Python logging and subprocess ouput and error streamPython 日志记录和子进程输出和错误流
【发布时间】:2012-02-02 14:39:40
【问题描述】:

我想启动一个 python 进程并将子进程错误消息记录到父脚本的日志对象中。理想情况下,我希望将日志流统一到一个文件中。我可以以某种方式访问​​日志记录类的输出流吗?我知道的一种解决方案是使用 proc log 进行日志记录。如下面的答案所述,我可以从 proc.stdin 和 stderr 中读取数据,但我会有重复的日志记录头。不知道有没有办法将日志类底层的文件描述符直接传递给子进程?

logging.basicConfig(filename="test.log",level=logging.DEBUG)
logging.info("Started")
procLog = open(os.path.expanduser("subproc.log"), 'w')
proc = subprocess.Popen(cmdStr, shell=True, stderr=procLog, stdout=procLog)
proc.wait()
procLog.flush()

【问题讨论】:

    标签: python logging


    【解决方案1】:

    基于Adam Rosenfield's code,你可以

    1. 使用select.select 阻塞,直到有输出可供读取 proc.stdoutproc.stderr,
    2. 读取并记录该输出,然后
    3. 重复该过程直到完成。

    请注意,以下内容写入/tmp/test.log 并运行命令ls -laR /tmp。根据您的需要进行更改。

    (PS:通常 /tmp 包含普通用户无法读取的目录,因此运行 ls -laR /tmp 会同时向 stdout 和 stderr 生成输出。下面的代码在生成这两个流时正确地交错了它们。)

    import logging
    import subprocess
    import shlex
    import select
    import fcntl
    import os
    import errno
    import contextlib
    
    logger = logging.getLogger(__name__)
    
    def make_async(fd):
        '''add the O_NONBLOCK flag to a file descriptor'''
        fcntl.fcntl(fd, fcntl.F_SETFL, fcntl.fcntl(fd, fcntl.F_GETFL) | os.O_NONBLOCK)
    
    def read_async(fd):
        '''read some data from a file descriptor, ignoring EAGAIN errors'''
        try:
            return fd.read()
        except IOError, e:
            if e.errno != errno.EAGAIN:
                raise e
            else:
                return ''
    
    def log_fds(fds):
        for fd in fds:
            out = read_async(fd)
            if out:
                logger.info(out)
    
    @contextlib.contextmanager
    def plain_logger():
        root = logging.getLogger()    
        hdlr = root.handlers[0]
        formatter_orig = hdlr.formatter
        hdlr.setFormatter(logging.Formatter('%(message)s'))
        yield 
        hdlr.setFormatter(formatter_orig)
    
    def main():
        # fmt = '%(name)-12s: %(levelname)-8s %(message)s'
        logging.basicConfig(filename = '/tmp/test.log', mode = 'w',
                            level = logging.DEBUG)
    
        logger.info("Started")
        cmdStr = 'ls -laR /tmp'
    
        with plain_logger():
            proc = subprocess.Popen(shlex.split(cmdStr),
                                    stdout = subprocess.PIPE, stderr = subprocess.PIPE)
            # without `make_async`, `fd.read` in `read_async` blocks.
            make_async(proc.stdout)
            make_async(proc.stderr)
            while True:
                # Wait for data to become available 
                rlist, wlist, xlist = select.select([proc.stdout, proc.stderr], [], [])
                log_fds(rlist)
                if proc.poll() is not None:
                    # Corner case: check if more output was created
                    # between the last call to read_async and now                
                    log_fds([proc.stdout, proc.stderr])                
                    break
    
        logger.info("Done")
    
    if __name__ == '__main__':
        main()
    

    编辑:

    您可以将stdoutstderr 重定向到logfile = open('/tmp/test.log', 'a')。 但是,这样做的一个小困难是,任何同时写入/tmp/test.log 的记录器处理程序都不会知道子进程正在写入什么,因此日志文件可能会出现乱码。

    如果您在子流程执行其业务时不进行记录调用,那么唯一的问题是在子流程完成后记录器处理程序在文件中的位置错误。这可以通过调用来修复

    handler.stream.seek(0, 2)
    

    因此处理程序将在文件末尾继续写入。


    import logging
    import subprocess
    import contextlib
    import shlex
    
    logger = logging.getLogger(__name__)
    
    @contextlib.contextmanager
    def suspended_logger():
        root = logging.getLogger()    
        handler = root.handlers[0]
        yield 
        handler.stream.seek(0, 2)
    
    def main():
        logging.basicConfig(filename = '/tmp/test.log', filemode = 'w',
                            level = logging.DEBUG)
    
        logger.info("Started")
        with suspended_logger():
            cmdStr = 'test2.py 1>>/tmp/test.log 2>&1'
            logfile = open('/tmp/test.log', 'a')
            proc = subprocess.Popen(shlex.split(cmdStr),
                                    stdout = logfile,
                                    stderr = logfile)
            proc.communicate()
        logger.info("Done")
    
    if __name__ == '__main__':
        main()
    

    【讨论】:

    • 这可行,但我试图附加到日志文件的输出是来自另一个程序的记录器流。所以,如果我这样做,我会在每行得到两个时间戳(如果不清楚,请告诉我)。那么,有没有办法直接写入可靠的日志文件描述符......绕过日志前缀?
    • 您可以暂时将日志记录格式更改为'%(message)s'。我在上面添加了一些代码来做到这一点。
    • 嗯,是的,这行得通,但是如果我可以将文件描述符传递给进程,那不是更优雅吗?真的不行吗。
    • 我认为您的原始解决方案是最简单的。我现在看到了主要使用记录器的优势......更好地格式化和控制流......甚至可以将流“发球”到控制台。谢谢。
    • 好的,我会将我原来的解决方案添加回帖子中。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-04-13
    • 2020-01-12
    • 2016-11-12
    • 2012-09-12
    • 1970-01-01
    • 2013-06-12
    • 1970-01-01
    相关资源
    最近更新 更多