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.

Jacob Marble
źródło

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ę .

Jacinda
źródło
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.

gnu_lorien
źródło
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.

Ned Batchelder
źródło
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.

Tiago Coutinho
źródło
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
Mikrofon
źródło
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
flaviovs
źródło
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() )
JiminyCricket
źródło
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()
guneysus
źródło
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.

Nabs
źródło
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
żywiołowy
źródło