¿Cómo imprimir el rastreo completo sin detener el programa?

781

Estoy escribiendo un programa que analiza 10 sitios web, localiza archivos de datos, guarda los archivos y luego los analiza para crear datos que puedan usarse fácilmente en la biblioteca NumPy. Hay un montón de errores que este archivo encuentra a través de enlaces incorrectos, XML mal formado, entradas faltantes y otras cosas que aún no he categorizado. Inicialmente hice este programa para manejar errores como este:

try:
    do_stuff()
except:
    pass

Pero ahora quiero registrar errores:

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

Tenga en cuenta que esto se imprime en un archivo de registro para su posterior revisión. Esto generalmente imprime datos muy inútiles. Lo que quiero es imprimir exactamente las mismas líneas impresas cuando el error se dispara sin el intento, excepto interceptar la excepción, pero no quiero que detenga mi programa ya que está anidado en una serie de bucles for que me gustaría ver hasta el final.

chriscauley
fuente

Respuestas:

585

Alguna otra respuesta ya ha señalado el módulo de rastreo .

Tenga en cuenta que print_exc, en algunos casos, no obtendrá lo que esperaría. En Python 2.x:

import traceback

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

    traceback.print_exc()

... mostrará el rastreo de la última excepción:

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

Si realmente necesita acceder al rastreo original, una solución es almacenar en caché la información de excepción tal como se devuelve exc_infoen una variable local y mostrarla usando 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

Productor:

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

Sin embargo, algunos escollos con esto:

  • Del documento de sys_info:

    Asignar el valor de retorno de rastreo a una variable local en una función que maneja una excepción causará una referencia circular . Esto evitará que cualquier cosa referenciada por una variable local en la misma función o por el rastreo no sea recolectada de basura. [...] Si necesita el rastreo, asegúrese de eliminarlo después del uso (mejor hacerlo con una prueba ... finalmente la declaración)

  • pero, del mismo documento:

    A partir de Python 2.2, dichos ciclos se reclaman automáticamente cuando se habilita la recolección de basura y se vuelven inalcanzables, pero sigue siendo más eficiente para evitar crear ciclos.


Por otro lado, al permitirle acceder al rastreo asociado con una excepción, Python 3 produce un resultado menos sorprendente:

import traceback

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

    traceback.print_tb(err.__traceback__)

... mostrará:

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

Si está depurando y solo quiere ver el seguimiento de la pila actual, simplemente puede llamar:

traceback.print_stack()

No es necesario generar manualmente una excepción solo para atraparla nuevamente.

dimo414
fuente
99
El módulo de rastreo hace exactamente eso: generar y capturar una excepción.
pppery
3
La salida va a STDERR por defecto BTW. No aparecía en mis registros porque estaba siendo redirigido a otro lugar.
mpen
101

¿Cómo imprimir el rastreo completo sin detener el programa?

Cuando no desee detener su programa por un error, debe manejar ese error con un intento / excepto:

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

Para extraer el rastreo completo, usaremos el tracebackmódulo de la biblioteca estándar:

import traceback

Y para crear un stacktrace decentemente complicado para demostrar que obtenemos el stacktrace completo:

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

def do_something_that_might_error():
    raise_error()

Impresión

Para imprimir el rastreo completo, use el traceback.print_excmétodo:

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

Que imprime:

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!

Mejor que imprimir, iniciar sesión:

Sin embargo, una mejor práctica es tener un registrador configurado para su módulo. Conocerá el nombre del módulo y podrá cambiar los niveles (entre otros atributos, como los controladores)

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

En cuyo caso, querrá la logger.exceptionfunción en su lugar:

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

Qué registros:

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 tal vez solo desee la cadena, en cuyo caso, querrá la traceback.format_excfunción en su lugar:

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

Qué registros:

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!

Conclusión

Y para las tres opciones, vemos que obtenemos el mismo resultado que cuando tenemos un error:

