Protokollieren nicht erfasster Ausnahmen in Python

181

Wie können Sie dazu führen, dass nicht erfasste Ausnahmen über das loggingModul ausgegeben werden und nicht über stderr?

Mir ist klar, dass der beste Weg, dies zu tun, wäre:

try:
    raise Exception, 'Throwing a boring exception'
except Exception, e:
    logging.exception(e)

Aber meine Situation ist so, dass es wirklich schön wäre, wenn logging.exception(...)sie automatisch aufgerufen würden, wenn eine Ausnahme nicht abgefangen wird.

Jacob Marmor
quelle

Antworten:

143

Wie Ned betonte, sys.excepthookwird jedes Mal aufgerufen, wenn eine Ausnahme ausgelöst und nicht erfasst wird. Die praktische Implikation davon ist, dass Sie in Ihrem Code das Standardverhalten überschreiben können, um sys.excepthookzu tun, was Sie wollen (einschließlich der Verwendung logging.exception).

Als Strohmann Beispiel:

>>> import sys
>>> def foo(exctype, value, tb):
...     print 'My Error Information'
...     print 'Type:', exctype
...     print 'Value:', value
...     print 'Traceback:', tb
... 

Überschreiben sys.excepthook:

>>> sys.excepthook = foo

Übernehmen Sie einen offensichtlichen Syntaxfehler (lassen Sie den Doppelpunkt weg) und erhalten Sie benutzerdefinierte Fehlerinformationen zurück:

>>> def bar(a, b)
My Error Information
Type: <type 'exceptions.SyntaxError'>
Value: invalid syntax (<stdin>, line 1)
Traceback: None

Weitere Informationen zu sys.excepthook: http://docs.python.org/library/sys.html#sys.excepthook

Jacinda
quelle
4
@Codemonkey Es ist kein reserviertes Schlüsselwort, sondern ein bereits vorhandener Typname. Sie können typeals Funktionsargument verwenden, obwohl IDEs sich über das Ausblenden des Globalen beschweren type(ähnlich wie bei der Verwendung var self = thisin Javascript). Es spielt keine Rolle, es sei denn, Sie müssen auf das typeObjekt in Ihrer Funktion zugreifen. In diesem Fall können Sie type_stattdessen als Argument verwenden.
Ryan P
3
Der Ausdruck „jedes Mal“ hier ist irreführend :: „sys.excepthook aufgerufen wird jedes Mal eine Ausnahme ausgelöst und abgefangene“ ... weil in einem Programm, es kann sein , genau eine „abgefangene“ Ausnahme. Wird auch sys.excepthookNICHT aufgerufen, wenn eine Ausnahme "ausgelöst" wird. Es wird aufgerufen, wenn das Programm aufgrund einer nicht erfassten Ausnahme beendet wird, die nur einmal auftreten kann.
Nawaz
2
@ Nawaz: Es kann mehr als einmal in einem REPL
jfs
2
@Nawaz Es kann auch mehrmals vorkommen, wenn das Programm Threads verwendet. Ich habe auch den Eindruck, dass GUI-Ereignisschleifen (wie Qt) weiterhin ausgeführt werden, obwohl die Ausnahme es zu sys.excepthook
three_pineapples
1
Wenn Sie versuchen, den obigen Code zu testen, stellen Sie sicher, dass Sie beim Testen Ihrer Funktion einen Traceback-Fehler generieren. SyntaxError werden von sys.excepthook nicht behandelt. Sie können print (1/0) verwenden und dies soll die Funktion aufrufen, die Sie definiert haben, um sys.excepthook
Parth Karia
175

Hier ist ein komplettes kleines Beispiel, das auch einige andere Tricks enthält:

import sys
import logging
logger = logging.getLogger(__name__)
handler = logging.StreamHandler(stream=sys.stdout)
logger.addHandler(handler)

def handle_exception(exc_type, exc_value, exc_traceback):
    if issubclass(exc_type, KeyboardInterrupt):
        sys.__excepthook__(exc_type, exc_value, exc_traceback)
        return

    logger.error("Uncaught exception", exc_info=(exc_type, exc_value, exc_traceback))

sys.excepthook = handle_exception

if __name__ == "__main__":
    raise RuntimeError("Test unhandled")
  • Ignorieren Sie KeyboardInterrupt, damit ein Konsolen-Python-Programm mit Strg + C beendet werden kann.

  • Verlassen Sie sich beim Formatieren der Ausnahme vollständig auf das Protokollierungsmodul von Python.

  • Verwenden Sie einen benutzerdefinierten Logger mit einem Beispielhandler. Dieser ändert die nicht behandelte Ausnahme so, dass sie zu stdout und nicht zu stderr wechselt. Sie können dem Logger-Objekt jedoch alle Arten von Handlern in demselben Stil hinzufügen.

