Obter descrição da exceção e rastreamento da pilha que causaram uma exceção, tudo como uma sequência

423

Eu já vi muitas postagens sobre rastreamento de pilha e exceções no Python. Mas não encontrei o que eu preciso.

Eu tenho um pedaço de código Python 2.7 que pode gerar uma exceção. Gostaria de pegá-lo e atribuir a uma string sua descrição completa e o rastreamento da pilha que causou o erro (simplesmente tudo o que usamos para ver no console). Eu preciso dessa string para imprimi-la em uma caixa de texto na GUI.

Algo assim:

try:
    method_that_can_raise_an_exception(params)
except Exception as e:
    print_to_textbox(complete_exception_description(e))

O problema é: qual é a função complete_exception_description?

azulado
fonte

Respostas:

616

Veja o tracebackmódulo, especificamente a format_exc()função. Aqui .

import traceback

try:
    raise ValueError
except ValueError:
    tb = traceback.format_exc()
else:
    tb = "No error"
finally:
    print tb
kindall
fonte
2
Isso funciona apenas com o último erro? O que acontece se você começar a passar o erro para outros bits de código? Estou escrevendo uma log_error(err)função.
AnnanFay
Funciona com o erro que foi detectado e tratado.
Kindall
74

Vamos criar um stacktrace decentemente complicado, para demonstrar que temos o stacktrace completo:

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

def do_something_that_might_error():
    raise_error()

Registrando o StackTrace Completo

Uma prática recomendada é ter um criador de logs configurado para o seu módulo. Ele saberá o nome do módulo e poderá alterar os níveis (entre outros atributos, como manipuladores)

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

E podemos usar esse logger para obter o erro:

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

Quais logs:

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!

E assim obtemos a mesma saída que quando temos um erro:

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

Obtendo apenas a string

Se você realmente deseja apenas a string, use a traceback.format_excfunção, demonstrando o registro da string aqui:

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

Quais logs:

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!
Aaron Hall
fonte
1
este é o melhor método ao usar python 3 (comparado com, por exemplo, algumas das respostas abaixo)?
Yunti 03/03/19
1
@Yunti Acredito que essa API tenha sido consistente nos Python 2 e 3. #
Aaron Hall
A formatação desta resposta foi discutida em meta: meta.stackoverflow.com/questions/386477/… .
Lundin
Enviei uma edição para o seguinte, mas não estava logado, mostrando-o como anônimo: except Exception as e: logger.exception("<<clearly and distinctly describe what failed here>>", exc_info=e)
arntg 19/11/19
@arntg Agradeço que você esteja tentando ajudar, mas essa edição seria uma mudança prejudicial. Tenha muito mais cuidado no futuro para entender completamente as APIs que você está tentando usar. Nesse caso, o exc_infoargumento espera uma "tupla de exceção", enquanto the erroré uma instância do Exceptionobjeto (ou subclasse) e não há necessidade de mudar errorpara e.
Aaron Hall
39

Com o Python 3, o código a seguir formatará um Exceptionobjeto exatamente como seria obtido usando traceback.format_exc():

import traceback

try: 
    method_that_can_raise_an_exception(params)
except Exception as ex:
    print(''.join(traceback.format_exception(etype=type(ex), value=ex, tb=ex.__traceback__)))

A vantagem é que apenas o Exceptionobjeto é necessário (graças ao __traceback__atributo gravado ) e, portanto, pode ser passado mais facilmente como argumento para outra função para processamento adicional.