>>> 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!
Aaron Hall
fuente
2
como se dijo anteriormente y también para mí, traceback.print_exc()sólo devuelve la última llamada: ¿cómo tener éxito para devolver varios niveles de la pila (y posiblemente todos levele s?)
Herve-Guérin
@geekobi No estoy seguro de lo que estás preguntando aquí. Demuestro que obtenemos el rastreo hasta el punto de entrada del programa / intérprete. ¿Qué no tienes claro?
Aaron Hall
1
Lo que dice @geekobi es que si atrapas y vuelves a subir, traceback.print_exc () solo devolverá la pila de subida, no la pila original.
fizloki
@fizloki, ¿cómo estás "resubiendo"? ¿Estás haciendo un raiseencadenamiento desnudo o de excepción, o estás ocultando el rastreo original? ver stackoverflow.com/questions/2052390/…
Aaron Hall
20

En primer lugar, no hacer uso prints para el registro, hay astable, probada y bien pensado módulo stdlib de hacerlo: logging. Definitivamente deberías usarlo en su lugar.

En segundo lugar, no se sienta tentado a hacer un lío con herramientas no relacionadas cuando hay un enfoque nativo y simple. Aquí está:

log = logging.getLogger(__name__)

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

Eso es. Ya terminaste.

Explicación para cualquiera que esté interesado en cómo funcionan las cosas bajo el capó

Lo log.exceptionque realmente está haciendo es solo una llamada a log.error(es decir, registrar un evento con nivel ERROR) e imprimir el rastreo en ese momento.

¿Por qué es mejor?

Bueno, aquí hay algunas consideraciones:

  • es justo lo correcto ;
  • es sencillo;
  • Es simple.

¿Por qué nadie debería usar tracebacko llamar al registrador exc_info=Trueo ensuciarse las manos sys.exc_info?

Bueno, solo porque! Todos existen para diferentes propósitos. Por ejemplo, traceback.print_excla salida es un poco diferente de las trazas producidas por el propio intérprete. Si lo usa, confundirá a cualquiera que lea sus registros, se golpeará la cabeza contra ellos.

Pasar exc_info=Truea las llamadas de registro es simplemente inapropiado. Pero es útil cuando se detectan errores recuperables y desea registrarlos (usando, por ejemplo, INFOnivel) con trazas, porque log.exceptionproduce registros de solo un nivel - ERROR.

Y definitivamente deberías evitar jugar sys.exc_infotanto como puedas. Simplemente no es una interfaz pública, es interna, puede usarla si definitivamente sabe lo que está haciendo. No está destinado solo a imprimir excepciones.

tosh
fuente
44
Tampoco funciona como está. Eso no es. No he terminado ahora: esta respuesta solo pierde el tiempo.
A. Rager
También agregaría que solo puedes hacer logging.exception(). No es necesario crear una instancia de registro a menos que tenga requisitos especiales.
Shital Shah
9

Además de la respuesta de @Aaron Hall, si está iniciando sesión, pero no desea usar logging.exception()(ya que se registra en el nivel ERROR), puede usar un nivel inferior y pasar exc_info=True. p.ej

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

Para obtener el seguimiento preciso de la pila, como una cadena, que se habría elevado si no hubiera ningún intento / excepción para pasar por encima de él, simplemente colóquelo en el bloque except que atrapa la excepción ofensiva.

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

Aquí se explica cómo usarlo (suponiendo que flaky_funcesté definido y logllame a su sistema de registro favorito):

import traceback
import sys

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

Es una buena idea atrapar y volver a subir KeyboardInterrupts, para que aún pueda matar el programa usando Ctrl-C. El registro está fuera del alcance de la pregunta, pero una buena opción es el registro . Documentación para los módulos sys y traceback .

Edward Newell
fuente
44
Esto no funciona en Python 3 y debe cambiarse a desired_trace = traceback.format_exc(). Pasar sys.exc_info()como argumento nunca fue lo correcto, pero se ignora silenciosamente en Python 2, pero no en Python 3 (3.6.4 de todos modos).
Martineau
2
KeyboardInterruptno se deriva (directa o indirectamente) de Exception. (Ambos se derivan de BaseException). Esto significa except Exception:que nunca atrapará a KeyboardInterrupt, y por lo tanto, except KeyboardInterrupt: raisees completamente innecesario.
AJNeufeld
traceback.format_exc(sys.exc_info())no funciona para mí con python 3.6.10
Nam G VU
6

