Wie kann ich meine Python-Fehler protokollieren?
try:
do_something()
except:
# How can I log my exception here, complete with its traceback?
Verwenden Sie logging.exception
from mit einem except:
-Handler, um die aktuelle Ausnahme mit einer Nachricht zu protokollieren.
import logging
LOG_FILENAME = '/tmp/logging_example.out'
logging.basicConfig(filename=LOG_FILENAME, level=logging.DEBUG)
logging.debug('This message should go to the log file')
try:
run_my_stuff()
except:
logging.exception('Got exception on main handler')
raise
Sehen Sie sich nun die Protokolldatei an, /tmp/logging_example.out
:
DEBUG:root:This message should go to the log file
ERROR:root:Got exception on main handler
Traceback (most recent call last):
File "/tmp/teste.py", line 9, in <module>
run_my_stuff()
NameError: name 'run_my_stuff' is not defined
Verwenden Sie die exc_info-Optionen möglicherweise besser, bleibt der Warn- oder Fehlertitel:
try:
# coode in here
except Exception as e:
logging.error(e, exc_info=True)
Mein Job hat mich kürzlich dazu gebracht, alle Tracebacks/Ausnahmen aus unserer Anwendung zu protokollieren. Ich habe zahlreiche Techniken ausprobiert, die andere online veröffentlicht hatten, z. B. die oben genannte, entschied mich jedoch für einen anderen Ansatz. Überschreiben von traceback.print_exception
.
Ich habe ein Schreiben an http://www.bbarrows.com/ Das wäre viel einfacher zu lesen, aber ich füge es hier ein.
Bei der Protokollierung aller Ausnahmen, auf die unsere Software in der Wildnis stoßen könnte, habe ich verschiedene Techniken zum Protokollieren der Python-Ausnahme-Tracebacks ausprobiert. Zuerst dachte ich, dass der Python-System-Ausnahmehaken sys.excepthook der perfekte Ort ist, um den Protokollierungscode einzufügen. Ich habe etwas Ähnliches ausprobiert:
import traceback
import StringIO
import logging
import os, sys
def my_excepthook(excType, excValue, traceback, logger=logger):
logger.error("Logging an uncaught exception",
exc_info=(excType, excValue, traceback))
sys.excepthook = my_excepthook
Dies funktionierte für den Haupt-Thread, aber ich stellte bald fest, dass mein sys.excepthook nicht in allen neuen Threads existierte, die mein Prozess startete. Dies ist ein riesiges Problem, da in diesem Projekt meistens alles in Threads geschieht.
Nachdem ich gegoogelt und eine Menge Dokumentation gelesen hatte, war die hilfreichste Information, die ich gefunden hatte, der Python Issue Tracker.
Der erste Beitrag im Thread zeigt ein Arbeitsbeispiel, in dem sys.excepthook
NICHT über Threads bestehen bleibt (siehe unten). Anscheinend handelt es sich hierbei um erwartetes Verhalten.
import sys, threading
def log_exception(*args):
print 'got exception %s' % (args,)
sys.excepthook = log_exception
def foo():
a = 1 / 0
threading.Thread(target=foo).start()
Die Nachrichten in diesem Python-Issue-Thread führen tatsächlich zu zwei vorgeschlagenen Hacks. Entweder die Unterklasse Thread
und die Ausführungsmethode in unserem eigenen try außer Block einfassen, um Ausnahmen abzufangen und zu protokollieren, oder den Affen-Patch threading.Thread.run
in Ihrem eigenen try außer dem Blockieren und Protokollieren der Ausnahmen ausführen.
Die erste Methode der Unterklasse Thread
scheint mir in Ihrem Code weniger elegant zu sein, da Sie Ihre benutzerdefinierte Thread
-Klasse ÜBERALL importieren und verwenden müssten, obwohl Sie einen Logingthread haben wollten. Dies wurde am Ende zu einem Ärger, weil ich unsere gesamte Codebasis durchsuchen und alle normalen Threads
durch diese benutzerdefinierte Thread
ersetzen musste. Es war jedoch klar, was diese Thread
tat, und es wäre für jemanden einfacher zu diagnostizieren und zu debuggen, wenn mit dem benutzerdefinierten Protokollierungscode etwas schief ging. Ein kundenspezifischer Protokollierungs-Thread könnte folgendermaßen aussehen:
class TracebackLoggingThread(threading.Thread):
def run(self):
try:
super(TracebackLoggingThread, self).run()
except (KeyboardInterrupt, SystemExit):
raise
except Exception, e:
logger = logging.getLogger('')
logger.exception("Logging an uncaught exception")
Die zweite Methode des Affenpatching threading.Thread.run
ist Nice, weil ich es gleich nach __main__
ausführen und meinen Protokollierungscode in allen Ausnahmen instrumentieren könnte. Das Patching von Affen kann beim Debuggen ärgerlich sein, da es die erwartete Funktionalität von etwas ändert. Der empfohlene Patch aus dem Python Issue Tracker war:
def installThreadExcepthook():
"""
Workaround for sys.excepthook thread bug
From
http://spyced.blogspot.com/2007/06/workaround-for-sysexcepthook-bug.html
(https://sourceforge.net/tracker/?func=detail&atid=105470&aid=1230540&group_id=5470).
Call once from __main__ before creating any threads.
If using psyco, call psyco.cannotcompile(threading.Thread.run)
since this replaces a new-style class method.
"""
init_old = threading.Thread.__init__
def init(self, *args, **kwargs):
init_old(self, *args, **kwargs)
run_old = self.run
def run_with_except_hook(*args, **kw):
try:
run_old(*args, **kw)
except (KeyboardInterrupt, SystemExit):
raise
except:
sys.excepthook(*sys.exc_info())
self.run = run_with_except_hook
threading.Thread.__init__ = init
Erst als ich mit dem Testen meiner Ausnahmelogging begann, wurde mir klar, dass ich alles falsch gemacht hatte.
Zum Testen hatte ich eine
raise Exception("Test")
irgendwo in meinem Code. Das Wrapping einer Methode, die diese Methode aufrief, war jedoch ein try, mit Ausnahme des Blocks, der das Traceback ausgab und die Ausnahme schluckte. Das war sehr frustrierend, weil ich sah, dass der Traceback in STDOUT gedruckt wurde, aber nicht protokolliert wurde. Ich beschloss dann, dass eine viel einfachere Methode zum Protokollieren der Tracebacks darin besteht, die Methode zu kopieren, die der gesamte Python-Code zum Drucken der Tracebacks selbst verwendet (traceback.print_exception .). Ich hatte etwas Ähnliches wie das Folgende:
def add_custom_print_exception():
old_print_exception = traceback.print_exception
def custom_print_exception(etype, value, tb, limit=None, file=None):
tb_output = StringIO.StringIO()
traceback.print_tb(tb, limit, tb_output)
logger = logging.getLogger('customLogger')
logger.error(tb_output.getvalue())
tb_output.close()
old_print_exception(etype, value, tb, limit=None, file=None)
traceback.print_exception = custom_print_exception
Dieser Code schreibt das Traceback in einen String-Puffer und protokolliert es mit dem Protokollierungsfehler. Ich habe einen benutzerdefinierten Protokollierungs-Handler eingerichtet, der den "customLogger" -Logger eingerichtet hat, der die ERROR-Protokolle aufnimmt und zur Analyse nach Hause schickt.
Sie können alle nicht erfassten Ausnahmen im Hauptthread protokollieren, indem Sie sys.excepthook
einen Handler zuweisen, möglicherweise mithilfe des Parameters exc_info
der Python-Protokollierungsfunktionen :
import sys
import logging
logging.basicConfig(filename='/tmp/foobar.log')
def exception_hook(exc_type, exc_value, exc_traceback):
logging.error(
"Uncaught exception",
exc_info=(exc_type, exc_value, exc_traceback)
)
sys.excepthook = exception_hook
raise Exception('Boom')
Wenn Ihr Programm jedoch Threads verwendet, beachten Sie, dass Threads, die mit threading.Thread
erstellt wurden, notsys.excepthook
auslösen, wenn eine nicht erfasste Ausnahme in ihnen auftritt, wie in Ausgabe 1230540 in Pythons Issue Tracker beschrieben. Einige Hacks wurden vorgeschlagen, um diese Einschränkung zu umgehen, beispielsweise das Affen-Patching Thread.__init__
, um self.run
mit einer alternativen run
-Methode zu überschreiben, die das Original in einen try
-Block einschließt und sys.excepthook
aus dem except
-Block aufruft. Alternativ können Sie den Einstiegspunkt für jeden Ihrer Threads einfach selbst in try
/except
umschließen.
Nicht erfasste Ausnahmemeldungen gehen an STDERR. Anstatt Ihre Protokollierung in Python selbst zu implementieren, können Sie STDERR mit einer beliebigen Shell, die Sie zum Ausführen Ihres Python-Skripts verwenden, an eine Datei senden. In einem Bash-Skript können Sie dies mit der Umleitung von Ausgaben tun, wie im BASH-Handbuch beschrieben .
Fehler an Datei anhängen, andere Ausgabe an das Terminal:
./test.py 2>> mylog.log
Datei mit verschachtelten STDOUT- und STDERR-Ausgaben überschreiben:
./test.py &> mylog.log
vielleicht nicht so stylisch, aber einfacher:
#!/bin/bash
log="/var/log/yourlog"
/path/to/your/script.py 2>&1 | (while read; do echo "$REPLY" >> $log; done)
Was ich gesucht habe:
import sys
import traceback
exc_type, exc_value, exc_traceback = sys.exc_info()
traceback_in_var = traceback.format_tb(exc_traceback)
Sehen: