HOWTO zur Protokollierung

Diese Seite enthält Tutorial-Informationen. Verweise auf die Referenz und ein Kochbuch zur Protokollierung findest du unter Weitere Quellen.

Grundlagen-Tutorial zur Protokollierung

Protokollierung ist ein Mittel, um Ereignisse zu verfolgen, die beim Lauf einer Software auftreten. Der Entwickler der Software ergänzt in seinem Code Protokollaufrufe, um anzuzeigen, dass bestimmte Ereignisse eingetreten sind. Ein Ereignis wird durch eine beschreibende Meldung dargestellt, die wahlweise veränderliche Daten enthalten kann (also Daten, die bei jedem Auftreten des Ereignisses anders sein können). Ereignisse haben außerdem eine Bedeutung, die der Entwickler dem Ereignis zuschreibt. Diese Bedeutung lässt sich auch Stufe oder Schweregrad nennen.

Wann man Protokollierung verwendet

Du kommst an die Protokollierungsfunktionalität, indem du über logger = logging.getLogger(__name__) einen Logger erzeugst und dann dessen Methoden debug(), info(), warning(), error() und critical() aufrufst. Um zu entscheiden, wann du Protokollierung verwendest, und um zu sehen, welche Logger-Methode wann zu verwenden ist, sieh dir die Tabelle unten an. Sie nennt für jede einer Reihe verbreiteter Aufgaben das beste Werkzeug für diese Aufgabe.

Aufgabe, die du erledigen willst

Das beste Werkzeug für die Aufgabe

Ausgabe auf der Konsole für die gewöhnliche Verwendung eines Befehlszeilenskripts oder -programms anzeigen

print()

Ereignisse melden, die im normalen Betrieb eines Programms auftreten (etwa zur Zustandsüberwachung oder Fehlersuche)

Die Methode info() eines Loggers (oder die Methode debug() für sehr ausführliche Ausgaben zu diagnostischen Zwecken)

Eine Warnung zu einem bestimmten Laufzeitereignis ausgeben

warnings.warn() in Bibliothekscode, wenn sich das Problem vermeiden lässt und die aufrufende Anwendung geändert werden sollte, um die Warnung zu beseitigen

Die Methode warning() eines Loggers, wenn die aufrufende Anwendung an der Lage nichts ändern kann, das Ereignis aber dennoch festgehalten werden sollte

Einen Fehler zu einem bestimmten Laufzeitereignis melden

Eine Ausnahme auslösen

Das Unterdrücken eines Fehlers melden, ohne eine Ausnahme auszulösen (etwa in einer Fehlerbehandlung in einem lang laufenden Serverprozess)

Die Methode error(), exception() oder critical() eines Loggers, je nachdem, was zum jeweiligen Fehler und Anwendungsbereich passt

Die Logger-Methoden sind nach der Stufe beziehungsweise dem Schweregrad der Ereignisse benannt, die sie verfolgen. Die Standardstufen und wann sie passen sind unten beschrieben (in aufsteigender Reihenfolge des Schweregrads):

Stufe

Wann sie verwendet wird

DEBUG

Ausführliche Informationen, üblicherweise nur bei der Diagnose von Problemen von Interesse.

INFO

Bestätigung, dass die Dinge wie erwartet funktionieren.

WARNING

Ein Hinweis darauf, dass etwas Unerwartetes passiert ist, oder ein Anzeichen für ein Problem in naher Zukunft (etwa „Festplattenplatz knapp“). Die Software arbeitet noch wie erwartet.

ERROR

Wegen eines schwerwiegenderen Problems konnte die Software eine Funktion nicht ausführen.

CRITICAL

Ein schwerer Fehler, der darauf hinweist, dass das Programm selbst möglicherweise nicht weiterlaufen kann.

Die Vorgabestufe ist WARNING. Das heißt, dass nur Ereignisse dieses Schweregrads und höher verfolgt werden, sofern das Paket logging nicht anders eingerichtet ist.

Verfolgte Ereignisse lassen sich auf unterschiedliche Weisen behandeln. Am einfachsten behandelt man verfolgte Ereignisse, indem man sie auf der Konsole ausgibt. Ein weiterer verbreiteter Weg ist, sie in eine Datei auf der Festplatte zu schreiben.

Ein einfaches Beispiel

Ein ganz einfaches Beispiel ist:

import logging
logging.warning('Watch out!')  # will print a message to the console
logging.info('I told you so')  # will not print anything

Wenn du diese Zeilen in ein Skript schreibst und es ausführst, siehst du:

WARNING:root:Watch out!

auf der Konsole ausgegeben. Die INFO-Meldung erscheint nicht, weil die Vorgabestufe WARNING ist. Die ausgegebene Meldung umfasst die Angabe der Stufe und die Beschreibung des Ereignisses aus dem Protokollaufruf, also „Watch out!“. Die tatsächliche Ausgabe lässt sich bei Bedarf recht flexibel formatieren. Die Formatierungsmöglichkeiten werden später ebenfalls erklärt.

Beachte, dass wir in diesem Beispiel Funktionen unmittelbar auf dem Modul logging verwenden, etwa logging.debug, anstatt einen Logger zu erzeugen und Funktionen darauf aufzurufen. Diese Funktionen arbeiten auf dem Wurzel-Logger, können aber nützlich sein, weil sie basicConfig() für dich aufrufen, falls das noch nicht geschehen ist – wie in diesem Beispiel. In größeren Programmen willst du die Protokollierungskonfiguration üblicherweise aber ausdrücklich steuern. Schon aus diesem Grund und auch aus anderen ist es besser, Logger zu erzeugen und deren Methoden aufzurufen.

In eine Datei protokollieren

Eine sehr verbreitete Lage ist, Protokollereignisse in einer Datei festzuhalten. Sehen wir uns das also als Nächstes an. Probiere das Folgende unbedingt in einem neu gestarteten Python-Interpreter aus und mache nicht einfach in der oben beschriebenen Sitzung weiter:

import logging
logger = logging.getLogger(__name__)
logging.basicConfig(filename='example.log', encoding='utf-8', level=logging.DEBUG)
logger.debug('This message should go to the log file')
logger.info('So should this')
logger.warning('And this, too')
logger.error('And non-ASCII stuff, too, like Øresund and Malmö')

Geändert in Version 3.9: Das Argument encoding wurde ergänzt. In früheren Python-Versionen oder wenn es nicht angegeben ist, wird die Kodierung verwendet, die open() standardmäßig nutzt. Im obigen Beispiel nicht gezeigt, aber jetzt ebenfalls möglich: ein Argument errors, das festlegt, wie Kodierungsfehler behandelt werden. Die verfügbaren Werte und die Vorgabe stehen in der Dokumentation zu open().

Und wenn wir die Datei nun öffnen und uns ansehen, was wir haben, sollten wir die Protokollmeldungen finden:

DEBUG:__main__:This message should go to the log file
INFO:__main__:So should this
WARNING:__main__:And this, too
ERROR:__main__:And non-ASCII stuff, too, like Øresund and Malmö

Dieses Beispiel zeigt außerdem, wie du die Protokollierungsstufe setzen kannst, die als Schwelle für das Verfolgen dient. In diesem Fall wurden alle Meldungen ausgegeben, weil wir die Schwelle auf DEBUG gesetzt haben.

Wenn du die Protokollierungsstufe über eine Befehlszeilenoption wie diese setzen willst:

--log=INFO

und du den für --log übergebenen Parameterwert in einer Variablen loglevel hast, kannst du Folgendes verwenden:

getattr(logging, loglevel.upper())

um den Wert zu bekommen, den du basicConfig() über das Argument level übergibst. Vielleicht willst du jede Benutzereingabe auf Fehler prüfen, etwa wie im folgenden Beispiel:

# assuming loglevel is bound to the string value obtained from the
# command line argument. Convert to upper case to allow the user to
# specify --log=DEBUG or --log=debug
numeric_level = getattr(logging, loglevel.upper(), None)
if not isinstance(numeric_level, int):
    raise ValueError('Invalid log level: %s' % loglevel)
logging.basicConfig(level=numeric_level, ...)

Der Aufruf von basicConfig() sollte vor allen Aufrufen von Logger-Methoden wie debug(), info() usw. kommen. Andernfalls wird dieses Protokollereignis möglicherweise nicht wie gewünscht behandelt.

Wenn du das obige Skript mehrmals ausführst, werden die Meldungen der aufeinanderfolgenden Läufe an die Datei example.log angehängt. Wenn jeder Lauf frisch beginnen soll, ohne die Meldungen früherer Läufe zu behalten, kannst du das Argument filemode angeben – indem du den Aufruf im obigen Beispiel so änderst:

logging.basicConfig(filename='example.log', filemode='w', level=logging.DEBUG)

Die Ausgabe ist dieselbe wie vorher, aber an die Protokolldatei wird nicht mehr angehängt, die Meldungen früherer Läufe gehen also verloren.

Veränderliche Daten protokollieren

Um veränderliche Daten zu protokollieren, verwende eine Formatzeichenkette für die Beschreibung des Ereignisses und hänge die veränderlichen Daten als Argumente an. Zum Beispiel:

import logging
logging.warning('%s before you %s', 'Look', 'leap!')

zeigt:

WARNING:root:Look before you leap!

Wie du siehst, verwendet das Einfügen veränderlicher Daten in die Ereignisbeschreibung den alten %-Stil der Zeichenkettenformatierung. Das dient der Rückwärtskompatibilität: Das Paket logging ist älter als neuere Formatierungsmöglichkeiten wie str.format() und string.Template. Diese neueren Formatierungsmöglichkeiten werden unterstützt, sie zu erkunden geht aber über den Rahmen dieses Tutorials hinaus: Näheres steht unter In der ganzen Anwendung bestimmte Formatierungsstile verwenden.

Das Format der angezeigten Meldungen ändern

Um das Format zu ändern, mit dem Meldungen angezeigt werden, musst du das gewünschte Format angeben:

import logging
logging.basicConfig(format='%(levelname)s:%(message)s', level=logging.DEBUG)
logging.debug('This message should appear on the console')
logging.info('So should this')
logging.warning('And this, too')

was Folgendes ausgeben würde:

DEBUG:This message should appear on the console
INFO:So should this
WARNING:And this, too

Beachte, dass das „root“, das in früheren Beispielen auftauchte, verschwunden ist. Alles, was in Formatzeichenketten vorkommen kann, findest du in der Dokumentation zu LogRecord attributes. Für die einfache Verwendung brauchst du aber nur levelname (den Schweregrad) und message (die Ereignisbeschreibung einschließlich der veränderlichen Daten) und willst vielleicht anzeigen, wann das Ereignis aufgetreten ist. Das ist im nächsten Abschnitt beschrieben.

Datum und Uhrzeit in Meldungen anzeigen

Um Datum und Uhrzeit eines Ereignisses anzuzeigen, würdest du „%(asctime)s“ in deine Formatzeichenkette schreiben:

import logging
logging.basicConfig(format='%(asctime)s %(message)s')
logging.warning('is when this event was logged.')

was etwa Folgendes ausgeben sollte:

2010-12-12 11:41:42,612 is when this event was logged.

Das Vorgabeformat für die Anzeige von Datum und Uhrzeit (oben gezeigt) folgt ISO8601 beziehungsweise RFC 3339. Wenn du mehr Kontrolle über die Formatierung von Datum und Uhrzeit brauchst, gib basicConfig ein Argument datefmt mit, wie in diesem Beispiel:

import logging
logging.basicConfig(format='%(asctime)s %(message)s', datefmt='%m/%d/%Y %I:%M:%S %p')
logging.warning('is when this event was logged.')

was etwa Folgendes anzeigen würde:

12/12/2010 11:46:36 AM is when this event was logged.

Das Format des Arguments datefmt ist dasselbe, das time.strftime() unterstützt.

Nächste Schritte

Damit endet das Grundlagen-Tutorial. Es sollte genügen, damit du mit der Protokollierung loslegen kannst. Das Paket logging bietet noch viel mehr, aber um das Beste daraus zu holen, musst du noch ein wenig mehr Zeit in das Lesen der folgenden Abschnitte stecken. Wenn du dazu bereit bist: Hol dir eines deiner Lieblingsgetränke und mach weiter.

Wenn deine Anforderungen an die Protokollierung einfach sind, dann nimm die obigen Beispiele, um Protokollierung in deine eigenen Skripte einzubauen, und wenn du auf Probleme stößt oder etwas nicht verstehst, stelle bitte eine Frage in der Kategorie Help des Python-Diskussionsforums – du solltest nicht lange auf Hilfe warten müssen.

Noch da? Du kannst mit den nächsten Abschnitten weitermachen, die ein etwas fortgeschritteneres und tiefergehendes Tutorial bieten als das Grundlagen-Tutorial oben. Danach kannst du dir das Kochbuch zur Protokollierung ansehen.