Deberá poner el try / except dentro del bucle más interno donde puede ocurrir el error, es decir

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

... y así

En otras palabras, necesitará ajustar las declaraciones que pueden fallar en try / excepto lo más específico posible, en el bucle más interno posible.

Ivo van der Wijk
fuente
6

Un comentario sobre los comentarios de esta respuesta : print(traceback.format_exc())hace un mejor trabajo para mí que traceback.print_exc(). Con este último, a helloveces se "mezcla" extrañamente con el texto de rastreo, como si ambos quieren escribir en stdout o stderr al mismo tiempo, produciendo resultados extraños (al menos al construir desde el interior de un editor de texto y ver el resultado en el Panel "Resultados de compilación").

Rastreo (última llamada más reciente):
Archivo "C: \ Users \ User \ Desktop \ test.py", línea 7, en el
infierno do_stuff ()
Archivo "C: \ Users \ User \ Desktop \ test.py", línea 4 , en do_stuff
1/0
ZeroDivisionError: división entera o módulo por cero
o
[Finalizado en 0.1s]

Entonces uso:

import traceback, sys

def do_stuff():
    1/0

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

No veo esto mencionado en ninguna de las otras respuestas. Si está pasando un objeto Exception por cualquier razón ...

En Python 3.5+ puede obtener un seguimiento de un objeto Exception utilizando traceback.TracebackException.from_exception () . Por ejemplo:

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()))

Sin embargo, el código anterior da como resultado:

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')

Estos son solo dos niveles de la pila, a diferencia de lo que se habría impreso en la pantalla si la excepción se hubiera elevado stack_lvl_2()y no interceptado (descomente la # raiselínea).

Según tengo entendido, eso se debe a que una excepción registra solo el nivel actual de la pila cuando se eleva, stack_lvl_3()en este caso. A medida que pasa de nuevo a través de la pila, se le agregan más niveles __traceback__. Pero lo interceptamos stack_lvl_2(), lo que significa que todo lo que consiguió registrar fueron los niveles 3 y 2. Para obtener el rastro completo tal como está impreso en stdout, tendríamos que atraparlo en el nivel más alto (¿más bajo?):

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()))

Lo que resulta en:

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')

Observe que la impresión de la pila es diferente, faltan la primera y la última línea. Porque es diferenteformat() .

Interceptar la excepción lo más lejos posible del punto en el que se generó hace que el código sea más simple y al mismo tiempo proporciona más información.

bgdnlp
fuente
Esto es mucho mejor que el (los) método (s) anterior (es), pero sigue siendo ridículamente complicado para imprimir un seguimiento de pila. Java toma menos código FGS.
elhefe
4

Obtenga el rastreo completo como una cadena del objeto de excepción con traceback.format_exception

Si solo tiene el objeto de excepción, puede obtener el rastreo como una cadena desde cualquier punto del código en Python 3 con:

import traceback

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

Ejemplo 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)

Salida:

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

Documentación: https://docs.python.org/3.7/library/traceback.html#traceback.format_exception

Consulte también: Extraer información de rastreo de un objeto de excepción

Probado en Python 3.7.3.

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
fuente
3

Desea el módulo de rastreo . Le permitirá imprimir volcados de pila como Python normalmente lo hace. En particular, la función print_last imprimirá la última excepción y un seguimiento de la pila.

nmichaels
fuente
2

Si ya tiene un objeto Error y desea imprimir todo, debe hacer esta llamada un poco incómoda:

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

Así es, print_exceptiontoma tres argumentos posicionales: el tipo de excepción, el objeto de excepción real y la propiedad de rastreo interno propia de la excepción.

En python 3.5 o posterior, el type(err)es opcional ... pero es un argumento posicional, por lo que aún debe pasar explícitamente Ninguno en su lugar.

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

No tengo idea de por qué todo esto no es solo traceback.print_exception(err). Por qué alguna vez querrías imprimir un error, junto con un rastreo que no sea el que pertenece a ese error, me supera.

nupanick
fuente