2014-09-23 3 views
2

Я написал несколько unittests, которые анализируют данные, которые регистрируются со стандартной функцией регистрации python. Используя некоторые из идей, которые я нашел здесь: Capture stdout from a script in Python о том, как захватить данные из stderr, я придумал следующий скрипт, который я упростил до минимума, чтобы проиллюстрировать проблему, с которой я столкнулся. (Ниже петля моделирует тот факт, что эта функция может быть вызвана различным UnitTests)Python StringIO неправильно отображает данные из 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() в конце е unction не работает, так как скрипт затем выдает ValueError при следующей функции.

Почему этот код не ведет себя так, как ожидалось, и каково решение этой проблемы?

+1

Это странное поведение. Если вы помещаете 'logging.error (" practicing ")' в верхнюю часть файла, все работает ... но игнорирует замену 'stderr'. – Veedrac

+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 по величине абсурдна, и это кажется неизбежным результатом.

+0

Я согласен с тем, что вы обычно не хотели бы записывать sys.stdout и sys.stderr, однако в этом конкретном случае я пишу unit-test для кода обработки ошибок, который записывает в stderr. Unittest должен проанализировать, что правильные журналы записываются для разных условий ошибки. –

+0

@AlexanderMarquardt Я не имею в виду * ты *, я говорю, что 'logging' является тем, кто абсурден! – Veedrac

+1

В конце концов я решил пойти с другим подходом к захвату журналов. У Python есть некоторые достойные макетные и патч-функции для модульного тестирования, которые позволяют переопределить функцию ведения журнала по умолчанию без необходимости возиться со stdio/stderr. –

Смежные вопросы