Registra l'eccezione con traceback


152

Come posso registrare i miei errori Python?

try:
    do_something()
except:
    # How can I log my exception here, complete with its traceback?

Risposte:


203

Utilizzare logging.exceptiondall'interno del except:gestore / blocco per registrare l'eccezione corrente insieme alle informazioni di traccia, precedute da un messaggio.

import logging
LOG_FILENAME = '/tmp/logging_example.out'
logging.basicConfig(filename=LOG_FILENAME, level=logging.DEBUG)

logging.debug('This message should go to the log file')

try:
    run_my_stuff()
except:
    logging.exception('Got exception on main handler')
    raise

Ora guardando il file di log, /tmp/logging_example.out:

DEBUG:root:This message should go to the log file
ERROR:root:Got exception on main handler
Traceback (most recent call last):
  File "/tmp/teste.py", line 9, in <module>
    run_my_stuff()
NameError: name 'run_my_stuff' is not defined

1
Ho esaminato il codice django per questo, e presumo che la risposta sia no, ma c'è un modo per limitare il traceback a una certa quantità di caratteri o profondità? Il problema è che per i traceback di grandi dimensioni, ci vuole abbastanza tempo.
Eduard Luca,

10
Nota che se definisci un logger logger = logging.getLogger('yourlogger')devi scrivere logger.exception('...')per far funzionare questo ...
576i

Possiamo modificarlo in modo che il messaggio venga stampato con il livello di registro INFO?
NM,

Tieni presente che per alcune app esterne, come Azure Insight, il trackback non viene archiviato nei log. È quindi necessario passarli esplicitamente alla stringa di messaggio come mostrato di seguito.
Edgar H,

139

Utilizzare le exc_infoopzioni potrebbe essere migliore, rimane un avviso o un titolo di errore:

try:
    # coode in here
except Exception as e:
    logging.error(e, exc_info=True)

Non riesco mai a ricordare come exc_info=si chiama il kwarg; Grazie!
berto

4
Ciò è identico a logging.exception, con l'eccezione che il tipo viene registrato in modo ridondante due volte. Usa semplicemente logging.exception a meno che tu non voglia un livello diverso dall'errore.
Wyrmwood,

@Wyrmwood non è identico in quanto è necessario fornire un messaggio alogging.exception
Peter Wood,

57

Il mio lavoro recentemente mi ha assegnato il compito di registrare tutti i traceback / eccezioni dalla nostra applicazione. Ho provato numerose tecniche che altri avevano pubblicato online come quella sopra, ma ho optato per un approccio diverso. Prevalente traceback.print_exception.

Ho una recensione su http://www.bbarrows.com/ Sarebbe molto più facile da leggere, ma lo incollerò anche qui.

Quando mi è stato assegnato il compito di registrare tutte le eccezioni che il nostro software potrebbe incontrare in natura, ho provato una serie di tecniche diverse per registrare i nostri traceback di eccezioni python. All'inizio ho pensato che l'hook di eccezione del sistema python, sys.excepthook, sarebbe stato il posto perfetto per inserire il codice di registrazione. Stavo provando qualcosa di simile a:

import traceback
import StringIO
import logging
import os, sys

def my_excepthook(excType, excValue, traceback, logger=logger):
    logger.error("Logging an uncaught exception",
                 exc_info=(excType, excValue, traceback))

sys.excepthook = my_excepthook  

Questo ha funzionato per il thread principale, ma ho presto scoperto che il mio sys.excepthook non esisteva tra i nuovi thread avviati dal mio processo. Questo è un grosso problema perché quasi tutto accade nei thread di questo progetto.

Dopo aver cercato su Google e letto molta documentazione, le informazioni più utili che ho trovato sono state dal tracker Issue di Python.

Il primo post sul thread mostra un esempio funzionante del sys.excepthookNON persistente tra i thread (come mostrato di seguito). Apparentemente questo è un comportamento previsto.

import sys, threading

def log_exception(*args):
    print 'got exception %s' % (args,)
sys.excepthook = log_exception

def foo():
    a = 1 / 0

threading.Thread(target=foo).start()

