Wie drucke ich den vollständigen Traceback, ohne das Programm anzuhalten?


779

Ich schreibe ein Programm, das 10 Websites analysiert, Datendateien findet, die Dateien speichert und sie dann analysiert, um Daten zu erstellen, die problemlos in der NumPy-Bibliothek verwendet werden können. Es gibt Unmengen von Fehlern, auf die diese Datei durch fehlerhafte Links, schlecht geformtes XML, fehlende Einträge und andere Dinge stößt, die ich noch nicht kategorisiert habe. Ich habe dieses Programm ursprünglich erstellt, um Fehler wie folgt zu behandeln:

try:
    do_stuff()
except:
    pass

Aber jetzt möchte ich Fehler protokollieren:

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

Beachten Sie, dass dies zur späteren Überprüfung in eine Protokolldatei gedruckt wird. Dies druckt normalerweise sehr nutzlose Daten. Ich möchte genau die gleichen Zeilen drucken, die gedruckt werden, wenn der Fehler ausgelöst wird, ohne dass try die Ausnahme abfängt. Ich möchte jedoch nicht, dass mein Programm angehalten wird, da es in einer Reihe von for-Schleifen verschachtelt ist, die ich möchte bis zur Fertigstellung sehen.

Antworten:


583

Eine andere Antwort hat bereits auf das Traceback- Modul hingewiesen .

Bitte beachten Sie, dass Sie print_excin einigen Eckfällen nicht das erhalten, was Sie erwarten würden. In Python 2.x:

import traceback

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

    traceback.print_exc()

... zeigt den Traceback der letzten Ausnahme an:

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

Wenn Sie wirklich auf das ursprüngliche Traceback zugreifen müssen, besteht eine Lösung darin, die von zurückgegebenen Ausnahmeinformationen zwischenzuspeichernexc_info in einer lokalen Variablen und zeigt es mit 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

Produzieren:

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

Einige Fallstricke dabei:

  • Aus dem Dokument von sys_info :

    Das Zuweisen des Traceback-Rückgabewerts zu einer lokalen Variablen in einer Funktion, die eine Ausnahme behandelt, führt zu einem Zirkelverweis . Dadurch wird verhindert, dass irgendetwas, auf das von einer lokalen Variablen in derselben Funktion oder vom Traceback verwiesen wird, durch Müll gesammelt wird. [...] Wenn Sie den Traceback benötigen, löschen Sie ihn nach der Verwendung (am besten mit einer try ... finally-Anweisung).

  • aber aus dem gleichen Dokument:

    Ab Python 2.2 werden solche Zyklen automatisch zurückgefordert, wenn die Speicherbereinigung aktiviert ist und nicht mehr erreichbar ist. Es bleibt jedoch effizienter, das Erstellen von Zyklen zu vermeiden.


Auf der anderen Seite liefert Python 3 ein weniger überraschendes Ergebnis , indem Sie auf den mit einer Ausnahme verbundenen Traceback zugreifen können :

import traceback

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

    traceback.print_tb(err.__traceback__)

... wird angezeigt:

  File "e3.py", line 4, in <module>
    raise TypeError("Oups!")


258

Wenn Sie debuggen und nur den aktuellen Stack-Trace anzeigen möchten, können Sie einfach Folgendes aufrufen:

traceback.print_stack()

Es ist nicht erforderlich, eine Ausnahme manuell auszulösen, um sie erneut abzufangen.


9
Das Traceback-Modul macht genau das - eine Ausnahme auslösen und abfangen.
pppery

3
Die Ausgabe geht standardmäßig an STDERR. Wurde nicht in meinen Protokollen angezeigt, da es an eine andere Stelle umgeleitet wurde.
Mpen

101

Wie drucke ich den vollständigen Traceback, ohne das Programm anzuhalten?

Wenn Sie Ihr Programm bei einem Fehler nicht anhalten möchten, müssen Sie diesen Fehler mit einem Versuch / außer behandeln:

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

Um den vollständigen Traceback zu extrahieren, verwenden wir das tracebackModul aus der Standardbibliothek:

import traceback

Und um einen anständig komplizierten Stacktrace zu erstellen, um zu demonstrieren, dass wir den vollständigen Stacktrace erhalten:

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

def do_something_that_might_error():
    raise_error()

Drucken

Verwenden Sie die Methode, um den vollständigen Traceback zu druckentraceback.print_exc :

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

Welche Drucke:

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!

Besser als drucken, protokollieren:

Es wird jedoch empfohlen, einen Logger für Ihr Modul einzurichten. Es kennt den Namen des Moduls und kann Ebenen ändern (unter anderem Attribute wie Handler).

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

In diesem Fall möchten Sie logger.exceptionstattdessen die Funktion:

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

Welche Protokolle:

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!

Oder vielleicht möchten Sie nur die Zeichenfolge. In diesem Fall möchten Sie traceback.format_excstattdessen die Funktion:

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

Welche Protokolle:

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!

Fazit

Und für alle drei Optionen erhalten wir dieselbe Ausgabe wie bei einem Fehler:

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

