Rejestrowanie nieprzechwyconych wyjątków w Pythonie


181

W jaki sposób sprawisz, że nieprzechwycone wyjątki będą wysyłane przez loggingmoduł, a nie do stderr?

Zdaję sobie sprawę, że najlepszym sposobem na to byłoby:

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

Ale moja sytuacja jest taka, że ​​byłoby naprawdę fajnie, gdyby logging.exception(...)były wywoływane automatycznie, gdy wyjątek nie zostanie złapany.



Odpowiedzi:


143

Jak zauważył Ned, sys.excepthookjest wywoływana za każdym razem, gdy wyjątek jest podnoszony i nieprzechwycony. Praktyczna konsekwencja tego jest taka, że ​​w swoim kodzie możesz nadpisać domyślne zachowanie, sys.excepthookaby robić, co chcesz (w tym używać logging.exception).

Jako przykład słomianego człowieka:

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

Zastąp sys.excepthook:

>>> sys.excepthook = foo

Popełnij oczywisty błąd składni (pomiń dwukropek) i odzyskaj niestandardowe informacje o błędzie:

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

Aby uzyskać więcej informacji sys.excepthook, przeczytaj dokumentację .


4
@Codemonkey To nie jest zastrzeżone słowo kluczowe, to wcześniejsza nazwa typu. Możesz użyć go typejako argumentu funkcji, chociaż IDE będą narzekać na ukrywanie globalnego type(podobnie jak var self = thisw Javascript). To naprawdę nie ma znaczenia, chyba że potrzebujesz dostępu do typeobiektu wewnątrz swojej funkcji, w takim przypadku możesz użyć type_zamiast tego jako argumentu.
— Ryan P

3
Wyrażenie „za każdym razem” wprowadza w błąd: „sys.excepthook jest wywoływany za każdym razem, gdy wyjątek jest zgłaszany i nieprzechwycony” … ponieważ w programie może istnieć dokładnie jeden „nieprzechwycony” wyjątek. Ponadto, sys.excepthooknie jest wywoływana, gdy wyjątek jest „podniesione”. Jest wywoływana, gdy program ma zamiar zakończyć działanie z powodu nieprzechwyconego wyjątku, który nie może się zdarzyć więcej niż raz.
— Nawaz

2
@Nawaz: może się to zdarzyć więcej niż raz w REPL
— jfs

2
@Nawaz Może się to również zdarzyć wiele razy, jeśli program używa wątków. Wydaje mi się również, że pętle zdarzeń GUI (takie jak Qt) działają, mimo że wyjątek dotarł do sys.excepthook
— three_pineapples

1
Każdy, kto próbuje przetestować powyższy kod, upewnij się, że wygenerowałeś błąd śledzenia podczas testowania swojej funkcji. SyntaxError nie są obsługiwane przez sys.excepthook. Możesz użyć print (1/0), a to wywoła funkcję, którą zdefiniowałeś, aby zastąpić sys.excepthook
— Parth Karia

177

Oto kompletny mały przykład, który zawiera również kilka innych sztuczek:

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")
  • Zignoruj ​​KeyboardInterrupt, aby program konsoli mógł wyjść za pomocą Ctrl + C.

  • W celu sformatowania wyjątku polegaj całkowicie na module rejestrowania języka Python.

  • Użyj niestandardowego programu rejestrującego z przykładową obsługą. To zmienia nieobsługiwany wyjątek, aby przejść do standardowego wyjścia zamiast na stderr, ale można dodać wszystkie rodzaje programów obsługi w tym samym stylu do obiektu rejestratora.


13
Chciałbym używać logger.critical()wewnątrz przewodnika excepthook, ponieważ nieprzechwycony Wyjątkiem jest dość krytyczny powiedziałbym.
— gitaarik

2
To jest najbardziej praktyczna odpowiedź IMO.
— David Morales

@gnu_lorien dzięki za fragment. W którym pliku byś to umieścił?
— stelios

@chefarov Główny plik, w którym inicjalizujesz wszystkie inne rejestrowanie
— gnu_lorien

Cześć, jak możemy zapisać te informacje do pliku takiego jak debug.log. próbuję dodać linię, logging.basicConfig(level=logging.DEBUG, filename="debug.log", format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')ale nie pomogło.
— GurhanCagin

26

Metoda sys.excepthookzostanie wywołana, jeśli wyjątek nie zostanie przechwycony: http://docs.python.org/library/sys.html#sys.excepthook

Gdy wyjątek jest zgłaszany i nieprzechwycony, interpreter wywołuje sys.excepthook z trzema argumentami: klasą wyjątku, instancją wyjątku i obiektem śledzenia. W sesji interaktywnej dzieje się to tuż przed zwróceniem sterowania do znaku zachęty; w programie w Pythonie dzieje się to tuż przed zakończeniem programu. Obsługę takich wyjątków najwyższego poziomu można dostosować, przypisując kolejną trzyargumentową funkcję do sys.excepthook.


2
Dlaczego wysyła klasę wyjątku? Czy nie możesz tego zawsze uzyskać, wywołując typeinstancję?
— Neil G,

Jaki jest rodzaj parametrów sys.excepthook?
— Martin Thoma

23

Dlaczego nie:

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

Oto wynik z sys.excepthookjak widać powyżej:

$ 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

Oto wynik z sys.excepthookwykomentowanym:

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

Jedyna różnica polega na tym, że ten pierwszy ma ERROR:root:Unhandled exception:na początku pierwszej linii.


Inną różnicą jest to, że ten pierwszy zapisuje ślad w systemie rejestrowania, więc stosowane są wszelkie zainstalowane programy obsługi i elementy formatujące. Ten ostatni pisze bezpośrednio do sys.stderr.
— radiografia

8

Aby zbudować na odpowiedzi Jacindy, ale używając obiektu rejestrującego:

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
Lepiej byłoby użyć functools.partial()zamiast lambda. Zobacz: docs.python.org/2/library/functools.html#functools.partial
— Mariusz Jamro

@MariuszJamro dlaczego?
— davr

4

Zawiń wywołanie wpisu aplikacji w try...exceptblok, abyś mógł przechwycić i zarejestrować (i być może ponownie zgłosić) wszystkie nieprzechwycone wyjątki. Np. Zamiast:

if __name__ == '__main__':
    main()

Zrób to:

if __name__ == '__main__':
    try:
        main()
    except Exception as e:
        logger.exception(e)
        raise

Nie o to chodzi. Celem pytania jest pytanie, co zrobić, gdy wyjątek NIE jest obsługiwany przez kod.
— Mayank Jaiswal

1
Cóż, Python jest językiem programowania, co oznacza, że ​​nie robi rzeczy „automatycznie” (tak jak chce OP), z wyjątkiem sytuacji , gdy o to poprosisz. Innymi słowy, nie ma sposobu, aby „automatycznie” rejestrować wszystkie wyjątki, chyba że zostanie to zaprogramowane - i to jest moja odpowiedź.
— flaviovs

1
Cóż, jeśli spojrzysz na odpowiedź Neda Batcheldera, zobaczysz coś, co nazywa się hakiem wyjątków. Musisz zdefiniować w jednym miejscu w kodzie, a wszystkie nieprzechwycone wyjątki są obsługiwane.
— Mayank Jaiswal

1
Hak wyjątku nie zmienia faktu, że nie jest „automatyczny” (w sensie, którego chce OP) - innymi słowy, nadal musisz go zakodować. Odpowiedź Neda (która używa haka wyjątków) naprawdę odnosi się do pierwotnego pytania - po prostu, moim zdaniem , sposób, w jaki to robi, jest znacznie mniej pytoniczny niż mój.
— flaviovs

1
To w dużej mierze zależy od twoich własnych celów. Jeśli programujesz tak, aby zadowolić IDE, to tak, wychwytywanie wszystkich wyjątków może nie być opcją. Ale jeśli chcesz z wdziękiem radzić sobie z błędami i wyświetlać użytkownikowi miłą informację zwrotną, to obawiam się, że będziesz musiał wyłapać wszystkie wyjątki. Ok, dość sarkazmu :-) - jeśli przyjrzysz się uważnie, zobaczysz, że kod przechwytuje wyjątek, ale podnosi go ponownie, więc jeśli twoje IDE nie robi czegoś „magicznego”, to nie powinno robić, i tak się pojawi wyjątek.
— flaviovs

3

Może mógłbyś zrobić coś na górze modułu, który przekierowuje stderr do pliku, a następnie zarejestrować ten plik na dole

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

Chociaż odpowiedź @ gnu_lorien dała mi dobry punkt wyjścia, mój program zawiesza się przy pierwszym wyjątku.

Przyszedłem z niestandardowym (i / lub) ulepszonym rozwiązaniem, które po cichu rejestruje Wyjątki funkcji, które są ozdobione @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

Aby odpowiedzieć na pytanie pana Zeusa omówione w sekcji komentarzy zaakceptowanej odpowiedzi, używam tego do logowania niezłapanych wyjątków w interaktywnej konsoli (testowane z PyCharm 2018-2019). Dowiedziałem się, sys.excepthookże nie działa w powłoce Pythona, więc zajrzałem głębiej i stwierdziłem, że mogę sys.exc_infozamiast tego użyć . Jednak sys.exc_infonie przyjmuje żadnych argumentów, inaczej sys.excepthookniż przyjmuje 3 argumenty.

Tutaj używam obu sys.excepthooki sys.exc_infodo rejestrowania obu wyjątków w interaktywnej konsoli i skrypcie z funkcją opakowania. Aby dołączyć funkcję hook do obu funkcji, mam dwa różne interfejsy w zależności od tego, czy podano argumenty, czy nie.

Oto kod:

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)

Konfigurację logowania można znaleźć w odpowiedzi gnu_lorien.


2

W moim przypadku (przy użyciu python 3) podczas korzystania z odpowiedzi @Jacindy zawartość śledzenia nie została wydrukowana. Zamiast tego, po prostu drukuje sam obiekt: <traceback object at 0x7f90299b7b90>.

Zamiast tego robię:

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
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.