Skip to content

Fehlerbehandlung und Logging

Robuste Python-Skripte entwickeln, Fehler behandeln und wichtige Ereignisse protokollieren.

In produktiven Umgebungen treten Fehler regelmäßig auf. Dateien können fehlen, Netzwerkverbindungen abbrechen, APIs nicht erreichbar sein oder Benutzereingaben ungültige Werte enthalten.

Professionelle Automatisierungsskripte müssen mit solchen Situationen umgehen können. Statt bei jedem Problem sofort abzustürzen, sollten sie Fehler erkennen, sinnvoll reagieren und nachvollziehbare Informationen protokollieren.

Python stellt hierfür ein leistungsfähiges Exception-System sowie umfangreiche Logging-Funktionen bereit. Beide gehören zu den wichtigsten Werkzeugen für den produktiven Einsatz von Automatisierungsskripten.

In diesem Kapitel lernen Sie, wie Fehler behandelt, Ausnahmen gezielt abgefangen und aussagekräftige Logdateien erzeugt werden.

Exceptions verstehen

Warum Exceptions in Python wichtig sind

In der Systemadministration und Automatisierung treten häufig unerwartete Situationen auf: Dateien fehlen, Netzwerkverbindungen brechen ab oder APIs liefern Fehler. Python nutzt Exceptions (Ausnahmen), um solche Fehler klar und kontrolliert zu behandeln – anstatt das Programm abrupt zu beenden.

Grundlagen: try, except, else und finally

Der Kern der Fehlerbehandlung in Python ist der try-Block, in dem potenziell fehlerhafte Anweisungen stehen. Tritt ein Fehler auf, wird die Ausführung sofort zum passenden except-Block weitergeleitet.

try:
    with open('/var/log/syslog') as f:
        lines = f.readlines()
except FileNotFoundError:
    print('Logdatei nicht gefunden.')
else:
    print(f'Datei erfolgreich gelesen, {len(lines)} Zeilen.')
finally:
    print('Versuch, die Logdatei zu lesen, ist abgeschlossen.')
  • except fängt spezifische Fehler (hier FileNotFoundError) ab.
  • else wird nur ausgeführt, wenn kein Fehler auftrat.
  • finally läuft immer, z.B. um Ressourcen freizugeben.

Typische Fehlerarten in Administrationsskripten

  • Dateifehler: Datei nicht gefunden (FileNotFoundError), Zugriffsrechte (PermissionError)
  • Netzwerkfehler: Verbindungsabbrüche, Zeitüberschreitungen (ConnectionError, TimeoutError)
  • Datenfehler: Ungültige JSON- oder CSV-Daten (json.JSONDecodeError, csv.Error)

Beispiel: API-Abfrage mit Fehlerbehandlung

import requests

try:
    response = requests.get('https://api.example.com/status', timeout=5)
    response.raise_for_status()  # HTTP-Fehler auslösen
    data = response.json()
except requests.exceptions.Timeout:
    print('API-Anfrage hat zu lange gedauert.')
except requests.exceptions.HTTPError as err:
    print(f'HTTP-Fehler: {err}')
except requests.exceptions.RequestException as err:
    print(f'Netzwerkfehler: {err}')
else:
    print('API-Daten erfolgreich empfangen:', data)

Best Practices für Fehlerbehandlung

  • Fange nur die Fehler, die du erwartest und sinnvoll behandeln kannst.
  • Vermeide zu breite except:-Blöcke, um unerwartete Fehler nicht zu verschleiern.
  • Nutze else für Code, der nur bei Erfolg ausgeführt werden soll.
  • Verwende finally, um wichtige Aufräumarbeiten sicherzustellen, z.B. das Schließen von Dateien oder Verbindungen.

Hinweis: Unterschied zu Shell-Skripten

In Shell-Skripten prüft man meist den Rückgabewert ($?), was schnell unübersichtlich wird, wenn viele Befehle nacheinander ausgeführt werden. Python erlaubt durch Exceptions eine klare Trennung von Fehler- und Erfolgslogik, was besonders bei komplexeren Automatisierungen die Wartbarkeit erhöht.

Zusammenfassung