2
Wie oben gesagt und auch für mich, traceback.print_exc()gibt nur den letzten Aufruf zurück: Wie gelingt es Ihnen, mehrere Ebenen des Stapels (und möglicherweise alle Ebenen?) zurückzugeben
Herve-Guerin

@geekobi Ich bin nicht sicher, was Sie hier fragen. Ich zeige, dass wir den Traceback bis zum Einstiegspunkt des Programms / Interpreters bekommen. Was ist dir nicht klar?
Aaron Hall

1
Was @geekobi sagt, ist, dass traceback.print_exc () beim Abfangen und erneuten Erhöhen nur den erneuten Erhöhungsstapel zurückgibt, nicht den ursprünglichen Stapel.
Fizloki

@fizloki wie geht es dir "reraising"? Führen Sie eine bloße raiseVerkettung oder Ausnahmeverkettung durch oder verstecken Sie den ursprünglichen Traceback? siehe stackoverflow.com/questions/2052390/…
Aaron Hall

21

Verwenden Sie prints nicht zum Protokollieren, es gibt ein stabiles, bewährtes und gut durchdachtes stdlib-Modul, um dies zu tun : logging. Sie sollten es auf jeden Fall stattdessen verwenden.

Zweitens wird nicht versucht eine tun Chaos mit nicht verwandten Tool , wenn es stammt und einfacher Ansatz. Hier ist es:

log = logging.getLogger(__name__)

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

Das ist es. Du bist jetzt fertig.

Erklärung für alle, die daran interessiert sind, wie die Dinge unter der Haube funktionieren

Was log.exceptiontatsächlich getan wird, ist nur ein Aufruf von log.error(dh ein Ereignis mit Ebene protokollieren ERROR) und dann ein Traceback drucken.

Warum ist es besser?

Nun, hier sind einige Überlegungen:

  • es ist genau richtig ;
  • es ist unkompliziert;
  • es ist einfach.

Warum sollte niemand tracebackLogger benutzen oder anrufen exc_info=Trueoder sich die Hände schmutzig machen sys.exc_info?

Nur weil! Sie existieren alle für unterschiedliche Zwecke. Beispielsweise unterscheidet sich traceback.print_excdie Ausgabe ein wenig von den vom Interpreter selbst erzeugten Tracebacks. Wenn Sie es verwenden, werden Sie jeden verwirren, der Ihre Protokolle liest, sie werden ihre Köpfe gegen sie schlagen.

Das Übergeben exc_info=True, um Anrufe zu protokollieren, ist einfach unangemessen. Es ist jedoch nützlich, wenn Sie behebbare Fehler abfangen und diese (z. B. mit INFOLevel) auch mit Tracebacks protokollieren möchten , da log.exceptionnur Protokolle mit nur einem Level erstellt werden.ERROR .

Und Sie sollten auf jeden Fall vermeiden, mit sys.exc_infoso viel wie möglich herumzuspielen . Es ist einfach keine öffentliche Schnittstelle, es ist eine interne - Sie können sie verwenden, wenn Sie definitiv wissen, was Sie tun. Es ist nicht nur zum Drucken von Ausnahmen gedacht.


4
Es funktioniert auch nicht so wie es ist. Das ist es nicht. Ich bin jetzt noch nicht fertig: Diese Antwort verschwendet nur Zeit.
A. Rager

Ich würde auch hinzufügen, dass Sie einfach tun können logging.exception(). Sie müssen keine Protokollinstanz erstellen, es sei denn, Sie haben spezielle Anforderungen.
Shital Shah

9

Zusätzlich zur Antwort von @Aaron Hall können Sie, wenn Sie sich anmelden, aber nicht verwenden möchten logging.exception()(da es sich auf der FEHLER-Ebene anmeldet), eine niedrigere Ebene verwenden und bestehen exc_info=True. z.B

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

7

Um die genaue Stapelverfolgung als Zeichenfolge zu erhalten, die ausgelöst worden wäre, wenn kein Versuch / Ausnahme vorhanden wäre, um darüber zu treten, platzieren Sie diese einfach in dem Block "Ausgenommen", der die betreffende Ausnahme abfängt.

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

So verwenden Sie es (vorausgesetzt, es flaky_funcist definiert und logruft Ihr bevorzugtes Protokollierungssystem auf):

import traceback
import sys

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

Es ist eine gute Idee, KeyboardInterrupts zu fangen und erneut zu erhöhen , damit Sie das Programm weiterhin mit Strg-C beenden können. Die Protokollierung liegt außerhalb des Bereichs der Frage, aber eine gute Option ist die Protokollierung . Dokumentation für die Sys- und Traceback- Module.


4
Dies funktioniert in Python 3 nicht und muss geändert werden desired_trace = traceback.format_exc(). Als sys.exc_info()Argument zu übergeben war nie das Richtige, wird aber in Python 2 stillschweigend ignoriert - aber nicht in Python 3 (3.6.4 sowieso).
Martineau