Fortgeschrittenes Tutorial zur Protokollierung

Die Bibliothek logging ist modular aufgebaut und bietet mehrere Kategorien von Bestandteilen: Logger, Handler, Filter und Formatter.

  • Logger legen die Schnittstelle offen, die der Anwendungscode unmittelbar verwendet.

  • Handler senden die (von Loggern erzeugten) Protokolldatensätze an das passende Ziel.

  • Filter bieten ein feinkörnigeres Mittel, um zu bestimmen, welche Protokolldatensätze ausgegeben werden.

  • Formatter legen die Anordnung der Protokolldatensätze in der endgültigen Ausgabe fest.

Die Informationen über ein Protokollereignis werden zwischen Loggern, Handlern, Filtern und Formattern in einer LogRecord-Instanz weitergegeben.

Protokolliert wird, indem Methoden auf Instanzen der Klasse Logger aufgerufen werden (im Folgenden Logger genannt). Jede Instanz hat einen Namen, und begrifflich sind sie in einer Namensraumhierarchie angeordnet, die Punkte als Trenner verwendet. So ist zum Beispiel ein Logger namens „scan“ der Elternteil der Logger „scan.text“, „scan.html“ und „scan.pdf“. Logger-Namen können beliebig sein und bezeichnen den Bereich einer Anwendung, aus dem eine protokollierte Meldung stammt.

Eine gute Konvention bei der Benennung von Loggern ist, in jedem Modul, das Protokollierung verwendet, einen Logger auf Modulebene zu verwenden, der so benannt ist:

logger = logging.getLogger(__name__)

Das heißt, dass die Logger-Namen der Paket- und Modulhierarchie folgen und schon am Logger-Namen naheliegend erkennbar ist, wo Ereignisse protokolliert werden.

Die Wurzel der Logger-Hierarchie heißt Wurzel-Logger. Das ist der Logger, den die Funktionen debug(), info(), warning(), error() und critical() verwenden, die einfach die gleichnamige Methode des Wurzel-Loggers aufrufen. Die Funktionen und die Methoden haben dieselben Signaturen. Der Name des Wurzel-Loggers wird in der Protokollausgabe als „root“ ausgegeben.

Es ist natürlich möglich, Meldungen an unterschiedliche Ziele zu protokollieren. Das Paket unterstützt das Schreiben von Protokollmeldungen in Dateien, an HTTP-GET/POST-Orte, per E-Mail über SMTP, an allgemeine Sockets, an Warteschlangen oder an betriebssystemspezifische Protokollmechanismen wie syslog oder das Ereignisprotokoll von Windows NT. Die Ziele werden von Handler-Klassen bedient. Du kannst deine eigene Zielklasse bauen, wenn du besondere Anforderungen hast, die keine der integrierten Handler-Klassen erfüllt.

Standardmäßig ist für keine Protokollmeldung ein Ziel gesetzt. Du kannst ein Ziel (etwa Konsole oder Datei) angeben, indem du basicConfig() verwendest, wie in den Tutorial-Beispielen. Rufst du die Funktionen debug(), info(), warning(), error() und critical() auf, prüfen sie, ob kein Ziel gesetzt ist. Ist keines gesetzt, setzen sie als Ziel die Konsole (sys.stderr) und ein Vorgabeformat für die angezeigte Meldung, bevor sie die eigentliche Ausgabe dem Wurzel-Logger übertragen.

Das Vorgabeformat, das basicConfig() für Meldungen setzt, ist:

severity:logger name:message

Du kannst das ändern, indem du basicConfig() über das Schlüsselwortargument format eine Formatzeichenkette übergibst. Alle Möglichkeiten, wie eine Formatzeichenkette aufgebaut wird, stehen unter Formatter Objects.

Der Fluss der Protokollierung

Der Fluss der Informationen über Protokollereignisse in Loggern und Handlern ist im folgenden Diagramm dargestellt.

Logger flow Create LogRecord Logging call in user code, e.g. logger.info(...) Stop Does a filter attached to logger reject the record? Pass record to handlers of current logger Is propagate true for current logger? Is there a parent logger? Set current logger to parent At least one handler in hierarchy? Use lastResort handler Handler enabled for level of record? Does a filter attached to handler reject the record? Stop Emit (includes formatting) Handler flow Logger enabled for level of call? No Yes Yes No No Yes Yes No No Yes No Yes No Yes Record passed to handler

Logger

Logger-Objekte haben eine dreifache Aufgabe. Erstens legen sie dem Anwendungscode mehrere Methoden offen, damit Anwendungen zur Laufzeit Meldungen protokollieren können. Zweitens bestimmen Logger-Objekte anhand des Schweregrads (dem Standardmittel zum Filtern) oder anhand von Filterobjekten, auf welche Protokollmeldungen sie reagieren. Drittens geben Logger-Objekte die betreffenden Protokollmeldungen an alle interessierten Protokoll-Handler weiter.

Die am häufigsten verwendeten Methoden von Logger-Objekten fallen in zwei Kategorien: Konfiguration und Senden von Meldungen.

Dies sind die häufigsten Konfigurationsmethoden:

  • Logger.setLevel() gibt die Protokollmeldung mit dem niedrigsten Schweregrad an, die ein Logger behandelt, wobei debug die niedrigste und critical die höchste integrierte Schweregradstufe ist. Ist die Schweregradstufe zum Beispiel INFO, behandelt der Logger nur INFO-, WARNING-, ERROR- und CRITICAL-Meldungen und ignoriert DEBUG-Meldungen.

  • Logger.addHandler() und Logger.removeHandler() fügen dem Logger-Objekt Handler-Objekte hinzu und entfernen sie wieder. Handler werden ausführlicher unter Handler behandelt.

  • Logger.addFilter() und Logger.removeFilter() fügen dem Logger-Objekt Filterobjekte hinzu und entfernen sie wieder. Filter werden ausführlicher unter Filter Objects behandelt.

Du musst diese Methoden nicht immer auf jedem Logger aufrufen, den du erzeugst. Siehe die letzten zwei Absätze dieses Abschnitts.