I messaggi su questo thread di Python Issue danno come risultato 2 hack suggeriti. Sottoclasse Threade racchiudi il metodo di esecuzione nel nostro tentativo tranne blocco per catturare e registrare le eccezioni o patch scimmia threading.Thread.runda eseguire nel tuo tentativo tranne bloccare e registrare le eccezioni.

Il primo metodo di sottoclasse Threadmi sembra meno elegante nel tuo codice in quanto dovresti importare e utilizzare la tua Threadclasse personalizzata OVUNQUE dove vuoi avere un thread di registrazione. Questo finì per essere una seccatura perché dovevo cercare in tutta la nostra base di codice e sostituire tutto normale Threadscon questa abitudine Thread. Tuttavia, era chiaro cosa Threadstava facendo e sarebbe stato più facile per qualcuno diagnosticare ed eseguire il debug in caso di problemi con il codice di registrazione personalizzato. Un thread di registrazione personalizzato potrebbe essere simile al seguente:

class TracebackLoggingThread(threading.Thread):
    def run(self):
        try:
            super(TracebackLoggingThread, self).run()
        except (KeyboardInterrupt, SystemExit):
            raise
        except Exception, e:
            logger = logging.getLogger('')
            logger.exception("Logging an uncaught exception")

Il secondo metodo di patching delle scimmie threading.Thread.runè carino perché potrei semplicemente eseguirlo subito dopo __main__e strumentare il mio codice di registrazione in tutte le eccezioni. Il patching delle scimmie può essere fastidioso per il debug, poiché modifica la funzionalità prevista di qualcosa. La patch suggerita dal tracker Python Issue era:

def installThreadExcepthook():
    """
    Workaround for sys.excepthook thread bug
    From
http://spyced.blogspot.com/2007/06/workaround-for-sysexcepthook-bug.html

(https://sourceforge.net/tracker/?func=detail&atid=105470&aid=1230540&group_id=5470).
    Call once from __main__ before creating any threads.
    If using psyco, call psyco.cannotcompile(threading.Thread.run)
    since this replaces a new-style class method.
    """
    init_old = threading.Thread.__init__
    def init(self, *args, **kwargs):
        init_old(self, *args, **kwargs)
        run_old = self.run
        def run_with_except_hook(*args, **kw):
            try:
                run_old(*args, **kw)
            except (KeyboardInterrupt, SystemExit):
                raise
            except:
                sys.excepthook(*sys.exc_info())
        self.run = run_with_except_hook
    threading.Thread.__init__ = init

Solo quando ho iniziato a testare la mia registrazione delle eccezioni mi sono reso conto che stavo andando tutto male.

Per testare avevo messo a

raise Exception("Test")

da qualche parte nel mio codice. Tuttavia, il wrapping di un metodo che ha chiamato questo metodo è stato un tentativo tranne il blocco che ha stampato il traceback e ha ingoiato l'eccezione. Questo è stato molto frustrante perché ho visto il traceback portato in STDOUT ma non registrato. Fu allora che decisi che un metodo molto più semplice per registrare i traceback era semplicemente quello di applicare una patch al metodo che tutto il codice python utilizza per stampare i traceback stessi, traceback.print_exception. Ho finito con qualcosa di simile al seguente:

def add_custom_print_exception():
    old_print_exception = traceback.print_exception
    def custom_print_exception(etype, value, tb, limit=None, file=None):
        tb_output = StringIO.StringIO()
        traceback.print_tb(tb, limit, tb_output)
        logger = logging.getLogger('customLogger')
        logger.error(tb_output.getvalue())
        tb_output.close()
        old_print_exception(etype, value, tb, limit=None, file=None)
    traceback.print_exception = custom_print_exception

Questo codice scrive il traceback in un buffer di stringhe e lo registra nella registrazione di ERRORE. Ho un gestore di registrazione personalizzato impostato il logger 'customLogger' che prende i registri di livello ERRORE e li invia a casa per l'analisi.