2
KeyboardInterruptwird nicht (direkt oder indirekt) von abgeleitet Exception. (Beide sind abgeleitet von BaseException.) Dies bedeutet, except Exception:dass niemals ein gefangen wird KeyboardInterrupt, und daher except KeyboardInterrupt: raiseist das völlig unnötig.
AJNeufeld

traceback.format_exc(sys.exc_info())funktioniert nicht für mich mit Python 3.6.10
Nam G VU

6

Sie müssen den try / außer in den innersten Loop legen, in dem der Fehler auftreten kann, d. H.

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

... und so weiter

Mit anderen Worten, Sie müssen Anweisungen, die beim Versuch fehlschlagen können, außer so spezifisch wie möglich, in die innerste Schleife wie möglich einschließen.


6

Eine Bemerkung zu den Kommentaren dieser Antwort : print(traceback.format_exc())macht einen besseren Job für mich als traceback.print_exc(). Bei letzterem wird das hellomanchmal seltsamerweise mit dem Traceback-Text "gemischt", beispielsweise wenn beide gleichzeitig in stdout oder stderr schreiben möchten, was zu einer seltsamen Ausgabe führt (zumindest beim Erstellen aus einem Texteditor heraus und beim Anzeigen der Ausgabe im Bereich "Ergebnisse erstellen").

Traceback (letzter Aufruf zuletzt):
Datei "C: \ Benutzer \ Benutzer \ Desktop \ test.py", Zeile 7, in
hell do_stuff ()
Datei "C: \ Benutzer \ Benutzer \ Desktop \ test.py", Zeile 4 , in do_stuff
1/0
ZeroDivisionError: Ganzzahldivision oder Modulo durch Null
o
[Fertig in 0,1 s]

Also benutze ich:

import traceback, sys

def do_stuff():
    1/0

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

5

Ich sehe dies in keiner der anderen Antworten erwähnt. Wenn Sie ein Ausnahmeobjekt aus irgendeinem Grund weitergeben ...

In Python 3.5+ können Sie mit traceback.TracebackException.from_exception () einen Trace von einem Exception-Objekt abrufen . Zum Beispiel:

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

Der obige Code führt jedoch zu:

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

Dies sind nur zwei Ebenen des Stapels, im Gegensatz zu dem, was auf dem Bildschirm gedruckt worden wäre, wenn die Ausnahme ausgelöst stack_lvl_2()und nicht abgefangen worden wäre (kommentieren Sie das aus# raise Zeile aus).

Soweit ich weiß, liegt dies daran, dass eine Ausnahme stack_lvl_3()in diesem Fall nur die aktuelle Ebene des Stapels aufzeichnet, wenn dieser ausgelöst wird. Wenn es wieder durch den Stapel geleitet wird, werden weitere Ebenen hinzugefügt __traceback__. Aber wir haben es abgefangen stack_lvl_2(), was bedeutet, dass es nur Level 3 und 2 aufnehmen musste. Um die vollständige Spur zu erhalten, wie sie auf stdout gedruckt ist, müssten wir sie auf der höchsten (niedrigsten?) Ebene fangen:

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

Was in ... resultiert:

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

Beachten Sie, dass der Stapeldruck unterschiedlich ist und die erste und die letzte Zeile fehlen. Weil es anders istformat() .

Wenn Sie die Ausnahme so weit wie möglich von dem Punkt entfernt abfangen, an dem sie ausgelöst wurde, wird der Code einfacher und es werden mehr Informationen bereitgestellt.


Dies ist viel besser als die vorherigen Methoden, ist aber immer noch lächerlich kompliziert, nur um eine Stapelspur auszudrucken. Java benötigt weniger Code FGS.
Elhefe

4

Ruft den vollständigen Traceback als String vom Ausnahmeobjekt mit ab traceback.format_exception

Wenn Sie nur das Ausnahmeobjekt haben, können Sie den Traceback als Zeichenfolge von jedem Punkt des Codes in Python 3 abrufen mit:

import traceback

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

Vollständiges Beispiel:

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

Ausgabe:

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

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

Siehe auch: Traceback-Informationen aus einem Ausnahmeobjekt extrahieren

Getestet in Python 3.7.3.



2

Wenn Sie bereits ein Fehlerobjekt haben und das Ganze drucken möchten, müssen Sie diesen etwas umständlichen Aufruf ausführen:

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

Das stimmt, print_exceptiondauert drei Positionsargumente: Die Art der Ausnahme, das tatsächlichen Ausnahmeobjekts, und die eigenen internen Zurückverfolgungs Unterkunft Ausnahme.

In Python 3.5 oder höher ist das type(err)optional ... aber es ist ein Positionsargument, sodass Sie an seiner Stelle immer noch None explizit übergeben müssen.

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

Ich habe keine Ahnung, warum das alles nicht gerecht ist traceback.print_exception(err). Warum Sie jemals einen Fehler zusammen mit einem anderen Traceback als dem, der zu diesem Fehler gehört, ausdrucken möchten, ist mir ein Rätsel.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.