Uzyskaj opis wyjątku i ślad stosu, który spowodował wyjątek, wszystkie w postaci ciągu

423

Widziałem wiele postów dotyczących śledzenia stosu i wyjątków w Pythonie. Ale nie znalazłem tego, czego potrzebuję.

Mam fragment kodu Python 2.7, który może zgłosić wyjątek. Chciałbym go złapać i przypisać ciągowi pełny opis i ślad stosu, który spowodował błąd (po prostu wszystko, co widzimy na konsoli). Potrzebuję tego ciągu, aby wydrukować go do pola tekstowego w GUI.

Coś takiego:

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

Problemem jest: jaka jest funkcja complete_exception_description?

niebieskawy
źródło

Odpowiedzi:

615

Zobacz tracebackmoduł, w szczególności format_exc()funkcję. Tutaj .

import traceback

try:
    raise ValueError
except ValueError:
    tb = traceback.format_exc()
else:
    tb = "No error"
finally:
    print tb
kindall
źródło
2
Czy to działa tylko z ostatnim błędem? Co się stanie, jeśli zaczniesz przekazywać błąd innym fragmentom kodu? Piszę log_error(err)funkcję.
AnnanFay,
Działa z błędem, który został wychwycony i obsłużony.
kindall
74

Stwórzmy dość skomplikowane śledzenie stosu, aby pokazać, że otrzymujemy pełny stos śledzenia:

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

def do_something_that_might_error():
    raise_error()

Rejestrowanie pełnego śledzenia śledzenia

Najlepszą praktyką jest skonfigurowanie rejestratora dla modułu. Będzie znał nazwę modułu i będzie mógł zmieniać poziomy (między innymi atrybutami, takimi jak moduły obsługi)

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

I możemy użyć tego rejestratora, aby uzyskać błąd:

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

Które dzienniki:

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!

Otrzymujemy takie same dane wyjściowe, jak w przypadku błędu:

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

Uzyskiwanie tylko sznurka

Jeśli naprawdę chcesz po prostu napisu, użyj traceback.format_exczamiast niego funkcji, demonstrując rejestrowanie napisu tutaj:

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

Które dzienniki:

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
źródło
1
czy jest to najlepsza metoda przy użyciu Pythona 3 (w porównaniu np. z niektórymi odpowiedziami poniżej)?
Yunti
1
@Yunti Wierzę, że ten interfejs API był spójny w Pythonie 2 i 3.
Aaron Hall
Formatowanie tej odpowiedzi zostało omówione na stronie meta: meta.stackoverflow.com/questions/386477/… .
Lundin
Wysłałem edycję do następujących, ale nie byłem zalogowany, więc pokazuję się jako anonimowy: except Exception as e: logger.exception("<<clearly and distinctly describe what failed here>>", exc_info=e)
arntg
@arntg Doceniam to, że próbujesz pomóc, ale ta edycja byłaby szkodliwą zmianą. Zachowaj ostrożność w przyszłości, aby w pełni zrozumieć interfejsy API, których próbujesz użyć. W takim przypadku exc_infoargument oczekuje „krotki wyjątku”, podczas gdy errorjest instancją Exceptionobiektu (lub podklasy) i nie ma potrzeby zmiany errorna e.
Aaron Hall
39

W Pythonie 3 następujący kod sformatuje Exceptionobiekt dokładnie tak, jak można go uzyskać za pomocą 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__)))

Zaletą jest to, że Exceptionpotrzebny jest tylko obiekt (dzięki zarejestrowanemu __traceback__atrybutowi), a zatem można go łatwiej przekazać jako argument do innej funkcji w celu dalszego przetwarzania.

Erwin Mayer
źródło
1
Jest lepszy niż sys.exc_info (), który nie jest dobrym stylem OO i używa zmiennej globalnej.
WeiChing 清 煒 清
To pytanie dotyczy w szczególności sposobu uzyskania śledzenia z obiektu wyjątku, tak jak to zrobiono tutaj: stackoverflow.com/questions/11414894/…
Ciro Santilli 法轮功 冠状 病 六四 事件
Jest prostszy sposób Python3 bez użycia .__traceback__i type, patrz 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

Użyć sys.exc_info () , aby zebrać informacje i funkcje w tracebackmodule, aby go sformatować. Tutaj kilka przykładów jego formatowania.

Cały ciąg wyjątku znajduje się w:

>>> 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
źródło
9

Dla osób używających Python-3

Za pomocą tracebackmodułu exception.__traceback__można wyodrębnić ślad stosu w następujący sposób:

  • pobierz bieżący ślad stosu za pomocątraceback.extract_stack()
  • usuń ostatnie trzy elementy (ponieważ są to wpisy na stosie, które doprowadziły mnie do mojej funkcji debugowania)
  • dołącz __traceback__obiekt wyjątku za pomocątraceback.extract_tb()
  • sformatuj całość za pomocą 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)

Prosta demonstracja:

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

def bar():
    return foo()

Gdy wywołujemy, otrzymujemy następujące dane wyjściowe 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.
źródło
Wygląda jak nieczytelny skomplikowany kod. W Python 3.5+ istnieje bardziej elegancki i prosty sposób: stackoverflow.com/a/58764987/5717886
don_vanchos
6

Możesz także rozważyć użycie wbudowanego modułu Python, cgitb , aby uzyskać naprawdę dobre, ładnie sformatowane informacje o wyjątkach, w tym wartości zmiennych lokalnych, kontekst kodu źródłowego, parametry funkcji itp.

Na przykład dla tego kodu ...

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)

otrzymujemy ten wyjątek wyjściowy ...

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
źródło
5

Jeśli chcesz uzyskać te same informacje, gdy wyjątek nie jest obsługiwany, możesz zrobić coś takiego. Zrób, import tracebacka następnie:

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

Używam Python 3.7.

SamuelN
źródło
3

W przypadku Python 3.5+ :

Możesz więc pobrać śledzenie stosu ze swojego wyjątku, jak z każdego innego wyjątku. Użyj traceback.TracebackExceptiondo tego (po prostu zastąp exswoim wyjątkiem):

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

Rozszerzony przykład i inne funkcje do tego celu:

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

Wynik będzie mniej więcej taki:

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

moje 2 centy:

import sys, traceback
try: 
  ...
except Exception, e:
  T, V, TB = sys.exc_info()
  print ''.join(traceback.format_exception(T,V,TB))
użytkownik 1155692
źródło
1

Jeśli Twoim celem jest, aby wyjątek i komunikat stacktrace wyglądały dokładnie tak, jak gdy python zgłasza błąd, poniższe działania działają zarówno w 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()

Działa poprzez usunięcie ostatniego format_stacktrace()wywołania ze stosu i dołączenie do reszty. Po uruchomieniu powyższy przykład daje następujące dane wyjściowe:

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
źródło
0

Zdefiniowałem następującą klasę pomocnika:

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

Którego później mogę użyć w następujący sposób:

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

A później można go spożywać w ten sposób:

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

(Tło: Byłem sfrustrowany, ponieważ używałem Promises razem z Exceptions, co niestety przekazuje wyjątki podniesione w jednym miejscu do procedury obsługi on_rejected w innym miejscu, a zatem trudno jest odzyskać traceback z pierwotnej lokalizacji)

qbolec
źródło