Как я могу регистрировать свои ошибки Python?
try:
do_something()
except:
# How can I log my exception here, complete with its traceback?
Как я могу регистрировать свои ошибки Python?
try:
do_something()
except:
# How can I log my exception here, complete with its traceback?
Используйте logging.exception
с помощью обработчика except:
для регистрации текущего исключения, добавленного с сообщением.
import logging
LOG_FILENAME = '/tmp/logging_example.out'
logging.basicConfig(filename=LOG_FILENAME, level=logging.DEBUG)
logging.debug('This message should go to the log file')
try:
run_my_stuff()
except:
logging.exception('Got exception on main handler')
raise
Теперь, смотря на файл журнала, /tmp/logging_example.out
:
DEBUG:root:This message should go to the log file
ERROR:root:Got exception on main handler
Traceback (most recent call last):
File "/tmp/teste.py", line 9, in <module>
run_my_stuff()
NameError: name 'run_my_stuff' is not defined
Использовать опции exc_info может быть лучше, остается предупреждение или заголовок ошибки:
try:
# coode in here
except Exception as e:
logging.error(e, exc_info=True)
Моя работа недавно поручила мне регистрировать все трассировки/исключения из нашего приложения. Я пробовал множество техник, которые другие публиковали в Интернете, например, выше, но решил использовать другой подход. Переопределение 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 не будет существовать ни в каких новых потоках моего процесса. Это огромная проблема, поскольку большинство из них происходит в потоках в этом проекте.
После поиска и чтения большого количества документации самая полезная информация, которую я нашел, была от трекера 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 действительно приводят к двум предложенным хакам. Любой подкласс Thread
и оберните метод запуска в нашей собственной попытке, кроме блока, чтобы улавливать и регистрировать исключения или патч обезьяны threading.Thread.run
для запуска в вашей собственной попытке, кроме блока и регистрации исключений.
Первый метод подкласса Thread
кажется мне менее изящным в вашем коде, так как вам придется импортировать и использовать свой собственный Thread
класс EVERYWHERE, в котором вы хотите иметь поток ведения журнала. Это закончилось тем, что мне пришлось искать всю базу кода и заменять все обычные 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", который берет журналы уровня ERROR и отправляет их домой для анализа.
Вы можете записывать все неперехваченные исключения в основном потоке, назначая обработчик sys.excepthook
, возможно, используя exc_info
параметра функций ведения журнала Python:
import sys
import logging
logging.basicConfig(filename='/tmp/foobar.log')
def exception_hook(exc_type, exc_value, exc_traceback):
logging.error(
"Uncaught exception",
exc_info=(exc_type, exc_value, exc_traceback)
)
sys.excepthook = exception_hook
raise Exception('Boom')
Если ваша программа использует потоки, однако обратите внимание, что потоки, созданные с помощью threading.Thread
, не будут запускать sys.excepthook
, когда возникает неперехваченное исключение внутри них, как отмечено в Issue 1230540 в поисковом журнале Python. Некоторые хаки были предложены там, чтобы обойти это ограничение, например, обезглавливание Thread.__init__
, чтобы перезаписать self.run
альтернативным методом run
, который обертывает оригинал в блоке try
и вызывает sys.excepthook
изнутри except
блок. Кроме того, вы можете просто вручную перенести точку входа для каждого из ваших потоков в try
/except
самостоятельно.
Неотправленные сообщения об исключениях отправляются в STDERR, поэтому вместо того, чтобы выполнять вход в Python, вы можете отправить STDERR в файл, используя любую оболочку, которую вы используете для запуска Python script. В Bash script вы можете сделать это с перенаправлением вывода, как описанным в руководстве Bash.
Добавить ошибки в файл, другой вывод на терминал:
./test.py 2>> mylog.log
Перезаписать файл с чередующимися выводами STDOUT и STDERR:
./test.py &> mylog.log
Вот простой пример, взятый из документации python 2.6:
import logging
LOG_FILENAME = '/tmp/logging_example.out'
logging.basicConfig(filename=LOG_FILENAME,level=logging.DEBUG,)
logging.debug('This message should go to the log file')
может быть не так стильно, но проще:
#!/bin/bash
log="/var/log/yourlog"
/path/to/your/script.py 2>&1 | (while read; do echo "$REPLY" >> $log; done)