Ist das Logger-Objekt eingerichtet, erzeugen die folgenden Methoden Protokollmeldungen:

  • Logger.debug(), Logger.info(), Logger.warning(), Logger.error() und Logger.critical() erzeugen alle Protokolldatensätze mit einer Meldung und einer Stufe, die ihrem jeweiligen Methodennamen entspricht. Die Meldung ist tatsächlich eine Formatzeichenkette, die die übliche Einsetzungssyntax von %s, %d, %f und so weiter enthalten kann. Der Rest ihrer Argumente ist eine Liste von Objekten, die zu den Einsetzungsfeldern in der Meldung passen. Von **kwargs interessiert die Protokollmethoden nur das Schlüsselwort exc_info, mit dem sie bestimmen, ob Informationen über eine Ausnahme protokolliert werden.

  • Logger.exception() erzeugt eine Protokollmeldung ähnlich wie Logger.error(). Der Unterschied ist, dass Logger.exception() zusätzlich einen Stapelauszug ausgibt. Rufe diese Methode nur aus einer Ausnahmebehandlung auf.

  • Logger.log() nimmt eine Protokollstufe als ausdrückliches Argument. Das ist zum Protokollieren von Meldungen etwas ausführlicher als die oben aufgeführten Hilfsmethoden für die Protokollstufen, aber so protokolliert man in eigenen Protokollstufen.

getLogger() gibt eine Referenz auf eine Logger-Instanz mit dem angegebenen Namen zurück, falls ein Name angegeben ist, andernfalls auf root. Die Namen sind durch Punkte getrennte hierarchische Strukturen. Mehrere Aufrufe von getLogger() mit demselben Namen geben eine Referenz auf dasselbe Logger-Objekt zurück. Logger, die in der hierarchischen Liste weiter unten stehen, sind Kind-Logger der Logger weiter oben in der Liste. So sind zum Beispiel, gegeben ein Logger mit dem Namen foo, Logger mit den Namen foo.bar, foo.bar.baz und foo.bam alle Nachkommen von foo.

Logger haben den Begriff der wirksamen Stufe. Ist auf einem Logger keine Stufe ausdrücklich gesetzt, wird stattdessen die Stufe seines Elternteils als seine wirksame Stufe verwendet. Hat der Elternteil keine ausdrücklich gesetzte Stufe, wird dessen Elternteil untersucht, und so weiter – alle Vorfahren werden durchsucht, bis eine ausdrücklich gesetzte Stufe gefunden ist. Der Wurzel-Logger hat immer eine ausdrücklich gesetzte Stufe (standardmäßig WARNING). Bei der Entscheidung, ob ein Ereignis verarbeitet wird, dient die wirksame Stufe des Loggers dazu, zu bestimmen, ob das Ereignis an die Handler des Loggers weitergegeben wird.

Kind-Logger geben Meldungen an die Handler weiter, die zu ihren Vorfahren-Loggern gehören. Deshalb ist es unnötig, Handler für alle Logger zu definieren und einzurichten, die eine Anwendung verwendet. Es genügt, Handler für einen Logger der obersten Ebene einzurichten und bei Bedarf Kind-Logger zu erzeugen. (Du kannst die Weitergabe allerdings abschalten, indem du das Attribut propagate eines Loggers auf False setzt.)

Handler

Handler-Objekte sind dafür zuständig, die passenden Protokollmeldungen (anhand ihres Schweregrads) an das für den Handler angegebene Ziel zu verteilen. Logger-Objekte können sich über die Methode addHandler() null oder mehr Handler-Objekte hinzufügen. Als Beispielfall will eine Anwendung vielleicht alle Protokollmeldungen in eine Protokolldatei senden, alle Protokollmeldungen ab error auf die Standardausgabe und alle Meldungen mit critical an eine E-Mail-Adresse. Dieser Fall braucht drei einzelne Handler, wobei jeder Handler dafür zuständig ist, Meldungen eines bestimmten Schweregrads an einen bestimmten Ort zu senden.

Die Standardbibliothek enthält eine ganze Reihe von Handler-Arten (siehe Nützliche Handler). Die Tutorials verwenden in ihren Beispielen vor allem StreamHandler und FileHandler.

Es gibt nur sehr wenige Methoden eines Handlers, um die sich Anwendungsentwickler kümmern müssen. Die einzigen Handler-Methoden, die für Anwendungsentwickler relevant scheinen, die die integrierten Handler-Objekte verwenden (also keine eigenen Handler bauen), sind die folgenden Konfigurationsmethoden:

  • Die Methode setLevel() gibt, genau wie bei Logger-Objekten, den niedrigsten Schweregrad an, der an das passende Ziel verteilt wird. Warum gibt es zwei Methoden setLevel()? Die im Logger gesetzte Stufe bestimmt, welchen Schweregrad von Meldungen er an seine Handler weitergibt. Die in jedem Handler gesetzte Stufe bestimmt, welche Meldungen dieser Handler weitersendet.

  • setFormatter() wählt ein Formatter-Objekt aus, das dieser Handler verwenden soll.

  • addFilter() und removeFilter() richten Filterobjekte auf Handlern ein beziehungsweise entfernen sie wieder.

Anwendungscode sollte keine Instanzen von Handler unmittelbar erzeugen und verwenden. Die Klasse Handler ist vielmehr eine Basisklasse, die die Schnittstelle festlegt, die alle Handler haben sollten, und ein Standardverhalten einrichtet, das Kindklassen verwenden (oder überschreiben) können.

Formatter

Formatter-Objekte legen die endgültige Reihenfolge, den Aufbau und den Inhalt der Protokollmeldung fest. Anders als bei der Basisklasse logging.Handler darf Anwendungscode Formatter-Klassen erzeugen, wobei du den Formatter wahrscheinlich ableiten könntest, wenn deine Anwendung besonderes Verhalten braucht. Der Konstruktor nimmt drei optionale Argumente – eine Formatzeichenkette für die Meldung, eine Formatzeichenkette für das Datum und einen Stilanzeiger.

logging.Formatter.__init__(fmt=None, datefmt=None, style='%')

Gibt es keine Formatzeichenkette für die Meldung, wird standardmäßig die rohe Meldung verwendet. Gibt es keine Formatzeichenkette für das Datum, ist das Vorgabeformat für das Datum:

%Y-%m-%d %H:%M:%S

mit den Millisekunden am Ende angehängt. Der style ist einer von '%', '{' oder '$'. Ist keiner davon angegeben, wird '%' verwendet.

Ist der style '%', verwendet die Formatzeichenkette für die Meldung eine Einsetzung im Stil %(<dictionary key>)s. Die möglichen Schlüssel sind unter LogRecord attributes dokumentiert. Ist der Stil '{', wird angenommen, dass die Formatzeichenkette für die Meldung mit str.format() verträglich ist (mit Schlüsselwortargumenten). Ist der Stil '$', sollte die Formatzeichenkette dem entsprechen, was string.Template.substitute() erwartet.

Geändert in Version 3.2: Der Parameter style wurde ergänzt.

Die folgende Formatzeichenkette für die Meldung protokolliert die Zeit in einem für Menschen lesbaren Format, den Schweregrad der Meldung und den Inhalt der Meldung, in dieser Reihenfolge:

'%(asctime)s - %(levelname)s - %(message)s'

Formatter verwenden eine vom Benutzer einstellbare Funktion, um die Entstehungszeit eines Datensatzes in ein Tupel umzuwandeln. Standardmäßig wird time.localtime() verwendet. Um das für eine bestimmte Formatter-Instanz zu ändern, setze das Attribut converter der Instanz auf eine Funktion mit derselben Signatur wie time.localtime() oder time.gmtime(). Um es für alle Formatter zu ändern, zum Beispiel wenn alle Protokollzeiten in GMT angezeigt werden sollen, setze das Attribut converter in der Klasse Formatter (auf time.gmtime für die Anzeige in GMT).

Protokollierung einrichten

Programmierer können die Protokollierung auf drei Wege einrichten:

  1. Logger, Handler und Formatter ausdrücklich in Python-Code erzeugen, der die oben aufgeführten Konfigurationsmethoden aufruft.

  2. Eine Konfigurationsdatei für die Protokollierung erzeugen und sie mit der Funktion fileConfig() einlesen.

  3. Ein Wörterbuch mit Konfigurationsinformationen erzeugen und es an die Funktion dictConfig() übergeben.

Die Referenzdokumentation zu den letzten zwei Möglichkeiten steht unter Configuration functions. Das folgende Beispiel richtet in Python-Code einen ganz einfachen Logger, einen Konsolen-Handler und einen einfachen Formatter ein:

import logging

# create logger
logger = logging.getLogger('simple_example')
logger.setLevel(logging.DEBUG)

# create console handler and set level to debug
ch = logging.StreamHandler()
ch.setLevel(logging.DEBUG)

# create formatter
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')

# add formatter to ch
ch.setFormatter(formatter)

# add ch to logger
logger.addHandler(ch)

# 'application' code
logger.debug('debug message')
logger.info('info message')
logger.warning('warn message')
logger.error('error message')
logger.critical('critical message')

Dieses Modul von der Befehlszeile auszuführen erzeugt die folgende Ausgabe:

$ python simple_logging_module.py
2005-03-19 15:10:26,618 - simple_example - DEBUG - debug message
2005-03-19 15:10:26,620 - simple_example - INFO - info message
2005-03-19 15:10:26,695 - simple_example - WARNING - warn message
2005-03-19 15:10:26,697 - simple_example - ERROR - error message
2005-03-19 15:10:26,773 - simple_example - CRITICAL - critical message

Das folgende Python-Modul erzeugt einen Logger, einen Handler und einen Formatter, die denen im oben aufgeführten Beispiel fast gleichen. Der einzige Unterschied sind die Namen der Objekte:

import logging
import logging.config

logging.config.fileConfig('logging.conf')

# create logger
logger = logging.getLogger('simpleExample')

# 'application' code
logger.debug('debug message')
logger.info('info message')
logger.warning('warn message')
logger.error('error message')
logger.critical('critical message')

Hier die Datei logging.conf:

[loggers]
keys=root,simpleExample

[handlers]
keys=consoleHandler

[formatters]
keys=simpleFormatter

[logger_root]
level=DEBUG
handlers=consoleHandler

[logger_simpleExample]
level=DEBUG
handlers=consoleHandler
qualname=simpleExample
propagate=0

[handler_consoleHandler]
class=StreamHandler
level=DEBUG
formatter=simpleFormatter
args=(sys.stdout,)

[formatter_simpleFormatter]
format=%(asctime)s - %(name)s - %(levelname)s - %(message)s

Die Ausgabe gleicht der des Beispiels ohne Konfigurationsdatei fast:

$ python simple_logging_config.py
2005-03-19 15:38:55,977 - simpleExample - DEBUG - debug message
2005-03-19 15:38:55,979 - simpleExample - INFO - info message
2005-03-19 15:38:56,054 - simpleExample - WARNING - warn message
2005-03-19 15:38:56,055 - simpleExample - ERROR - error message
2005-03-19 15:38:56,130 - simpleExample - CRITICAL - critical message

Du siehst, dass der Weg über die Konfigurationsdatei ein paar Vorteile gegenüber dem Weg über Python-Code hat, vor allem die Trennung von Konfiguration und Code und die Möglichkeit, dass auch Nichtprogrammierer die Protokollierungseigenschaften mühelos ändern können.

Warnung

Die Funktion fileConfig() nimmt einen Parameter disable_existing_loggers mit Vorgabewert, der aus Gründen der Rückwärtskompatibilität standardmäßig True ist. Das kann sein, was du willst, oder auch nicht, denn es führt dazu, dass alle Logger außer dem Wurzel-Logger, die vor dem Aufruf von fileConfig() bestanden, abgeschaltet werden – sofern sie (oder ein Vorfahr) nicht ausdrücklich in der Konfiguration genannt sind. Näheres steht in der Referenzdokumentation. Gib für diesen Parameter False an, wenn du das möchtest.

Das Wörterbuch, das an dictConfig() übergeben wird, kann ebenfalls einen booleschen Wert unter dem Schlüssel disable_existing_loggers angeben, der – wenn er im Wörterbuch nicht ausdrücklich angegeben ist – standardmäßig auch als True ausgelegt wird. Das führt zu dem oben beschriebenen Abschalten von Loggern, was möglicherweise nicht ist, was du willst. Gib in diesem Fall den Schlüssel ausdrücklich mit dem Wert False an.

Beachte, dass die in Konfigurationsdateien genannten Klassennamen entweder relativ zum Modul logging sein müssen oder absolute Werte, die sich über die üblichen Importmechanismen auflösen lassen. Du könntest also entweder WatchedFileHandler (relativ zum Modul logging) oder mypackage.mymodule.MyHandler verwenden (für eine Klasse, die im Paket mypackage und im Modul mymodule definiert ist, wobei mypackage auf dem Python-Importpfad verfügbar ist).