gnu_lorien
quelle
13
Ich würde logger.critical()innerhalb des Excepthook-Handlers verwenden, da eine nicht erfasste Ausnahme ziemlich kritisch ist, würde ich sagen.
Gitaarik
1
Dies ist die praktischste Antwort IMO.
David Morales
@gnu_lorien danke für das snippet. In welche Datei würden Sie das legen?
Stelios
@chefarov Die Hauptdatei, in der Sie alle anderen Protokollierungen initialisieren
gnu_lorien
Hallo, wie können wir diese Informationen in eine Datei wie debug.log schreiben? Ich versuche es, Zeile hinzuzufügen, hat logging.basicConfig(level=logging.DEBUG, filename="debug.log", format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')aber nicht geholfen.
GurhanCagin
26

Die Methode sys.excepthookwird aufgerufen, wenn eine Ausnahme nicht erfasst wird: http://docs.python.org/library/sys.html#sys.excepthook

Wenn eine Ausnahme ausgelöst und nicht erfasst wird, ruft der Interpreter sys.excepthook mit drei Argumenten auf, der Ausnahmeklasse, der Ausnahmeinstanz und einem Traceback-Objekt. In einer interaktiven Sitzung geschieht dies unmittelbar bevor die Steuerung an die Eingabeaufforderung zurückgegeben wird. In einem Python-Programm geschieht dies kurz vor dem Beenden des Programms. Die Behandlung solcher Ausnahmen der obersten Ebene kann angepasst werden, indem sys.excepthook eine weitere Funktion mit drei Argumenten zugewiesen wird.

Ned Batchelder
quelle
2
Warum sendet es die Ausnahmeklasse? Können Sie das nicht immer erreichen, indem Sie typedie Instanz aufrufen ?
Neil G
23

Warum nicht:

import sys
import logging
import traceback

def log_except_hook(*exc_info):
    text = "".join(traceback.format_exception(*exc_info))
    logging.error("Unhandled exception: %s", text)

sys.excepthook = log_except_hook

None()

Hier ist die Ausgabe mit sys.excepthookwie oben gezeigt:

$ python tb.py
ERROR:root:Unhandled exception: Traceback (most recent call last):
  File "tb.py", line 11, in <module>
    None()
TypeError: 'NoneType' object is not callable

Hier ist die Ausgabe mit dem sys.excepthookauskommentierten:

$ python tb.py
Traceback (most recent call last):
  File "tb.py", line 11, in <module>
    None()
TypeError: 'NoneType' object is not callable

Der einzige Unterschied ist, dass der erstere ERROR:root:Unhandled exception:am Anfang der ersten Zeile steht.

Tiago Coutinho
quelle
Ein weiterer Unterschied besteht darin, dass erstere den Trace in das Protokollierungssystem schreibt, sodass alle von Ihnen installierten Handler und Formatierer angewendet werden. Letzterer schreibt direkt an sys.stderr.
Radiaph
8

Um auf Jacindas Antwort aufzubauen, aber ein Logger-Objekt zu verwenden:

def catchException(logger, typ, value, traceback):
    logger.critical("My Error Information")
    logger.critical("Type: %s" % typ)
    logger.critical("Value: %s" % value)
    logger.critical("Traceback: %s" % traceback)

# Use a partially applied function
func = lambda typ, value, traceback: catchException(logger, typ, value, traceback)
sys.excepthook = func
Mike
quelle
2
Es wäre besser, functools.partial()anstelle von Lambda zu verwenden. Siehe: docs.python.org/2/library/functools.html#functools.partial
Mariusz Jamro
@MariuszJamro warum?
Davr
4

Schließen Sie Ihren App-Eintrag in einen try...exceptBlock ein , damit Sie alle nicht erfassten Ausnahmen abfangen und protokollieren (und möglicherweise erneut auslösen) können. ZB statt:

if __name__ == '__main__':
    main()

Mach das:

if __name__ == '__main__':
    try:
        main()
    except Exception as e:
        logger.exception(e)
        raise
Flaviovs
quelle
Dies ist nicht die Frage, die gestellt wird. Die Frage soll fragen, was zu tun ist, wenn die Ausnahme NICHT vom Code behandelt wird.
Mayank Jaiswal
1
Nun, Python ist eine Programmiersprache, und dies impliziert, dass es Dinge nicht "automatisch" macht (wie es das OP will), außer wenn und wann Sie es darum bitten. Mit anderen Worten, es gibt keine Möglichkeit, alle Ausnahmen "automatisch" zu protokollieren, es sei denn, Sie codieren dafür - und das steht in meiner Antwort.
Flaviovs
1
Wenn Sie sich die Antwort von Ned Batchelder ansehen, gibt es etwas, das als Ausnahmehaken bezeichnet wird. Sie müssen an einer Stelle in Ihrem Code definieren, und alle nicht erfassten Ausnahmen werden behandelt.
Mayank Jaiswal
1
Der Ausnahmehaken ändert nichts an der Tatsache, dass er nicht "automatisch" ist (in dem Sinne, wie es das OP wünscht) - mit anderen Worten, Sie müssen ihn immer noch codieren. Neds Antwort (die den Ausnahmehaken verwendet) spricht wirklich die ursprüngliche Frage an - es ist nur so, dass meiner Meinung nach die Art und Weise, wie sie das macht, viel weniger pythonisch ist als meine.
Flaviovs
1
Es hängt so ziemlich von Ihren eigenen Zielen ab. Wenn Sie programmieren, um die IDE zufrieden zu stellen, ist es möglicherweise nicht möglich, alle Ausnahmen abzufangen. Wenn Sie jedoch Fehler ordnungsgemäß behandeln und dem Benutzer ein nettes Feedback geben möchten, müssen Sie leider alle Ausnahmen abfangen. Ok, genug Sarkasmus :-) - Wenn Sie genau hinschauen, werden Sie feststellen, dass der Code die Ausnahme abfängt, aber dann erneut auslöst. Wenn Ihre IDE also nichts "Magisches" tut, sollte es nicht funktionieren, wird es trotzdem die Ausnahme.
Flaviovs
3

