Registrazione delle eccezioni non rilevate in Python


181

In che modo si generano eccezioni non rilevate nell'output tramite il loggingmodulo anziché in stderr?

Mi rendo conto che il modo migliore per farlo sarebbe:

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

Ma la mia situazione è tale che sarebbe davvero bello se logging.exception(...)venissero invocati automaticamente ogni volta che non viene rilevata un'eccezione.



Risposte:


143

Come sottolineato da Ned, sys.excepthookviene invocato ogni volta che viene sollevata e non rilevata un'eccezione. L'implicazione pratica di ciò è che nel tuo codice puoi sovrascrivere il comportamento predefinito di sys.excepthookfare quello che vuoi (incluso l'uso logging.exception).

Ad esempio un uomo di paglia:

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

Ignora sys.excepthook:

>>> sys.excepthook = foo

Commette evidente errore di sintassi (tralascia i due punti) e recupera le informazioni di errore personalizzate:

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

Per ulteriori informazioni sys.excepthook, leggi i documenti .


4
@Codemonkey Non è una parola chiave riservata, è un nome di tipo preesistente. Puoi usare typecome argomento di funzione, anche se gli IDE si lamenteranno di nascondere il globale type(proprio come usare var self = thisin Javascript). Non importa a meno che non sia necessario accedere typeall'oggetto all'interno della funzione, nel qual caso è possibile utilizzare type_come argomento.
Ryan P,

3
La frase "ogni volta" qui è fuorviante :: "sys.excepthook viene invocato ogni volta che viene sollevata e non rilevata un'eccezione" ... perché in un programma può esserci esattamente un'eccezione "non rilevata". Inoltre, sys.excepthookNON viene chiamato quando viene sollevata un'eccezione. Viene chiamato quando il programma sta per terminare a causa di un'eccezione non rilevata, che non può avvenire più di una volta.
Nawaz,

2
@Nawaz: può succedere più di una volta in un REPL
jfs il

2
@Nawaz Può succedere anche più volte se il programma utilizza i thread. Mi sembra anche che i loop degli eventi della GUI (come Qt) continuino a funzionare, anche se l'eccezione è arrivata a sys.excepthook
three_pineapples il

1
Chiunque provi a testare il codice sopra riportato, assicurati di generare un errore di traceback durante il test della tua funzione. SyntaxError non viene gestito da sys.excepthook. Puoi usare print (1/0) e questo invocherà la funzione che hai definito per sovrascrivere sys.excepthook
Parth Karia

177

Ecco un piccolo esempio completo che include anche alcuni altri trucchi:

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")
  • Ignora KeyboardInterrupt in modo che un programma python della console possa uscire con Ctrl + C.

  • Affidati interamente al modulo di registrazione di Python per la formattazione dell'eccezione.

  • Utilizzare un logger personalizzato con un gestore di esempio. Questa modifica l'eccezione non gestita per passare a stdout anziché a stderr, ma è possibile aggiungere tutti i tipi di gestori con lo stesso stile all'oggetto logger.


13
Vorrei utilizzare logger.critical()all'interno del gestore di eccezioni, dal momento che un'eccezione non rilevata è piuttosto critica, direi.
gitaarik,

2
Questa è la risposta più pratica IMO.
David Morales,

@gnu_lorien grazie per lo snippet. In quale file inseriresti questo?
stelios,

@chefarov Il file principale in cui si inizializza tutte le altre registrazioni
gnu_lorien

Ciao, come possiamo scrivere su file come debug.log queste informazioni. lo provo per aggiungere la linea logging.basicConfig(level=logging.DEBUG, filename="debug.log", format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')ma non ha aiutato.
GurhanCagin,

26

Il metodo sys.excepthookverrà invocato se non viene rilevata un'eccezione: http://docs.python.org/library/sys.html#sys.excepthook

Quando viene sollevata e non rilevata un'eccezione, l'interprete chiama sys.excepthook con tre argomenti, la classe di eccezione, l'istanza di eccezione e un oggetto traceback. In una sessione interattiva ciò accade poco prima che il controllo venga restituito al prompt; in un programma Python ciò accade poco prima della chiusura del programma. La gestione di tali eccezioni di livello superiore può essere personalizzata assegnando un'altra funzione a tre argomenti a sys.excepthook.


2
Perché invia la classe di eccezione? Non puoi sempre ottenerlo chiamando typel'istanza?
Neil G,

Qual è il tipo di parametri di sys.excepthook?
Martin Thoma,

23

Perchè no:

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

Ecco l'output con sys.excepthookcome visto sopra:

$ 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

Ecco l'output con il sys.excepthookcommento:

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

L'unica differenza è che il primo ha ERROR:root:Unhandled exception:all'inizio della prima riga.


Un'altra differenza è che il primo scrive la traccia nel sistema di registrazione, quindi vengono applicati tutti i gestori e i formattatori installati. Quest'ultimo scrive direttamente a sys.stderr.
Radiaph,

8

Per costruire sulla risposta di Jacinda, ma usando un oggetto 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
Sarebbe meglio usare al functools.partial()posto di lambda. Vedi: docs.python.org/2/library/functools.html#functools.partial
Mariusz Jamro

@MariuszJamro perché?
davr

4

Avvolgi la chiamata della tua app in un try...exceptblocco in modo da poter catturare e registrare (e forse ri-rilanciare) tutte le eccezioni non rilevate. Ad esempio invece di:

if __name__ == '__main__':
    main()

Fai questo:

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

Non è questa la domanda. L'intenzione della domanda è di chiedere cosa fare quando l'eccezione NON è gestita dal codice.
Mayank Jaiswal,

1
Bene, Python è un linguaggio di programmazione, e questo implica che non fa le cose "automaticamente" (come vuole l'OP), tranne se e quando gli chiedi di farlo. In altre parole, non c'è modo di "automaticamente" registrare tutte le eccezioni, a meno che non lo si codifichi, ed è quello che è nella mia risposta.
Flaviov,

1
Bene, se guardi la risposta di Ned Batchelder, c'è qualcosa chiamato hook di eccezione. Devi definire un punto nel tuo codice e tutte le eccezioni non rilevate vengono gestite.
Mayank Jaiswal,

1
L'hook dell'eccezione non cambia il fatto che non è "automatico" (nel senso che l'OP vuole) - in altre parole, devi ancora codificarlo. La risposta di Ned (che usa l'hook dell'eccezione) affronta davvero la domanda originale - è solo che, secondo me , il modo in cui lo fa è molto meno pitonico del mio.
Flaviov

1
Dipende praticamente dai tuoi obiettivi. Se si programma per soddisfare l'IDE, sì, catturare tutte le eccezioni potrebbe non essere un'opzione. Ma se vuoi gestire gli errori con garbo e mostrare un buon feedback all'utente, temo che dovrai cogliere tutte le eccezioni. Ok, abbastanza sarcasmo :-) - se guardi attentamente vedrai che il codice intercetta l'eccezione, ma poi rilancia, quindi a meno che il tuo IDE non stia facendo qualcosa di "magico" non dovrebbe farlo, continuerà comunque l'eccezione.
Flaviov

3

Forse potresti fare qualcosa nella parte superiore di un modulo che reindirizza stderr a un file e quindi registra quel file in fondo

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

Sebbene la risposta di @ gnu_lorien mi abbia dato un buon punto di partenza, il mio programma si blocca alla prima eccezione.

Sono arrivato con una soluzione personalizzata (e / o) migliorata, che registra in silenzio Eccezioni di funzioni decorate @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

Per rispondere alla domanda di Mr.Zeus discussa nella sezione commenti della risposta accettata, la utilizzo per registrare eccezioni non rilevate in una console interattiva (testata con PyCharm 2018-2019). Ho scoperto sys.excepthookche non funziona in una shell Python, quindi ho guardato più in profondità e ho scoperto che avrei potuto usare sys.exc_infoinvece. Tuttavia, sys.exc_infonon accetta argomenti a differenza di sys.excepthookquello che accetta 3 argomenti.

Qui, uso entrambi sys.excepthooke sys.exc_infoper registrare entrambe le eccezioni in una console interattiva e uno script con una funzione wrapper. Per associare una funzione hook ad entrambe le funzioni, ho due interfacce diverse a seconda che vengano forniti argomenti o meno.

Ecco il codice:

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)

La configurazione della registrazione può essere trovata nella risposta di gnu_lorien.


2

Nel mio caso (utilizzo python 3) quando si utilizza la risposta di @Jacinda, il contenuto del traceback non è stato stampato. Invece, si limita a stampare l'oggetto stesso: <traceback object at 0x7f90299b7b90>.

Invece faccio:

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
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.