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       | "print()"                              |
| gewöhnliche Verwendung eines          |                                        |
| Befehlszeilenskripts oder -programms  |                                        |
| anzeigen                              |                                        |
+---------------------------------------+----------------------------------------+
| Ereignisse melden, die im normalen    | Die Methode "info()" eines Loggers     |
| Betrieb eines Programms auftreten     | (oder die Methode "debug()" für sehr   |
| (etwa zur Zustandsüberwachung oder    | ausführliche Ausgaben zu               |
| Fehlersuche)                          | diagnostischen Zwecken)                |
+---------------------------------------+----------------------------------------+
| Eine Warnung zu einem bestimmten      | "warnings.warn()" in Bibliothekscode,  |
| Laufzeitereignis ausgeben             | 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      | Eine Ausnahme auslösen                 |
| Laufzeitereignis melden               |                                        |
+---------------------------------------+----------------------------------------+
| Das Unterdrücken eines Fehlers        | Die Methode "error()", "exception()"   |
| melden, ohne eine Ausnahme auszulösen | oder "critical()" eines Loggers, je    |
| (etwa in einer Fehlerbehandlung in    | nachdem, was zum jeweiligen Fehler und |
| einem lang laufenden Serverprozess)   | 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.

[Bild]


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 Kinder 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
Kinder 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              | Setze "logging.logMultiprocessing" auf "False".     |
| "multiprocessing" zur Verwaltung mehrerer Prozesse.   |                                                     |
+-------------------------------------------------------+-----------------------------------------------------+
| Aktueller Name der "asyncio.Task" bei Verwendung von  | Setze "logging.logAsyncioTasks" auf "False".        |
| "asyncio".                                            |                                                     |
+-------------------------------------------------------+-----------------------------------------------------+

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
