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.')
exceptfängt spezifische Fehler (hierFileNotFoundError) ab.elsewird nur ausgeführt, wenn kein Fehler auftrat.finallylä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
elsefü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:
levelbestimmt den minimalen Schweregrad, der ausgegeben wird.formatdefiniert das Aussehen der Logeinträge.%(asctime)sfügt einen Zeitstempel hinzu.%(levelname)senthält den Schweregrad.%(name)senthält den Namen des Loggers.%(message)senthä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:
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
DEBUGfü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.
Im Vergleich dazu:
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/exceptgezielt, um erwartete Fehlerarten sauber zu behandeln. - Verwende
loggingstattprint()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.