Wie protokolliere ich einen Python-Fehler mit Debug-Informationen?

468

Ich drucke Python-Ausnahmemeldungen in eine Protokolldatei mit logging.error:

import logging
try:
    1/0
except ZeroDivisionError as e:
    logging.error(e)  # ERROR:root:division by zero

Ist es möglich, detailliertere Informationen über die Ausnahme und den Code zu drucken, der sie generiert hat, als nur die Ausnahmezeichenfolge? Dinge wie Zeilennummern oder Stapelspuren wären großartig.

wahrscheinlich am Strand
quelle

Antworten:

733

logger.exception gibt neben der Fehlermeldung einen Stack-Trace aus.

Zum Beispiel:

import logging
try:
    1/0
except ZeroDivisionError as e:
    logging.exception("message")

Ausgabe:

ERROR:root:message
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
ZeroDivisionError: integer division or modulo by zero

@Paulo Überprüfen Sie die Hinweise: " Beachten Sie , dass Sie in Python 3 die logging.exceptionMethode direkt innerhalb des exceptTeils aufrufen müssen . Wenn Sie diese Methode an einer beliebigen Stelle aufrufen, wird möglicherweise eine bizarre Ausnahme angezeigt. Die Dokumentation weist darauf hin."

SiggyF
quelle
131
Die exceptionMethode ruft einfach auf error(message, exc_info=1). Sobald Sie exc_infoaus einem Ausnahmekontext an eine der Protokollierungsmethoden übergeben, erhalten Sie einen Traceback.
Helmut Grohne
16
Sie können auch festlegen sys.excepthook(siehe hier ), um zu vermeiden, dass Sie Ihren gesamten Code in try / exception einschließen müssen.
jul
23
Sie könnten einfach schreiben, except Exception:weil Sie ein keiner Weise verwenden;)
Marco Ferrari
21
Möglicherweise möchten Sie dies überprüfen, ewenn Sie versuchen, Ihren Code interaktiv zu debuggen. :) Deshalb schließe ich es immer ein.
Vicki Laidler
4
Korrigieren Sie mich, wenn ich falsch liege. In diesem Fall wird die Ausnahme nicht wirklich behandelt. Daher ist es sinnvoll, sie raiseam Ende des exceptBereichs hinzuzufügen . Andernfalls wird der Lauf fortgesetzt, als ob alles in Ordnung wäre.
Dror
184

Eine nette Sache über logging.exceptiondie SiggyF Antwort nicht zeigt , ist , dass Sie in einer beliebigen Nachricht passieren können, und die Protokollierung zeigt noch die vollen Zurückverfolgungs mit allen Ausnahme - Details:

import logging
try:
    1/0
except ZeroDivisionError:
    logging.exception("Deliberate divide by zero traceback")

Mit dem Standardprotokollierungsverhalten (in neueren Versionen), bei dem nur Fehler gedruckt werden sys.stderr, sieht es folgendermaßen aus:

>>> import logging
>>> try:
...     1/0
... except ZeroDivisionError:
...     logging.exception("Deliberate divide by zero traceback")
... 
ERROR:root:Deliberate divide by zero traceback
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
ZeroDivisionError: integer division or modulo by zero
ncoghlan
quelle
Kann eine Ausnahme ohne Angabe einer Nachricht protokolliert werden?
Stevoisiak
@StevenVascellaro - Ich würde vorschlagen, dass Sie bestehen, ''wenn Sie wirklich keine Nachricht eingeben möchten. Die Funktion kann jedoch nicht ohne mindestens ein Argument aufgerufen werden, sodass Sie ihr etwas geben müssen.
ArtOfWarfare
147

Die Verwendung von exc_infoOptionen ist möglicherweise besser, damit Sie die Fehlerstufe auswählen können (wenn Sie sie verwenden exception, befindet sie sich immer auf der errorStufe):

try:
    # do something here
except Exception as e:
    logging.critical(e, exc_info=True)  # log exception info at CRITICAL log level
Flycee
quelle
@CivFan: Ich habe mir die anderen Änderungen oder das Post-Intro nicht angesehen. Dieses Intro wurde auch von einem Drittanbieter-Editor hinzugefügt. Ich sehe in den gelöschten Kommentaren nirgendwo, dass dies jemals beabsichtigt war, aber ich kann meine Bearbeitung genauso gut rückgängig machen und die Kommentare entfernen. Es ist zu lange her, dass die Abstimmung hier für etwas anderes als die bearbeitete Version stattgefunden hat .
Martijn Pieters
Befindet sich logging.fataleine Methode in der Protokollierungsbibliothek? Ich sehe nur critical.
Ian
1
@ Ian Es ist ein Alias ​​zu critical, genau wie warnzu warning.
0xc0de
35

