Jak wydrukować pełny traceback bez zatrzymywania programu?

777

Piszę program, który analizuje 10 stron internetowych, lokalizuje pliki danych, zapisuje pliki, a następnie analizuje je, aby utworzyć dane, które można łatwo wykorzystać w bibliotece NumPy. Istnieje mnóstwo błędów, które ten plik napotyka przez złe linki, źle sformułowany XML, brakujące wpisy i inne rzeczy, które jeszcze nie skategoryzowałem. Początkowo stworzyłem ten program do obsługi takich błędów:

try:
    do_stuff()
except:
    pass

Ale teraz chcę rejestrować błędy:

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

Uwaga: drukuje się do pliku dziennika w celu późniejszego przejrzenia. Zwykle drukuje to bardzo bezużyteczne dane. Chcę wydrukować dokładnie te same wiersze, które zostaną wydrukowane, gdy błąd zostanie uruchomiony bez przechwytywania wyjątku przez try-wyjątkiem, ale nie chcę, aby zatrzymał mój program, ponieważ jest zagnieżdżony w szeregu pętli for, które chciałbym do końca.

Chrisrisley
źródło

Odpowiedzi:

580

Niektóre inne odpowiedzi już wskazały moduł śledzenia wstecznego .

Zauważ, że print_excw niektórych przypadkach narożnych nie uzyskasz tego, czego można oczekiwać. W Python 2.x:

import traceback

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

    traceback.print_exc()

... wyświetli ślad ostatniego wyjątku:

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

Jeśli naprawdę potrzebujesz dostępu do oryginalnego śledzenia, jednym z rozwiązań jest buforowanie informacji o wyjątkach zwróconych exc_infow zmiennej lokalnej i wyświetlanie ich za pomocą 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

Produkcja:

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

Kilka pułapek:

  • Z dokumentu sys_info:

    Przypisanie wartości zwracanej śledzenia do zmiennej lokalnej w funkcji obsługującej wyjątek spowoduje odwołanie cykliczne . Zapobiegnie to gromadzeniu śmieci, do których odwołuje się zmienna lokalna w tej samej funkcji lub funkcja śledzenia wstecznego. [...] Jeśli potrzebujesz śledzenia, pamiętaj, aby usunąć go po użyciu (najlepiej zrobić z instrukcją try ... wreszcie)

  • ale z tego samego dokumentu:

    Począwszy od Python 2.2, takie cykle są automatycznie odzyskiwane, gdy jest włączone odśmiecanie i stają się nieosiągalne, ale pozostaje bardziej efektywne, aby uniknąć tworzenia cykli.


Z drugiej strony, pozwalając na dostęp do śledzenia powiązanego z wyjątkiem, Python 3 daje mniej zaskakujący wynik:

import traceback

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

    traceback.print_tb(err.__traceback__)

... wyświetli się:

  File "e3.py", line 4, in <module>
    raise TypeError("Oups!")
Sylvain Leroux
źródło
708

traceback.format_exc()lub sys.exc_info()dostarczy więcej informacji, jeśli tego właśnie chcesz.

import traceback
import sys

try:
    do_stuff()
except Exception:
    print(traceback.format_exc())
    # or
    print(sys.exc_info()[2])
woltowanie
źródło
1
print(sys.exc_info()[0]odciski <class 'Exception'>.
weberc2,
2
nie używaj exc ... traceback zawiera wszystkie informacje stackoverflow.com/questions/4564559/…
qrtLs
258

Jeśli debugujesz i po prostu chcesz zobaczyć bieżący ślad stosu, możesz po prostu wywołać:

traceback.print_stack()

Nie ma potrzeby ręcznego podnoszenia wyjątku, aby go ponownie złapać.

dimo414
źródło
9
Moduł śledzenia wstecz robi dokładnie to - zgłasza i przechwytuje wyjątek.
pppery
3
Wyjście domyślnie przechodzi do STDERR BTW. Nie pojawiał się w moich logach, ponieważ był przekierowywany gdzie indziej.
mpen
101

Jak wydrukować pełny traceback bez zatrzymywania programu?

Jeśli nie chcesz zatrzymać programu z powodu błędu, musisz obsłużyć ten błąd za pomocą try / wyjątkiem:

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

Aby wyodrębnić pełny traceback, użyjemy tracebackmodułu ze standardowej biblioteki:

import traceback

I aby stworzyć 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()

Druk

Aby wydrukować pełny ślad, użyj traceback.print_excmetody:

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

Które wydruki:

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!

Lepsze niż drukowanie, logowanie:

Jednak 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__)

W takim przypadku będziesz potrzebować logger.exceptionfunkcji:

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!

A może po prostu chcesz ciąg, w którym to przypadku będziesz potrzebować traceback.format_excfunkcji:

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

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!

Wniosek