In Python 3.2 wurde ein neues Mittel zum Einrichten der Protokollierung eingeführt, das Wörterbücher verwendet, um die Konfigurationsinformationen aufzunehmen. Das bietet eine Obermenge der Funktionalität des oben beschriebenen Wegs über Konfigurationsdateien und ist die empfohlene Konfigurationsmethode für neue Anwendungen und Installationen. Weil ein Python-Wörterbuch die Konfigurationsinformationen aufnimmt und du dieses Wörterbuch auf verschiedene Weisen füllen kannst, hast du mehr Möglichkeiten zur Konfiguration. So kannst du zum Beispiel eine Konfigurationsdatei im JSON-Format verwenden oder, wenn du Zugang zu YAML-Verarbeitung hast, eine Datei im YAML-Format, um das Konfigurationswörterbuch zu füllen. Oder du kannst das Wörterbuch natürlich in Python-Code aufbauen, es über einen Socket in Pickle-Form empfangen oder welchen Weg auch immer nehmen, der für deine Anwendung passt.

Hier ein Beispiel derselben Konfiguration wie oben, im YAML-Format für den neuen, wörterbuchbasierten Weg:

version: 1
formatters:
  simple:
    format: '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
handlers:
  console:
    class: logging.StreamHandler
    level: DEBUG
    formatter: simple
    stream: ext://sys.stdout
loggers:
  simpleExample:
    level: DEBUG
    handlers: [console]
    propagate: no
root:
  level: DEBUG
  handlers: [console]

Mehr Informationen über die Protokollierung mit einem Wörterbuch stehen unter Configuration functions.

Was passiert, wenn keine Konfiguration angegeben ist

Ist keine Protokollierungskonfiguration angegeben, kann es zu einer Lage kommen, in der ein Protokollereignis ausgegeben werden muss, aber kein Handler gefunden werden kann, der das Ereignis ausgibt.

Das Ereignis wird über einen „Handler der letzten Instanz“ ausgegeben, der in lastResort gespeichert ist. Dieser interne Handler gehört zu keinem Logger und verhält sich wie ein StreamHandler, der die Beschreibung des Ereignisses auf den aktuellen Wert von sys.stderr schreibt (und damit alle wirksamen Umleitungen beachtet). Die Meldung wird nicht formatiert – ausgegeben wird nur die nackte Ereignisbeschreibung. Die Stufe des Handlers ist auf WARNING gesetzt, es werden also alle Ereignisse dieses und höheren Schweregrads ausgegeben.

Geändert in Version 3.2: In Python-Versionen vor 3.2 ist das Verhalten wie folgt:

  • Ist raiseExceptions False (Produktionsbetrieb), wird das Ereignis stillschweigend verworfen.

  • Ist raiseExceptions True (Entwicklungsbetrieb), wird einmal die Meldung „No handlers could be found for logger X.Y.Z“ ausgegeben.

Um das Verhalten von vor 3.2 zu bekommen, lässt sich lastResort auf None setzen.

Protokollierung für eine Bibliothek einrichten

Wenn du eine Bibliothek entwickelst, die Protokollierung verwendet, solltest du darauf achten, zu dokumentieren, wie die Bibliothek Protokollierung verwendet – zum Beispiel die Namen der verwendeten Logger. Auch die Konfiguration der Protokollierung braucht etwas Überlegung. Wenn die verwendende Anwendung keine Protokollierung einsetzt und Bibliothekscode Protokollaufrufe macht, werden (wie im vorigen Abschnitt beschrieben) Ereignisse mit Schweregrad WARNING und höher auf sys.stderr ausgegeben. Das gilt als bestes Standardverhalten.

Wenn du aus irgendeinem Grund nicht willst, dass diese Meldungen ohne jede Protokollierungskonfiguration ausgegeben werden, kannst du dem Logger der obersten Ebene deiner Bibliothek einen Handler anhängen, der nichts tut. Das verhindert die Ausgabe der Meldung, weil für die Ereignisse der Bibliothek immer ein Handler gefunden wird – er erzeugt nur keine Ausgabe. Richtet der Nutzer der Bibliothek Protokollierung für die Anwendung ein, ergänzt diese Konfiguration vermutlich einige Handler, und wenn die Stufen passend eingerichtet sind, senden Protokollaufrufe im Bibliothekscode ihre Ausgabe wie üblich an diese Handler.

Ein Handler, der nichts tut, gehört zum Paket logging: NullHandler (seit Python 3.1). Eine Instanz dieses Handlers ließe sich dem Logger der obersten Ebene des Protokoll-Namensraums hinzufügen, den die Bibliothek verwendet (falls du verhindern willst, dass die protokollierten Ereignisse deiner Bibliothek ohne Protokollierungskonfiguration auf sys.stderr ausgegeben werden). Wird alle Protokollierung einer Bibliothek foo über Logger mit Namen wie „foo.x“, „foo.x.y“ usw. abgewickelt, dann sollte der Code:

import logging
logging.getLogger('foo').addHandler(logging.NullHandler())

die gewünschte Wirkung haben. Erzeugt eine Organisation eine Reihe von Bibliotheken, kann der angegebene Logger-Name „orgname.foo“ anstelle von nur „foo“ lauten.

Bemerkung

Es ist sehr ratsam, in deiner Bibliothek nicht in den Wurzel-Logger zu protokollieren. Verwende stattdessen einen Logger mit einem eindeutigen und leicht erkennbaren Namen, etwa dem __name__ des Pakets oder Moduls auf der obersten Ebene deiner Bibliothek. In den Wurzel-Logger zu protokollieren macht es dem Anwendungsentwickler schwer oder unmöglich, die Ausführlichkeit der Protokollierung oder die Handler deiner Bibliothek so einzurichten, wie er möchte.

Bemerkung

Es ist sehr ratsam, den Loggern deiner Bibliothek keine anderen Handler als NullHandler hinzuzufügen. Der Grund: Die Konfiguration von Handlern ist das Vorrecht des Anwendungsentwicklers, der deine Bibliothek verwendet. Der Anwendungsentwickler kennt seine Zielgruppe und weiß, welche Handler für seine Anwendung am passendsten sind: Fügst du „unter der Haube“ Handler hinzu, beeinträchtigst du womöglich seine Fähigkeit, Unit-Tests durchzuführen und Protokolle zu liefern, die seinen Anforderungen entsprechen.

Protokollierungsstufen

Die Zahlenwerte der Protokollierungsstufen stehen in der folgenden Tabelle. Sie sind vor allem dann von Interesse, wenn du eigene Stufen festlegen willst und sie bestimmte Werte im Verhältnis zu den vordefinierten Stufen haben müssen. Legst du eine Stufe mit demselben Zahlenwert fest, überschreibt sie den vordefinierten Wert. Der vordefinierte Name geht verloren.

Stufe

Zahlenwert

CRITICAL

50

