Wie man in Python 3 protokolliert

Einführung

Das logging-Modul ist Teil der standardmäßigen Python-Bibliothek und bietet die Verfolgung von Ereignissen, die während der Ausführung von Software auftreten. Sie können Protokollaufrufe zu Ihrem Code hinzufügen, um anzuzeigen, welche Ereignisse aufgetreten sind.

Das logging-Modul ermöglicht sowohl diagnostisches Protokollieren, das Ereignisse im Zusammenhang mit dem Betrieb einer Anwendung aufzeichnet, als auch Audit-Protokollierung, die die Ereignisse von Benutzertransaktionen zur Analyse aufzeichnet. Es wird insbesondere verwendet, um Ereignisse in eine Datei aufzuzeichnen.

Voraussetzungen

Sie sollten Python 3 installiert und eine Programmierumgebung auf Ihrem Computer oder Server eingerichtet haben. Wenn Sie keine Programmierumgebung eingerichtet haben, können Sie sich an die Installations- und Einrichtungsanleitungen für eine lokale Programmierumgebung oder für eine Programmierumgebung auf Ihrem Server entsprechend Ihrem Betriebssystem (Ubuntu, CentOS, Debian usw.) halten.

Warum die logging-Modul verwenden

Das logging-Modul führt eine Aufzeichnung der Ereignisse innerhalb eines Programms, sodass es möglich ist, Ausgaben im Zusammenhang mit einem der Ereignisse zu sehen, die während der Laufzeit eines Softwarestücks auftreten.

Sie sind möglicherweise eher vertraut damit, zu überprüfen, ob Ereignisse auftreten, indem Sie die print()-Anweisung in Ihrem Code verwenden. Die print()-Anweisung bietet eine grundlegende Möglichkeit, um Fehler in Ihrem Code zu beheben. Obwohl das Einbetten von print()-Anweisungen in Ihrem Code den Ausführungsfluss und den aktuellen Zustand Ihres Programms verfolgen kann, erweist sich diese Lösung aus einigen Gründen als weniger wartbar als die Verwendung des logging-Moduls:

  • Es wird schwierig, zwischen Debugging-Ausgaben und normalen Programm-Ausgaben zu unterscheiden, da die beiden gemischt sind
  • Beim Verwenden von print()-Anweisungen, die im Code verteilt sind, gibt es keine effiziente Möglichkeit, diejenigen zu deaktivieren, die Debugging-Ausgaben liefern
  • Es wird schwierig, alle print()-Anweisungen zu entfernen, wenn Sie mit dem Debuggen fertig sind
  • Es gibt kein Protokoll, das sofort verfügbare diagnostische Informationen enthält

Es ist eine gute Idee, sich daran zu gewöhnen, das logging-Modul in Ihrem Code zu verwenden, da dies für Anwendungen, die über kleine Python-Skripte hinauswachsen, geeigneter ist und einen nachhaltigen Ansatz zum Debuggen bietet.

Weil Protokolle Ihnen Verhalten und Fehler im Laufe der Zeit zeigen können, können sie Ihnen auch ein besseres Gesamtbild davon geben, was in Ihrem Anwendungs­entwicklungs­prozess vor sich geht.

Debug-Nachrichten auf der Konsole ausgeben

Info: Um dem Beispielcode in diesem Tutorial zu folgen, öffnen Sie eine Python-Interaktive Shell auf Ihrem lokalen System, indem Sie den Befehl python3 ausführen. Dann können Sie die Beispiele kopieren, einfügen oder bearbeiten, indem Sie sie nach der Eingabeaufforderung >>> hinzufügen.

Wenn Sie daran gewöhnt sind, die print()-Anweisung zu verwenden, um zu sehen, was in einem Programm passiert, sind Sie vielleicht daran gewöhnt, ein Programm zu sehen, das eine Klasse definiert und Objekte instanziiert, die etwas wie folgt generieren:

pizza.py
class Pizza():
    def __init__(self, name, price):
        self.name = name
        self.price = price
        print("Pizza created: {} (${})".format(self.name, self.price))

    def make(self, quantity=1):
        print("Made {} {} pizza(s)".format(quantity, self.name))

    def eat(self, quantity=1):
        print("Ate {} pizza(s)".format(quantity, self.name))

pizza_01 = Pizza("artichoke", 15)
pizza_01.make()
pizza_01.eat()

pizza_02 = Pizza("margherita", 12)
pizza_02.make(2)
pizza_02.eat()

Der obige Code hat eine __init__-Methode, um den Namen und den Preis eines Objekts der Klasse Pizza zu definieren. Dann hat er zwei Methoden, eine namens make() zum Herstellen von Pizzen und eine namens eat() zum Essen von Pizzen. Diese beiden Methoden nehmen den Parameter Menge auf, der auf 1 initialisiert wird.

Nun lassen Sie uns das Programm ausführen:

  1. python pizza.py

Wir erhalten die folgende Ausgabe:

Output
Pizza created: artichoke ($15) Made 1 artichoke pizza(s) Ate 1 pizza(s) Pizza created: margherita ($12) Made 2 margherita pizza(s) Ate 1 pizza(s)

Während die print()-Anweisung es uns ermöglicht, zu sehen, dass der Code funktioniert, können wir stattdessen das logging-Modul verwenden.

Lassen Sie uns die print()-Anweisungen im gesamten Code entfernen oder auskommentieren und import logging oben in die Datei einfügen:

pizza.py
import logging


class Pizza():
    def __init__(self, name, value):
        self.name = name
        self.value = value
...

Das logging-Modul hat standardmäßig eine Standardstufe von WARNING, die eine Stufe über DEBUG liegt. Da wir das logging-Modul in diesem Beispiel zum Debuggen verwenden werden, müssen wir die Konfiguration ändern, damit die Stufe logging.DEBUG Informationen für uns auf die Konsole zurückgibt. Wir können das tun, indem wir die folgende Zeile unterhalb der Importanweisung hinzufügen:

pizza.py
import logging

logging.basicConfig(level=logging.DEBUG)


class Pizza():
...

Diese Stufe von logging.DEBUG bezieht sich auf einen konstanten Ganzzahlenwert, den wir im obigen Code verwenden, um eine Schwelle festzulegen. Die Stufe DEBUG ist 10.

Jetzt werden wir alle print()-Anweisungen stattdessen durch logging.debug()-Anweisungen ersetzen. Anders als logging.DEBUG, das eine Konstante ist, ist logging.debug() eine Methode des logging-Moduls. Beim Arbeiten mit dieser Methode können wir denselben String wie bei print() übergeben, wie im Folgenden gezeigt:

pizza.py
import logging

logging.basicConfig(level=logging.DEBUG)


class Pizza():
    def __init__(self, name, price):
        self.name = name
        self.price = price
        logging.debug("Pizza created: {} (${})".format(self.name, self.price))

    def make(self, quantity=1):
        logging.debug("Made {} {} pizza(s)".format(quantity, self.name))

    def eat(self, quantity=1):
        logging.debug("Ate {} pizza(s)".format(quantity, self.name))

pizza_01 = Pizza("artichoke", 15)
pizza_01.make()
pizza_01.eat()

pizza_02 = Pizza("margherita", 12)
pizza_02.make(2)
pizza_02.eat()

Zu diesem Zeitpunkt erhalten wir beim Ausführen des Programms mit dem Befehl python pizza.py diese Ausgabe:

Output
DEBUG:root:Pizza created: artichoke ($15) DEBUG:root:Made 1 artichoke pizza(s) DEBUG:root:Ate 1 pizza(s) DEBUG:root:Pizza created: margherita ($12) DEBUG:root:Made 2 margherita pizza(s) DEBUG:root:Ate 1 pizza(s)

