Регистрация неперехваченных исключений в Python

181

Как вы вызываете необработанные исключения для вывода через loggingмодуль, а не для stderr?

Я понимаю, что лучший способ сделать это будет:

try:
    raise Exception, 'Throwing a boring exception'
except Exception, e:
    logging.exception(e)

Но моя ситуация такова, что было бы очень хорошо, если бы logging.exception(...)они вызывались автоматически всякий раз, когда исключение не было обнаружено.

Джейкоб Мрамор
источник

Ответы:

143

Как указал Нед, sys.excepthookон вызывается каждый раз, когда возникает исключение и не выполняется. Практическое следствие этого заключается в том, что в вашем коде вы можете переопределить поведение по умолчанию, sys.excepthookчтобы делать все, что вы хотите (включая использование logging.exception).

Как пример соломенного человека:

>>> import sys
>>> def foo(exctype, value, tb):
...     print 'My Error Information'
...     print 'Type:', exctype
...     print 'Value:', value
...     print 'Traceback:', tb
... 

Переопределить sys.excepthook:

>>> sys.excepthook = foo

Зафиксируйте очевидную синтаксическую ошибку (не указывайте двоеточие) и получите информацию об ошибке:

>>> def bar(a, b)
My Error Information
Type: <type 'exceptions.SyntaxError'>
Value: invalid syntax (<stdin>, line 1)
Traceback: None

Для получения дополнительной информации sys.excepthookчитайте документы .

Jacinda
источник
4
@Codemonkey Это не зарезервированное ключевое слово, это уже существующее имя типа. Вы можете использовать typeв качестве аргумента функции, хотя IDE будут жаловаться на сокрытие глобального type(очень похоже на использование var self = thisв Javascript). Это на самом деле не имеет значения, если вам не нужен доступ к typeобъекту внутри вашей функции, и в этом случае вы можете использовать type_вместо этого аргумент.
Райан П
3
Фраза «каждый раз» здесь вводит в заблуждение: «sys.excepthook вызывается каждый раз, когда исключение вызывается и обрабатывается» ... потому что в программе может быть ровно одно «необнаруженное» исключение. Кроме того, sys.excepthookНЕ вызывается, когда возникает исключение. Он вызывается, когда программа собирается завершиться из-за необработанного исключения, которое не может произойти более одного раза.
Наваз
2
@Nawaz: это может произойти несколько раз в РЕПЛ
JFS
2
@Nawaz Это также может произойти несколько раз, если программа использует потоки. Мне также кажется, что циклы событий GUI (например, Qt) продолжают работать, хотя исключение было сделано для sys.excepthook
three_pineapples
1
Любой, кто пытается протестировать приведенный выше код, должен убедиться, что вы генерируете ошибку трассировки при тестировании своей функции. Ошибка синтаксиса не обрабатывается sys.excepthook. Вы можете использовать print (1/0), и это вызовет функцию, которую вы определили для переопределения sys.excepthook
Karia
177

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

import sys
import logging
logger = logging.getLogger(__name__)
handler = logging.StreamHandler(stream=sys.stdout)
logger.addHandler(handler)

def handle_exception(exc_type, exc_value, exc_traceback):
    if issubclass(exc_type, KeyboardInterrupt):
        sys.__excepthook__(exc_type, exc_value, exc_traceback)
        return

    logger.error("Uncaught exception", exc_info=(exc_type, exc_value, exc_traceback))

sys.excepthook = handle_exception

if __name__ == "__main__":
    raise RuntimeError("Test unhandled")
  • Игнорируйте KeyboardInterrupt, чтобы консольная программа на Python могла завершиться нажатием Ctrl + C.

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

  • Используйте собственный регистратор с примером обработчика. Это изменяет необработанное исключение, чтобы перейти к stdout, а не к stderr, но вы можете добавить все виды обработчиков в этом же стиле к объекту регистратора.

