Моя работа недавно поручила мне регистрировать все трассировки / исключения из нашего приложения. Я перепробовал множество техник, которые другие опубликовали в сети, например, выше, но остановился на другом подходе. Переопределение traceback.print_exception
.
У меня есть запись на http://www.bbarrows.com/ Это было бы намного легче читать, но я также вставлю ее сюда.
Когда мне было поручено записывать в журнал все исключения, с которыми наше программное обеспечение может столкнуться в дикой природе, я попытался использовать несколько различных методов для регистрации наших трассировок исключений Python. Сначала я подумал, что хук системных исключений python, sys.excepthook, будет идеальным местом для вставки кода регистрации. Я пытался что-то похожее на:
import traceback
import StringIO
import logging
import os, sys
def my_excepthook(excType, excValue, traceback, logger=logger):
logger.error("Logging an uncaught exception",
exc_info=(excType, excValue, traceback))
sys.excepthook = my_excepthook
Это сработало для основного потока, но вскоре я обнаружил, что мой sys.excepthook не будет существовать ни в каких новых потоках, запущенных моим процессом. Это огромная проблема, потому что большинство всего происходит в потоках в этом проекте.
После поиска в Google и прочтения большого количества документации, самая полезная информация, которую я нашел, была от трекера Python Issue.
В первом сообщении в теме показан рабочий пример sys.excepthook
НЕ сохраняющегося во всех потоках (как показано ниже). Видимо, это ожидаемое поведение.
import sys, threading
def log_exception(*args):
print 'got exception %s' % (args,)
sys.excepthook = log_exception
def foo():
a = 1 / 0
threading.Thread(target=foo).start()
Сообщения в этом потоке Python Issue действительно приводят к 2 предложенным хаки. Либо подкласс Thread
и оберните метод run в нашу собственную попытку, кроме блока, чтобы перехватить и записать в журнал исключения, или обезьянький патч threading.Thread.run
для запуска в своей собственной попытке, кроме блока и зарегистрировать исключения.
Первый метод создания подклассов Thread
кажется мне менее изящным в вашем коде, так как вам придется импортировать и использовать пользовательский класс Thread
ВЕЗДЕ, где вы хотите создать поток ведения журнала. Это оказалось хлопотным, потому что мне пришлось искать всю нашу кодовую базу и заменять все обычные Threads
на эти Thread
. Однако было ясно, что делает Thread
, и кому-то будет проще диагностировать и отлаживать, если что-то пойдет не так с пользовательским кодом регистрации. Обычная ветка может выглядеть так:
class TracebackLoggingThread(threading.Thread):
def run(self):
try:
super(TracebackLoggingThread, self).run()
except (KeyboardInterrupt, SystemExit):
raise
except Exception, e:
logger = logging.getLogger('')
logger.exception("Logging an uncaught exception")
Второй метод исправления обезьяны threading.Thread.run
хорош, потому что я мог бы просто запустить его один раз сразу после __main__
и обработать мой код регистрации во всех исключениях. Патч обезьяны может быть раздражающим для отладки, поскольку он изменяет ожидаемую функциональность чего-либо. Предлагаемый патч от трекера Python Issue:
def installThreadExcepthook():
"""
Workaround for sys.excepthook thread bug
From
http://spyced.blogspot.com/2007/06/workaround-for-sysexcepthook-bug.html
(https://sourceforge.net/tracker/?func=detail&atid=105470&aid=1230540&group_id=5470).
Call once from __main__ before creating any threads.
If using psyco, call psyco.cannotcompile(threading.Thread.run)
since this replaces a new-style class method.
"""
init_old = threading.Thread.__init__
def init(self, *args, **kwargs):
init_old(self, *args, **kwargs)
run_old = self.run
def run_with_except_hook(*args, **kw):
try:
run_old(*args, **kw)
except (KeyboardInterrupt, SystemExit):
raise
except:
sys.excepthook(*sys.exc_info())
self.run = run_with_except_hook
threading.Thread.__init__ = init
Только когда я начал тестировать свою регистрацию исключений, я понял, что я все делал неправильно.
Для проверки я поместил
raise Exception("Test")
где-то в моем коде. Однако перенос метода, вызвавшего этот метод, был попыткой, за исключением блока, который распечатывал трассировку и проглотил исключение. Это очень расстраивало, потому что я видел, как трассировку принесли в STDOUT, но не регистрировали. Тогда я решил, что гораздо более простой метод регистрации трассировок заключается в том, чтобы просто пропатчить метод, который весь код python использует для печати самих трассировок traceback.print_exception.
Я закончил с чем-то похожим на следующее:
def add_custom_print_exception():
old_print_exception = traceback.print_exception
def custom_print_exception(etype, value, tb, limit=None, file=None):
tb_output = StringIO.StringIO()
traceback.print_tb(tb, limit, tb_output)
logger = logging.getLogger('customLogger')
logger.error(tb_output.getvalue())
tb_output.close()
old_print_exception(etype, value, tb, limit=None, file=None)
traceback.print_exception = custom_print_exception
Этот код записывает трассировку в String Buffer и записывает ее в журнал ERROR. У меня есть собственный обработчик журналов, который настроил регистратор 'customLogger', который берет журналы уровня ОШИБКИ и отправляет их домой для анализа.