Quiero capturar y registrar excepciones sin salir, por ejemplo,

try:
    do_stuff()
except Exception as err:
    print(Exception, err)
    # I want to print the entire traceback here,
    # not just the exception name and details

Quiero imprimir exactamente el mismo resultado que se imprime cuando se genera la excepción sin que try/except intercepte la excepción, y no quiero que salga de mi programa.

respuesta

traceback.format_exc()o sys.exc_info()dará 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()[2])

Algunas otras respuestas ya han 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()

... 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, algunas trampas con esto:

  • Del documento de sys_info:

    Assigning the traceback return value to a local variable in a function that is handling an exception will cause a circular reference. This will prevent anything referenced by a local variable in the same function or by the traceback from being garbage collected. [...] If you do need the traceback, make sure to delete it after use (best done with a try ... finally statement)

  • pero, del mismo documento:

    Beginning with Python 2.2, such cycles are automatically reclaimed when garbage collection is enabled and they become unreachable, but it remains more efficient to avoid creating cycles.


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!")

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 volver a detectarla.

How to print the full traceback without halting the program?

Cuando no desea 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, utilice 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. 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 logger.exceptionfunción en su lugar:

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

Que registra:

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

Que registra:

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!

cual usar

Las preocupaciones de rendimiento no son importantes aquí, ya que IO suele dominar. Preferiría, ya que hace precisamente lo que se solicita de una manera compatible con versiones posteriores:

logger.exception(error)

Los niveles de registro y las salidas se pueden ajustar, lo que facilita el apagado sin tocar el código. Y, por lo general, hacer lo que se necesita directamente es la forma más eficiente de hacerlo.

Primero, no use prints para iniciar sesión, hay un stdlibmódulo estable, probado y bien pensado para hacer eso: logging. Definitivamente deberías usarlo en su lugar.

En segundo lugar, no caer en la tentación de hacer un lío con herramientas no relacionadas cuando existe 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 todo. Ya has terminado.

Explicación para cualquiera que esté interesado en cómo funcionan las cosas debajo del 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 seguimiento luego.

¿Por qué es mejor?

Bueno, aquí hay algunas consideraciones:

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

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

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

Pasar exc_info=Trueal registro de llamadas es simplemente inapropiado. Pero , es útil cuando se detectan errores recuperables y desea registrarlos (usando, por ejemplo, INFOnivel) con rastreos también, porque log.exceptionproduce registros de un solo nivel: ERROR.

Y definitivamente debes evitar jugar con sys.exc_infotodo lo que puedas. Simplemente no es una interfaz pública, es interna: puede usarla si definitivamente sabe lo que está haciendo. No está diseñado solo para imprimir excepciones.

traceback.format_exception(exception_object)

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_obj = e

tb_str = ''.join(traceback.format_exception(None, exc_obj, exc_obj.__traceback__))
print(tb_str)

Producción:

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.9/library/traceback.html#traceback.format_exception

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

Probado en Python 3.9

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

try:
    do_something_that_might_error()
except Exception:
    logging.info('General exception noted.', exc_info=True)

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