Exceptions sind in Python das Mittel der Wahl, um Fehler kontrolliert zu behandeln. Sie helfen dabei, Automatisierungsskripte stabil zu halten und auf typische Fehler im Administrationsalltag gezielt zu reagieren. Das Verständnis von try, except, else und finally ist dabei die Basis für robuste Python-Skripte.

Logging in der Praxis

Warum Logging in Automatisierungsskripten wichtig ist

In der täglichen Administration und Automatisierung ist es entscheidend, nicht nur Fehler zu erkennen, sondern auch nachvollziehen zu können, was ein Skript wann getan hat. Anders als bei interaktiven Programmen laufen Automatisierungsskripte häufig unbeaufsichtigt, beispielsweise als Cron-Job, geplanter Task oder innerhalb einer CI/CD-Pipeline. Logging liefert eine dauerhafte Aufzeichnung dieser Abläufe und erleichtert Fehlersuche, Monitoring und Dokumentation.

Während print()-Ausgaben lediglich temporär auf der Konsole erscheinen, ermöglicht Logging eine strukturierte und langfristige Speicherung wichtiger Informationen.

Das Python-Logging-Modul: Grundfunktionen

Python bringt mit dem Modul logging eine leistungsfähige Standardlösung für professionelles Logging mit. Statt direkt auf dem Root-Logger zu arbeiten, ist es üblich, pro Modul einen eigenen Logger zu verwenden.

Ein einfaches Beispiel:

import logging

logging.basicConfig(
    filename="admin_script.log",
    level=logging.INFO,
    format="%(asctime)s %(levelname)s %(name)s: %(message)s",
)

logger = logging.getLogger(__name__)

logger.info("Skript gestartet")

try:
    with open("/var/log/syslog") as file:
        lines = file.readlines()

    logger.info(
        "Logdatei erfolgreich gelesen (%s Zeilen)",
        len(lines),
    )

except FileNotFoundError:
    logger.error("Logdatei nicht gefunden")

Durch __name__ wird automatisch der Modulname in den Logeinträgen protokolliert. In größeren Anwendungen erleichtert dies die Zuordnung von Meldungen erheblich.

Logger konfigurieren

Bevor Logger verwendet werden können, muss festgelegt werden, wie und wohin Logmeldungen geschrieben werden. Diese Konfiguration erfolgt typischerweise einmal beim Start der Anwendung. Dabei werden unter anderem das Log-Level, das Ausgabeformat sowie die Ausgabemedien definiert.

Für kleinere Anwendungen genügt häufig eine Konfiguration über logging.basicConfig():

import logging

logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s %(levelname)s %(name)s: %(message)s",
)

logger = logging.getLogger(__name__)

logger.info("Anwendung gestartet")

Die Parameter haben folgende Bedeutung:

  • level bestimmt den minimalen Schweregrad, der ausgegeben wird.
  • format definiert das Aussehen der Logeinträge.
  • %(asctime)s fügt einen Zeitstempel hinzu.
  • %(levelname)s enthält den Schweregrad.
  • %(name)s enthält den Namen des Loggers.
  • %(message)s enthält die eigentliche Meldung.

Für produktive Anwendungen werden häufig mehrere Ausgabekanäle verwendet. So können beispielsweise Meldungen gleichzeitig in eine Datei und auf die Konsole geschrieben werden:

import logging

logger = logging.getLogger(__name__)

formatter = logging.Formatter(
    "%(asctime)s %(levelname)s %(name)s: %(message)s"
)

file_handler = logging.FileHandler(
    "application.log"
)
file_handler.setFormatter(formatter)

console_handler = logging.StreamHandler()
console_handler.setFormatter(formatter)

logger.setLevel(logging.INFO)

logger.addHandler(file_handler)
logger.addHandler(console_handler)

logger.info("Anwendung gestartet")

Diese Trennung über sogenannte Handler ist die bevorzugte Vorgehensweise in größeren Anwendungen. Sie ermöglicht es, unterschiedliche Formate, Log-Level oder Ziele unabhängig voneinander zu konfigurieren. So können beispielsweise Debug-Meldungen ausschließlich in Dateien gespeichert werden, während auf der Konsole nur Warnungen und Fehler erscheinen.

