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.