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