Die Protokollnachrichten haben das Schweregradniveau DEBUG sowie das Wort root darin eingebettet, das sich auf das Niveau Ihres Python-Moduls bezieht. Das logging-Modul kann mit einer Hierarchie von Protokollführern verwendet werden, die unterschiedliche Namen haben, sodass Sie für jedes Ihrer Module einen anderen Protokollführer verwenden können.

Zum Beispiel können Sie Protokollführer gleich unterschiedlichen Protokollführern setzen, die unterschiedliche Namen und unterschiedliche Ausgaben haben:

logger1 = logging.getLogger("module_1")
logger2 = logging.getLogger("module_2")

logger1.debug("Module 1 debugger")
logger2.debug("Module 2 debugger")
Output
DEBUG:module_1:Module 1 debugger DEBUG:module_2:Module 2 debugger

Jetzt, da wir ein Verständnis dafür haben, wie das logging-Modul verwendet werden kann, um Nachrichten auf die Konsole zu drucken, gehen wir dazu über, das logging-Modul zu verwenden, um Nachrichten in eine Datei zu drucken.

Nachrichten in eine Datei protokollieren

Der Hauptzweck des logging-Moduls besteht darin, Nachrichten in eine Datei zu protokollieren, anstatt auf eine Konsole. Das Aufbewahren einer Datei mit Nachrichten bietet Ihnen im Laufe der Zeit Daten, die Sie konsultieren und quantifizieren können, um zu sehen, welche Änderungen an Ihrem Code vorgenommen werden müssen.

Um mit der Protokollierung in eine Datei zu beginnen, können wir die logging.basicConfig()-Methode ändern, um einen filename-Parameter einzuschließen. In diesem Fall nennen wir die Datei test.log:

pizza.py
import logging

logging.basicConfig(filename="test.log", level=logging.DEBUG)


class Pizza():
    def __init__(self, name, price):
        self.name = name
        self.price = price
        logging.debug("Pizza created: {} (${})".format(self.name, self.price))

    def make(self, quantity=1):
        logging.debug("Made {} {} pizza(s)".format(quantity, self.name))

    def eat(self, quantity=1):
        logging.debug("Ate {} pizza(s)".format(quantity, self.name))

pizza_01 = Pizza("artichoke", 15)
pizza_01.make()
pizza_01.eat()

pizza_02 = Pizza("margherita", 12)
pizza_02.make(2)
pizza_02.eat()

Der obige Code ist derselbe wie im vorherigen Abschnitt, außer dass wir jetzt den Dateinamen für das Protokoll hinzugefügt haben, um gedruckt zu werden. Sobald wir den Code mit dem Befehl python pizza.py ausführen, sollten wir eine neue Datei in unserem Verzeichnis namens test.log haben.

Lassen Sie uns die Datei test.log mit nano (oder dem Texteditor Ihrer Wahl) öffnen:

  1. nano test.log

Wenn die Datei geöffnet ist, erhalten wir Folgendes:

test.log
DEBUG:root:Pizza created: artichoke ($15)
DEBUG:root:Made 1 artichoke pizza(s)
DEBUG:root:Ate 1 pizza(s)
DEBUG:root:Pizza created: margherita ($12)
DEBUG:root:Made 2 margherita pizza(s)
DEBUG:root:Ate 1 pizza(s)

Dies ähnelt der Konsolenausgabe, die wir im vorherigen Abschnitt gesehen haben, nur dass sie sich jetzt in der Datei test.log befindet.

Lassen Sie uns die Datei mit STRG + x schließen und zurück zur Datei pizza.py wechseln, um den Code zu ändern.

Wir behalten einen Großteil des Codes bei, ändern jedoch die Parameter in den beiden Pizza-Instanzen pizza_01 und pizza_02:

pizza.py
import logging

logging.basicConfig(filename="test.log", level=logging.DEBUG)


