Игнорировать и регистрировать ошибку с помощью contextlib contextmanager - PullRequest
0 голосов
/ 08 мая 2018

Я хочу, чтобы менеджер контекста перехватил исключение, распечатал трассировку стека и затем разрешил продолжить выполнение.

Я хочу знать, смогу ли я сделать это с помощью contextlib contextmanager decorator. Если нет, то как я могу это сделать?

Документация предлагает следующее:

В тот момент, когда генератор дает результат, выполняется блок, вложенный в оператор with. Затем генератор возобновляется после выхода из блока. Если в блоке возникает необработанное исключение, оно повторно вызывается внутри генератора в точке, где произошел выход. Таким образом, вы можете использовать оператор try… кроме… finally, чтобы перехватить ошибку (если она есть) или убедиться, что какая-то очистка произошла. Если исключение захвачено просто для того, чтобы зарегистрировать его или выполнить какое-либо действие (а не полностью его подавить), генератор должен повторно вызвать это исключение.

Итак, я пробую очевидный подход, к которому ведет меня документация:

import contextlib
import logging


@contextlib.contextmanager
def log_error():
    try:
        yield
    except Exception as e:
        logging.exception('hit exception')
    finally:
        print 'done with contextmanager'


def something_inside_django_app():
    with log_error():
        raise Exception('alan!')


something_inside_django_app()


print 'next block of code'

Это производит вывод

ERROR:root:hit exception
Traceback (most recent call last):
  File "exception_test.py", line 8, in log_error
    yield
  File "exception_test.py", line 17, in something_inside_django_app
    raise Exception('alan!')
Exception: alan!
done with contextmanager
next block of code

Это теряет важную информацию о том, откуда возникло исключение. Подумайте, что вы получите, когда вы настроите менеджер контекста на , а не и исключение:

Traceback (most recent call last):
  File "exception_test.py", line 20, in <module>
    something_inside_django_app()
  File "exception_test.py", line 17, in something_inside_django_app
    raise Exception('alan!')
Exception: alan!

Да, мне удалось сказать, что исключение было поднято из строки 17, большое спасибо, но предыдущий вызов в строке 20 потерял информацию. Как сделать так, чтобы менеджер контекста дал мне фактический полный стек вызовов, а не его усеченную версию? Напомним, я хочу выполнить два требования:

  • имеет менеджер контекста python, подавляющий исключение, возникающее в коде, который он переносит
  • распечатать трассировку стека, которая была бы сгенерирована этим кодом, если бы я не использовал менеджер контекста

Если это невозможно сделать с помощью декоратора, тогда я буду использовать другой стиль диспетчера контекста. Если это невозможно сделать с помощью менеджеров контекста, точка, я хотел бы знать, что такое хорошая питоновская альтернатива.

1 Ответ

0 голосов
/ 25 мая 2018

Я обновил свое решение для этой проблемы здесь:

https://gist.github.com/AlanCoding/288ee96b60e24c1f2cca47326e2c0af1

Было больше контекста, который пропустил вопрос. Чтобы получить полный стек в точке исключения, нам понадобится и обратная трассировка, возвращенная диспетчеру контекста, и текущий контекст. Затем мы можем склеить вершину стека с дном стека.

Чтобы лучше проиллюстрировать пример использования, рассмотрим следующее:

def err_method1():
    print [1, 2][4]


def err_method2():
    err_method1()


def outside_method1():
    with log_error():
        err_method2()


def outside_method2():
    outside_method1()

outside_method2()

Чтобы действительно выполнить то, что ищет этот вопрос, мы хотим видеть оба внешних метода и оба внутренних метода в стеке вызовов.

Вот решение, которое действительно работает для этого:

class log_error(object):

    def __enter__(self):
        return

    def __exit__(self, exc_type, exc_value, exc_traceback):
        if exc_value:
            # We want the _full_ traceback with the context, so first we
            # get context for the current stack, and delete the last 2
            # layers of context, saying that we're in the __exit__ method...
            top_stack = StringIO.StringIO()
            tb.print_stack(file=top_stack)
            top_lines = top_stack.getvalue().strip('\n').split('\n')[:-4]
            top_stack.close()
            # Now, we glue that stack to the stack from the local error
            # that happened within the context manager
            full_stack = StringIO.StringIO()
            full_stack.write('Traceback (most recent call last):\n')
            full_stack.write('\n'.join(top_lines))
            full_stack.write('\n')
            tb.print_tb(exc_traceback, file=full_stack)
            full_stack.write('{}: {}'.format(exc_type.__name__, str(exc_value)))
            sinfo = full_stack.getvalue()
            full_stack.close()
            # Log the combined stack
            logging.error('Log message\n{}'.format(sinfo))
        return True

Отслеживание выглядит так:

ERROR:root:Log message
Traceback (most recent call last):
  File "exception_test.py", line 71, in <module>
    outside_method2()
  File "exception_test.py", line 69, in outside_method2
    outside_method1()
  File "exception_test.py", line 65, in outside_method1
    err_method2()
  File "exception_test.py", line 60, in err_method2
    err_method1()
  File "exception_test.py", line 56, in err_method1
    print [1, 2][4]
IndexError: list index out of range

Это та же информация, которую вы ожидаете от выполнения logging.exception в попытке - за исключением того же кода, который вы переносите в менеджере контекста.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...