Come stampare il traceback completo senza arrestare il programma?


781

Sto scrivendo un programma che analizza 10 siti Web, individua i file di dati, salva i file e quindi li analizza per creare dati che possono essere facilmente utilizzati nella libreria NumPy. Ci sono tonnellate di errori che questo file incontra attraverso collegamenti errati, XML mal formato, voci mancanti e altre cose che devo ancora classificare. Inizialmente ho creato questo programma per gestire errori come questo:

try:
    do_stuff()
except:
    pass

Ma ora voglio registrare gli errori:

try:
    do_stuff()
except Exception, err:
    print Exception, err

Si noti che questo sta stampando su un file di registro per una revisione successiva. Questo di solito stampa dati molto inutili. Quello che voglio è stampare esattamente le stesse righe stampate quando l'errore si innesca senza il tentativo, tranne l'intercettazione dell'eccezione, ma non voglio che interrompa il mio programma poiché è nidificato in una serie di cicli for che vorrei vedere fino al completamento.

Risposte:


584

Qualche altra risposta ha già sottolineato il modulo traceback .

Si noti che con print_exc, in alcuni casi angolari, non si otterrà ciò che ci si aspetterebbe. In Python 2.x:

import traceback

try:
    raise TypeError("Oups!")
except Exception, err:
    try:
        raise TypeError("Again !?!")
    except:
        pass

    traceback.print_exc()

... visualizzerà il traceback dell'ultima eccezione:

Traceback (most recent call last):
  File "e.py", line 7, in <module>
    raise TypeError("Again !?!")
TypeError: Again !?!

Se è davvero necessario accedere al traceback originale, una soluzione consiste nel memorizzare nella cache le informazioni sull'eccezione restituite da exc_infouna variabile locale e visualizzarle utilizzando print_exception:

import traceback
import sys

try:
    raise TypeError("Oups!")
except Exception, err:
    try:
        exc_info = sys.exc_info()

        # do you usefull stuff here
        # (potentially raising an exception)
        try:
            raise TypeError("Again !?!")
        except:
            pass
        # end of useful stuff


    finally:
        # Display the *original* exception
        traceback.print_exception(*exc_info)
        del exc_info

Produrre:

Traceback (most recent call last):
  File "t.py", line 6, in <module>
    raise TypeError("Oups!")
TypeError: Oups!

Alcune insidie ​​con questo però:

  • Dal documento di sys_info:

    L'assegnazione del valore di ritorno del traceback a una variabile locale in una funzione che gestisce un'eccezione genererà un riferimento circolare . Ciò eviterà che qualsiasi raccolta a cui fa riferimento una variabile locale nella stessa funzione o dal traceback venga raccolta garbage. [...] Se hai bisogno del traceback, assicurati di eliminarlo dopo l'uso (meglio farlo con un'istruzione try ... finally)

  • ma, dallo stesso documento:

    A partire da Python 2.2, tali cicli vengono recuperati automaticamente quando è abilitata la garbage collection e diventano irraggiungibili, ma rimane più efficiente per evitare di creare cicli.


D'altra parte, consentendo di accedere al traceback associato a un'eccezione, Python 3 produce un risultato meno sorprendente:

import traceback

try:
    raise TypeError("Oups!")
except Exception as err:
    try:
        raise TypeError("Again !?!")
    except:
        pass

    traceback.print_tb(err.__traceback__)

... visualizzerà:

  File "e3.py", line 4, in <module>
    raise TypeError("Oups!")


258

Se stai eseguendo il debug e vuoi solo vedere la traccia dello stack corrente, puoi semplicemente chiamare:

traceback.print_stack()

Non è necessario sollevare manualmente un'eccezione solo per catturarla di nuovo.


9
Il modulo traceback fa esattamente questo: solleva e rileva un'eccezione.
pepery

3
L'output passa a STDERR per impostazione predefinita BTW. Non compariva nei miei registri perché veniva reindirizzato da qualche altra parte.
mpen

101

Come stampare il traceback completo senza arrestare il programma?

Quando non si desidera arrestare il programma in caso di errore, è necessario gestirlo con un tentativo / tranne:

try:
    do_something_that_might_error()
except Exception as error:
    handle_the_error(error)

Per estrarre il traceback completo, useremo il tracebackmodulo dalla libreria standard:

import traceback

E per creare uno stacktrace abbastanza complicato per dimostrare che otteniamo l'intero stacktrace:

def raise_error():
    raise RuntimeError('something bad happened!')

def do_something_that_might_error():
    raise_error()

Stampa

Per stampare il traceback completo, utilizzare il traceback.print_excmetodo:

try:
    do_something_that_might_error()
except Exception as error:
    traceback.print_exc()

Che stampa:

Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
  File "<stdin>", line 2, in do_something_that_might_error
  File "<stdin>", line 2, in raise_error
RuntimeError: something bad happened!

Meglio che stampare, registrare:

Tuttavia, è consigliabile disporre di un logger impostato per il modulo. Conoscerà il nome del modulo e sarà in grado di cambiare i livelli (tra gli altri attributi, come i gestori)

import logging
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)

In tal caso, ti consigliamo logger.exceptioninvece la funzione:

try:
    do_something_that_might_error()
except Exception as error:
    logger.exception(error)

Quali registri:

ERROR:__main__:something bad happened!
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
  File "<stdin>", line 2, in do_something_that_might_error
  File "<stdin>", line 2, in raise_error
RuntimeError: something bad happened!

O forse vuoi solo la stringa, nel qual caso, preferirai traceback.format_excinvece la funzione:

try:
    do_something_that_might_error()
except Exception as error:
    logger.debug(traceback.format_exc())

Quali registri:

DEBUG:__main__:Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
  File "<stdin>", line 2, in do_something_that_might_error
  File "<stdin>", line 2, in raise_error
RuntimeError: something bad happened!

Conclusione

E per tutte e tre le opzioni, vediamo che abbiamo lo stesso output di quando abbiamo un errore:

>>> do_something_that_might_error()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in do_something_that_might_error
  File "<stdin>", line 2, in raise_error
RuntimeError: something bad happened!

2
come detto sopra e anche per me, traceback.print_exc()restituisce solo l'ultima chiamata: come riesci a restituire diversi livelli dello stack (e forse tutti i livelli?)
herve-guerin

@geekobi Non sono sicuro di quello che stai chiedendo qui. Dimostro che otteniamo il traceback fino al punto di ingresso del programma / interprete. Cosa non hai chiarito?
Aaron Hall

1
Quello che @geekobi sta dicendo è che se catturi e ri-rilasci, traceback.print_exc () restituirà semplicemente lo stack di re-raise, non lo stack originale.
Fizloki,

@fizloki come stai "rilanciando"? Stai facendo un raiseconcatenamento nudo o eccezionale o stai nascondendo il traceback originale? vedi stackoverflow.com/questions/2052390/…
Aaron Hall

21

Innanzitutto, non usare prints per il logging, c'è un modulo stdlib astabile, provato e ben congegnato per farlo:logging . Sicuramente si dovrebbe usarlo al posto.

In secondo luogo, non essere tentato di fare un casino con strumenti non correlati quando esiste un approccio nativo e semplice. Ecco qui:

log = logging.getLogger(__name__)

try:
    call_code_that_fails()
except MyError:
    log.exception('Any extra info you want to see in your logs')

Questo è tutto. Hai finito adesso.

Spiegazione per chiunque sia interessato a come funzionano le cose sotto il cofano