ERROR

40

WARNING

30

INFO

20

DEBUG

10

NOTSET

0

Stufen lassen sich außerdem Loggern zuordnen – entweder vom Entwickler gesetzt oder durch das Laden einer gespeicherten Protokollierungskonfiguration. Wird eine Protokollmethode auf einem Logger aufgerufen, vergleicht der Logger seine eigene Stufe mit der Stufe des Methodenaufrufs. Ist die Stufe des Loggers höher als die des Methodenaufrufs, wird tatsächlich keine Protokollmeldung erzeugt. Das ist der grundlegende Mechanismus, der die Ausführlichkeit der Protokollausgabe steuert.

Protokollmeldungen werden als Instanzen der Klasse LogRecord kodiert. Entscheidet ein Logger, ein Ereignis tatsächlich zu protokollieren, wird aus der Protokollmeldung eine LogRecord-Instanz erzeugt.

Protokollmeldungen unterliegen einem Verteilmechanismus über Handler, die Instanzen von Unterklassen der Klasse Handler sind. Handler sind dafür zuständig, sicherzustellen, dass eine protokollierte Meldung (in Form eines LogRecord) an einem bestimmten Ort (oder einer Reihe von Orten) landet, der für die Zielgruppe dieser Meldung nützlich ist (etwa Endbenutzer, Mitarbeiter der Unterstützung, Systemverwalter, Entwickler). Handlern werden LogRecord-Instanzen übergeben, die für bestimmte Ziele gedacht sind. Jeder Logger kann null, einen oder mehrere Handler haben (über die Methode addHandler() von Logger). Neben allen unmittelbar zu einem Logger gehörenden Handlern werden alle Handler, die zu allen Vorfahren des Loggers gehören, zum Verteilen der Meldung aufgerufen (sofern das Flag propagate eines Loggers nicht auf einen falschen Wert gesetzt ist – an diesem Punkt endet die Weitergabe nach oben).

Genau wie Logger können Handler Stufen haben. Die Stufe eines Handlers wirkt genauso als Filter wie die Stufe eines Loggers. Entscheidet ein Handler, ein Ereignis tatsächlich zu verteilen, wird die Methode emit() verwendet, um die Meldung an ihr Ziel zu senden. Die meisten selbst geschriebenen Unterklassen von Handler müssen dieses emit() überschreiben.

Eigene Stufen

Eigene Stufen festzulegen ist möglich, sollte aber nicht nötig sein, weil die bestehenden Stufen auf Grundlage praktischer Erfahrung gewählt wurden. Wenn du aber überzeugt bist, eigene Stufen zu brauchen, sollte dabei große Vorsicht herrschen, und es ist möglicherweise eine ganz schlechte Idee, eigene Stufen festzulegen, wenn du eine Bibliothek entwickelst. Der Grund: Legen mehrere Bibliotheksautoren alle ihre eigenen Stufen fest, besteht die Gefahr, dass die Protokollausgabe mehrerer gemeinsam verwendeter Bibliotheken für den verwendenden Entwickler schwer zu steuern und/oder zu deuten ist, weil ein gegebener Zahlenwert in verschiedenen Bibliotheken Verschiedenes bedeuten kann.

Nützliche Handler

Neben der Basisklasse Handler werden viele nützliche Unterklassen angeboten:

  1. StreamHandler-Instanzen senden Meldungen an Ströme (dateiartige Objekte).

  2. FileHandler-Instanzen senden Meldungen an Dateien auf der Festplatte.

  3. BaseRotatingHandler ist die Basisklasse für Handler, die Protokolldateien an einem bestimmten Punkt rollieren. Sie ist nicht dafür gedacht, unmittelbar erzeugt zu werden. Verwende stattdessen RotatingFileHandler oder TimedRotatingFileHandler.

  4. RotatingFileHandler-Instanzen senden Meldungen an Dateien auf der Festplatte und unterstützen eine maximale Größe der Protokolldatei sowie das Rollieren von Protokolldateien.

  5. TimedRotatingFileHandler-Instanzen senden Meldungen an Dateien auf der Festplatte und rollieren die Protokolldatei in bestimmten Zeitabständen.

  6. SocketHandler-Instanzen senden Meldungen an TCP/IP-Sockets. Seit 3.4 werden auch Unix-Domain-Sockets unterstützt.

  7. DatagramHandler-Instanzen senden Meldungen an UDP-Sockets. Seit 3.4 werden auch Unix-Domain-Sockets unterstützt.

  8. SMTPHandler-Instanzen senden Meldungen an eine bestimmte E-Mail-Adresse.

  9. SysLogHandler-Instanzen senden Meldungen an einen Unix-syslog-Dienst, möglicherweise auf einem fernen Rechner.

  10. NTEventLogHandler-Instanzen senden Meldungen an ein Ereignisprotokoll von Windows NT/2000/XP.

  11. MemoryHandler-Instanzen senden Meldungen in einen Puffer im Speicher, der geleert wird, sobald bestimmte Bedingungen erfüllt sind.

  12. HTTPHandler-Instanzen senden Meldungen über GET- oder POST-Semantik an einen HTTP-Server.

  13. WatchedFileHandler-Instanzen beobachten die Datei, in die sie protokollieren. Ändert sich die Datei, wird sie geschlossen und über den Dateinamen wieder geöffnet. Dieser Handler ist nur auf Unix-artigen Systemen nützlich; Windows unterstützt den zugrunde liegenden Mechanismus nicht.

  14. QueueHandler-Instanzen senden Meldungen an eine Warteschlange, etwa an die in den Modulen queue oder multiprocessing umgesetzten.

  15. NullHandler-Instanzen tun mit Fehlermeldungen nichts. Sie werden von Bibliotheksentwicklern verwendet, die Protokollierung nutzen, aber die Meldung „No handlers could be found for logger XXX“ vermeiden wollen, die erscheinen kann, wenn der Nutzer der Bibliothek keine Protokollierung eingerichtet hat. Mehr Informationen stehen unter Protokollierung für eine Bibliothek einrichten.

Added in version 3.1: Die Klasse NullHandler.

Added in version 3.2: Die Klasse QueueHandler.

Die Klassen NullHandler, StreamHandler und FileHandler sind im Kernpaket logging definiert. Die anderen Handler sind in einem Untermodul definiert, logging.handlers. (Es gibt außerdem ein weiteres Untermodul, logging.config, für die Konfigurationsfunktionalität.)