2
Un approccio piuttosto interessante. Una domanda: add_custom_print_exceptionnon sembra trovarsi sul sito a cui ti sei collegato e invece c'è un codice finale abbastanza diverso lì. Quale diresti migliore / più definitivo e perché? Grazie!
fantabolous,

Grazie, ottima risposta!
101

C'è un errore di battitura. sulla chiamata delegata a old_print_exception, limite e file devono essere passati limite e file, non Nessuno - old_print_exception (etype, value, tb, limit, file)
Marvin,

Per l'ultimo blocco di codice, anziché inizializzare StringIO e stamparne l'eccezione, puoi semplicemente chiamare logger.error(traceback.format_tb())(o format_exc () se vuoi anche le informazioni sull'eccezione).
James,

8

Puoi registrare tutte le eccezioni non rilevate sul thread principale assegnando un gestore a sys.excepthook, forse usando il exc_infoparametro delle funzioni di registrazione di Python :

import sys
import logging

logging.basicConfig(filename='/tmp/foobar.log')

def exception_hook(exc_type, exc_value, exc_traceback):
    logging.error(
        "Uncaught exception",
        exc_info=(exc_type, exc_value, exc_traceback)
    )

sys.excepthook = exception_hook

raise Exception('Boom')

Se il vostro programma usa le discussioni, però, quindi notare che i thread creati utilizzando threading.Threadvolontà non grilletto sys.excepthookquando si verifica un'eccezione non rilevata al loro interno, come indicato nella Problema 1.230.540 su issue tracker di Python. Alcuni hack sono stati suggeriti lì per aggirare questa limitazione, come il patching delle scimmie Thread.__init__da sovrascrivere self.runcon un runmetodo alternativo che avvolge l'originale in un tryblocco e chiama sys.excepthookdall'interno del exceptblocco. In alternativa, si può solo avvolgere manualmente il punto di ingresso per ognuno dei vostri thread in try/ exceptte stesso.


3

I messaggi di eccezione non rilevati vanno a STDERR, quindi invece di implementare la registrazione in Python stesso, è possibile inviare STDERR a un file usando qualsiasi shell che si sta utilizzando per eseguire lo script Python. In uno script Bash, puoi farlo con il reindirizzamento dell'output, come descritto nella guida BASH .

Esempi

Aggiungi errori al file, altri output al terminale:

./test.py 2>> mylog.log

Sovrascrivi file con output STDOUT e STDERR interlacciati:

./test.py &> mylog.log


2

Puoi ottenere il traceback usando un logger, a qualsiasi livello (DEBUG, INFO, ...). Si noti che utilizzando logging.exception, il livello è ERRORE.

# test_app.py
import sys
import logging

logging.basicConfig(level="DEBUG")

def do_something():
    raise ValueError(":(")

try:
    do_something()
except Exception:
    logging.debug("Something went wrong", exc_info=sys.exc_info())
DEBUG:root:Something went wrong
Traceback (most recent call last):
  File "test_app.py", line 10, in <module>
    do_something()
  File "test_app.py", line 7, in do_something
    raise ValueError(":(")
ValueError: :(

MODIFICARE:

Anche questo funziona (usando python 3.6)

logging.debug("Something went wrong", exc_info=True)

1

Ecco una versione che utilizza sys.excepthook

import traceback
import sys

logger = logging.getLogger()

def handle_excepthook(type, message, stack):
     logger.error(f'An unhandled exception occured: {message}. Traceback: {traceback.format_tb(stack)}')

sys.excepthook = handle_excepthook

Che ne dici di usare {traceback.format_exc()}invece di {traceback.format_tb(stack)}?
variabile dal

0

forse non così elegante, ma più semplice:

#!/bin/bash
log="/var/log/yourlog"
/path/to/your/script.py 2>&1 | (while read; do echo "$REPLY" >> $log; done)

-1

Ecco un semplice esempio tratto dalla documentazione di Python 2.6 :

import logging
LOG_FILENAME = '/tmp/logging_example.out'
logging.basicConfig(filename=LOG_FILENAME,level=logging.DEBUG,)

logging.debug('This message should go to the log file')

4
La domanda era come registrare il traceback
Konstantin Schubert,
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.