class Pizza():
    def __init__(self, name, price):
        self.name = name
        self.price = price
        logging.debug("Pizza created: {} (${})".format(self.name, self.price))

    def make(self, quantity=1):
        logging.debug("Made {} {} pizza(s)".format(quantity, self.name))

    def eat(self, quantity=1):
        logging.debug("Ate {} pizza(s)".format(quantity, self.name))

# Ändern Sie die Parameter des pizza_01-Objekts
pizza_01 = Pizza("Sicilian", 18)
pizza_01.make(5)
pizza_01.eat(4)

# Ändern Sie die Parameter des pizza_02-Objekts
pizza_02 = Pizza("quattro formaggi", 16)
pizza_02.make(2)
pizza_02.eat(2)

Mit diesen Änderungen führen wir das Programm erneut mit dem Befehl python pizza.py aus.

Nachdem das Programm ausgeführt wurde, können wir unsere Datei test.log erneut mit nano öffnen:

  1. nano test.log

Beim Überprüfen der Datei sehen wir, dass mehrere neue Zeilen hinzugefügt wurden und dass die vorherigen Zeilen vom letzten Mal, als das Programm ausgeführt wurde, beibehalten wurden:

test.log
DEBUG:root:Pizza created: artichoke ($15)
DEBUG:root:Made 1 artichoke pizza(s)
DEBUG:root:Ate 1 pizza(s)
DEBUG:root:Pizza created: margherita ($12)
DEBUG:root:Made 2 margherita pizza(s)
DEBUG:root:Ate 1 pizza(s)
DEBUG:root:Pizza created: Sicilian ($18)
DEBUG:root:Made 5 Sicilian pizza(s)
DEBUG:root:Ate 4 pizza(s)
DEBUG:root:Pizza created: quattro formaggi ($16)
DEBUG:root:Made 2 quattro formaggi pizza(s)
DEBUG:root:Ate 2 pizza(s)

Obwohl diese Informationen sicherlich nützlich sind, können wir das Protokoll informativer gestalten, indem wir zusätzliche LogRecord-Attribute hinzufügen. Vor allem möchten wir einen lesbaren Zeitstempel hinzufügen, der uns angibt, wann der LogRecord erstellt wurde.

Wir können dieses Attribut zu einem Parameter namens format hinzufügen und darauf verweisen, wie in der Tabelle mit dem String %(asctime)s gezeigt. Zusätzlich müssen wir den Namen des DEBUG-Levels beibehalten, indem wir den String %(levelname)s einfügen, und um die angeforderte Ausgabe des Loggers beizubehalten, fügen wir den String %(message)s ein. Jedes dieser Attribute wird durch einen Doppelpunkt getrennt, wie im hinzugefügten Code gezeigt:

pizza.py
import logging

logging.basicConfig(
    filename="test.log",
    level=logging.DEBUG,
    format="%(asctime)s:%(levelname)s:%(message)s"
    )


class Pizza():
    def __init__(self, name, price):
        self.name = name
        self.price = price
        logging.debug("Pizza created: {} (${})".format(self.name, self.price))

    def make(self, quantity=1):
        logging.debug("Made {} {} pizza(s)".format(quantity, self.name))

    def eat(self, quantity=1):
        logging.debug("Ate {} pizza(s)".format(quantity, self.name))

pizza_01 = Pizza("Sicilian", 18)
pizza_01.make(5)
pizza_01.eat(4)

pizza_02 = Pizza("quattro formaggi", 16)
pizza_02.make(2)
pizza_02.eat(2)

Wenn wir den obigen Code mit den hinzugefügten Attributen mit dem Befehl python pizza.py ausführen, werden neue Zeilen zu unserer Datei test.log hinzugefügt, die den lesbareren Zeitstempel sowie den Level-Namen DEBUG und die zugehörigen Nachrichten enthalten, die dem Logger als Strings übergeben werden.

