Python Logging – Einfachste Anleitung mit vollständigem Code und Beispielen
Mit dem Logging-Modul können Sie Ihre Codeläufe überwachen, so dass Sie bei einem Absturz des Codes die Logs überprüfen und die Ursache dafür ermitteln können. Die Log-Meldungen haben eine eingebaute Hierarchie – beginnend mit Debugging-, Informations-, Warn-, Fehler- und kritischen Meldungen. Sie können auch Traceback-Informationen einschließen. Es ist für kleine bis große Python-Projekte mit mehreren Modulen konzipiert und wird für jede modulare Python-Programmierung dringend empfohlen. In diesem Beitrag wird die Verwendung des Logging-Moduls einfach und klar erklärt.
Logging in Python – Vereinfachte Anleitung mit vollständigem Code und Beispielen. Foto von Andrea Reiman.
Inhalt
-
- Warum Logging?
- Ein grundlegendes Logging-Beispiel
- Die 5 Ebenen des Loggings
- Wie man in eine Datei statt auf die Konsole protokolliert
- Wie man das Logging-Format ändert
- Warum es nicht die beste Idee ist, mit dem Root-Logger für alle Module zu arbeiten
- Wie man einen neuen Logger erstellt?
- Was ist ein File Handler und ein Formatter und wie richtet man sie ein?
- Wie fügt man Traceback-Informationen in protokollierte Nachrichten ein
- Übungen
- Abschluss
Warum Protokollierung?
Stellen Sie sich vor, Sie führen ein Python-Skript aus, und Sie wollen wissen, welcher Teil des Skripts ausgeführt wurde, zu welchem Zeitpunkt und in bestimmten Fällen prüfen, welche Werte die Variablen enthalten.
Normalerweise geben Sie vielleicht nur ‚print()
‚ aussagekräftige Meldungen aus, damit Sie sie in der Konsole sehen können. Und das ist wahrscheinlich alles, was Sie brauchen, wenn Sie kleine Programme entwickeln.
Das Problem ist: Wenn Sie diesen Ansatz bei größeren Projekten mit vielen Modulen verwenden, wollen Sie einen strukturierteren, robusteren Ansatz.
Warum?
Weil der Code verschiedene Stadien durchläuft, wie z.B. in der Entwicklung, beim Debugging, beim Review, beim Testen oder wenn er in Produktion geht, wollen Sie verschiedene Ebenen von Details in den gedruckten Meldungen sehen.
Die Art der Meldungen, die Sie während der Entwicklung oder beim Debugging ausgeben wollen, kann sich sehr von dem unterscheiden, was Sie sehen wollen, wenn es in Produktion geht. Je nach Zweck möchten Sie, dass der Code verschiedene Arten von Meldungen ausgibt.
Stellen Sie sich vor, Sie machen das alles nur mit if else
und print
Anweisungen.
Außerdem ist eine gewisse Hierarchie in den ausgegebenen Meldungen erforderlich.
Das heißt, während eines bestimmten ‚Testlaufs‘ wollen Sie nur Warnungen und Fehlermeldungen sehen. Während der ‚Fehlersuche‘ hingegen werden detailliertere Meldungen benötigt, die bei der Fehlersuche helfen. Außerdem wird das Python-Skript unübersichtlich, wenn man ausgeben möchte, auf welchem Modul und zu welcher Zeit die Codes ausgeführt wurden.
Alle diese Probleme werden durch das logging
-Modul gut gelöst.
Mit Hilfe der Protokollierung können Sie:
- Steuern Sie die Meldungsebene, um nur die erforderlichen Meldungen zu protokollieren
- Steuern Sie, wo die Protokolle angezeigt oder gespeichert werden sollen
- Steuern Sie, wie die Protokolle mit eingebauteneingebauten Nachrichtenvorlagen
- Erkennen, von welchem Modul die Nachrichten kommen
Sie werden vielleicht sagen: ‚Ich sehe, dass logging
nützlich sein kann, aber es scheint zu technisch und ein bisschen schwer zu verstehen zu sein‘. Nun, ja, logging
erfordert ein wenig Lernkurve, aber das ist es, wofür dieser Beitrag hier ist: Logging leicht erlernbar zu machen.
Ohne weitere Verzögerung, lassen Sie uns gleich loslegen.
Ein grundlegendes Logging-Beispiel
Python bietet ein eingebautes logging
-Modul, das Teil der Python-Standardbibliothek ist. Sie brauchen also nichts zu installieren.
Um die Protokollierung zu verwenden, müssen Sie lediglich die Grundkonfiguration mit logging.basicConfig()
einrichten. Eigentlich ist dies auch optional. Das werden wir gleich sehen.
Anstatt print()
rufen Sie dann logging.{level}(message)
auf, um die Meldung in der Konsole anzuzeigen.
import logginglogging.basicConfig(level=logging.INFO)def hypotenuse(a, b): """Compute the hypotenuse""" return (a**2 + b**2)**0.5logging.info("Hypotenuse of {a}, {b} is {c}".format(a=3, b=4, c=hypotenuse(a,b)))#> INFO:root:Hypotenuse of 3, 4 is 5.0
Die gedruckte Logmeldung hat das folgende Standardformat: {LEVEL}:{LOGGER}:{MESSAGE}.
Im obigen Fall ist der Level info
, denn, ich habe logging.info()
aufgerufen.
Der Logger heißt root
, weil das der Standard-Logger ist und ich noch keinen neuen angelegt habe.
Aber was ist überhaupt ein Logger?
Ein Logger ist wie eine Entität, die Sie erstellen und konfigurieren können, um verschiedene Arten und Formate von Meldungen zu protokollieren.
Sie können einen Logger konfigurieren, der auf der Konsole ausgibt, und einen anderen Logger, der die Protokolle in eine Datei sendet, einen anderen Logging-Level hat und spezifisch für ein bestimmtes Modul ist. Weitere Erklärungen und Beispiele dazu folgen.
Schließlich ist die Nachricht der String, den ich an logging.info()
übergeben habe.
Nun, was wäre passiert, wenn Sie logging.basicConfig(level=logging.INFO)
nicht eingerichtet hätten?
Antwort: Das Protokoll wäre nicht ausgegeben worden.
Warum?
Um das zu wissen, müssen wir die Ebenen der Protokollierung verstehen.
Die 5 Ebenen der Protokollierung
logging
Es gibt 5 verschiedene hierarchische Ebenen der Protokollierung, für die ein bestimmter Logger konfiguriert werden kann.
Lassen Sie uns sehen, was die Python-Doku über jede Ebene zu sagen hat:
- DEBUG: Detaillierte Informationen, für die Diagnose von Problemen. Wert=10.
- INFO: Bestätigen, dass die Dinge wie erwartet funktionieren. Wert=20.
- WARNUNG: Es ist etwas Unerwartetes passiert, oder es deutet auf ein Problem hin. Aber die Software funktioniert trotzdem wie erwartet. Wert=30.
- FEHLER: Schwerwiegenderes Problem, die Software ist nicht in der Lage, eine Funktion auszuführen. Wert=40
- KRITISCH: Schwerwiegender Fehler, das Programm kann möglicherweise nicht weiterlaufen. Wert=50
Nun zurück zur vorherigen Frage, was passiert wäre, wenn Sie logging.basicConfig(level=logging.INFO)
im vorherigen Beispiel nicht eingerichtet hätten.
Die Antwort lautet: Das Protokoll wäre nicht gedruckt worden, weil der Standard-Logger der ‚root‘ ist und sein Standard-BasicConfig-Level ‚WARNING‘ ist. Das bedeutet, dass nur Meldungen von logging.warning()
und höheren Ebenen protokolliert werden.
Die Meldung von logging.info()
würde also nicht gedruckt werden. Und das ist der Grund, warum die Grundkonfiguration zunächst auf INFO
gesetzt wurde (in logging.basicConfig(level=logging.INFO)
).
Hätte ich stattdessen die Ebene als logging.ERROR
eingestellt, würden nur die Meldungen von logging.error
und logging.critical
protokolliert werden. Klar?
import logginglogging.basicConfig(level=logging.WARNING)def hypotenuse(a, b): """Compute the hypotenuse""" return (a**2 + b**2)**0.5kwargs = {'a':3, 'b':4, 'c':hypotenuse(3, 4)}logging.debug("a = {a}, b = {b}".format(**kwargs))logging.info("Hypotenuse of {a}, {b} is {c}".format(**kwargs))logging.warning("a={a} and b={b} are equal".format(**kwargs))logging.error("a={a} and b={b} cannot be negative".format(**kwargs))logging.critical("Hypotenuse of {a}, {b} is {c}".format(**kwargs))#> WARNING:root:a=3 and b=3 are equal#> ERROR:root:a=-1 and b=4 cannot be negative#> CRITICAL:root:Hypotenuse of a, b is 5.0
Wie man in eine Datei statt in die Konsole protokolliert
Um die Log-Meldungen vom Root-Logger in eine Datei zu senden, müssen Sie das Dateiargument in logging.basicConfig()
import logginglogging.basicConfig(level=logging.INFO, file='sample.log')
Jetzt gehen alle nachfolgenden Logmeldungen direkt in die Datei ’sample.log‘ in Ihrem aktuellen Arbeitsverzeichnis. Wenn Sie sie an eine Datei in einem anderen Verzeichnis senden möchten, geben Sie den vollständigen Dateipfad an.
Wie Sie das Logging-Format ändern
Das Logging-Modul bietet Kurzbefehle, um den protokollierten Meldungen verschiedene Details hinzuzufügen. Das folgende Bild aus den Python-Dokumenten zeigt diese Liste.
Lassen Sie uns das Format der Log-Meldung so ändern, dass TIME, LEVEL und die MESSAGE angezeigt werden. Um das zu tun, fügen Sie einfach das Format zum logging.basiconfig()
’s format Argument hinzu.
import logginglogging.basicConfig(level=logging.INFO, format='%(asctime)s :: %(levelname)s :: %(message)s')logging.info("Just like that!")#> 2019-02-17 11:40:38,254 :: INFO :: Just like that!
6. Warum die Arbeit mit dem Root-Logger für alle Module nicht die beste Idee ist
Weil sie alle denselben „Root“-Logger verwenden werden.
Aber warum ist das schlecht?
Lassen Sie uns den folgenden Code betrachten:
# 1. code inside myprojectmodule.pyimport logginglogging.basicConfig(file='module.log')#-----------------------------# 2. code inside main.py (imports the code from myprojectmodule.py)import loggingimport myprojectmodule # This runs the code in myprojectmodule.pylogging.basicConfig(file='main.log') # No effect, because!
Stellen Sie sich vor, Sie haben ein oder mehrere Module in Ihrem Projekt. Und diese Module verwenden das Basis-Root-Modul. Dann wird beim Importieren des Moduls (‚myprojectmodule.py
‚) der gesamte Code dieses Moduls ausgeführt und der Logger wird konfiguriert.
Nach der Konfiguration kann der Root-Logger in der Hauptdatei (die das Modul ‚myprojectmodule
‚ importiert hat) die Einstellungen des Root-Loggers nicht mehr ändern. Denn das einmal gesetzte logging.basicConfig()
kann nicht mehr geändert werden.
Das heißt, wenn Sie die Meldungen von myprojectmodule
in eine Datei und die Logs vom Hauptmodul in eine andere Datei loggen wollen, kann der Root-Logger das nicht.
Dazu müssen Sie einen neuen Logger erstellen.
Wie erstellt man einen neuen Logger?
Sie können einen neuen Logger mit der Methode ‚logger.getLogger(name)
‚ erstellen. Wenn ein Logger mit dem gleichen Namen existiert, dann wird dieser Logger verwendet.
Während Sie dem Logger so ziemlich jeden Namen geben können, ist es üblich, die Variable __name__
wie folgt zu verwenden:
logger = logging.getLogger(__name__)logger.info('my logging message')
Aber warum sollte man __name__
als Namen für den Logger verwenden, anstatt einen Namen fest zu codieren?
Weil die Variable __name__
den Namen des Moduls (Python-Datei) enthält, das den Code aufgerufen hat. Wenn sie also innerhalb eines Moduls verwendet wird, erzeugt sie einen Logger, der den Wert des Attributs __name__
des Moduls trägt.
Dadurch müssen Sie den internen Code nicht ändern, wenn Sie den Modulnamen (Dateinamen) in Zukunft ändern.
Nun, sobald Sie einen neuen Logger erstellt haben, sollten Sie daran denken, alle Ihre Meldungen mit dem neuen logger.info()
zu protokollieren, anstatt mit der logging.info()
-Methode der Wurzel.
Ein weiterer zu beachtender Aspekt ist, dass alle Logger eine eingebaute Hierarchie haben.
Was meine ich damit?
Zum Beispiel, wenn Sie den Root-Logger so konfiguriert haben, dass er Meldungen in eine bestimmte Datei protokolliert. Sie haben auch einen benutzerdefinierten Logger, für den Sie den Dateihandler nicht so konfiguriert haben, dass er Nachrichten an die Konsole oder eine andere Protokolldatei sendet.
In diesem Fall wird der benutzerdefinierte Logger zurückgreifen und in die Datei schreiben, die vom Root-Logger selbst festgelegt wurde. Solange, bis Sie die Protokolldatei Ihres benutzerdefinierten Loggers konfigurieren.
Was ist also ein File-Handler und wie richtet man einen ein?
Was ist ein File-Handler und wie richtet man einen ein?
Die Klassen FileHandler()
und Formatter()
werden verwendet, um die Ausgabedatei und das Format der Meldungen für andere Logger als den Root-Logger einzurichten.
Erinnern Sie sich, wie wir vorhin den Dateinamen und das Format der Nachricht im Root-Logger (innerhalb von logging.basicConfig()
) eingerichtet haben?
Wir haben einfach die Parameter filename
und format
in logging.basicConfig()
angegeben und alle nachfolgenden Logs gingen in diese Datei.
Wenn Sie jedoch einen separaten Logger erstellen, müssen Sie diese einzeln mit den Objekten logging.FileHandler()
und logging.Formatter()
einrichten.
Ein FileHandler
wird verwendet, um Ihren benutzerdefinierten Logger dazu zu bringen, in eine andere Datei zu loggen. Ebenso wird ein Formatter
verwendet, um das Format Ihrer protokollierten Nachrichten zu ändern.
import logging# Gets or creates a loggerlogger = logging.getLogger(__name__) # set log levellogger.setLevel(logging.WARNING)# define file handler and set formatterfile_handler = logging.FileHandler('logfile.log')formatter = logging.Formatter('%(asctime)s : %(levelname)s : %(name)s : %(message)s')file_handler.setFormatter(formatter)# add file handler to loggerlogger.addHandler(file_handler)# Logslogger.debug('A debug message')logger.info('An info message')logger.warning('Something is not right.')logger.error('A Major error has happened.')logger.critical('Fatal error. Cannot continue')
Beachten Sie, wie wir den Formatierer auf das ‚file_handler
‚ und nicht direkt auf das ‚logger
‚ setzen.
Angenommen, der obige Code wird vom Hauptprogramm aus ausgeführt, dann wird, wenn man in das Arbeitsverzeichnis schaut, eine Datei mit dem Namen logfile.log
erstellt, wenn sie nicht existiert und würde die folgenden Meldungen enthalten.
#> 2019-02-17 12:40:14,797 : WARNING : __main__ : Something is not right.#> 2019-02-17 12:40:14,798 : ERROR : __main__ : A Major error has happened.#> 2019-02-17 12:40:14,798 : CRITICAL : __main__ : Fatal error. Cannot continue
Noch einmal: Das Formatter
wird auf das FileHandler
-Objekt gesetzt und nicht direkt auf den Logger. Etwas, an das Sie sich vielleicht gewöhnen sollten.
Wie man Traceback-Informationen in protokollierte Nachrichten einfügt
Neben ‚debug
info
warning
error
‚ und ‚critical
‚ können Sie Ausnahmen protokollieren, die alle zugehörigen Traceback-Informationen enthalten.
Mit logger.exception
können Sie Traceback-Informationen protokollieren, sollte der Code auf einen Fehler stoßen. logger.exception
protokolliert die in seinen Argumenten angegebene Nachricht sowie die Traceback-Information der Fehlermeldung.
Unten sehen Sie ein schönes Beispiel.
import logging# Create or get the loggerlogger = logging.getLogger(__name__) # set log levellogger.setLevel(logging.INFO)def divide(x, y): try: out = x / y except ZeroDivisionError: logger.exception("Division by zero problem") else: return out# Logslogger.error("Divide {x} / {y} = {c}".format(x=10, y=0, c=divide(10,0)))#> ERROR:__main__:Division by zero problem#> Traceback (most recent call last):#> File "<ipython-input-16-a010a44fdc0a>", line 12, in divide#> out = x / y#> ZeroDivisionError: division by zero#> ERROR:__main__:None
Übungen
- Erstellen Sie ein neues Projektverzeichnis und eine neue Python-Datei namens ‚
example.py
‚. Importieren Sie das Logging-Modul und konfigurieren Sie den Root-Logger auf die Ebene der ‚debug‘-Meldungen. Loggen Sie eine ‚info‘-Meldung mit dem Text: „This is root logger’s logging message!“. - Konfigurieren Sie den Root-Logger so, dass er die Meldung „This is root logger’s logging message!“ wie folgt zu formatieren:
#> 2019-03-03 17:18:32,703 :: INFO :: Module <stdin> :: Line No 1 :: This is root logger's logging message!
Lösung anzeigen
import logginglogging.basicConfig(level=logging.INFO, format='%(asctime)s :: %(levelname)s :: Module %(module)s :: Line No %(lineno)s :: %(message)s')logging.info("This is root logger's logging mesage!")
- Erzeugen Sie eine weitere Python-Datei im gleichen Verzeichnis mit dem Namen ‚
mymod.py
‚ und erstellen Sie einen neuen Logger mit dem Namen des Moduls. Konfigurieren Sie ihn auf die Ebene der ‚Fehlermeldungen‘ und sorgen Sie dafür, dass er die Log-Ausgaben an eine Datei mit dem Namen „mymod_{current_date}.log“ sendet. - Vom oben erstellten Logger ‚mymod‘ loggen Sie die folgende ‚kritische‘ Meldung in die besagte Log-Datei: „Dies ist eine kritische Meldung! Don’t ignore it“.
Abschluss
Vielen Glückwunsch, wenn Sie die Aufgaben lösen konnten!
Das war ziemlich nützlich und einfach, nicht wahr?
Das Logging ist ein großartiges Werkzeug, aber es ist in Data Science Workflows nicht so beliebt, wie es sein sollte. Ich hoffe, die Logging-Konzepte sind klar und das nächste Mal, wenn Sie an einem Python-basierten Projekt arbeiten, ist meine freundliche Bitte an Sie, daran zu denken, dem logging
-Modul eine Chance zu geben.
Happy Logging!