flowchart TD

    A[Application Code]

    A --> B[logger.info]
    A --> C[logger.warning]
    A --> D[logger.error]

    B --> E[Logger]
    C --> E
    D --> E

    E --> F[Formatter]

    F --> G[FileHandler]
    F --> H[StreamHandler]

    G --> I[application.log]
    H --> J[Terminal]

    K[Log Level Filter] --> E

Schweregrade sinnvoll nutzen

Python unterscheidet mehrere Schweregrade:

Level Bedeutung
DEBUG Detaillierte Informationen für Entwickler
INFO Wichtige Informationen zum normalen Ablauf
WARNING Unerwartete Situationen ohne unmittelbaren Fehler
ERROR Fehler, die einzelne Funktionen beeinträchtigen
CRITICAL Schwere Fehler, die das Programm möglicherweise unbrauchbar machen

Beispiel:

import logging

logger = logging.getLogger(__name__)

logger.debug("Verbindung wird aufgebaut")
logger.info("Import gestartet")
logger.warning("Konfigurationswert fehlt")
logger.error("Datei konnte nicht gelesen werden")
logger.critical("Datenbank nicht erreichbar")

Für produktive Anwendungen ist INFO häufig ein sinnvoller Standardwert.

Beispielausgabe eines Loggers

Bei Verwendung der folgenden Konfiguration:

logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s %(levelname)s %(name)s: %(message)s",
)

logger = logging.getLogger(__name__)

könnte die Ausgabe wie folgt aussehen:

2026-02-15 09:41:12,534 INFO __main__: Anwendung gestartet
2026-02-15 09:41:12,535 INFO __main__: Verbindung zur Datenbank aufgebaut
2026-02-15 09:41:13,021 WARNING __main__: Konfigurationswert nicht gefunden, Standardwert wird verwendet
2026-02-15 09:41:15,442 ERROR __main__: Datei customers.csv konnte nicht gelesen werden

Die einzelnen Bestandteile eines Logeintrags sind:

Bestandteil Bedeutung
2026-02-15 09:41:12,534 Zeitstempel
INFO Schweregrad
main Name des Loggers bzw. Moduls
Anwendung gestartet Eigentliche Lognachricht

In einem größeren Projekt erscheinen hier statt __main__ typischerweise Modulnamen:

2026-02-15 09:41:12,534 INFO user_service: Benutzer angelegt: Alice
2026-02-15 09:41:13,102 INFO payment_service: Zahlung gestartet
2026-02-15 09:41:13,842 ERROR api_client: Verbindung zur API fehlgeschlagen

Dadurch lässt sich schnell erkennen, aus welchem Teil der Anwendung eine Meldung stammt.

Fehler mit Stacktrace protokollieren

Ein häufiger Fehler besteht darin, Ausnahmen lediglich als Text auszugeben. In der Praxis sind Stacktraces oft entscheidend für die Fehlersuche.

import logging

logger = logging.getLogger(__name__)

try:
    result = 10 / 0

except ZeroDivisionError:
    logger.exception(
        "Fehler bei der Berechnung"
    )

Die Methode logger.exception() erzeugt automatisch einen vollständigen Stacktrace und sollte innerhalb eines except-Blocks bevorzugt werden.

Logging in mehreren Modulen

Ein großer Vorteil von getLogger(__name__) besteht darin, dass jedes Modul einen eigenen Logger erhält.

# user_service.py

import logging

logger = logging.getLogger(__name__)


def create_user(name: str) -> None:
    logger.info(
        "Benutzer wird angelegt: %s",
        name,
    )
# payment_service.py

import logging

logger = logging.getLogger(__name__)


def process_payment(amount: float) -> None:
    logger.info(
        "Zahlung gestartet: %.2f EUR",
        amount,
    )

Mögliche Logausgabe:

INFO user_service Benutzer wird angelegt: Alice
INFO payment_service Zahlung gestartet: 99.95 EUR

In größeren Projekten erleichtert dies die Analyse erheblich.

Logging in der Praxis: API-Abfrage

Ein typisches Szenario ist die Überwachung externer Dienste.

import logging

import requests

logger = logging.getLogger(__name__)

