Записать исключение с помощью traceback


Ответы:


203

Используйте 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

1
Посмотрел код django для этого, и я предполагаю, что ответ - нет, но есть ли способ ограничить обратную трассировку определенным количеством символов или глубины? Проблема в том, что для больших трассировок это занимает довольно много времени.
Эдуард Лука

10
Обратите внимание, что если вы определяете регистратор с logger = logging.getLogger('yourlogger')вами, вы должны написать, logger.exception('...')чтобы это работало ...
576i

Можем ли мы изменить это так, чтобы сообщение печаталось с уровнем журнала INFO?
NM

Обратите внимание, что для некоторых внешних приложений, таких как Azure Insight, трекбек не сохраняется в журналах. Затем необходимо передать их явно в строку сообщения, как показано ниже.
Эдгар Х

139

exc_infoВарианты использования могут быть лучше, остается предупреждение или название ошибки:

try:
    # coode in here
except Exception as e:
    logging.error(e, exc_info=True)

Я никогда не могу вспомнить, как exc_info=называется kwarg; Спасибо!
Берто

4
Это идентично logging.exception, за исключением того, что тип регистрируется с избыточностью дважды. Просто используйте logging.exception, если вы не хотите уровень, отличный от ошибки.
Wyrmwood

@ Wyrmwood это не идентично, так как вы должны послать сообщениеlogging.exception
Питер Вуд

58

Недавно моя работа поручила мне регистрировать все трассировки / исключения из нашего приложения. Я перепробовал множество техник, которые другие опубликовали в сети, например, выше, но остановился на другом подходе. Перекрытие 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 действительно приводят к двум предложенным хаки. Либо создайте подкласс Threadи оберните метод run в нашем собственном try, кроме block, чтобы перехватывать и регистрировать исключения, либо monkey patch threading.Thread.runдля запуска в вашем собственном try, кроме block и регистрировать исключения.

Первый метод создания подклассов 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', который берет журналы уровня ОШИБКИ и отправляет их домой для анализа.


2
Довольно интересный подход. Один вопрос - add_custom_print_exceptionне похоже на сайт, на который вы ссылаетесь, и вместо этого есть несколько иной конечный код. Какой из них вы бы назвали лучшим / более окончательным и почему? Спасибо!
Фантабол

Спасибо, отличный ответ!
101

Существует опечатка. на делегированном вызове old_print_exception предел и файл должны быть переданы limit и file, а не None - old_print_exception (etype, value, tb, limit, file)
Марвин

Для вашего последнего блока кода вместо инициализации StringIO и вывода на него исключения вы можете просто вызвать logger.error(traceback.format_tb())(или format_exc (), если вы тоже хотите получить информацию об исключении).
Джеймс

8

Вы можете регистрировать все неперехваченные исключения в главном потоке, назначив обработчик 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когда внутри них возникает неперехваченное исключение, как отмечено в выпуске 1230540 в системе отслеживания проблем Python. Там было предложено несколько хаков, чтобы обойти это ограничение, например, monkey-patching Thread.__init__для перезаписи self.runальтернативным runметодом, который оборачивает оригинал в tryблок и вызывает sys.excepthookизнутри exceptблока. Кроме того, вы можете просто вручную обернуть точку входа для каждого из ваших потоков в try/ exceptсебя.


3

Необработанные сообщения об исключениях отправляются в STDERR, поэтому вместо того, чтобы осуществлять регистрацию в самом Python, вы можете отправить STDERR в файл, используя любую оболочку, которую вы используете для запуска скрипта Python. В сценарии Bash это можно сделать с помощью перенаправления вывода, как описано в руководстве по BASH .

Примеры

Добавить ошибки в файл, другой вывод в терминал:

./test.py 2>> mylog.log

Перезаписать файл с чередованием вывода STDOUT и STDERR:

./test.py &> mylog.log


2

Вы можете получить трассировку с помощью регистратора на любом уровне (DEBUG, INFO, ...). Обратите внимание, что при использовании logging.exception, уровень является ОШИБКА.

# test_app.py
import sys
import logging

logging.basicConfig(level="DEBUG")

def do_something():
    raise ValueError(":(")

try:
    do_something()
except Exception:
    logging.debug("Something went wrong", exc_info=sys.exc_info())
DEBUG:root:Something went wrong
Traceback (most recent call last):
  File "test_app.py", line 10, in <module>
    do_something()
  File "test_app.py", line 7, in do_something
    raise ValueError(":(")
ValueError: :(

РЕДАКТИРОВАТЬ:

Это тоже работает (с использованием Python 3.6)

logging.debug("Something went wrong", exc_info=True)

1

Вот версия, которая использует sys.excepthook

import traceback
import sys

logger = logging.getLogger()

def handle_excepthook(type, message, stack):
     logger.error(f'An unhandled exception occured: {message}. Traceback: {traceback.format_tb(stack)}')

sys.excepthook = handle_excepthook

Как насчет использования {traceback.format_exc()}вместо {traceback.format_tb(stack)}?
переменная

0

возможно не так стильно, но проще

#!/bin/bash
log="/var/log/yourlog"
/path/to/your/script.py 2>&1 | (while read; do echo "$REPLY" >> $log; done)

-1

Вот простой пример, взятый из документации по 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')

4
Вопрос заключался в том, как записать трассировку
Константин Шуберт
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.