Protokollierte Meldungen werden für die Darstellung über Instanzen der Klasse Formatter formatiert. Sie werden mit einer Formatzeichenkette initialisiert, die zur Verwendung mit dem %-Operator und einem Wörterbuch passt.

Um mehrere Meldungen in einem Stapel zu formatieren, lassen sich Instanzen von BufferingFormatter verwenden. Neben der Formatzeichenkette (die auf jede Meldung im Stapel angewendet wird) gibt es die Möglichkeit, Formatzeichenketten für Kopf und Fuß anzugeben.

Wenn das Filtern anhand der Logger-Stufe und/oder der Handler-Stufe nicht genügt, lassen sich sowohl Logger- als auch Handler-Instanzen Instanzen von Filter hinzufügen (über deren Methode addFilter()). Bevor sie entscheiden, eine Meldung weiter zu verarbeiten, fragen sowohl Logger als auch Handler alle ihre Filter um Erlaubnis. Gibt irgendein Filter einen falschen Wert zurück, wird die Meldung nicht weiter verarbeitet.

Die grundlegende Funktionalität von Filter erlaubt das Filtern nach einem bestimmten Logger-Namen. Wird diese Eigenschaft genutzt, lässt der Filter Meldungen durch, die an den genannten Logger und seine Kind-Logger gesendet werden, und verwirft alle anderen.

Ausnahmen, die während der Protokollierung ausgelöst werden

Das Paket logging ist darauf angelegt, Ausnahmen zu verschlucken, die beim Protokollieren im Produktionsbetrieb auftreten. Das soll verhindern, dass Fehler, die beim Behandeln von Protokollereignissen auftreten – etwa eine falsche Protokollierungskonfiguration, Netzwerkfehler oder ähnliche Fehler –, die Anwendung, die Protokollierung verwendet, vorzeitig beenden.

Ausnahmen vom Typ SystemExit und KeyboardInterrupt werden nie verschluckt. Andere Ausnahmen, die während der Methode emit() einer Handler-Unterklasse auftreten, werden an deren Methode handleError() übergeben.

Die Standardumsetzung von handleError() in Handler prüft, ob eine Variable auf Modulebene, raiseExceptions, gesetzt ist. Ist sie gesetzt, wird ein Traceback auf sys.stderr ausgegeben. Ist sie nicht gesetzt, wird die Ausnahme verschluckt.

Bemerkung

Der Vorgabewert von raiseExceptions ist True. Der Grund: Während der Entwicklung willst du üblicherweise über alle auftretenden Ausnahmen Bescheid wissen. Es ist ratsam, raiseExceptions für den Produktionsbetrieb auf False zu setzen.

Beliebige Objekte als Meldungen verwenden

In den vorangegangenen Abschnitten und Beispielen wurde angenommen, dass die beim Protokollieren des Ereignisses übergebene Meldung eine Zeichenkette ist. Das ist aber nicht die einzige Möglichkeit. Du kannst ein beliebiges Objekt als Meldung übergeben. Seine Methode __str__() wird aufgerufen, wenn das Protokollsystem es in eine Zeichenkettendarstellung umwandeln muss. Du kannst das Berechnen einer Zeichenkettendarstellung sogar ganz vermeiden, wenn du willst – so gibt der SocketHandler zum Beispiel ein Ereignis aus, indem er es mit pickle serialisiert und über die Leitung sendet.

Optimierung

Das Formatieren der Argumente einer Meldung wird aufgeschoben, bis es sich nicht mehr vermeiden lässt. Die Argumente, die der Protokollmethode übergeben werden, zu berechnen kann aber ebenfalls teuer sein, und du willst das vielleicht vermeiden, wenn der Logger dein Ereignis einfach wegwirft. Um zu entscheiden, was zu tun ist, kannst du die Methode isEnabledFor() aufrufen, die ein Stufenargument nimmt und wahr zurückgibt, wenn der Logger das Ereignis für diese Aufrufstufe erzeugen würde. Du kannst Code wie diesen schreiben:

if logger.isEnabledFor(logging.DEBUG):
    logger.debug('Message with %s, %s', expensive_func1(),
                                        expensive_func2())

sodass die Aufrufe von expensive_func1 und expensive_func2 nie erfolgen, wenn die Schwelle des Loggers über DEBUG liegt.

Bemerkung

In manchen Fällen kann isEnabledFor() selbst teurer sein, als dir lieb ist (etwa bei tief verschachtelten Loggern, bei denen eine ausdrückliche Stufe nur weit oben in der Logger-Hierarchie gesetzt ist). In solchen Fällen (oder wenn du vermeiden willst, in engen Schleifen eine Methode aufzurufen) kannst du das Ergebnis eines Aufrufs von isEnabledFor() in einer lokalen oder in einer Instanzvariablen zwischenspeichern und diese verwenden, anstatt die Methode jedes Mal aufzurufen. Ein solcher zwischengespeicherter Wert müsste nur dann neu berechnet werden, wenn sich die Protokollierungskonfiguration dynamisch während des Laufs der Anwendung ändert (was nicht besonders häufig vorkommt).

Es gibt weitere Optimierungen für bestimmte Anwendungen, die genauer steuern müssen, welche Protokollinformationen gesammelt werden. Hier eine Liste von Dingen, mit denen du Verarbeitung während der Protokollierung vermeiden kannst, die du nicht brauchst:

Was du nicht sammeln willst

Wie du das Sammeln vermeidest

Informationen darüber, von wo Aufrufe erfolgten.

Setze logging._srcfile auf None. Das vermeidet den Aufruf von sys._getframe(), was deinen Code in Umgebungen wie PyPy beschleunigen kann (das Code, der sys._getframe() verwendet, nicht beschleunigen kann).

Informationen über Threads.

Setze logging.logThreads auf False.

Aktuelle Prozesskennung (os.getpid())

Setze logging.logProcesses auf False.

Aktueller Prozessname bei Verwendung von multiprocessing zur Verwaltung mehrerer Prozesse.

Setze logging.logMultiprocessing auf False.

Aktueller Name der asyncio.Task bei Verwendung von asyncio.

Setze logging.logAsyncioTasks auf False.

Beachte außerdem, dass das Kernmodul logging nur die grundlegenden Handler enthält. Wenn du logging.handlers und logging.config nicht importierst, belegen sie keinen Speicher.

Weitere Quellen

Siehe auch

Modul logging

API-Referenz zum Modul logging.

Modul logging.config

Konfigurations-API zum Modul logging.

Modul logging.handlers

Nützliche Handler, die das Modul logging mitbringt.

Ein Kochbuch zur Protokollierung