35.3 Logdateien – logging
Das Modul logging stellt ein flexibles Interface zum Protokollieren eines Programmablaufs bereit. Protokolliert wird der Programmablauf, indem an verschiedenen Stellen im Programm Meldungen an das logging-Modul abgesetzt werden. Diese Meldungen können unterschiedliche Dringlichkeitsstufen haben. So gibt es beispielsweise Fehlermeldungen, Warnungen oder Debug-Informationen. Das Modul logging kann diese Meldungen auf vielfältige Weise verarbeiten. Üblich ist es, die Meldung mit einem Zeitstempel zu versehen und entweder auf dem Bildschirm auszugeben oder in eine Datei zu schreiben.
In diesem Abschnitt wird die Verwendung des Moduls logging anhand mehrerer Beispiele im interaktiven Modus gezeigt. Um die Beispielprogramme korrekt ausführen zu können, muss zuvor das Modul logging eingebunden sein:
>>> import logging
Bevor Meldungen an den Logger geschickt werden können, muss dieser durch Aufruf der Funktion basicConfig initialisiert werden. Der Funktion basicConfig werden verschiedene Schlüsselwortparameter übergeben. Im folgenden Beispiel wird ein Logger eingerichtet, der alle eingehenden Meldungen in die Logdatei programm.log schreibt:
>>> logging.basicConfig(filename = "programm.log")
Jetzt können mithilfe der im Modul enthaltenen Funktion log Meldungen an den Logger übergeben werden. Die Funktion log bekommt dabei die Dringlichkeitsstufe der Meldung als ersten und die Meldung selbst in Form eines Strings als zweiten Parameter übergeben:
>>> logging.log(logging.ERROR, "Ein Fehler ist aufgetreten")
>>> logging.log(logging.INFO, "Dies ist eine Information")
Durch das Aufrufen der Funktion shutdown wird der Logger korrekt deinitialisiert, und eventuell noch anstehende Schreiboperationen werden durchgeführt:
>>> logging.shutdown()
Natürlich sind nicht nur die Dringlichkeitsstufen ERROR und INFO verfügbar. Tabelle 35.1 listet alle vordefinierten Stufen auf, aus denen Sie wählen können. Die Tabelle ist dabei nach Dringlichkeit geordnet, wobei die dringendste Stufe zuletzt aufgeführt wird.
Level | Beschreibung |
---|---|
NOTSET | keine Dringlichkeitsstufe |
DEBUG | eine Meldung, die nur für den Programmierer zur Fehlersuche interessant ist |
INFO | eine Informationsmeldung über den Programmstatus |
WARNING | eine Warnmeldung, die auf einen möglichen Fehler hinweist |
ERROR | eine Fehlermeldung, nach der das Programm weiterarbeiten kann |
CRITICAL | eine Meldung über einen kritischen Fehler, der das sofortige Beenden des Programms oder der aktuell durchgeführten Operation zur Folge hat |
Aus Gründen des Komforts existiert zu jeder Dringlichkeitsstufe eine eigene Funktion. So sind die beiden Funktionsaufrufe von log aus dem letzten Beispiel äquivalent zu:
logging.error("Ein Fehler ist aufgetreten")
logging.info("Dies ist eine Information")
Wenn Sie sich die Logdatei nach dem Aufruf dieser beiden Funktionen ansehen, werden Sie feststellen, dass es lediglich einen einzigen Eintrag gibt:
ERROR:root:Ein Fehler ist aufgetreten
Das liegt daran, dass der Logger in seiner Basiskonfiguration nur Meldungen loggt, deren Dringlichkeit größer oder gleich der einer Warnung ist. Um auch Debug- und Info-Meldungen mitzuloggen, müssen Sie beim Aufruf der Funktion basicConfig im Schlüsselwortparameter level einen geeigneten Wert übergeben:
logging.basicConfig(
filename="programm.log",
level = logging.DEBUG)
logging.error("Ein Fehler ist aufgetreten")
logging.info("Dies ist eine Information")
In diesem Beispiel wurde die Mindestdringlichkeit auf DEBUG gesetzt. Das bedeutet, dass alle Meldungen, die mindestens eine Dringlichkeit von DEBUG haben, geloggt werden. Folglich erscheinen auch beide Meldungen in der Logdatei:
ERROR:root:Ein Fehler ist aufgetreten
INFO:root:Dies ist eine Information
Tabelle 35.2 listet die wichtigsten Schlüsselwortparameter auf, die der Funktion basicConfig übergeben werden können.
Parameter | Beschreibung |
---|---|
datefmt | Spezifiziert das Datumsformat. Näheres dazu erfahren Sie im folgenden Abschnitt. |
filemode | Gibt den Modus* an, in dem die Logdatei geöffnet werden soll (Standardwert: "a"). |
filename | Gibt den Dateinamen der Logdatei an. |
format | Spezifiziert das Meldungsformat. Näheres dazu erfahren Sie im folgenden Abschnitt. |
handlers | Gibt eine Liste von Handlern an, die registriert werden sollen. Näheres dazu erfahren Sie in Abschnitt 35.3.2, »Logging Handler«. |
level | Legt die Mindestdringlichkeit für Meldungen fest, damit diese in der Logdatei erscheinen. |
stream | Gibt einen Stream an, in den die Logmeldungen geschrieben werden sollen. Wenn die Parameter stream und filename gemeinsam angegeben werden, wird stream ignoriert. |
style | Bestimmt die Formatierungssyntax für die Meldung. Der voreingestellte Wert "%" bedingt die alte %-Syntax aus Python 2, während ein Wert von "{" die neue Syntax zur String-Formatierung** erzwingt. |
* Die verschiedenen Modi, in denen Dateien geöffnet werden können, sind in Abschnitt 6.4, »Das Dateiobjekt erzeugen«, aufgeführt. ** Näheres zur String-Formatierung erfahren Sie in Abschnitt 13.4.3. |
35.3.1 Das Meldungsformat anpassen
Wie in den vorangegangenen Beispielen zu sehen war, wird ein Eintrag in einer Logdatei standardmäßig nicht mit einem Zeitstempel versehen. Es gibt eine Möglichkeit, das Format der geloggten Meldung anzupassen. Dazu übergeben Sie beim Funktionsaufruf von basicConfig den Schlüsselwortparameter format:
logging.basicConfig(
filename="programm.log",
level = logging.DEBUG,
style = "{",
format = "{asctime} [{levelname:8}] {message}")
logging.error("Ein Fehler ist aufgetreten")
logging.info("Dies ist eine Information")
logging.error("Und schon wieder ein Fehler")
Sie sehen, dass ein Format-String übergeben wurde, der die Vorlage für eine Meldung enthält, wie sie später in der Logdatei stehen soll. Dabei stehen die Bezeichner asctime für den Timestamp, levelname für die Dringlichkeitsstufe und message für die Meldung. Die von diesem Beispiel generierten Meldungen sehen so aus:
2017-02-05 14:28:55,811 [ERROR ] Ein Fehler ist aufgetreten
2017-02-05 14:29:00,690 [INFO ] Dies ist eine Information
2017-02-05 14:29:12,686 [ERROR ] Und schon wieder ein Fehler
Tabelle 35.3 listet die wichtigsten Bezeichner auf, die innerhalb des format-Format-Strings verwendet werden dürfen. Je nach Kontext, in dem die Meldung erzeugt wird, haben einige der Bezeichner keine Bedeutung.
Bezeichner | Beschreibung |
---|---|
asctime | Zeitpunkt der Meldung. Das Datums- und Zeitformat kann beim Funktionsaufruf von basicConfig über den Parameter datefmt angegeben werden. Näheres dazu folgt im Anschluss an diese Tabelle. |
filename | der Dateiname der Programmdatei, in der die Meldung abgesetzt wurde |
funcName | der Name der Funktion, in der die Meldung abgesetzt wurde |
levelname | die Dringlichkeitsstufe der Meldung |
lineno | die Quellcodezeile, in der die Meldung abgesetzt wurde |
message | der Text der Meldung |
module | Der Name des Moduls, in dem die Meldung abgesetzt wurde. Der Modulname entspricht dem Dateinamen ohne Dateiendung. |
pathname | der Pfad zur Programmdatei, in der die Meldung abgesetzt wurde |
process | die ID des Prozesses, in dem die Meldung abgesetzt wurde |
thread | die ID des Threads, in dem die Meldung abgesetzt wurde |
Was an diesen Meldungen noch stört, ist das Format des Zeitstempels. Zum einen wird das amerikanische Datumsformat verwendet, und zum anderen ist eine Auflösung bis auf die Millisekunde für unsere Zwecke etwas zu fein. Das Format des Timestamps kann beim Aufruf von basicConfig über den Schlüsselwortparameter datefmt angegeben werden:
logging.basicConfig(
filename="programm.log",
level = logging.DEBUG,
style = "{",
format = "{asctime} [{levelname:8}] {message}",
datefmt = "%d.%m.%Y %H:%M:%S")
logging.error("Ein Fehler ist aufgetreten")
Die in der Vorlage für das Datumsformat verwendeten Platzhalter wurden in Abschnitt 17.1, »Elementare Zeitfunktionen – time«, eingeführt. Die von diesem Beispiel erzeugte Meldung sieht folgendermaßen aus:
05.02.2017 14:38:49 [ERROR ] Ein Fehler ist aufgetreten
35.3.2 Logging Handler
Bisher haben wir ausschließlich besprochen, wie das Modul logging dazu verwendet werden kann, alle eingehenden Meldungen in eine Datei zu schreiben. Tatsächlich ist das Modul in dieser Beziehung sehr flexibel und erlaubt es, nicht nur in Dateien, sondern beispielsweise auch in Streams zu schreiben oder die Meldungen über eine Netzwerkverbindung zu schicken. Dafür werden sogenannte Logging Handler verwendet. Um genau zu sein, haben wir in den vorangegangenen Abschnitten bereits einen impliziten Handler verwendet, ohne uns darüber im Klaren zu sein.
Um einen speziellen Handler einzurichten, muss eine Instanz der Handler-Klasse erzeugt werden. Diese kann dann vom Logger verwendet werden. Im folgenden Beispiel sollen alle Meldungen auf einen Stream, nämlich sys.stdout, geschrieben werden; dazu wird die Handler-Klasse logging.StreamHandler verwendet:
import logging
import sys
handler = logging.StreamHandler(sys.stdout)
frm = logging.Formatter("{asctime} {levelname}: {message}",
"%d.%m.%Y %H:%M:%S", style="{")
handler.setFormatter(frm)
logger = logging.getLogger()
logger.addHandler(handler)
logger.setLevel(logging.DEBUG)
logger.critical("Ein wirklich kritischer Fehler")
logger.warning("Und eine Warnung hinterher")
logger.info("Dies hingegen ist nur eine Info")
Zunächst wird der Handler, in diesem Fall ein StreamHandler, instanziiert. Im nächsten Schritt wird eine Instanz der Klasse Formatter erzeugt. Diese Klasse kapselt die Formatierungsanweisungen, die wir in den vorangegangenen Beispielen beim Aufruf der Funktion basicConfig übergeben haben. Mithilfe der Methode setFormatter werden dem Handler die Formatierungsanweisungen bekannt gegeben.
Um den Handler beim Logger zu registrieren, benötigen wir Zugriff auf die bisher implizit verwendete Logger-Instanz. Diesen Zugriff erlangen wir über die Funktion getLogger. Danach wird über addHandler der Handler hinzugefügt und über setLevel die gewünschte Dringlichkeitsstufe eingestellt.
Die Meldungen werden im Folgenden nicht über Funktionen des Moduls logging, sondern über die Methoden critical, warning und info der Logger-Instanz logger abgesetzt. Das Beispielprogramm gibt folgenden Text auf dem Bildschirm aus:
05.02.2017 17:21:46 CRITICAL: Ein wirklich kritischer Fehler
05.02.2017 17:21:46 WARNING: Und eine Warnung hinterher
05.02.2017 17:21:46 INFO: Dies hingegen ist nur eine Info
Im Folgenden sollen die wichtigsten zusätzlichen Handler-Klassen beschrieben werden, die im Paket logging bzw. logging.handlers enthalten sind.
logging.FileHandler(filename, [mode, encoding, delay])
Dieser Handler schreibt die Logeinträge in die Datei filename. Dabei wird die Datei im Modus mode geöffnet. Der Handler FileHandler kann auch implizit durch Angabe der Schlüsselwortparameter filename und filemode beim Aufruf der Funktion basicConfig verwendet werden.
Der Parameter encoding kann dazu verwendet werden, das zum Schreiben der Datei verwendete Encoding festzulegen. Wenn Sie für den letzten Parameter True übergeben, wird mit dem Öffnen der Datei so lange gewartet, bis tatsächlich Daten geschrieben werden sollen.
logging.StreamHandler([stream])
Dieser Handler schreibt die Logeinträge in den Stream stream. Beachten Sie, dass der Handler StreamHandler auch implizit durch Angabe des Schlüsselwortparameters stream beim Aufruf der Funktion basicConfig verwendet werden kann.
logging.handlers.SocketHandler(host, port) logging.handlers.DatagramHandler(host, port)
Diese Handler senden die Logeinträge über eine TCP-Schnittstelle (SocketHandler) bzw. über eine UDP-Netzwerkschnittstelle (DatagramHandler) an den Rechner mit dem Hostnamen host unter Verwendung des Ports port.
logging.handlers.SMTPHandler(mailhost, from, to, subject, [credentials])
Dieser Handler sendet die Logeinträge als E-Mail an die Adresse to. Dabei werden subject als Betreff und from als Absenderadresse eingetragen. Über den Parameter mailhost geben Sie den zu verwendenden SMTP-Server an. Sollte dieser Server eine Authentifizierung verlangen, können Sie ein Tupel, das Benutzername und Passwort enthält, für den optionalen letzten Parameter credentials übergeben.