cgitb — Informes detallados de rastreo

Propósito:cgitb proporciona información de rastreo más detallada que traceback.

cgitb es una valiosa herramienta de depuración en la biblioteca estándar. Originalmente fue diseñada para mostrar errores e información de depuración en aplicaciones web y luego se actualizó para incluir también la salida de texto sin formato, pero desafortunadamente nunca fue renombrada. Esto ha llevado a la oscuridad, y el módulo no se usa con tanta frecuencia como podría ser.

Impresión estándar de rastreos

El comportamiento de manejo de excepciones predeterminado de Python es imprimir un rastreo a la secuencia de salida de error estándar con la pila de llamadas que conduce a la posición de error. Este resultado básico con frecuencia contiene suficiente información para comprender la causa de la excepción y permitir una solución.

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)

Este programa de muestra tiene un error sutil en func2().

$ python3 cgitb_basic_traceback.py

Traceback (most recent call last):
  File "cgitb_basic_traceback.py", line 18, in <module>
    func1(1, 5)
  File "cgitb_basic_traceback.py", line 16, in func1
    return func2(a, c)
  File "cgitb_basic_traceback.py", line 11, in func2
    return a / divisor
ZeroDivisionError: division by zero

Habilitación de rastreos detallados

Si bien el rastreo básico incluye suficiente información para detectar el error, habilitar cgitb brinda más detalles. cgitb reemplaza a sys.excepthook con una función que proporciona rastreos de seguimiento extendidos.

cgitb_local_vars.py
import cgitb
cgitb.enable(format='text')

El informe de error de este ejemplo es mucho más extenso que el original. Cada cuadro de la pila está listado, junto con:

  • La ruta completa al archivo fuente, en lugar de solo el nombre base
  • Los valores de los argumentos para cada función en la pila
  • Algunas líneas de contexto de origen alrededor de la línea en la ruta de error
  • Los valores de las variables en la expresión que causan el error

Tener acceso a las variables involucradas en la pila de errores puede ayudar a encontrar un error lógico que ocurra en algún lugar más alto en la pila que la línea donde se genera la excepción real.

$ python3 cgitb_local_vars.py

ZeroDivisionError
Python 3.6.4: .../bin/python3
Sun Mar 18 16:20:19 2018

A problem occurred in a Python script.  Here is the sequence of
function calls leading up to the error, in the order they
occurred.

 .../cgitb_local_vars.py in <module>()
   18 def func1(a, b):
   19     c = b - 5
   20     return func2(a, c)
   21
   22 func1(1, 5)
func1 = <function func1>

 .../cgitb_local_vars.py in func1(a=1, b=5)
   18 def func1(a, b):
   19     c = b - 5
   20     return func2(a, c)
   21
   22 func1(1, 5)
global func2 = <function func2>
a = 1
c = 0

 .../cgitb_local_vars.py in func2(a=1, divisor=0)
   13
   14 def func2(a, divisor):
   15     return a / divisor
   16
   17
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 22, in <module>
    func1(1, 5)
  File "cgitb_local_vars.py", line 20, in func1
    return func2(a, c)
  File "cgitb_local_vars.py", line 15, in func2
    return a / divisor
ZeroDivisionError: division by zero

En el caso de este código con un ZeroDivisionError , es evidente que el problema se introduce en el cálculo del valor de c en func1(), en lugar de donde se usa el valor en func2().

El final de la salida también incluye los detalles completos del objeto de excepción (en caso de que tenga atributos distintos a message que serían útiles para la depuración) y la forma original de un Impresión de rastreo.

Variables locales en rastreos

El código en cgitb que examina las variables utilizadas en el marco de la pila que conduce al error es lo suficientemente inteligente como para evaluar los atributos del objeto para mostrarlos también.

cgitb_with_classes.py
import cgitb
cgitb.enable(format='text', context=12)


class BrokenClass:
    """This class has an error.
    """

    def __init__(self, a, b):
        """Be careful passing arguments in here.
        """
        self.a = a
        self.b = b
        self.c = self.a * self.b
        # Really
        # long
        # comment
        # goes
        # here.
        self.d = self.a / self.b
        return

o = BrokenClass(1, 0)

Si una función o método incluye muchos comentarios en línea, espacios en blanco u otro código que lo hace muy largo, entonces tener el valor predeterminado de cinco líneas de contexto puede no proporcionar suficiente dirección. Cuando el cuerpo de la función se expulsa de la ventana de código que se muestra, no hay suficiente contexto para comprender la ubicación del error. El uso de un valor de contexto más grande con cgitb resuelve este problema. Al pasar un número entero como argumento de context a enable() se controla la cantidad de código que se muestra para cada línea del rastreo.

Esta salida muestra que self.a y self.b están involucrados en el código propenso a errores.

$ python3 cgitb_with_classes.py

ZeroDivisionError
Python 3.6.4: .../bin/python3
Sun Mar 18 16:20:19 2018

