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

Estoy escribiendo un progtwig que analiza 10 sitios web, localiza archivos de datos, guarda los archivos y luego los analiza para crear datos que se pueden usar fácilmente en la biblioteca NumPy. Hay toneladas de errores que este archivo encuentra a través de enlaces defectuosos, XML mal formado, entradas faltantes y otras cosas que aún no he categorizado. Inicialmente hice este progtwig 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 está imprimiendo en un archivo de registro para una revisión posterior. Esto suele imprimir datos muy inútiles. Lo que quiero es imprimir exactamente las mismas líneas impresas cuando el error se dispare sin el bash, excepto interceptar la excepción, pero no quiero que detenga mi progtwig, ya que está nested en una serie de bucles for que me gustaría Ver hasta su finalización.

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

Tenga en cuenta que con print_exc , en algunos casos de esquina, no obtendrá lo que espera. En Python 2.x:

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

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

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

Si realmente necesita acceder a la solución original de traceback, una es almacenar en caché las informaciones de excepción devueltas por exc_info en una variable local y mostrarlas con 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  raise TypeError("Oups!") TypeError: Oups! 

Pocos escollos con esto sin embargo:

  • Desde el documento de sys_info :

    Asignar el valor de retorno de rastreo a una variable local en una función que está manejando 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 sea recolectada como basura. […] Si necesitas el rastreo, asegúrate de eliminarlo después de usarlo (lo mejor es hacerlo con una statement de prueba … finalmente)

  • Pero, desde el mismo documento:

    A partir de Python 2.2, dichos ciclos se reclaman automáticamente cuando la recolección de basura está habilitada y se vuelven inalcanzables, pero sigue siendo más eficiente para evitar la creación de 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  raise TypeError("Oups!") 

traceback.format_exc() o sys.exc_info() proporcionará más información si eso es lo que quieres.

 import traceback import sys try: do_stuff() except Exception: print(traceback.format_exc()) # or print(sys.exc_info()[0]) 

Si está depurando y solo desea ver el seguimiento de stack actual, simplemente puede llamar:

traceback.print_stack()

No hay necesidad de hacer manualmente una excepción para volver a atraparla.

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

Cuando no quiere detener su progtwig por un error, necesita manejar ese error con un bash / excepto:

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

Para extraer el rastreo completo, usaremos el módulo de traceback 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 método traceback.print_exc :

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

Que imprime:

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

Mejor que la impresión, el registro:

Sin embargo, una práctica recomendada es tener un registrador configurado para su módulo. Sabrá 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 función logger.exception 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 "", line 2, in  File "", line 2, in do_something_that_might_error File "", line 2, in raise_error RuntimeError: something bad happened! 

O tal vez solo quieras la cadena, en cuyo caso querrás la función traceback.format_exc 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 "", line 2, in  File "", line 2, in do_something_that_might_error File "", line 2, in raise_error RuntimeError: something bad happened! 

Conclusión

Y para las tres opciones, vemos que obtenemos la misma salida que cuando tenemos un error:

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

Además de la respuesta de @Aaron Hall, si está registrando, pero no quiere 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) 

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, deberá ajustar las declaraciones que pueden fallar en el bash / excepto lo más específico posible, en el bucle más interno posible.

Para obtener la traza precisa de la stack, como una cadena, que se hubiera elevado si no hubiera habido un bash / excepto que hubiera un paso sobre ella, simplemente coloque esto en el bloque de excepción que atrapa la excepción ofensiva.

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

A continuación, le indicamos cómo usarlo (asumiendo que flaky_func está definido y el log llama 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 capturar y volver a subir KeyboardInterrupt s, de modo que aún pueda eliminar el progtwig usando Ctrl-C. El registro está fuera del scope de la pregunta, pero una buena opción es el registro . Documentación para los módulos sys y traceback .

Primero, no use print s para el registro, hay un módulo de stdlib estable, probado y bien pensado para hacer eso: el logging . Definitivamente deberías usarlo en su lugar.

En segundo lugar, no se sienta tentado a ensuciarse con herramientas no relacionadas cuando existe un enfoque nativo y simple. Aquí es:

 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 has terminado.

Explicación para cualquier persona que esté interesada en cómo van las cosas bajo el capó.

Lo que log.exception haciendo log.exception es, en realidad, solo llamar a log.error (es decir, evento de registro con nivel ERROR ) e imprimir el rastreo a continuación.

¿Por qué es mejor?

Bueno, aquí hay algunas consideraciones:

  • es justo
  • es sencillo;
  • Es simple.

¿Por qué nadie debería usar treceback ni llamar al registrador con exc_info=True ni ensuciar sus manos con sys.exc_info ?

¡Bueno porque! Todos ellos existen para diferentes propósitos. Por ejemplo, la salida de traceback.print_exc es un poco diferente de las trazas producidas por el propio intérprete. Si vas a usarlo, confundirás a cualquiera que lo golpee con la cabeza.

Pasar exc_info=True para registrar llamadas es simplemente inapropiado. Pero , es útil cuando captura errores recuperables y desea registrarlos (usando, por ejemplo, el nivel INFO ) también con log.exception , ya que log.exception produce registros de solo un nivel: ERROR .

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

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

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, el hello veces se “mezcla” de forma extraña con el texto de rastreo, como si ambos quisieran escribir en stdout o stderr al mismo tiempo, produciendo una salida extraña (al menos cuando se construye desde un editor de texto y se ve la salida en el panel “Crear resultados”).

Rastreo (llamadas recientes más última):
Archivo “C: \ Users \ User \ Desktop \ test.py”, línea 7, en
hell 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
[Acabado en 0.1s]

Así que uso:

 import traceback, sys def do_stuff(): 1/0 try: do_stuff() except Exception: print(traceback.format_exc()) print('hello')