10. Logging und Monitoring
Logging und Monitoring
Abschnitt betitelt „Logging und Monitoring“Eine Anwendung, deren Zustand niemand kennt, wird nicht betrieben. Sie wird gehofft.
Bisher haben Sie erfahren, dass etwas kaputt ist, weil jemand es Ihnen gesagt hat. Danach begann das Raten: Container-Logs durchscrollen, versuchen nachzustellen, im Zweifel neu starten. Das funktioniert bei einem Projekt mit fünf Benutzern und hört genau dann auf zu funktionieren, wenn es darauf ankommt.
Am Ende dieser zwei Wochen beantwortet Ihr System drei Fragen ohne Rateschleife:
- Läuft es? Und zwar, bevor jemand anruft.
- Was ist gerade passiert? Mehr Fehler als sonst, langsamer als sonst, seit wann?
- Warum war die Anfrage von 10:42 Uhr fehlerhaft? Diese eine, für diese eine Person.
Die drei Sichten
Abschnitt betitelt „Die drei Sichten“| Sicht | Beantwortet | Form | Kosten |
|---|---|---|---|
| Logs | Was ist im Einzelfall passiert? | Ereignisse mit Kontext | wachsen mit dem Verkehr |
| Metriken | Wie viel, wie schnell, wie oft falsch? | Zahlen über die Zeit | gering und konstant |
| Traces | Wo im Weg ging die Zeit verloren? | Aufrufbaum je Anfrage | mittel, meist mit Stichproben |
Die drei ersetzen einander nicht. Eine Metrik sagt Ihnen, dass die Fehlerrate seit 10:40 Uhr steigt. Das Log sagt Ihnen, welcher Fehler es ist. Der Trace sagt Ihnen, dass die Zeit im Aufruf des fremden Zahlungsdienstes verloren geht.
journalctl und docker logs aus dem Vorjahr waren der Anfang. Jetzt wird daraus etwas, das man durchsuchen, zählen und alarmieren kann.
Strukturiertes Logging
Abschnitt betitelt „Strukturiertes Logging“Von der Textzeile zum Ereignis
Abschnitt betitelt „Von der Textzeile zum Ereignis“Booking 812 could not be savedDiese Zeile ist für einen Menschen lesbar und für eine Maschine wertlos. Man kann nicht danach filtern, nicht zählen, nicht mit einer Person oder einer Version verknüpfen. Ein Ereignis dagegen ist ein Objekt mit Feldern:
{ "time": "2026-04-17T10:42:03.221Z", "level": "error", "message": "Booking could not be saved", "service": "werkstatt-web", "version": "1.4.2", "environment": "production", "correlationId": "0f8c2b1e-…", "userId": "u_7231", "bookingId": 812, "error": "P2002: unique constraint violation booking_machine_time"}Die Regel dahinter: Kontext gehört in Felder, nicht in den Text. Sobald Sie Werte in die Nachricht hineinschreiben, ist jede Zeile anders und nichts mehr gruppierbar.
import pino from "pino";
export const logger = pino({ level: process.env.LOG_LEVEL ?? "info", base: { service: "werkstatt-web", version: process.env.APP_VERSION, // from chapter 9 environment: process.env.ENVIRONMENT, }, redact: { paths: ["req.headers.authorization", "req.headers.cookie", "*.password", "*.token"], censor: "[redacted]", },});Das Feld version schließt den Kreis zum vorigen Kapitel: Jeder Eintrag weiß, aus welchem Stand er stammt. Nach einer Auslieferung sehen Sie damit sofort, ob die neuen Fehler aus der neuen Version kommen.
| Stufe | Wofür | Beispiel |
|---|---|---|
error | Etwas ist fehlgeschlagen, jemand muss es ansehen | Datenbank nicht erreichbar, unbehandelte Ausnahme |
warn | Auffällig, aber abgefangen | fremder Dienst antwortete erst im zweiten Versuch |
info | Fachlich bedeutsame Ereignisse | Buchung angelegt, Anmeldung fehlgeschlagen, Release gestartet |
debug | Entwicklungshilfe | Inhalt einer Abfrage, Zwischenwerte |
In Produktion steht die Stufe auf info. debug in Produktion füllt die Platte, kostet Geld und macht das Wesentliche unauffindbar. Und eine Ausnahme, die abgefangen und auf info protokolliert wird, ist keine, die jemand findet.
Korrelation über Systemgrenzen
Abschnitt betitelt „Korrelation über Systemgrenzen“Eine Benutzeraktion erzeugt mehrere Einträge in mehreren Systemen: Frontend, Webservice, Hintergrundauftrag, fremder Dienst. Ohne eine gemeinsame Kennung sind das lose Zeilen.
import { AsyncLocalStorage } from "node:async_hooks";
export const context = new AsyncLocalStorage<{ correlationId: string }>();
export function withCorrelation(request: Request, next: () => Promise<Response>) { const id = request.headers.get("x-correlation-id") ?? crypto.randomUUID(); return context.run({ correlationId: id }, next);}
export function log() { return logger.child({ correlationId: context.getStore()?.correlationId });}Diese Kennung geht drei Wege: in jeden Protokolleintrag, als Kopfzeile an jeden fremden Dienst, und in die Fehlerantwort an den Client, wo sie im problem+json-Objekt aus dem Vorjahr steht. Damit hat der Satz „bei mir stand Fehler 500, die Kennung war 0f8c2b1e” plötzlich einen Wert: Sie tippen sie in die Suche und sehen den ganzen Vorgang.
Was nicht ins Log gehört
Abschnitt betitelt „Was nicht ins Log gehört“Passwörter, Token, Cookies, Sitzungskennungen, vollständige Anfragekörper, Zahlungsdaten, Mailadressen und Klarnamen, wo eine Kennung reicht. Ein Protokoll ist eine Datenverarbeitung wie jede andere, mit Zweck, Rechtsgrundlage und Frist aus Kapitel 4.
Zwei praktische Folgen. Erstens: Filtern Sie zentral in der Logbibliothek, nicht an jeder Aufrufstelle. Zweitens: Legen Sie die Aufbewahrungsdauer fest und setzen Sie sie durch. 14 bis 30 Tage sind für die Fehlersuche üblich und für die 72-Stunden-Meldepflicht ausreichend.
Sammlung
Abschnitt betitelt „Sammlung“Auf einem einzelnen Server ist die Mindestausstattung eine begrenzte Rotation, damit die Platte nicht vollläuft:
services: web: logging: driver: json-file options: max-size: "10m" max-file: "3"Rechnen Sie einmal überschlägig, was Sie erzeugen: Bei 200 Anfragen pro Minute und einem Ereignis von einem halben Kilobyte je Anfrage sind das rund 140 Megabyte am Tag, bei zwei Ereignissen entsprechend mehr. Das ist auf einem kleinen Server relevant, und es ist der Grund, warum debug in Produktion nicht nur unübersichtlich, sondern teuer ist.
Besser ist ein Sammler, der die Ausgabe aller Container an einen Ort bringt und durchsuchbar macht, etwa Loki mit Promtail und Grafana als Oberfläche, oder ein gehosteter Dienst. Der Unterschied zeigt sich erst im Störfall, dann aber deutlich: Suche über alle Dienste und einen Zeitraum statt docker logs in vier Fenstern.
Metriken
Abschnitt betitelt „Metriken“Die vier goldenen Signale
Abschnitt betitelt „Die vier goldenen Signale“| Signal | Frage | Beispielmetrik |
|---|---|---|
| Verkehr | Wie viel ist los? | Anfragen pro Sekunde |
| Fehler | Wie viel davon geht schief? | Anteil der Antworten mit 5xx |
| Latenz | Wie lange dauert es? | Antwortzeit als p50 und p95 |
| Sättigung | Wie voll ist das System? | CPU, Speicher, Verbindungen, Warteschlangenlänge |
Dazu kommen fachliche Kennzahlen, und die sind für Ihr Projekt oft die aussagekräftigeren: angelegte Buchungen pro Stunde, fehlgeschlagene Anmeldungen, offene Zahlungen, Länge der Offline-Warteschlange aus Kapitel 8. Wenn die Buchungen pro Stunde auf null fallen, ist etwas kaputt, auch wenn jede einzelne Anfrage mit 200 antwortet.
import { Counter, Histogram } from "prom-client";
export const requests = new Counter({ name: "http_requests_total", help: "Number of HTTP requests", labelNames: ["method", "route", "status"],});
export const duration = new Histogram({ name: "http_request_duration_seconds", help: "Response time in seconds", labelNames: ["method", "route"], buckets: [0.05, 0.1, 0.25, 0.5, 1, 2, 5],});
export const bookings = new Counter({ name: "bookings_total", help: "Bookings created", labelNames: ["outcome"],});Dashboards, die man benutzt
Abschnitt betitelt „Dashboards, die man benutzt“Ein Dashboard ist nicht dazu da, beeindruckend auszusehen. Es beantwortet im Störfall in dreißig Sekunden die Frage: Liegt es an uns, und woran?
Bewährt hat sich eine Übersichtsseite mit sechs bis acht Kacheln, nämlich Verkehr, Fehlerrate, p95, Sättigung, zwei fachliche Kennzahlen und der laufenden Version. Dahinter drei Detailseiten: Anwendung, Datenbank, fremde Dienste. Dreißig Kacheln auf einer Seite bedeuten, dass niemand die eine findet, auf die es ankommt.
Alarmierung
Abschnitt betitelt „Alarmierung“Auf Symptome alarmieren
Abschnitt betitelt „Auf Symptome alarmieren“Ein Alarm auf „CPU über 80 Prozent” feuert regelmäßig, ohne dass jemand etwas merkt. Ein Alarm auf „mehr als fünf Prozent der Anfragen scheitern” feuert, wenn Benutzer betroffen sind. Alarmieren Sie auf das, was der Benutzer spürt, und benutzen Sie die Ursachen als Diagnose danach.
groups: - name: werkstatt rules: - alert: HighErrorRate expr: | sum(rate(http_requests_total{status=~"5.."}[5m])) / sum(rate(http_requests_total[5m])) > 0.05 for: 5m labels: severity: critical annotations: summary: "More than 5 percent server errors for 5 minutes" runbook: "docs/runbooks/fehlerrate.md"Drei Bestandteile machen den Unterschied. Die Schwelle trennt Rauschen von Störung. Das Zeitfenster for verhindert, dass ein einzelner Ausreißer das Telefon läuten lässt. Und die Handlungsanweisung sagt der Person um sieben Uhr früh, was sie zuerst ansehen soll.
Die Gefahr: Alarmmüdigkeit
Abschnitt betitelt „Die Gefahr: Alarmmüdigkeit“Ein Alarm, der täglich feuert und den man täglich wegklickt, ist schlechter als kein Alarm, weil er die anderen mit entwertet. Prüfen Sie deshalb jeden Alarm gegen zwei Fragen: Muss ein Mensch etwas tun? Und könnte er sofort etwas tun? Wenn eine der beiden Antworten nein lautet, gehört das auf ein Dashboard und nicht in eine Benachrichtigung.
Für den Schulkontext ist eine Bereitschaft um drei Uhr nachts weder verlangt noch sinnvoll. Realistisch sind zwei bis drei Alarme in einen Chatkanal oder per Mail, mit der Vereinbarung, wer werktags hineinsieht.
Tracing in Grundzügen
Abschnitt betitelt „Tracing in Grundzügen“Eine Spanne ist ein Zeitabschnitt mit Namen, etwa „Datenbankabfrage” oder „Aufruf Zahlungsdienst”. Ein Trace ist der Baum aller Spannen einer Anfrage. Weitergegeben wird der Zusammenhang über eine Kopfzeile, standardisiert als traceparent.
Der Nutzen entsteht dort, wo mehrere Beteiligte im Spiel sind: Eine Anfrage dauert 2,4 Sekunden, und der Trace zeigt, dass davon 2,1 Sekunden im fremden Dienst aus Kapitel 3 liegen. Ohne Trace diskutiert das Team eine Stunde über die Datenbank.
Für dieses Jahr genügt es, OpenTelemetry mit automatischer Instrumentierung einzuschalten und einen Trace zu lesen. Erhoben wird mit Stichproben, weil vollständiges Tracing bei viel Verkehr teuer wird.
Fehlersuche im Betrieb
Abschnitt betitelt „Fehlersuche im Betrieb“-
Symptom festhalten. Was genau, wo, seit wann, wie oft. „Geht nicht” ist kein Symptom.
-
Umfang bestimmen. Alle Benutzer oder einer? Alle Seiten oder eine? Das entscheidet, ob Sie in Metriken oder in Logs suchen.
-
Version prüfen. Wann war die letzte Auslieferung? Ein Fehler, der um 10:38 Uhr beginnt, und ein Release um 10:37 Uhr sind selten unabhängig.
-
Korrelations-ID besorgen. Aus der Fehlermeldung des Benutzers, aus dem Frontend-Fehlerbericht oder über die Suche nach Zeitraum und Benutzerkennung.
-
Log lesen, gefiltert auf diese Kennung. Damit haben Sie den vollständigen Weg der Anfrage.
-
Metrik dazunehmen. Ist es ein Einzelfall oder ein Muster? Was ist noch auffällig, Sättigung, Datenbank, fremder Dienst?
-
Trace ansehen, wenn die Zeit das Problem ist.
-
Ursache benennen und aufschreiben. Zwei Sätze in
docs/betrieb.md: Symptom, Ursache, Behebung. Diese Notizen sind später die Eingriffstabelle für die Übergabe.
Ergänzend gehören Fehler aus dem Frontend gemeldet, weil sie in Ihren Serverlogs gar nicht auftauchen. Ein Dienst wie Sentry oder eine gleichwertige Lösung nimmt sie entgegen; damit die Meldung lesbar ist, braucht er die Quellkarten des Builds, und damit sie zuordenbar ist, die Version aus Kapitel 9.
Üben lässt sich das nur an einer echten Störung. Bauen Sie eine ein: eine Route, die für einen bestimmten Parameter eine Ausnahme wirft, oder ein Zeitlimit, das zu knapp gesetzt ist. Ein anderes Teammitglied löst sie aus, ohne zu sagen was, und Sie finden sie mit Ihren eigenen Werkzeugen. Die Zeit bis zum Fund ist ein guter Gradmesser dafür, ob Ihr Monitoring taugt.
Häufige Stolperfallen
Abschnitt betitelt „Häufige Stolperfallen“console.logohne Kontext, und in Produktion sucht man nach Zeilen, die alle gleich aussehen.- Werte in den Text interpoliert statt in Felder gelegt.
- Personenbezogene Daten im Log, wo das Löschkonzept nicht hinreicht.
debugin Produktion. Platte voll, nichts gefunden.- Keine Korrelations-ID, und damit keine Möglichkeit, einen Einzelfall zu verfolgen.
- Kein Bezug zwischen Eintrag und Version.
- Metrik mit Benutzerkennung als Beschriftung. Das Monitoring erstickt.
- Dashboard mit dreißig Kacheln, das im Störfall nichts beantwortet.
- Alarme auf Ursachen statt auf Symptome.
- Alarme, die täglich feuern und deshalb ignoriert werden.
- Alarm ohne Handlungsanweisung.
- Monitoring nur auf demselben Server, der ausfallen kann.
- Keine Aufbewahrungsfrist für Logs.
Lernergebnisse
Abschnitt betitelt „Lernergebnisse“- Erklären: Logs, Metriken und Traces unterscheiden und angeben, welche Frage womit beantwortet wird.
- Anwenden: strukturiertes Logging mit Basisfeldern, Stufen und zentraler Filterung einrichten.
- Anwenden: eine Korrelations-ID über Frontend, Webservice, Hintergrundarbeit und fremde Dienste führen.
- Erklären: begründen, welche Daten aus Datenschutzgründen nicht protokolliert werden dürfen.
- Anwenden: die vier goldenen Signale und eigene fachliche Kennzahlen erheben.
- Beurteilen: Beschriftungen so wählen, dass die Kardinalität beherrschbar bleibt.
- Erschaffen: ein Dashboard bauen, das im Störfall in kurzer Zeit eine Aussage liefert.
- Beurteilen: entscheiden, welche Ereignisse alarmwürdig sind, und Alarme mit Schwelle, Zeitfenster und Anweisung versehen.
- Analysieren: eine Störung im Betrieb entlang eines festen Vorgehens eingrenzen und die Ursache benennen.
Arbeit am Jahresprojekt
Abschnitt betitelt „Arbeit am Jahresprojekt“- Strukturiertes Logging in allen Diensten, mit Version, Umgebung und zentraler Filterung sensibler Felder.
- Korrelations-ID über alle Schichten, sichtbar in der Fehlerantwort an den Client.
- Festgelegte Aufbewahrungsfrist für Logs, abgestimmt mit
docs/datenschutz.md. - Dashboard mit den vier goldenen Signalen, zwei fachlichen Kennzahlen und der laufenden Version.
- Mindestens zwei begründete Alarme mit Schwelle, Zeitfenster und Handlungsanweisung, dazu eine Prüfung von außen.
- Ein dokumentierter Fehlersuchvorgang an einer provozierten Störung: Symptom, Weg, Ursache, Zeitbedarf.
Passende Übungen
Abschnitt betitelt „Passende Übungen“- Aufgabe - Von console.log zum strukturierten Ereignis
- Aufgabe - Korrelations-ID durch alle Schichten
- Aufgabe - Dashboard für den Störfall
- Aufgabe - Provozierte Störung finden und dokumentieren