cgitb - Report Dettagliati di Traceback
Scopo: Fornisce informazioni di traceback più dettagliate rispetto al modulo traceback.
Il modulo cgitb è un prezioso strumento di debugging nella libreria standard. Originariamente progettato per mostrare errori ed informazioni di debug nelle applicazioni web, è stato recentemente aggiornato per includere anche risultati in testo semplice, ma sfortunatamente non è mai stato rinominato, il che lo ha reso poco appariscente e non utilizzato così spesso come meriterebbe.
Scaricamenti del Traceback Standard
Il comportamento predefinito di Python nella gestione delle eccezioni è stampare un traceback nel canale di errore standard in uscita con lo stack di chiamata che conduce fino alla posizione dell'errore. Questo basico risultato spesso contiene sufficienti informazioni per capire la causa dell'eccezione e consentirne una risoluzione.
# cgitb_basic_traceback.py
def func2(a, divisor):
return a / divisor
def func1(a, b):
c = b - 5
return func2(a, c)
func1(1, 5)
Questo programma di esempio ha un errore in func2()
.
$ python3 cgitb_basic_traceback.py Traceback (most recent call last): File "cgitb_basic_traceback.py", line 11, in <module> func1(1, 5) File "cgitb_basic_traceback.py", line 9, in func1 return func2(a, c) File "cgitb_basic_traceback.py", line 4, in func2 return a / divisor ZeroDivisionError: division by zero
Abilitare Traceback Dettagliati
Sebbene il traceback basico includa sufficienti informazioni per identificare l'errore, abilitando cgitb si ottengono maggiori dettagli. Il modulo sostituisce sys.excepthook
con una funzione che fornisce traceback arricchiti.
# cgitb_local_vars.py
import cgitb
cgitb.enable(format='text')
def func2(a, divisor):
return a / divisor
def func1(a, b):
c = b - 5
return func2(a, c)
func1(1, 5)
Il rapporto dell'errore da questo esempio è molto più estensivo dell'originale. Ciascuna struttura dello stack viene elencata, assieme a:
- - il percorso completo del file sorgente, in luogo del nome del file
- - i valori degli argomenti per ciascuna funzione nello stack
- - qualche riga di contesto del sorgente attorno alla riga nel percorso dell'errore
- - i valori delle variabili nell'espressione che ha causato l'errore
Avere l'accesso alle variabili coinvolte nello stack di errore può aiutare a trovare un errore logico che si manifesta da qualche parte più in alto rispetto alla riga nella quale l'effettiva eccezione è stata generata.
$ python3 cgitb_local_vars.py ZeroDivisionError Python 3.8.5: /dati/dev/python/pymotw3restyling/.venv/bin/python3 Thu Jun 3 09:14:08 2021 A problem occurred in a Python script. Here is the sequence of function calls leading up to the error, in the order they occurred. /dati/dev/python/pymotw3restyling/dumpscripts/cgitb_local_vars.py in <module> 11 def func1(a, b): 12 c = b - 5 13 return func2(a, c) 14 15 func1(1, 5) func1 = <function func1> /dati/dev/python/pymotw3restyling/dumpscripts/cgitb_local_vars.py in func1(a=1, b=5) 11 def func1(a, b): 12 c = b - 5 13 return func2(a, c) 14 15 func1(1, 5) global func2 = <function func2> a = 1 c = 0 /dati/dev/python/pymotw3restyling/dumpscripts/cgitb_local_vars.py in func2(a=1, divisor=0) 6 7 def func2(a, divisor): 8 return a / divisor 9 10 a = 1 divisor = 0 ZeroDivisionError: division by zero __cause__ = None __class__ = <class 'ZeroDivisionError'> __context__ = None __delattr__ = <method-wrapper '__delattr__' of ZeroDivisionError object> __dict__ = {} __dir__ = <built-in method __dir__ of ZeroDivisionError object> __doc__ = 'Second argument to a division or modulo operation was zero.' __eq__ = <method-wrapper '__eq__' of ZeroDivisionError object> __format__ = <built-in method __format__ of ZeroDivisionError object> __ge__ = <method-wrapper '__ge__' of ZeroDivisionError object> __getattribute__ = <method-wrapper '__getattribute__' of ZeroDivisionError object> __gt__ = <method-wrapper '__gt__' of ZeroDivisionError object> __hash__ = <method-wrapper '__hash__' of ZeroDivisionError object> __init__ = <method-wrapper '__init__' of ZeroDivisionError object> __init_subclass__ = <built-in method __init_subclass__ of type object> __le__ = <method-wrapper '__le__' of ZeroDivisionError object> __lt__ = <method-wrapper '__lt__' of ZeroDivisionError object> __ne__ = <method-wrapper '__ne__' of ZeroDivisionError object> __new__ = <built-in method __new__ of type object> __reduce__ = <built-in method __reduce__ of ZeroDivisionError object> __reduce_ex__ = <built-in method __reduce_ex__ of ZeroDivisionError object> __repr__ = <method-wrapper '__repr__' of ZeroDivisionError object> __setattr__ = <method-wrapper '__setattr__' of ZeroDivisionError object> __setstate__ = <built-in method __setstate__ of ZeroDivisionError object> __sizeof__ = <built-in method __sizeof__ of ZeroDivisionError object> __str__ = <method-wrapper '__str__' of ZeroDivisionError object> __subclasshook__ = <built-in method __subclasshook__ of type object> __suppress_context__ = False __traceback__ = <traceback object> args = ('division by zero',) with_traceback = <built-in method with_traceback of ZeroDivisionError object> The above is a description of an error in a Python program. Here is the original traceback: Traceback (most recent call last): File "cgitb_local_vars.py", line 15, in <module> func1(1, 5) File "cgitb_local_vars.py", line 13, in func1 return func2(a, c) File "cgitb_local_vars.py", line 8, in func2 return a / divisor ZeroDivisionError: division by zero
Nel caso di questo codice con un errore ZeroDivisionError
è evidente che il problema è stato introdotto nel calcolo del valore di c
in func1()
e non dove il valore è stato usato in func2()
.
Alla fine del risultato viene anche incluso il dettaglio completo dell'oggetto di eccezione (nel caso abbia altri attributi oltre a message
che potrebbero essere utili per il debugging e la forma originale dello scaricamento del traceback).
Variabili Locali nel Traceback
Il codice in cgitb che esamina le variabili usate nella struttura dello stack che conduce all'errore è sufficientemente intelligente per valutare gli attributi dell'oggetto e per visualizzarli.
# cgitb_with_classes.py
import cgitb
cgitb.enable(format='text', context=12)
class BrokenClass:
"""Questa classe ha un errore
"""
def __init__(self, a, b):
"""Cautela nel passare argomenti qui..
"""
self.a = a
self.b = b
self.c = self.a * self.b
# Un commento
# veramente
# lungo
# va
# qui.
self.d = self.a / self.b
return
o = BrokenClass(1, 0)
Se una funzione od un metodo include molti commenti in linea, caratteri di spaziatura verticali od orizzontali od altro codice che li rende molto lunghi, avere il contesto predefinito di cinque righe non fornirebbe indicazioni sufficienti. Quando il corpo di una funzione viene spinto al di fuori della finestra di codice mostrato nel risultato, non c'è sufficiente contesto per comprendere la locazione dell'errore. Usando un valore di contesto più grande con cgitb risolve il problema. Passando un intero come argomento di context
per enable()
si controlla il numero di righe di codice visualizzate per ciascuna riga di traceback.
Questo risultato mostra che self.a
e self.b
sono coinvolte in questo codice soggetto ad errori.
$ python3 cgitb_with_classes.py ZeroDivisionError Python 3.8.5: /dati/dev/python/pymotw3restyling/.venv/bin/python3 Thu Jun 3 09:14:08 2021 A problem occurred in a Python script. Here is the sequence of function calls leading up to the error, in the order they occurred. /dati/dev/python/pymotw3restyling/dumpscripts/cgitb_with_classes.py in <module> 14 self.a = a 15 self.b = b 16 self.c = self.a * self.b 17 # Un commento 18 # veramente 19 # lungo 20 # va 21 # qui. 22 self.d = self.a / self.b 23 return 24 25 o = BrokenClass(1, 0) o undefined BrokenClass = <class '__main__.BrokenClass'> /dati/dev/python/pymotw3restyling/dumpscripts/cgitb_with_classes.py in __init__(self=<__main__.BrokenClass object>, a=1, b=0) 14 self.a = a 15 self.b = b 16 self.c = self.a * self.b 17 # Un commento 18 # veramente 19 # lungo 20 # va 21 # qui. 22 self.d = self.a / self.b 23 return 24 25 o = BrokenClass(1, 0) self = <__main__.BrokenClass object> self.d undefined self.a = 1 self.b = 0 ZeroDivisionError: division by zero __cause__ = None __class__ = <class 'ZeroDivisionError'> __context__ = None __delattr__ = <method-wrapper '__delattr__' of ZeroDivisionError object> __dict__ = {} __dir__ = <built-in method __dir__ of ZeroDivisionError object> __doc__ = 'Second argument to a division or modulo operation was zero.' __eq__ = <method-wrapper '__eq__' of ZeroDivisionError object> __format__ = <built-in method __format__ of ZeroDivisionError object> __ge__ = <method-wrapper '__ge__' of ZeroDivisionError object> __getattribute__ = <method-wrapper '__getattribute__' of ZeroDivisionError object> __gt__ = <method-wrapper '__gt__' of ZeroDivisionError object> __hash__ = <method-wrapper '__hash__' of ZeroDivisionError object> __init__ = <method-wrapper '__init__' of ZeroDivisionError object> __init_subclass__ = <built-in method __init_subclass__ of type object> __le__ = <method-wrapper '__le__' of ZeroDivisionError object> __lt__ = <method-wrapper '__lt__' of ZeroDivisionError object> __ne__ = <method-wrapper '__ne__' of ZeroDivisionError object> __new__ = <built-in method __new__ of type object> __reduce__ = <built-in method __reduce__ of ZeroDivisionError object> __reduce_ex__ = <built-in method __reduce_ex__ of ZeroDivisionError object> __repr__ = <method-wrapper '__repr__' of ZeroDivisionError object> __setattr__ = <method-wrapper '__setattr__' of ZeroDivisionError object> __setstate__ = <built-in method __setstate__ of ZeroDivisionError object> __sizeof__ = <built-in method __sizeof__ of ZeroDivisionError object> __str__ = <method-wrapper '__str__' of ZeroDivisionError object> __subclasshook__ = <built-in method __subclasshook__ of type object> __suppress_context__ = False __traceback__ = <traceback object> args = ('division by zero',) with_traceback = <built-in method with_traceback of ZeroDivisionError object> The above is a description of an error in a Python program. Here is the original traceback: Traceback (most recent call last): File "cgitb_with_classes.py", line 25, in <module> o = BrokenClass(1, 0) File "cgitb_with_classes.py", line 22, in __init__ self.d = self.a / self.b ZeroDivisionError: division by zero
Proprietà dell'Eccezione
Oltre alle variabili locali da ciascuna struttura di stack, cgitb mostra tutte le proprietà dell'oggetto di eccezione. Proprietà extra su tipi di eccezione personalizzata sono stampate come parte del rapporto di errore.
# cgitb_exception_properties.py
import cgitb
cgitb.enable(format='text')
class MyException(Exception):
"""Aggiunge proprieta' extra ad una eccezione personalizzata
"""
def __init__(self, message, bad_value):
self.bad_value = bad_value
Exception.__init__(self, message)
return
raise MyException('Messaggio normale', bad_value=99)
In questo esempio, la proprietà bad_value
viene inclusa assieme ai valori standard di message
ed args
.
$ python3 cgitb_exception_properties.py MyException Python 3.8.5: /dati/dev/python/pymotw3restyling/.venv/bin/python3 Thu Jun 3 09:14:08 2021 A problem occurred in a Python script. Here is the sequence of function calls leading up to the error, in the order they occurred. /dati/dev/python/pymotw3restyling/dumpscripts/cgitb_exception_properties.py in <module> 12 self.bad_value = bad_value 13 Exception.__init__(self, message) 14 return 15 16 raise MyException('Messaggio normale', bad_value=99) MyException = <class '__main__.MyException'> bad_value undefined MyException: Messaggio normale __cause__ = None __class__ = <class '__main__.MyException'> __context__ = None __delattr__ = <method-wrapper '__delattr__' of MyException object> __dict__ = {'bad_value': 99} __dir__ = <built-in method __dir__ of MyException object> __doc__ = "Aggiunge proprieta' extra ad una eccezione personalizzata\n " __eq__ = <method-wrapper '__eq__' of MyException object> __format__ = <built-in method __format__ of MyException object> __ge__ = <method-wrapper '__ge__' of MyException object> __getattribute__ = <method-wrapper '__getattribute__' of MyException object> __gt__ = <method-wrapper '__gt__' of MyException object> __hash__ = <method-wrapper '__hash__' of MyException object> __init__ = <bound method MyException.__init__ of MyException('Messaggio normale')> __init_subclass__ = <built-in method __init_subclass__ of type object> __le__ = <method-wrapper '__le__' of MyException object> __lt__ = <method-wrapper '__lt__' of MyException object> __module__ = '__main__' __ne__ = <method-wrapper '__ne__' of MyException object> __new__ = <built-in method __new__ of type object> __reduce__ = <built-in method __reduce__ of MyException object> __reduce_ex__ = <built-in method __reduce_ex__ of MyException object> __repr__ = <method-wrapper '__repr__' of MyException object> __setattr__ = <method-wrapper '__setattr__' of MyException object> __setstate__ = <built-in method __setstate__ of MyException object> __sizeof__ = <built-in method __sizeof__ of MyException object> __str__ = <method-wrapper '__str__' of MyException object> __subclasshook__ = <built-in method __subclasshook__ of type object> __suppress_context__ = False __traceback__ = <traceback object> __weakref__ = None args = ('Messaggio normale',) bad_value = 99 with_traceback = <built-in method with_traceback of MyException object> The above is a description of an error in a Python program. Here is the original traceback: Traceback (most recent call last): File "cgitb_exception_properties.py", line 16, in <module> raise MyException('Messaggio normale', bad_value=99) MyException: Messaggio normale
Risultato in HTML
Questo esposto non sarebbe esaustivo senza menzionare il formato di rapporto originale HTML di cgitb, visto che fu in origine sviluppato per gestire eccezioni in applicazioni web. Per produrre un rapporto in formato HTML si escluda l'argomento format
(oppure si specifichi "html
"). Le applicazioni web di oggi sono costruite usando una struttura che include un servizio di rapporto di errori, quindi la forma HTML è in gran parte obsoleta.
Registrare i Traceback
In molte situazioni, la stampa dei dettagli del traceback al canale di errore standard è la soluzione migliore. Tuttavia, in un sistema in produzione registrare gli errori è anche meglio. La funzione enable()
include un argomento opzionale, logdir
, che consente la registrazione dell'errore. Quando viene passato un nome di directory, ciascuna eccezione viene registrata nel suo proprio file nella directory indicata.
# cgitb_log_exception.py
import cgitb
import os
LOGDIR = os.path.join(os.path.dirname(__file__), 'LOGS')
if not os.path.exists(LOGDIR):
os.makedirs(LOGDIR)
cgitb.enable(
logdir=LOGDIR,
display=False,
format='text',
)
def func(a, divisor):
return a / divisor
func(1, 0)
Sebbene sia disabilitata la visualizzazione dell'errore, viene stampato un messaggio che descrive dove trovare la registrazione dell'errore.
$ python3 cgitb_log_exception.py <p>A problem occurred in a Python script. /dati/dev/python/pymotw3restyling/dumpscripts/LOGS/tmpjat5isfc.txt contains the description of this error.
Vedere anche:
- cgitb
- La documentazione della libreria standard per questo modulo.
- traceback
- Il modulo della libreria standard per lavorare con i traceback
- inspect
- Il modulo inspect include altre funzioni per sondare stutture nello stack
- improved traceback module
- Discussione nella mailing list dello sviluppo di Python circa i miglioramenti al modulo traceback ed altre migliorie che gli sviluppatori utilizzano localmente.