url = "https://api.example.com/status"

try:
    response = requests.get(
        url,
        timeout=5,
    )

    response.raise_for_status()

    data = response.json()

    logger.info(
        "API erfolgreich abgefragt: %s",
        data.get("status"),
    )

except requests.exceptions.Timeout:
    logger.error(
        "Zeitüberschreitung bei der API-Anfrage"
    )

except requests.exceptions.RequestException:
    logger.exception(
        "API-Anfrage fehlgeschlagen"
    )

Dadurch lassen sich Kommunikationsprobleme mit externen Systemen später nachvollziehen.

Lang laufende Prozesse protokollieren

Gerade bei Batch-Verarbeitung oder ETL-Prozessen ist es sinnvoll, Fortschritte zu protokollieren.

import logging

logger = logging.getLogger(__name__)

files = [
    "customers.csv",
    "orders.csv",
    "products.csv",
]

for file_name in files:
    logger.info(
        "Verarbeite Datei %s",
        file_name,
    )

logger.info(
    "Import abgeschlossen"
)

Eine solche Protokollierung erleichtert später die Analyse von Laufzeiten und Fehlern.

Rotierende Logdateien

Logdateien wachsen mit der Zeit erheblich. Für langfristig laufende Anwendungen empfiehlt sich daher eine automatische Rotation.

import logging

from logging.handlers import RotatingFileHandler

handler = RotatingFileHandler(
    "application.log",
    maxBytes=1_000_000,
    backupCount=5,
)

logging.basicConfig(
    handlers=[handler],
    level=logging.INFO,
    format="%(asctime)s %(levelname)s %(name)s: %(message)s",
)

logger = logging.getLogger(__name__)

logger.info(
    "Anwendung gestartet"
)

Sobald die Datei etwa ein Megabyte erreicht, wird automatisch eine neue Logdatei angelegt.

Tipps für den Administrationsalltag

  • Verwenden Sie getLogger(__name__) statt direkt auf dem Root-Logger zu arbeiten.
  • Nutzen Sie logger.exception() für unerwartete Fehler.
  • Protokollieren Sie wichtige Prozessschritte mit INFO.
  • Nutzen Sie DEBUG für detaillierte Diagnoseinformationen.
  • Planen Sie Logrotation von Anfang an ein.
  • Vermeiden Sie die Ausgabe sensibler Daten wie Passwörter oder Tokens.
  • Verwenden Sie Platzhalter (%s) statt f-Strings, damit unnötige Formatierungsarbeit vermieden wird.

Vergleich zu print()

Viele Einsteiger beginnen mit print(), stoßen aber schnell an Grenzen.

print("Import gestartet")

Im Vergleich dazu:

logger.info(
    "Import gestartet"
)

Logging bietet:

  • Zeitstempel
  • Schweregrade
  • Modulnamen
  • Ausgabe in Dateien
  • Ausgabe an mehrere Ziele gleichzeitig
  • Erweiterbarkeit durch Handler und Formatter

Dadurch eignet es sich deutlich besser für produktive Anwendungen.

Zusammenfassung

Logging gehört zu den wichtigsten Werkzeugen professioneller Softwareentwicklung. Bereits mit wenigen Zeilen Konfiguration lassen sich Anwendungen transparent und nachvollziehbar gestalten. Die Verwendung von logging.getLogger(__name__) ermöglicht eine saubere Strukturierung über mehrere Module hinweg und erleichtert die Fehlersuche erheblich. Insbesondere in Automatisierungsskripten, Batch-Prozessen und produktiven Anwendungen sollte Logging daher von Anfang an eingeplant werden.

Zusammenfassung

  • Nutze try/except gezielt, um erwartete Fehlerarten sauber zu behandeln.
  • Verwende logging statt print() für dauerhafte und strukturierte Protokollierung.
  • Plane Rückfallstrategien, um Skripte robust gegen Ausfälle zu machen.
  • Dokumentiere Fehler und Erfolge im Log, um spätere Analysen zu erleichtern.

Mit diesen Techniken werden Python-Skripte zu verlässlichen Helfern im Administrationsalltag, die auch bei Fehlern kontrolliert reagieren und wertvolle Informationen liefern.