test.log
DEBUG:root:Pizza created: Sicilian ($18)
DEBUG:root:Made 5 Sicilian pizza(s)
DEBUG:root:Ate 4 pizza(s)
DEBUG:root:Pizza created: quattro formaggi ($16)
DEBUG:root:Made 2 quattro formaggi pizza(s)
DEBUG:root:Ate 2 pizza(s)
2021-08-19 23:31:34,484:DEBUG:Pizza created: Sicilian ($18)
2021-08-19 23:31:34,484:DEBUG:Made 5 Sicilian pizza(s)
2021-08-19 23:31:34,484:DEBUG:Ate 4 pizza(s)
2021-08-19 23:31:34,484:DEBUG:Pizza created: quattro formaggi ($16)
2021-08-19 23:31:34,484:DEBUG:Made 2 quattro formaggi pizza(s)
2021-08-19 23:31:34,484:DEBUG:Ate 2 pizza(s)

Je nach Ihren Anforderungen möchten Sie möglicherweise zusätzliche LogRecord-Attribute in Ihrem Code verwenden, um Ihre Protokolldateien relevant für Sie zu machen.

Das Protokollieren von Debug- und anderen Nachrichten in separaten Dateien bietet Ihnen ein umfassendes Verständnis Ihres Python-Programms im Laufe der Zeit und ermöglicht es Ihnen, Ihren Code in einer Weise zu debuggen und zu modifizieren, die durch die historische Arbeit am Programm sowie die auftretenden Ereignisse und Transaktionen informiert ist.

Tabelle der Protokollierungsstufen

Als Entwickler können Sie einem Ereignis, das im Logger erfasst wird, eine Bedeutungsebene zuweisen, indem Sie einen Schweregrad festlegen. Die Schweregrade werden in der folgenden Tabelle angezeigt.

Logging-Level sind technisch gesehen Ganzzahlen (eine Konstante), und sie erhöhen sich alle um 10, beginnend mit NOTSET, das den Logger auf den numerischen Wert 0 initialisiert.

Sie können auch Ihre eigenen Ebenen relativ zu den vordefinierten Ebenen definieren. Wenn Sie eine Ebene mit dem gleichen numerischen Wert definieren, überschreiben Sie den Namen, der mit diesem Wert verbunden ist.

In der folgenden Tabelle sind die verschiedenen Ebenennamen, ihr numerischer Wert, die Funktion, die Sie verwenden können, um die Ebene aufzurufen, und wofür diese Ebene verwendet wird, aufgeführt.

Level Numeric Value Function Used to
CRITICAL 50 logging.critical() Show a serious error, the program may be unable to continue running
ERROR 40 logging.error() Show a more serious problem
WARNING 30 logging.warning() Indicate something unexpected happened, or could happen
INFO 20 logging.info() Confirm that things are working as expected
DEBUG 10 logging.debug() Diagnose problems, show detailed information

Das Modul logging setzt das Standardlevel auf WARNING, daher werden WARNING, ERROR und CRITICAL standardmäßig protokolliert. In obigem Beispiel haben wir die Konfiguration so geändert, dass das DEBUG-Level mit folgendem Code einbezogen wird:

logging.basicConfig(level=logging.DEBUG)

Sie können mehr über die Befehle und die Arbeit mit dem Debugger in der offiziellen logging-Dokumentation nachlesen.

Abschluss

Die Fehlerbehebung ist ein wichtiger Schritt in jedem Softwareentwicklungsprojekt. Das logging-Modul ist Teil der Standard-Python-Bibliothek, bietet eine Verfolgung von Ereignissen, die während der Ausführung der Software auftreten, und kann diese Ereignisse in eine separate Protokolldatei ausgeben, um Ihnen zu ermöglichen, den Ablauf Ihres Codes nachzuvollziehen. Dies gibt Ihnen die Möglichkeit, Ihren Code basierend auf dem Verständnis der verschiedenen Ereignisse zu debuggen, die im Laufe der Zeit bei der Ausführung Ihres Programms auftreten.

Source:
https://www.digitalocean.com/community/tutorials/how-to-use-logging-in-python-3