Vielleicht könnten Sie oben in einem Modul etwas tun, das stderr in eine Datei umleitet, und diese Datei dann unten protokollieren

sock = open('error.log', 'w')               
sys.stderr = sock

doSomething() #makes errors and they will log to error.log

logging.exception(open('error.log', 'r').read() )
JiminyCricket
quelle
3

Obwohl mir die Antwort von @ gnu_lorien einen guten Ausgangspunkt gab, stürzt mein Programm bei der ersten Ausnahme ab.

Ich kam mit einer angepassten (und / oder) verbesserten Lösung, die Ausnahmen von Funktionen, mit denen dekoriert ist, stillschweigend protokolliert @handle_error.

import logging

__author__ = 'ahmed'
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
    logging.critical(exc_value.message, exc_info=(exc_type, exc_value, exc_traceback))


def handle_error(func):
    import sys

    def __inner(*args, **kwargs):
        try:
            return func(*args, **kwargs)
        except Exception, e:
            exc_type, exc_value, exc_tb = sys.exc_info()
            handle_exception(exc_type, exc_value, exc_tb)
        finally:
            print(e.message)
    return __inner


@handle_error
def main():
    raise RuntimeError("RuntimeError")


if __name__ == "__main__":
    for _ in xrange(1, 20):
        main()
Guneysos
quelle
2

Um die Frage von Herrn Zeus zu beantworten, die im Kommentarbereich der akzeptierten Antwort besprochen wurde, verwende ich diese, um nicht erfasste Ausnahmen in einer interaktiven Konsole zu protokollieren (getestet mit PyCharm 2018-2019). Ich fand heraus, sys.excepthookdass es in einer Python-Shell nicht funktioniert, also schaute ich tiefer und stellte fest, dass ich sys.exc_infostattdessen verwenden könnte. Jedoch sys.exc_infonimmt im Gegensatz zu keine Argumentesys.excepthook zu, dass drei Argumente annimmt.

Hier benutze ich beide sys.excepthookundsys.exc_info , um beide Ausnahmen in einer interaktiven Konsole und einem Skript mit einer Wrapper-Funktion zu protokollieren. Um beiden Funktionen eine Hook-Funktion zuzuweisen, habe ich zwei verschiedene Schnittstellen, je nachdem, ob Argumente angegeben werden oder nicht.

Hier ist der Code:

def log_exception(exctype, value, traceback):
    logger.error("Uncaught exception occurred!",
                 exc_info=(exctype, value, traceback))


def attach_hook(hook_func, run_func):
    def inner(*args, **kwargs):
        if not (args or kwargs):
            # This condition is for sys.exc_info
            local_args = run_func()
            hook_func(*local_args)
        else:
            # This condition is for sys.excepthook
            hook_func(*args, **kwargs)
        return run_func(*args, **kwargs)
    return inner


sys.exc_info = attach_hook(log_exception, sys.exc_info)
sys.excepthook = attach_hook(log_exception, sys.excepthook)

Das Protokollierungssetup finden Sie in der Antwort von gnu_lorien.

Nabs
quelle
2

In meinem Fall (mit python 3) bei Verwendung von @Jacindas Antwort wurde der Inhalt des Tracebacks nicht gedruckt. Stattdessen wird nur das Objekt selbst gedruckt : <traceback object at 0x7f90299b7b90>.

Stattdessen mache ich:

import sys
import logging
import traceback

def custom_excepthook(exc_type, exc_value, exc_traceback):
    # Do not print exception when user cancels the program
    if issubclass(exc_type, KeyboardInterrupt):
        sys.__excepthook__(exc_type, exc_value, exc_traceback)
        return

    logging.error("An uncaught exception occurred:")
    logging.error("Type: %s", exc_type)
    logging.error("Value: %s", exc_value)

    if exc_traceback:
        format_exception = traceback.format_tb(exc_traceback)
        for line in format_exception:
            logging.error(repr(line))

sys.excepthook = custom_excepthook
veuncent
quelle