Erwin Mayer
fonte
1
É melhor que sys.exc_info (), que não é bom estilo OO e usa variável global.
WeiChing é
Esta pergunta especificamente como obter o rastreamento do objeto de exceção como você tem feito aqui: stackoverflow.com/questions/11414894/...
Ciro Santilli郝海东冠状病六四事件法轮功
Existe uma maneira mais simples Python3 sem usar .__traceback__e type, veja stackoverflow.com/a/58764987/5717886
don_vanchos
34
>>> import sys
>>> import traceback
>>> try:
...   5 / 0
... except ZeroDivisionError as e:
...   type_, value_, traceback_ = sys.exc_info()
>>> traceback.format_tb(traceback_)
['  File "<stdin>", line 2, in <module>\n']
>>> value_
ZeroDivisionError('integer division or modulo by zero',)
>>> type_
<type 'exceptions.ZeroDivisionError'>
>>>
>>> 5 / 0
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ZeroDivisionError: integer division or modulo by zero

Você usa sys.exc_info () para coletar as informações e as funções no tracebackmódulo para formatá-las. Aqui estão alguns exemplos para formatá-lo.

Toda a cadeia de exceção está em:

>>> ex = traceback.format_exception(type_, value_, traceback_)
>>> ex
['Traceback (most recent call last):\n', '  File "<stdin>", line 2, in <module>\n', 'ZeroDivisionError: integer division or modulo by zero\n']
aeter
fonte
9

Para aqueles que usam Python-3

O uso do tracebackmódulo e exception.__traceback__um pode extrair o rastreamento de pilha da seguinte maneira:

  • pegue o rastreamento de pilha atual usandotraceback.extract_stack()
  • remova os três últimos elementos (como essas são entradas na pilha que me levaram à minha função de depuração)
  • acrescente o __traceback__objeto de exceção usandotraceback.extract_tb()
  • formatar a coisa toda usando traceback.format_list()
import traceback
def exception_to_string(excp):
   stack = traceback.extract_stack()[:-3] + traceback.extract_tb(excp.__traceback__)  # add limit=?? 
   pretty = traceback.format_list(stack)
   return ''.join(pretty) + '\n  {} {}'.format(excp.__class__,excp)

Uma demonstração simples:

def foo():
    try:
        something_invalid()
    except Exception as e:
        print(exception_to_string(e))

def bar():
    return foo()

Obtemos a seguinte saída quando chamamos bar():

  File "./test.py", line 57, in <module>
    bar()
  File "./test.py", line 55, in bar
    return foo()
  File "./test.py", line 50, in foo
    something_invalid()

  <class 'NameError'> name 'something_invalid' is not defined
Mike N
fonte
Parece um código complicado ilegível. Em Python 3.5+ há uma maneira mais elegante e simples: stackoverflow.com/a/58764987/5717886
don_vanchos
6

Você também pode considerar usar o módulo Python embutido , cgitb , para obter algumas informações de exceção realmente boas e bem formatadas, incluindo valores de variáveis ​​locais, contexto do código fonte, parâmetros de função etc.

Por exemplo, para este código ...

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)

obtemos essa saída de exceção ...

ZeroDivisionError
Python 3.4.2: C:\tools\python\python.exe
Tue Sep 22 15:29:33 2015

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

 c:\TEMP\cgittest2.py in <module>()
    7 def func1(a, b):
    8   c = b - 5
    9   return func2(a, c)
   10
   11 func1(1, 5)
func1 = <function func1>

 c:\TEMP\cgittest2.py in func1(a=1, b=5)
    7 def func1(a, b):
    8   c = b - 5
    9   return func2(a, c)
   10
   11 func1(1, 5)
global func2 = <function func2>
a = 1
c = 0

 c:\TEMP\cgittest2.py in func2(a=1, divisor=0)
    3
    4 def func2(a, divisor):
    5   return a / divisor
    6
    7 def func1(a, b):
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>
    __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 "cgittest2.py", line 11, in <module>
    func1(1, 5)
  File "cgittest2.py", line 9, in func1
    return func2(a, c)
  File "cgittest2.py", line 5, in func2
    return a / divisor
ZeroDivisionError: division by zero
samaspin
fonte
5

Se você deseja obter as mesmas informações fornecidas quando uma exceção não é tratada, você pode fazer algo assim. Faça import tracebacke depois:

try:
    ...