gnu_lorien
источник
13
Я бы использовал logger.critical()внутри обработчика exchook, так как я бы сказал, что неперехваченное исключение довольно критично.
Гитаарик
2
Это самый практичный ответ ИМО.
Дэвид Моралес
@gnu_lorien спасибо за фрагмент. В какой файл вы положите это?
stelios
@chefarov Основной файл, где вы инициализируете все остальные записи
gnu_lorien
Привет, Как мы можем записать в файл, как debug.log эту информацию. Я пытаюсь добавить строку logging.basicConfig(level=logging.DEBUG, filename="debug.log", format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')Но не помогло.
GurhanCagin
26

Метод sys.excepthookбудет вызван, если исключение не найдено: http://docs.python.org/library/sys.html#sys.excepthook

Когда исключение возникает и не обрабатывается, интерпретатор вызывает sys.excepthook с тремя аргументами: классом исключения, экземпляром исключения и объектом трассировки. В интерактивном сеансе это происходит непосредственно перед тем, как управление возвращается к приглашению; в программе Python это происходит непосредственно перед выходом из программы. Обработка таких исключений верхнего уровня может быть настроена путем назначения другой функции с тремя аргументами для sys.excepthook.

Нед Бэтчелдер
источник
2
Почему он отправляет класс исключения? Разве вы не всегда можете получить это, позвонив typeв инстанс?
Нил Дж
Какой тип параметров sys.excepthook?
Мартин Тома
23

Почему нет:

import sys
import logging
import traceback

def log_except_hook(*exc_info):
    text = "".join(traceback.format_exception(*exc_info))
    logging.error("Unhandled exception: %s", text)

sys.excepthook = log_except_hook

None()

Вот результат, sys.excepthookкак показано выше:

$ python tb.py
ERROR:root:Unhandled exception: Traceback (most recent call last):
  File "tb.py", line 11, in <module>
    None()
TypeError: 'NoneType' object is not callable

Вот вывод с sys.excepthookзакомментированным:

$ python tb.py
Traceback (most recent call last):
  File "tb.py", line 11, in <module>
    None()
TypeError: 'NoneType' object is not callable

Разница лишь в том, что первый находится ERROR:root:Unhandled exception:в начале первой строки.

Тьяго Коутиньо
источник
Другое отличие состоит в том, что первый записывает трассировку в систему ведения журнала, поэтому применяются все установленные вами обработчики и средства форматирования. Последний пишет напрямую sys.stderr.
Радиаф
8

Чтобы построить ответ Jacinda, но с использованием объекта logger:

def catchException(logger, typ, value, traceback):
    logger.critical("My Error Information")
    logger.critical("Type: %s" % typ)
    logger.critical("Value: %s" % value)
    logger.critical("Traceback: %s" % traceback)

# Use a partially applied function
func = lambda typ, value, traceback: catchException(logger, typ, value, traceback)
sys.excepthook = func
Майк
источник
2
Было бы лучше использовать functools.partial()вместо лямбды. См .: docs.python.org/2/library/functools.html#functools.partial
Мариуш Джамро
@MariuszJamro почему?
Давр
4

Оберните входной вызов приложения в try...exceptблок, чтобы вы могли перехватывать и регистрировать (и, возможно, повторно поднимать) все необработанные исключения. Например, вместо:

if __name__ == '__main__':
    main()

Сделай это:

if __name__ == '__main__':
    try:
        main()
    except Exception as e:
        logger.exception(e)
        raise
flaviovs
источник
Это не тот вопрос, который задают. Задача состоит в том, чтобы спросить, что делать, когда исключение НЕ обрабатывается кодом.
Mayank Jaiswal
1
Ну, Python - это язык программирования, и это подразумевает, что он не делает вещи «автоматически» (как этого хочет OP), за исключением случаев, когда и когда вы просите его это сделать. Другими словами, нет способа «автоматически» регистрировать все исключения, если только вы не кодируете это - и это то, что в моем ответе.
flaviovs
1
Что ж, если вы посмотрите на ответ Неда Батчелдера, то есть нечто, называемое ловушкой исключения. Вы должны определить в одном месте в своем коде, и все ваши неперехваченные исключения будут обработаны.
Mayank Jaiswal
1
Хук исключений не меняет того факта, что он не является «автоматическим» (в том смысле, в котором хочет OP) - другими словами, вам все равно придется его кодировать. Ответ Неда (который использует хук исключения) действительно отвечает на первоначальный вопрос - просто, на мой взгляд , способ, которым он это делает, гораздо менее питонен, чем мой.
flaviovs
1
Это в значительной степени зависит от ваших собственных целей. Если вы программируете, чтобы угодить IDE, то да, перехват всех исключений может быть невозможным. Но если вы хотите корректно обрабатывать ошибки и отображать приятные отзывы для пользователей, то я боюсь, что вам нужно будет перехватить все исключения. Хорошо, хватит сарказма :-) - если вы посмотрите внимательно, вы увидите, что код перехватывает исключение, но затем повторно поднимается, поэтому, если ваша IDE не делает что-то «волшебное», оно не должно делать, оно все равно получит исключение.
flaviovs
3

