【问题标题】:Python StringIO is not correctly capturing data from stderrPython StringIO 未正确从 stderr 捕获数据
【发布时间】:2014-11-17 07:22:15
【问题描述】:

我编写了一些单元测试来分析使用标准 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

为什么这段代码的行为不像预期的那样,纠正这个问题的解决方案是什么?

【问题讨论】:

  • 这确实是奇怪的行为。如果您将logging.error("practicing") 放在文件顶部,则一切正常...但忽略您替换stderr
  • 是的,这很奇怪!

标签: python unit-testing stderr stringio


【解决方案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 对象,并丢失。

解决此问题的一种方法是在更新stderr 时通过handlers 并替换任何引用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 的想法是荒谬的,这似乎是必然的结果。

【讨论】:

  • 我同意您通常不想捕获 sys.stdout 和 sys.stderr,但是在这种特殊情况下,我正在为写入 stderr 的错误处理代码编写单元测试。单元测试必须分析为不同的错误条件写入了正确的日志。
  • @AlexanderMarquardt 我不是指,我是说logging是荒谬的!
  • 最后我决定采用不同的方法来捕获日志。 Python 有一些不错的单元测试模拟和补丁功能,允许您覆盖默认的日志记录功能,而不必乱用 stdio/stderr。
猜你喜欢
  • 1970-01-01
  • 2014-10-23
  • 1970-01-01
  • 2016-01-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-06-28
  • 2013-09-02
相关资源
最近更新 更多