except Exception as e:
    print(traceback.print_tb(e.__traceback__))

Estou usando o Python 3.7.

SamuelN
fonte
3

Para Python 3.5+ :

Portanto, você pode obter o rastreamento de pilha da sua exceção e de qualquer outra exceção. Use traceback.TracebackExceptionpara isso (basta substituir expor sua exceção):

print("".join(traceback.TracebackException.from_exception(ex).format())

Um exemplo estendido e outros recursos para fazer isso:

import traceback

try:
    1/0
except Exception as ex:
    print("".join(traceback.TracebackException.from_exception(ex).format()) == traceback.format_exc() == "".join(traceback.format_exception(type(ex), ex, ex.__traceback__))) # This is True !!
    print("".join(traceback.TracebackException.from_exception(ex).format()))

A saída será algo como isto:

True
Traceback (most recent call last):
  File "untidsfsdfsdftled.py", line 29, in <module>
    1/0
ZeroDivisionError: division by zero
don_vanchos
fonte
1

meus 2 centavos:

import sys, traceback
try: 
  ...
except Exception, e:
  T, V, TB = sys.exc_info()
  print ''.join(traceback.format_exception(T,V,TB))
usuário 1155692
fonte
1

Se seu objetivo é fazer com que a mensagem de exceção e de rastreamento de pilha seja exatamente como quando o python gera um erro, o seguinte funciona no python 2 + 3:

import sys, traceback


def format_stacktrace():
    parts = ["Traceback (most recent call last):\n"]
    parts.extend(traceback.format_stack(limit=25)[:-2])
    parts.extend(traceback.format_exception(*sys.exc_info())[1:])
    return "".join(parts)

# EXAMPLE BELOW...

def a():
    b()


def b():
    c()


def c():
    d()


def d():
    assert False, "Noooh don't do it."


print("THIS IS THE FORMATTED STRING")
print("============================\n")

try:
    a()
except:
    stacktrace = format_stacktrace()
    print(stacktrace)

print("THIS IS HOW PYTHON DOES IT")
print("==========================\n")
a()

Funciona removendo a última format_stacktrace()chamada da pilha e juntando-se ao restante. Quando executado, o exemplo acima fornece a seguinte saída:

THIS IS THE FORMATTED STRING
============================

Traceback (most recent call last):
  File "test.py", line 31, in <module>
    a()
  File "test.py", line 12, in a
    b()
  File "test.py", line 16, in b
    c()
  File "test.py", line 20, in c
    d()
  File "test.py", line 24, in d
    assert False, "Noooh don't do it."
AssertionError: Noooh don't do it.

THIS IS HOW PYTHON DOES IT
==========================

Traceback (most recent call last):
  File "test.py", line 38, in <module>
    a()
  File "test.py", line 12, in a
    b()
  File "test.py", line 16, in b
    c()
  File "test.py", line 20, in c
    d()
  File "test.py", line 24, in d
    assert False, "Noooh don't do it."
AssertionError: Noooh don't do it.
Rune Kaagaard
fonte
0

Eu defini a seguinte classe auxiliar:

import traceback
class TracedExeptions(object):
    def __init__(self):
        pass
    def __enter__(self):
        pass

    def __exit__(self, etype, value, tb):
      if value :
        if not hasattr(value, 'traceString'):
          value.traceString = "\n".join(traceback.format_exception(etype, value, tb))
        return False
      return True

Que mais tarde posso usar assim:

with TracedExeptions():
  #some-code-which-might-throw-any-exception

E depois pode consumi-lo assim:

def log_err(ex):
  if hasattr(ex, 'traceString'):
    print("ERROR:{}".format(ex.traceString));
  else:
    print("ERROR:{}".format(ex));

(Antecedentes: fiquei frustrado por usar Promises junto com Exceptions, que infelizmente passa exceções geradas em um local para um manipulador on_rejected em outro local e, portanto, é difícil obter o retorno do local original)

qbolec
fonte