En Python 3.5+, puede obtener un seguimiento de un objeto Exception usando 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 generado stack_lvl_2()y no se hubiera interceptado (elimine el comentario de 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 genera, stack_lvl_3()en este caso. A medida que vuelve a pasar a través de la pila, se agregan más niveles a su archivo __traceback__. Pero lo interceptamos en stack_lvl_2(), lo que significa que todo lo que pudo registrar fueron los niveles 3 y 2. Para obtener el seguimiento completo impreso en la salida estándar, tendríamos que capturarlo en el nivel más alto (¿el 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 donde se generó hace que el código sea más simple y al mismo tiempo brinda más información.

Tendrá que poner el intento/excepto dentro del lazo 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á envolver las declaraciones que pueden fallar en try/except de la manera más específica posible, en el bucle más interno posible.

Para obtener el seguimiento de pila preciso , como una cadena, que se habría generado si no hubiera intento/excepto para pasar por alto, simplemente colóquelo en el bloque de excepción que captura la excepción infractora.

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 capturar y volver a subir KeyboardInterrupts, de modo 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 iniciar sesión . Documentación para los módulos sys y traceback .

Si ya tiene un objeto de error y desea imprimir todo, debe realizar 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 de la excepción.

En python 3.5 o posterior, type(err)es opcional... pero es un argumento posicional, por lo que aún debe pasar Ninguno explícitamente 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é querrías imprimir un error, junto con un rastreo que no sea el que pertenece a ese error, está más allá de mí.

Una observación 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 seguimiento, como si ambos quisieran escribir en stdout o stderr al mismo tiempo, produciendo un resultado extraño (al menos cuando se construye desde dentro de un editor de texto y se ve el resultado en el panel "Generar resultados").

Traceback (most recent call last):
File "C:\Users\User\Desktop\test.py", line 7, in
hell do_stuff()
File "C:\Users\User\Desktop\test.py", line 4, in do_stuff
1/0
ZeroDivisionError: integer division or modulo by zero
o
[Finished in 0.1s]

Así que uso:

import traceback, sys

def do_stuff():
    1/0

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

En python3 (funciona en 3.9) podemos definir una función y podemos usarla donde queramos imprimir los detalles.

import traceback

def get_traceback(e):
    lines = traceback.format_exception(type(e), e, e.__traceback__)
    return ''.join(lines)

try:
    1/0
except Exception as e:
    print('------Start--------')
    print(get_traceback(e))
    print('------End--------')

try:
    spam(1,2)
except Exception as e:
    print('------Start--------')
    print(get_traceback(e))
    print('------End--------')

La salida sería como:

bash-3.2$ python3 /Users/soumyabratakole/PycharmProjects/pythonProject/main.py
------Start--------
Traceback (most recent call last):
  File "/Users/soumyabratakole/PycharmProjects/pythonProject/main.py", line 26, in <module>
    1/0
ZeroDivisionError: division by zero

------End--------
------Start--------
Traceback (most recent call last):
  File "/Users/soumyabratakole/PycharmProjects/pythonProject/main.py", line 33, in <module>
    spam(1,2)
NameError: name 'spam' is not defined

------End--------
import io
import traceback

try:
    call_code_that_fails()
except:

    errors = io.StringIO()
    traceback.print_exc(file=errors)
    contents = str(errors.getvalue())
    print(contents)
    errors.close()

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

solución pitón 3

stacktrace_helper.py:

from linecache import getline
import sys
import traceback


def get_stack_trace():
    exc_type, exc_value, exc_tb = sys.exc_info()
    trace = traceback.format_stack()
    trace = list(filter(lambda x: ("\\lib\\" not in x and "/lib/" not in x and "stacktrace_helper.py" not in x), trace))
    ex_type = exc_type.__name__
    ex_line = exc_tb.tb_lineno
    ex_file = exc_tb.tb_frame.f_code.co_filename
    ex_message = str(exc_value)
    line_code = ""
    try:
        line_code = getline(ex_file, ex_line).strip()
    except:
        pass

    trace.insert(
        0, f'File "{ex_file}", line {ex_line}, line_code: {line_code} , ex: {ex_type} {ex_message}',
    )
    return trace


def get_stack_trace_str(msg: str = ""):
    trace = list(get_stack_trace())
    trace_str = "\n".join(list(map(str, trace)))
    trace_str = msg + "\n" + trace_str
    return trace_str

Esta es mi solución para escribir el error en un archivo de registro y también en la consola:

import logging, sys
import traceback
logging.basicConfig(filename='error.log', level=logging.DEBUG)

def handle_exception(exc_type, exc_value, exc_traceback):
    import sys
    if issubclass(exc_type, KeyboardInterrupt):
        sys.__excepthook__(exc_type, exc_value, exc_traceback)
        return
    exc_info=(exc_type, exc_value, exc_traceback)
    logging.critical("\nDate:" + str(datetime.datetime.now()), exc_info=(exc_type, exc_value, exc_traceback))
    print("An error occured, check error.log to see the error details")
    traceback.print_exception(*exc_info)


sys.excepthook = handle_exception

Podrías hacerlo:

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