Реєстрація нездійснених винятків у 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, прочитайте документи .


4
@Codemonkey Це не зарезервоване ключове слово, це попереднє ім'я типу. Ви можете використовувати typeяк аргумент функції, хоча IDE будуть скаржитися на приховування глобального type(подібно до використання var self = thisв Javascript). Це насправді не має значення, якщо вам не потрібно отримати доступ до typeоб'єкта всередині вашої функції, і в цьому випадку ви можете використовувати type_як аргумент.
Ryan P

3
Словосполучення "кожен раз" тут вводить в оману: "" sys.excepthook викликається щоразу, коли виняток збільшується і не вловлюється " ... тому що в програмі може бути рівно один виняток" uncaught ". Також sys.excepthookНЕ називається, коли виняток "підвищено". Він називається, коли програма припиняється через невдале виключення, що не може статися не один раз.
Наваз

2
@Nawaz: це може траплятися не раз у відповідь
jfs

2
@Nawaz Це також може статися кілька разів, якщо програма використовує теми. Також мені здається, що цикли подій GUI (як Qt) продовжують працювати, хоча виняток зробив це для sys.excepthook
three_pineapples

1
Кожен, хто намагається перевірити вищевказаний код, переконайтеся, що ви генеруєте помилку відстеження під час тестування функції. SyntaxError не обробляється sys.excepthook. Ви можете використовувати print (1/0), і це запустить функцію, яку ви визначили, щоб перекрити sys.excepthook
Parth

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, щоб програма консольного пітона могла вийти за допомогою Ctrl + C.

  • Цілком покладайтеся на модуль реєстрації python для форматування винятку.

  • Використовуйте користувальницький реєстратор із прикладом обробника. Це змінює необроблений виняток, щоб перейти до stdout, а не stderr, але ви можете додати всілякі обробники в цьому ж стилі до об’єкта реєстратора.


13
Я б використовував logger.critical()всередині обробника Osimhook, оскільки я б сказав, що невдале виключення є надзвичайно важливим.
gitaarik

2
Це найбільш практична відповідь ІМО.
Девід Моралес

@gnu_lorien дякую за фрагмент У який файл ви б це помістили?
stelios

@chefarov Основний файл, де ви ініціалізуєте всі інші
журнали

Привіт, як ми можемо записати файл, як 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 з трьома аргументами, класом виключень, екземпляром винятку та об'єктом trackback. В інтерактивному сеансі це відбувається безпосередньо перед поверненням контролю в підказку; в програмі 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

Щоб побудувати на відповіді Джасінда, але використовуючи об’єкт реєстратора:

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

Це не те, що задається питанням. Намір питання полягає в тому, щоб запитати, що робити, коли виняток НЕ обробляється кодом.
Mayank Jaiswal

1
Ну, Python - це мова програмування, і це означає, що він не робить речі "автоматично" (як хоче ОП), за винятком випадків, коли і коли ви попросите це зробити. Іншими словами, немає ніякого способу "автоматично" записувати всі винятки, якщо ви не кодуєте це - і це те, що є в моїй відповіді.
flaviovs

1
Що ж, якщо ви подивитесь на відповідь Неда Батчелдера, то щось називається гачком виключення. Ви повинні визначити у своєму коді на одному місці, і всі ваші невиконані винятки обробляються.
Mayank Jaiswal

1
Гак винятку не змінює той факт, що він не є "автоматичним" (в тому сенсі, який хоче ОП) - іншими словами, вам все одно доведеться його кодувати. Відповідь Неда (яка використовує гачок винятку) справді стосується оригінального питання - це, на мою думку , саме так , як на мене , набагато менше пітонічного, ніж мій.
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() )

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()

2

Щоб відповісти на запитання Mr.Zeus, обговорене в розділі коментарів прийнятої відповіді, я використовую це для того, щоб записувати невиконані винятки в інтерактивній консолі (протестовано з PyCharm 2018-2019). Я виявив, sys.excepthookщо не працює в оболонці пітона, тому я заглянув глибше і виявив, що можу використовувати sys.exc_infoзамість цього. Однак sys.exc_infoне бере аргументів на відміну від того, sys.excepthookщо бере 3 аргументи.

Тут я використовую обидва 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.


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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.