Python StringIO无法正确捕获stderr中的数据

时间:2014-09-23 07:14:43

标签: python unit-testing stderr stringio

我编写了一些单元测试,用于分析使用标准python日志记录功能记录的数据。使用我在这里找到的一些想法:Capture stdout from a script in Python关于如何从stderr捕获数据,我已经提出了以下脚本,我将其简化为最低限度,以说明我遇到的问题。 (下面的循环模拟了可以从各种单元测试中调用此函数的事实)

import logging, sys
from StringIO import StringIO

def get_stderr():
    saved_stderr = sys.stderr

    stderr_string_io = StringIO()
    sys.stderr = stderr_string_io
    try:
        logging.error("Foobar!!!")

    finally:
        # set the stdout and stderr back to their original values
        sys.stderr = saved_stderr

    err_output = stderr_string_io.getvalue()
    return err_output

for x in [1, 2]:
    err_output = get_stderr()
    print  "Run %d: %s" % (x, err_output)

如果运行脚本,它将提供以下输出,其中第二次循环迭代的日志记录输出完全丢失:

Run 1: ERROR:root:Foobar!!!
Run 2: 
Process finished with exit code 0

虽然我希望它能提供以下输出:

Run 1: ERROR:root:Foobar!!!
Run 2: ERROR:root:Foobar!!!
Process finished with exit code 0

注意:在函数末尾执行stderr_string_io.close()不起作用,因为脚本会在下次执行函数时抛出ValueError

为什么此代码的行为不符合预期,解决此问题的解决方案是什么?

1 个答案:

答案 0 :(得分:1)

致电时

logging.error

它运行

def error(msg, *args, **kwargs):
    if len(root.handlers) == 0:
        basicConfig()
    root.error(msg, *args, **kwargs)

由于一开始没有根处理程序,它运行basicConfig而没有参数,这样做:

def basicConfig():
    _acquireLock()
    try:
        if len(root.handlers) == 0:
            h = StreamHandler(None)
            handlers = [h]
            dfs = None
            style = '%'
            fs = kwargs.get("format", _STYLES[style][1])
            fmt = Formatter(fs, dfs, style)
            for h in handlers:
                if h.formatter is None:
                    h.setFormatter(fmt)
                root.addHandler(h)
    finally:
        _releaseLock()

我删除了没有参数时无法运行的代码。

所以这已设置handlers = [StreamHandler(None)]

class StreamHandler(Handler):
    def __init__(self, stream=None):
        Handler.__init__(self)
        if stream is None:
            stream = sys.stderr
        self.stream = stream

这意味着您将顶级记录器永久地附加到您调用它时stdout的任何内容。

这会导致您的问题,因为您丢弃了该输出。这意味着输出将转到死StringIO对象,并丢失。

解决此问题的一种方法是在更新handlers时通过stderr并替换引用stderr的任何内容:

import logging, sys
from StringIO import StringIO

def get_stderr():
    saved_stderr = sys.stderr
    stderr_string_io = StringIO()

    for handler in logging.root.handlers:
        if handler.stream is sys.stderr:
            handler.stream = stderr_string_io

    sys.stderr = stderr_string_io

    try:
        logging.error("Foobar!!!")

    finally:
        # set the stdout and stderr back to their original values
        for handler in logging.root.handlers:
            if handler.stream is sys.stderr:
                handler.stream = saved_stderr

        sys.stderr = saved_stderr

    err_output = stderr_string_io.getvalue()
    return err_output

for x in [1, 2]:
    err_output = get_stderr()
    print  "Run %d: %s" % (x, err_output)

我不知道这会有多好用。它也不会捕获任何不是根记录器的记录器。就个人而言,按价值捕获sys.stdout的想法是荒谬的,这似乎是不可避免的结果。