I dla wszystkich trzech opcji widzimy, że 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!
Aaron Hall
źródło
2
jak powiedziano powyżej i dla mnie, traceback.print_exc()zwraca tylko ostatnie wywołanie: jak udało ci się zwrócić kilka poziomów stosu (i ewentualnie wszystkie poziomy?)
herve-guerin
@geekobi Nie jestem pewien, o co tu pytasz. Pokazuję, że uzyskujemy powrót do punktu wejścia programu / tłumacza. Co nie jest jasne?
Aaron Hall
1
@Geekobi mówi, że jeśli złapiesz i przebijesz, traceback.print_exc () zwróci tylko stos ponownego przebijania, a nie oryginalny stos.
fizloki
@fizloki jak się masz? Robisz raisełańcuchy typu „goły” lub „wyjątkowy”, czy ukrywasz pierwotny ślad? patrz stackoverflow.com/questions/2052390/…
Aaron Hall
21

Po pierwsze, nie używaj prints do logowania, istnieje astabilny, sprawdzone i dobrze przemyślane moduł stdlib do zrobienia, że: logging. Zdecydowanie powinieneś go użyć zamiast tego.

Po drugie, nie ulegaj pokusie zrobienia bałaganu z niepowiązanymi narzędziami, gdy istnieje natywne i proste podejście. Oto on:

log = logging.getLogger(__name__)

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

Otóż ​​to. Już skończyłeś.

Wyjaśnienie dla każdego, kto jest zainteresowany tym, jak rzeczy działają pod maską

To, co log.exceptionfaktycznie robi, to po prostu wywołanie log.error(tj. Rejestrowanie zdarzenia z poziomem ERROR) i wydrukowanie śledzenia.

Dlaczego to jest lepsze?

Oto kilka uwag:

  • jest w sam raz ;
  • to jest proste;
  • to jest proste.

Dlaczego nikt nie powinien używać tracebackrejestratora ani do niego dzwonić exc_info=Trueani brudzić sobie rąk sys.exc_info?

Tylko dlatego! Wszystkie istnieją do różnych celów. Na przykład traceback.print_excdane wyjściowe nieco różnią się od tracebacków wytwarzanych przez samego tłumacza. Jeśli go użyjesz, zdezorientujesz każdego, kto czyta twoje dzienniki, będą walić w nie głowami.

Przekazywanie exc_info=Truedo rejestrowania połączeń jest po prostu nieodpowiednie. Ale , to jest przydatne podczas połowu błędy możliwe do odzyskania i chcesz je log (przy użyciu np INFOpoziom) z tracebacks jak dobrze, ponieważ log.exceptionprodukuje dzienniki tylko na jednym poziomie - ERROR.

I zdecydowanie powinieneś unikać bałaganu, sys.exc_infona ile możesz. Po prostu nie jest to interfejs publiczny, lecz wewnętrzny - możesz go użyć, jeśli na pewno wiesz, co robisz. Nie jest przeznaczony do drukowania wyjątków.

bzdury
źródło
4
To również nie działa tak, jak jest. To nie to. Jeszcze nie skończyłem: ta odpowiedź po prostu marnuje czas.
A. Rager
Dodałbym również, że możesz to po prostu zrobić logging.exception(). Nie musisz tworzyć wystąpienia dziennika, chyba że masz specjalne wymagania.
Shital Shah
9

Oprócz odpowiedzi @Aarona Halla, jeśli logujesz się, ale nie chcesz używać logging.exception()(ponieważ loguje się na poziomie ERROR), możesz użyć niższego poziomu i przekazać exc_info=True. na przykład

try:
    do_something_that_might_error()
except Exception:
    logger.info('General exception noted.', exc_info=True)
Mark McDonald
źródło
7

Aby uzyskać dokładny ślad stosu, jako ciąg znaków, który zostałby podniesiony, gdyby nie próbowano go przekroczyć, po prostu umieść go w bloku wyjątku, który przechwytuje wyjątek.

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

Oto jak z niego korzystać (zakładając, że flaky_funcjest zdefiniowane i logwywołuje twój ulubiony system rejestrowania):

import traceback
import sys

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

Dobrym pomysłem jest łapanie i przebijanie KeyboardInterrupt, abyś mógł nadal zabijać program za pomocą Ctrl-C. Logowanie jest poza zakresem pytania, ale dobrą opcją jest logowanie . Dokumentacja dla sys i modułów śledzenia .

Edward Newell
źródło
4
To nie działa w Pythonie 3 i musi zostać zmienione na desired_trace = traceback.format_exc(). Przekazywanie sys.exc_info()jako argument nigdy nie było właściwą rzeczą, ale jest dyskretnie ignorowane w Pythonie 2 - ale nie w Pythonie 3 (w każdym razie 3.6.4).
martineau
2
KeyboardInterruptnie pochodzi (bezpośrednio ani pośrednio) z Exception. (Oba pochodzą od BaseException.) Oznacza to, except Exception:że nigdy nie złapie KeyboardInterrupt, a zatem except KeyboardInterrupt: raisejest całkowicie niepotrzebne.
AJNeufeld,
traceback.format_exc(sys.exc_info())nie działa dla mnie z python 3.6.10
Nam G VU
6