A problem occurred in a Python script.  Here is the sequence of
function calls leading up to the error, in the order they
occurred.

 .../cgitb_with_classes.py in <module>()
   21         self.a = a
   22         self.b = b
   23         self.c = self.a * self.b
   24         # Really
   25         # long
   26         # comment
   27         # goes
   28         # here.
   29         self.d = self.a / self.b
   30         return
   31
   32 o = BrokenClass(1, 0)
o undefined
BrokenClass = <class '__main__.BrokenClass'>

 .../cgitb_with_classes.py in
 __init__(self=<__main__.BrokenClass object>, a=1, b=0)
   21         self.a = a
   22         self.b = b
   23         self.c = self.a * self.b
   24         # Really
   25         # long
   26         # comment
   27         # goes
   28         # here.
   29         self.d = self.a / self.b
   30         return
   31
   32 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 32, in <module>
    o = BrokenClass(1, 0)
  File "cgitb_with_classes.py", line 29, in __init__
    self.d = self.a / self.b
ZeroDivisionError: division by zero

Propiedades de excepción

Además de las variables locales de cada marco de pila, cgitb muestra todas las propiedades del objeto de excepción. Las propiedades adicionales en los tipos de excepción personalizados se imprimen como parte del informe de error.

cgitb_exception_properties.py
import cgitb
cgitb.enable(format='text')


class MyException(Exception):
    """Add extra properties to a special exception
    """

    def __init__(self, message, bad_value):
        self.bad_value = bad_value
        Exception.__init__(self, message)
        return

raise MyException('Normal message', bad_value=99)

En este ejemplo, la propiedad bad_value se incluye junto con los valores estándar de message y args.

$ python3 cgitb_exception_properties.py

MyException
Python 3.6.4: .../bin/python3
Sun Mar 18 16:20:19 2018

A problem occurred in a Python script.  Here is the sequence of
function calls leading up to the error, in the order they
occurred.

 .../cgitb_exception_properties.py in <module>()
   19         self.bad_value = bad_value
   20         Exception.__init__(self, message)
   21         return
   22
   23 raise MyException('Normal message', bad_value=99)
MyException = <class '__main__.MyException'>
bad_value undefined
MyException: Normal message
    __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__ = 'Add extra properties to a special exception\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('Normal message',)>
    __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 = ('Normal message',)
    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 23, in <module>
    raise MyException('Normal message', bad_value=99)
MyException: Normal message

Salida HTML

Debido a que cgitb se desarrolló originalmente para manejar excepciones en aplicaciones web, ninguna discusión estaría completa sin mencionar su formato de salida HTML original. Todos los ejemplos anteriores muestran la salida de texto sin formato. Para producir HTML en su lugar, omite el argumento format (o especifica "html"). La mayoría de las aplicaciones web modernas se construyen utilizando un marco que incluye una función de informe de errores, por lo que el formulario HTML es en gran parte obsoleto.

Registro de rastreos

Para muchas situaciones, la impresión de los detalles de rastreo al error estándar es la mejor resolución. En un sistema de producción, sin embargo, registrar los errores es aún mejor. La función enable() incluye un argumento opcional, logdir, para habilitar el registro de errores. Cuando se proporciona un nombre de directorio, cada excepción se registra en su propio archivo en el directorio dado.

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)

Aunque se suprime la pantalla de error, se imprime un mensaje que describe a dónde ir para encontrar el registro de errores.

$ python3 cgitb_log_exception.py

<p>A problem occurred in a Python script.
.../LOGS/tmpq7icvee3.txt contains the description of this error.

$ ls LOGS

tmpq7icvee3.txt

$ cat LOGS/*.txt

ZeroDivisionError
Python 3.6.4: .../bin/python3
Sun Mar 18 16:20:19 2018

A problem occurred in a Python script.  Here is the sequence of
function calls leading up to the error, in the order they
occurred.

 .../cgitb_log_exception.py in <module>()
   24
   25 def func(a, divisor):
   26     return a / divisor
   27
   28 func(1, 0)
func = <function func>

 .../cgitb_log_exception.py in func(a=1, divisor=0)
   24
   25 def func(a, divisor):
   26     return a / divisor
   27
   28 func(1, 0)
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_log_exception.py", line 28, in <module>
    func(1, 0)
  File "cgitb_log_exception.py", line 26, in func
    return a / divisor
ZeroDivisionError: division by zero

Ver también

  • Documentación de la biblioteca estándar para cgitb
  • traceback – Módulo de biblioteca estándar para trabajar con rastreos.
  • inspect – El módulo inspect incluye más funciones para examinar la pila.
  • sys – El módulo sys proporciona acceso al valor de excepción actual y al controlador excepthook invocado cuando se produce una excepción.
  • Módulo de rastreo mejorado – Discusión en la lista de correo de desarrollo de Python sobre mejoras en el módulo de rastreo y mejoras relacionadas que otros desarrolladores usan localmente.