Zitieren

Was ist, wenn Ihre Anwendung auf andere Weise protokolliert - ohne das loggingModul zu verwenden?

Jetzt tracebackkönnte hier verwendet werden.

import traceback

def log_traceback(ex, ex_traceback=None):
    if ex_traceback is None:
        ex_traceback = ex.__traceback__
    tb_lines = [ line.rstrip('\n') for line in
                 traceback.format_exception(ex.__class__, ex, ex_traceback)]
    exception_logger.log(tb_lines)
  • Verwenden Sie es in Python 2 :

    try:
        # your function call is here
    except Exception as ex:
        _, _, ex_traceback = sys.exc_info()
        log_traceback(ex, ex_traceback)
  • Verwenden Sie es in Python 3 :

    try:
        x = get_number()
    except Exception as ex:
        log_traceback(ex)
zangw
quelle
Warum haben Sie "_, _, ex_traceback = sys.exc_info ()" außerhalb der Funktion log_traceback platziert und dann als Argument übergeben? Warum nicht direkt in der Funktion verwenden?
Basil Musa
@BasilMusa, um Ihre Frage zu beantworten, kurz gesagt, um mit Python 3 kompatibel zu sein, da das ex_tracebackvon ex.__traceback__unter Python 3 ist, aber ex_tracebackvon sys.exc_info()unter Python 2.
zangw
12

Wenn Sie einfache Protokolle verwenden, sollten alle Ihre Protokolldatensätze dieser Regel entsprechen : one record = one line. Nach dieser Regel können Sie grepund andere Tools verwenden, um Ihre Protokolldateien zu verarbeiten.

Traceback-Informationen sind jedoch mehrzeilig. Meine Antwort ist also eine erweiterte Version der Lösung, die zangw oben in diesem Thread vorgeschlagen hat. Das Problem ist, dass Traceback-Zeilen enthalten sein können. Daher müssen \nwir zusätzliche Arbeit leisten, um diese Zeilenenden zu entfernen:

import logging


logger = logging.getLogger('your_logger_here')

def log_app_error(e: BaseException, level=logging.ERROR) -> None:
    e_traceback = traceback.format_exception(e.__class__, e, e.__traceback__)
    traceback_lines = []
    for line in [line.rstrip('\n') for line in e_traceback]:
        traceback_lines.extend(line.splitlines())
    logger.log(level, traceback_lines.__str__())

Danach (wenn Sie Ihre Protokolle analysieren) können Sie die erforderlichen Traceback-Zeilen aus Ihrer Protokolldatei kopieren / einfügen und Folgendes tun:

ex_traceback = ['line 1', 'line 2', ...]
for line in ex_traceback:
    print(line)

Profitieren!

Doomatel
quelle
9

Diese Antwort baut auf den oben genannten ausgezeichneten auf.

In den meisten Anwendungen rufen Sie logging.exception (e) nicht direkt auf. Höchstwahrscheinlich haben Sie einen für Ihre Anwendung oder Ihr Modul spezifischen benutzerdefinierten Logger wie folgt definiert:

# Set the name of the app or module
my_logger = logging.getLogger('NEM Sequencer')
# Set the log level
my_logger.setLevel(logging.INFO)

# Let's say we want to be fancy and log to a graylog2 log server
graylog_handler = graypy.GELFHandler('some_server_ip', 12201)
graylog_handler.setLevel(logging.INFO)
my_logger.addHandler(graylog_handler)

Verwenden Sie in diesem Fall einfach den Logger, um die Ausnahme (e) wie folgt aufzurufen:

try:
    1/0
except ZeroDivisionError, e:
    my_logger.exception(e)
Wille
quelle
Dies ist in der Tat ein nützlicher Abschluss, wenn Sie einen dedizierten Logger nur für Ausnahmen benötigen.
LogicOnAbstractions
7

Sie können den Stack-Trace ausnahmslos protokollieren.

https://docs.python.org/3/library/logging.html#logging.Logger.debug

Das zweite optionale Schlüsselwortargument ist stack_info, das standardmäßig False ist. Wenn true, werden der Protokollierungsnachricht Stapelinformationen hinzugefügt, einschließlich des tatsächlichen Protokollierungsaufrufs. Beachten Sie, dass dies nicht die gleichen Stapelinformationen sind wie die, die durch Angabe von exc_info angezeigt werden: Ersteres sind Stapelrahmen vom unteren Ende des Stapels bis zum Protokollierungsaufruf im aktuellen Thread, während letzterer Informationen zu abgewickelten Stapelrahmen sind. Folgen einer Ausnahme bei der Suche nach Ausnahmebehandlungsroutinen.