Będziesz musiał umieścić try / oprócz wewnątrz najbardziej wewnętrznej pętli, w której może wystąpić błąd, tj

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

... i tak dalej

Innymi słowy, będziesz musiał zawinąć instrukcje, które mogą się nie powieść w try / oprócz możliwie najbardziej szczegółowych, w najbardziej wewnętrznej pętli, jak to możliwe.

Ivo van der Wijk
źródło
6

Uwaga na temat komentarzy do tej odpowiedzi : print(traceback.format_exc())robi dla mnie lepszą robotę niż traceback.print_exc(). W tym drugim przypadku hellojest on dziwnie „mieszany” z tekstem śledzenia, tak jakby obaj chcieli jednocześnie pisać na stdout lub stderr, wytwarzając dziwne dane wyjściowe (przynajmniej podczas budowania z edytora tekstu i przeglądania wyników w Panel „Buduj wyniki”).

Traceback (ostatnie ostatnie połączenie):
Plik „C: \ Users \ User \ Desktop \ test.py”, wiersz 7, w
piekle do_stuff ()
Plik „C: \ Users \ User \ Desktop \ test.py”, wiersz 4 , w do_stuff
1/0 ZeroDivisionError
: dzielenie liczb całkowitych lub modulo przez zero
o
[Zakończony w 0,1 s]

Więc używam:

import traceback, sys

def do_stuff():
    1/0

try:
    do_stuff()
except Exception:
    print(traceback.format_exc())
    print('hello')
Basj
źródło
5

Nie widzę tego w żadnej innej odpowiedzi. Jeśli omijasz obiekt wyjątku z jakiegokolwiek powodu ...

W Pythonie 3.5+ można uzyskać śledzenie z obiektu wyjątku za pomocą traceback.TracebackException.from_exception () . Na przykład:

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

Powyższy kod powoduje jednak:

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

To tylko dwa poziomy stosu, w przeciwieństwie do tego, co wydrukowano by na ekranie, gdyby wyjątek został podniesiony stack_lvl_2()i nie został przechwycony (odkomentowanie # raiselinii).

Jak rozumiem, stack_lvl_3()dzieje się tak, ponieważ wyjątek rejestruje tylko bieżący poziom stosu, gdy jest podnoszony, w tym przypadku. Gdy jest on przekazywany z powrotem przez stos, dodaje się do niego kolejne poziomy __traceback__. Ale przechwyciliśmy go stack_lvl_2(), co oznacza, że ​​wszystko, co udało mu się nagrać, to poziomy 3 i 2. Aby uzyskać pełny ślad wydrukowany na standardowym wyjściu, musielibyśmy go złapać na najwyższym (najniższym?) Poziomie:

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

Co skutkuje w:

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

Zauważ, że wydruk na stosie jest inny, brakuje pierwszego i ostatniego wiersza. Ponieważ jest inaczejformat() .

Przechwytywanie wyjątku jak najdalej od miejsca, w którym został zgłoszony, upraszcza kod, jednocześnie zapewniając więcej informacji.

bgdnlp
źródło
Jest to o wiele lepsze niż poprzednie metody, ale nadal jest absurdalnie skomplikowane, aby wydrukować ślad stosu. Java zajmuje mniej kodu FGS.
elhefe
3

Chcesz moduł śledzenia wstecznego . Pozwala wydrukować zrzuty stosów, tak jak zwykle robi to Python. W szczególności funkcja print_last wydrukuje ostatni wyjątek i ślad stosu.

nmichaels
źródło
3

Uzyskaj pełny traceback jako ciąg znaków od obiektu wyjątku za pomocą traceback.format_exception

Jeśli masz tylko obiekt wyjątku, możesz uzyskać traceback jako ciąg znaków z dowolnego punktu kodu w Pythonie 3 za pomocą:

import traceback

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

Pełny przykład:

#!/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)

Wynik:

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

Dokumentacja: https://docs.python.org/3.7/library/traceback.html#traceback.format_exception

Zobacz także: Wyodrębnij informacje śledzenia z obiektu wyjątku

Testowane w Pythonie 3.7.3.

Ciro Santilli
źródło
2

Jeśli masz już obiekt Error i chcesz wydrukować całość, musisz wykonać to nieco niezręczne wywołanie:

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

Zgadza się, print_exceptionprzyjmuje trzy argumenty pozycyjne: typ wyjątku, rzeczywisty obiekt wyjątku i własną wewnętrzną właściwość traceback wyjątku.

W Pythonie 3.5 lub nowszym type(err)opcja jest opcjonalna ... ale jest to argument pozycyjny, więc nadal musisz jawnie przekazać None w jej miejsce.

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

Nie mam pojęcia, dlaczego to wszystko nie jest po prostu traceback.print_exception(err). Dlaczego miałbyś kiedykolwiek chcieć wydrukować błąd wraz ze śledzeniem innym niż ten, który należy do tego błędu, jest poza mną.

nupanick
źródło