Может быть, вы могли бы сделать что-то в верхней части модуля, который перенаправляет stderr в файл, а затем зарегистрировать этот файл в нижней части

sock = open('error.log', 'w')               
sys.stderr = sock

doSomething() #makes errors and they will log to error.log

logging.exception(open('error.log', 'r').read() )
JiminyCricket
источник
3

Хотя ответ @ gnu_lorien дал мне хорошую отправную точку, моя программа аварийно завершает работу при первом исключении.

Я пришел с настроенным (и / или) улучшенным решением, которое молча регистрирует исключения из функций, которые украшены @handle_error.

import logging

__author__ = 'ahmed'
logging.basicConfig(filename='error.log', level=logging.DEBUG)


def handle_exception(exc_type, exc_value, exc_traceback):
    import sys
    if issubclass(exc_type, KeyboardInterrupt):
        sys.__excepthook__(exc_type, exc_value, exc_traceback)
        return
    logging.critical(exc_value.message, exc_info=(exc_type, exc_value, exc_traceback))


def handle_error(func):
    import sys

    def __inner(*args, **kwargs):
        try:
            return func(*args, **kwargs)
        except Exception, e:
            exc_type, exc_value, exc_tb = sys.exc_info()
            handle_exception(exc_type, exc_value, exc_tb)
        finally:
            print(e.message)
    return __inner


@handle_error
def main():
    raise RuntimeError("RuntimeError")


if __name__ == "__main__":
    for _ in xrange(1, 20):
        main()
guneysus
источник
2

Чтобы ответить на вопрос г-на Зеуса, который обсуждался в разделе комментариев принятого ответа, я использую его для регистрации необработанных исключений в интерактивной консоли (протестировано с PyCharm 2018-2019). Я обнаружил, sys.excepthookчто не работает в оболочке Python, поэтому я посмотрел глубже и обнаружил, что я мог бы использовать sys.exc_infoвместо этого. Однако sys.exc_infoне принимает аргументов, в отличие от sys.excepthook3 аргументов.

Здесь я использую и то, sys.excepthookи другое, sys.exc_infoчтобы регистрировать оба исключения в интерактивной консоли и сценарий с функцией оболочки. Чтобы прикрепить функцию ловушки к обеим функциям, у меня есть два разных интерфейса в зависимости от того, заданы аргументы или нет.

Вот код:

def log_exception(exctype, value, traceback):
    logger.error("Uncaught exception occurred!",
                 exc_info=(exctype, value, traceback))


def attach_hook(hook_func, run_func):
    def inner(*args, **kwargs):
        if not (args or kwargs):
            # This condition is for sys.exc_info
            local_args = run_func()
            hook_func(*local_args)
        else:
            # This condition is for sys.excepthook
            hook_func(*args, **kwargs)
        return run_func(*args, **kwargs)
    return inner


sys.exc_info = attach_hook(log_exception, sys.exc_info)
sys.excepthook = attach_hook(log_exception, sys.excepthook)

Настройка регистрации может быть найдена в ответе gnu_lorien.

NABS
источник
2

В моем случае (использование python 3) при использовании ответа @Jacinda содержимое трассировки не было напечатано. Вместо этого, он просто печатает сам объект: <traceback object at 0x7f90299b7b90>.

Вместо этого я делаю:

import sys
import logging
import traceback

def custom_excepthook(exc_type, exc_value, exc_traceback):
    # Do not print exception when user cancels the program
    if issubclass(exc_type, KeyboardInterrupt):
        sys.__excepthook__(exc_type, exc_value, exc_traceback)
        return

    logging.error("An uncaught exception occurred:")
    logging.error("Type: %s", exc_type)
    logging.error("Value: %s", exc_value)

    if exc_traceback:
        format_exception = traceback.format_tb(exc_traceback)
        for line in format_exception:
            logging.error(repr(line))

sys.excepthook = custom_excepthook
veuncent
источник