Beispiel:

>>> import logging
>>> logging.basicConfig(level=logging.DEBUG)
>>> logging.getLogger().info('This prints the stack', stack_info=True)
INFO:root:This prints the stack
Stack (most recent call last):
  File "<stdin>", line 1, in <module>
>>>
Baczek
quelle
5

Ein bisschen Dekorationsbehandlung (sehr locker inspiriert von der Vielleicht-Monade und dem Heben). Sie können Anmerkungen vom Typ Python 3.6 sicher entfernen und einen älteren Formatierungsstil für Nachrichten verwenden.

fehlbar

from functools import wraps
from typing import Callable, TypeVar, Optional
import logging


A = TypeVar('A')


def fallible(*exceptions, logger=None) \
        -> Callable[[Callable[..., A]], Callable[..., Optional[A]]]:
    """
    :param exceptions: a list of exceptions to catch
    :param logger: pass a custom logger; None means the default logger, 
                   False disables logging altogether.
    """
    def fwrap(f: Callable[..., A]) -> Callable[..., Optional[A]]:

        @wraps(f)
        def wrapped(*args, **kwargs):
            try:
                return f(*args, **kwargs)
            except exceptions:
                message = f'called {f} with *args={args} and **kwargs={kwargs}'
                if logger:
                    logger.exception(message)
                if logger is None:
                    logging.exception(message)
                return None

        return wrapped

    return fwrap

Demo:

In [1] from fallible import fallible

In [2]: @fallible(ArithmeticError)
    ...: def div(a, b):
    ...:     return a / b
    ...: 
    ...: 

In [3]: div(1, 2)
Out[3]: 0.5

In [4]: res = div(1, 0)
ERROR:root:called <function div at 0x10d3c6ae8> with *args=(1, 0) and **kwargs={}
Traceback (most recent call last):
  File "/Users/user/fallible.py", line 17, in wrapped
    return f(*args, **kwargs)
  File "<ipython-input-17-e056bd886b5c>", line 3, in div
    return a / b

In [5]: repr(res)
'None'

Sie können diese Lösung auch so ändern, dass sie etwas aussagekräftigeres als Nonedas exceptTeil zurückgibt (oder die Lösung generisch machen, indem Sie diesen Rückgabewert in fallibleden Argumenten angeben).

Eli Korvigo
quelle
0

Aktivieren Sie in Ihrem Protokollierungsmodul (falls benutzerdefiniertes Modul) einfach stack_info.

api_logger.exceptionLog("*Input your Custom error message*",stack_info=True)
Piyush Kumar
quelle
-1

Wenn Sie mit der zusätzlichen Abhängigkeit fertig werden können, verwenden Sie twisted.log. Sie müssen Fehler nicht explizit protokollieren und geben auch den gesamten Traceback und die gesamte Zeit an die Datei oder den Stream zurück.

Jakob Bowyer
quelle
8
Vielleicht twistedist es eine gute Empfehlung, aber diese Antwort trägt nicht wirklich viel dazu bei. Es wird weder angegeben, wie es verwendet wird twisted.log, welche Vorteile es gegenüber dem loggingModul aus der Standardbibliothek hat, noch erklärt, was unter "Sie müssen Fehler nicht explizit protokollieren" zu verstehen ist .
Mark Amery
-8

Eine saubere Methode besteht darin, format_exc()die Ausgabe zu verwenden und dann zu analysieren, um den relevanten Teil zu erhalten:

from traceback import format_exc

try:
    1/0
except Exception:
    print 'the relevant part is: '+format_exc().split('\n')[-2]

Grüße

Caraconan
quelle
4
Huh? Warum ist das "der relevante Teil" ? Alles, was Sie tun .split('\n')[-2]müssen, ist , die Zeilennummer und den Traceback aus dem Ergebnis wegzuwerfenformat_exc() - nützliche Informationen, die Sie normalerweise möchten! Was mehr ist, macht es nicht einmal einen guten Job davon ; Wenn Ihre Ausnahmemeldung eine neue Zeile enthält, wird bei diesem Ansatz nur die letzte Zeile der Ausnahmemeldung gedruckt. Dies bedeutet, dass Sie die Ausnahmeklasse und den größten Teil der Ausnahmemeldung zusätzlich zum Traceback verlieren. -1.
Mark Amery