Quello che log.exceptionsta effettivamente facendo è solo una chiamata a log.error(cioè, registra l'evento con il livello ERROR) e stampa il traceback.

Perché è meglio?

Bene, ecco alcune considerazioni:

  • è giusto ;
  • è semplice;
  • è semplice.

Perché nessuno dovrebbe usare tracebacko chiamare il logger con exc_info=Trueo sporcarsi le manisys.exc_info ?

Bene, solo perché! Esistono tutti per scopi diversi. Ad esempio, traceback.print_excl'output è leggermente diverso dai traceback prodotti dall'interprete stesso. Se lo usi, confonderai chiunque legga i tuoi registri, sbatteranno la testa contro di loro.

Passare exc_info=Trueper registrare le chiamate è semplicemente inappropriato. Tuttavia , è utile quando si rilevano errori recuperabili e si desidera registrarli (utilizzando, ad esempio, il INFOlivello) anche con traceback, perché log.exceptionproduce registri di un solo livello - ERROR.

E dovresti assolutamente evitare di scherzare sys.exc_infoil più possibile. Non è solo un'interfaccia pubblica, è interna - puoi usarla se sai sicuramente cosa stai facendo. Non è inteso solo per la stampa di eccezioni.


4
Inoltre non funziona così com'è. Non è quello. Non ho finito ora: questa risposta fa solo perdere tempo.
A. Rager,

Vorrei anche aggiungere che puoi semplicemente fare logging.exception(). Non è necessario creare un'istanza di registro a meno che non si disponga di requisiti speciali.
Shital Shah,

9

Oltre alla risposta di @Aaron Hall, se si sta effettuando il login, ma non si desidera utilizzare logging.exception()(poiché registra a livello di ERRORE), è possibile utilizzare un livello inferiore e passare exc_info=True. per esempio

try:
    do_something_that_might_error()
except Exception:
    logger.info('General exception noted.', exc_info=True)

7

Per ottenere l' esatta traccia dello stack, come una stringa, che sarebbe stata sollevata se non ci fossero stati tentativi / salvo per passarci sopra, basta posizionarlo nel blocco tranne che intercetta l'eccezione offensiva.

desired_trace = traceback.format_exc(sys.exc_info())

Ecco come usarlo (supponendo che flaky_funcsia definito e logchiama il tuo sistema di registrazione preferito):

import traceback
import sys

try:
    flaky_func()
except KeyboardInterrupt:
    raise
except Exception:
    desired_trace = traceback.format_exc(sys.exc_info())
    log(desired_trace)

È una buona idea catturare e rilanciare di nuovo KeyboardInterrupts, in modo da poter ancora uccidere il programma usando Ctrl-C. La registrazione non rientra nell'ambito della domanda, ma una buona opzione è la registrazione . Documentazione per i moduli sys e traceback .


4
Questo non funziona in Python 3 e deve essere modificato in desired_trace = traceback.format_exc(). Passare sys.exc_info()come argomento non è mai stato la cosa giusta da fare, ma viene silenziosamente ignorato in Python 2, ma non in Python 3 (3.6.4 comunque).
martineau,

2
KeyboardInterruptnon è derivato (direttamente o indirettamente) da Exception. (Entrambi sono derivati ​​da BaseException.) Questo significa except Exception:che non prenderà mai un KeyboardInterrupt, e quindi except KeyboardInterrupt: raiseè completamente inutile.
AJNeufeld,

traceback.format_exc(sys.exc_info())non funziona per me con Python 3.6.10
Nam G VU il

6

Dovrai mettere il try / tranne all'interno del più lobo interno in cui può verificarsi l'errore, ad es

for i in something:
    for j in somethingelse:
        for k in whatever:
            try:
                something_complex(i, j, k)
            except Exception, e:
                print e
        try:
            something_less_complex(i, j)
        except Exception, e:
            print e

... e così via

In altre parole, sarà necessario racchiudere le istruzioni che potrebbero non riuscire nel tentativo / tranne il più specifico possibile, nel ciclo più interno possibile.


6

Un'osservazione sui commenti di questa risposta : print(traceback.format_exc())fa un lavoro migliore per me che traceback.print_exc(). Con quest'ultimo, a hellovolte viene stranamente "mescolato" con il testo di traceback, come se entrambi volessero scrivere su stdout o stderr allo stesso tempo, producendo un output strano (almeno quando si costruisce dall'interno di un editor di testo e si visualizza l'output nel Pannello "Crea risultati").

Traceback (ultima chiamata più recente):
file "C: \ Users \ User \ Desktop \ test.py", riga 7, in
hell do_stuff ()
File "C: \ Users \ User \ Desktop \ test.py", riga 4 , in do_stuff
1/0
ZeroDivisionError: divisione intera o modulo per zero
o
[Terminato in 0.1s]

Quindi io uso:

import traceback, sys

def do_stuff():
    1/0

try:
    do_stuff()
except Exception:
    print(traceback.format_exc())
    print('hello')

5

Non lo vedo menzionato in nessuna delle altre risposte. Se stai passando intorno a un oggetto Exception per qualsiasi motivo ...

In Python 3.5+ è possibile ottenere una traccia da un oggetto Exception utilizzando traceback.TracebackException.from_exception () . Per esempio:

import traceback


def stack_lvl_3():
    raise Exception('a1', 'b2', 'c3')


def stack_lvl_2():
    try:
        stack_lvl_3()
    except Exception as e:
        # raise
        return e


def stack_lvl_1():
    e = stack_lvl_2()
    return e

e = stack_lvl_1()

tb1 = traceback.TracebackException.from_exception(e)
print(''.join(tb1.format()))

Tuttavia, il codice sopra riportato comporta:

Traceback (most recent call last):
  File "exc.py", line 10, in stack_lvl_2
    stack_lvl_3()
  File "exc.py", line 5, in stack_lvl_3
    raise Exception('a1', 'b2', 'c3')
Exception: ('a1', 'b2', 'c3')

Questo è solo due livelli della pila, al contrario di ciò che sarebbe stato stampato sullo schermo se l'eccezione fosse stata sollevata stack_lvl_2()e non intercettata (decommenta la # raiselinea).

A quanto ho capito, è perché un'eccezione registra solo il livello corrente dello stack quando viene sollevato, stack_lvl_3()in questo caso. Man mano che viene passato indietro attraverso lo stack, vengono aggiunti più livelli al suo __traceback__. Ma lo abbiamo intercettato stack_lvl_2(), il che significa che tutto ciò che è stato registrato erano i livelli 3 e 2. Per ottenere la traccia completa stampata su stdout dovremmo prenderlo al livello più alto (più basso?):

import traceback


def stack_lvl_3():
    raise Exception('a1', 'b2', 'c3')


def stack_lvl_2():
    stack_lvl_3()


def stack_lvl_1():
    stack_lvl_2()


try:
    stack_lvl_1()
except Exception as exc:
    tb = traceback.TracebackException.from_exception(exc)

print('Handled at stack lvl 0')
print(''.join(tb.stack.format()))

Che si traduce in:

Handled at stack lvl 0
  File "exc.py", line 17, in <module>
    stack_lvl_1()
  File "exc.py", line 13, in stack_lvl_1
    stack_lvl_2()
  File "exc.py", line 9, in stack_lvl_2
    stack_lvl_3()
  File "exc.py", line 5, in stack_lvl_3
    raise Exception('a1', 'b2', 'c3')

Si noti che la stampa in pila è diversa, mancano la prima e l'ultima riga. Perché è diversoformat() .

L'intercettazione dell'eccezione il più lontano possibile dal punto in cui è stata sollevata rende il codice più semplice fornendo allo stesso tempo maggiori informazioni.


Questo è molto meglio dei precedenti metodi, ma è ancora ridicolmente contorto solo per stampare una traccia stack. Java prende meno codice FGS.
elhefe,

4

Ottieni il traceback completo come stringa dall'oggetto eccezione con traceback.format_exception

Se hai solo l'oggetto eccezione, puoi ottenere il traceback come stringa da qualsiasi punto del codice in Python 3 con:

import traceback

''.join(traceback.format_exception(None, exc_obj, exc_obj.__traceback__))

Esempio completo:

#!/usr/bin/env python3

import traceback

def f():
    g()

def g():
    raise Exception('asdf')

try:
    g()
except Exception as e:
    exc = e

tb_str = ''.join(traceback.format_exception(None, exc_obj, exc_obj.__traceback__))
print(tb_str)

Produzione:

Traceback (most recent call last):
  File "./main.py", line 12, in <module>
    g()
  File "./main.py", line 9, in g
    raise Exception('asdf')
Exception: asdf

Documentazione: https://docs.python.org/3.7/library/traceback.html#traceback.format_exception

Guarda anche: Estrai informazioni di traceback da un oggetto eccezione

Testato in Python 3.7.3.


3

Vuoi il modulo traceback . Ti permetterà di stampare dump dello stack come fa normalmente Python. In particolare, il funzione print_last stamperà l'ultima eccezione e una traccia dello stack.


2

Se hai già un oggetto Error e vuoi stampare tutto, devi fare questa chiamata leggermente imbarazzante:

import traceback
traceback.print_exception(type(err), err, err.__traceback__)

Esatto, ne print_exceptionprende tre argomenti posizionali: il tipo di eccezione, l'oggetto di eccezione effettivo e la proprietà di traceback interna dell'eccezione.

In Python 3.5 o versioni successive, l' type(err)opzione è facoltativa ... ma è un argomento posizionale, quindi devi comunque passare esplicitamente None al suo posto.

traceback.print_exception(None, err, err.__traceback__)

Non ho idea del perché tutto ciò non sia giusto traceback.print_exception(err). Il motivo per cui vorresti mai stampare un errore, insieme a un traceback diverso da quello che appartiene a quell'errore, è al di là di me.

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.