Рефакторинг обработки ошибок и получить правильную трассировку стека в Python

Я получил много кода, как это:

try:
   # do a lot of stuff
except StuffError as e:
    log.exception(e):
    send_mail_to_admin()
    raise e

Для СУХОГО я хотел бы преобразовать это в:

def post_mortem(log, e):
    log.exception(e):
    send_mail_to_admin() 
    # some other stuff
    raise e

Затем сделайте:

try:
   # do a lot of stuff
except StuffError as e:
    post_mortem(log, e)

Проблема в том, что теперь я не получаю правильную трассировку стека, так как исключение возникает из другого файла.

Как я могу получить ту же трассировку стека, которую я имел бы с первым кодом?

2 ответа

Решение

.. Вы хотели это СУХОЙ? Попробуйте это тогда:)

class ReportExceptions(object):
    def __enter__(self):
        pass

    def __exit__(self, exc_type, exc_value, tb):
        if exc_type is not None:
            print("Sending mail to admin about {0!r}".format(exc_value))

И используйте это так:

with ReportExceptions():
    # your code here..

С помощью диспетчера контекста вам не нужно заботиться о повторном возбуждении исключений, сохранении трассировок или других вещей: __exit__() Метод будет выполнен несмотря ни на что, передав вам необходимую информацию. Если вы ничего не делаете с этим (например. return True) исключение просто продолжит свой путь..

Примечание: вы можете создать экземпляр диспетчера контекста только один раз, например:

class ReportExceptions(object):
    def __init__(self, email):
        self.email = email

    def __enter__(self):
        pass

    def __exit__(self, exc_type, exc_value, tb):
        if exc_type is not None:
            print("Sending mail to {0!r} about {1!r}"
                  "".format(self.email, exc_value))

report_exceptions = ReportExceptions('foo@bar.com')

затем:

with report_exceptions:
    # ..your code here..

К вашему сведению: повышение исключения с помощью пользовательской трассировки

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

try:
    100 / 0
except ZeroDivisionError, e:
    exc_info = sys.exc_info()

...позже...

raise exc_info[0], exc_info[1], exc_info[2]

(Синтаксис на самом деле raise expression, expression, expressionЯ не могу придумать, как лучше использовать кортеж напрямую...)

Осматривать кадры вдоль пути

Вы можете получить доступ tb атрибуты для проверки выполнения фреймов вдоль пути, например, местные жители из "самой внешней" точки tb.tb_frame.f_localsвнутренняя рамка находится на tb.tb_next.tb_frame, так далее...

(Вы также можете использовать inspect модуль, см. /questions/46211547/inspecttrace-vs-traceback/46211555#46211555)

Пройти exc_info() информация также, как один из параметров, подобных этому

post_mortem(log, e, sys.exc_info()[2])

И в post_mortem

def post_mortem(log, e, traceBack):
    traceback.print_tb(traceBack)

Чтобы получить всю трассировку стека, вы можете сделать так, как показано в этом примере

import traceback, sys

def post_mortem(log, e, tb):
    print "".join(traceback.format_list(traceback.extract_stack()[:-2]) + [traceback.format_tb(tb)[0]])

def throwError():
    try:
        raise NameError("I don't like your name")
    except NameError as e:
        post_mortem("", e, sys.exc_info()[2])

def callThrowError():
    throwError()

callThrowError()

Выход

  File "/home/thefourtheye/Desktop/Test.py", line 15, in <module>
    callThrowError()
  File "/home/thefourtheye/Desktop/Test.py", line 13, in callThrowError
    throwError()
  File "/home/thefourtheye/Desktop/Test.py", line 8, in throwError
    raise NameError("I don't like your name")
Другие вопросы по тегам