Erstellung von beobachtbaren Node.js-APIs: Protokollierung, Metriken und Tracing
Erfahren Sie, wie strukturiertes Logging, Anfragenverfolgung, Metriken sowie gezielte Benachrichtigungen zusammenwirken, um das Debuggen von Produktions-Node.js-APIs erheblich entlastender zu gestalten.
Quellen: When Your Node.js API Breaks: What I Learned About Logging and Observability
Wenn Sie zum ersten Mal mit dem Schreiben von Node.js-APIs beginnen, gilt Ihr Hauptaugenmerk der Überprüfung, ob der Code tatsächlich funktioniert.
Der Endpunkt gibt die erwartete Antwort zurück.
Die Datenbankabfrage läuft korrekt ab.
Die Authentifizierung verhält sich wie vorgesehen.
Frontend und Backend kommunizieren problemlos miteinander.
Zu diesem Zeitpunkt könnten Sie annehmen, die API sei fertiggestellt.
Aber irgendwann taucht eine andere Frage auf:
Was passiert, wenn etwas kaputtgeht und Sie gerade nicht auf den Code schauen?
Eine Anfrage fehlschlägt, sobald sie live ist.
Eine Datenbankabfrage dauert plötzlich zu lange.
Ein externer Dienst wird still.
Ein Benutzer teilt Ihnen mit, dass etwas nicht funktioniert.
Ein Endpunkt beginnt plötzlich 500-Fehler auszusenden.
Der Dienst selbst könnte dennoch weiterhin aktiv und in Betrieb sein.
Die eigentliche Herausforderung besteht darin herauszufinden, warum es sich falsch verhält.
Dies ist der Punkt, an dem Logging und Observability nicht mehr nur eine nachträgliche Maßnahme waren, sondern zu einem zentralen Bestandteil der API-Entwicklung wurden.
Eine API zu schreiben, die korrekt funktioniert, ist eine Herausforderung.
Eine API zu schreiben, die man tatsächlich diagnostizieren kann, wenn sie aufhört zu funktionieren, ist eine völlig andere Herausforderung.
Logging war das Erste, was ich neu überdenken musste
Wie die meisten Menschen, die das Programmieren lernen, drehen sich die frühen Logging-Gewohnheiten in der Regel um console.log().
Etwas in der Art:
console.log("Login request received");
console.log(user);
console.log("Database query completed");
Der Ansatz funktioniert gut für die lokale Entwicklung.
Er ist schnell.
Er ist unkompliziert.
Und wenn man auf dem eigenen Rechner debuggt, hat man bereits genügend Kontext, um die Ausgabe zu verstehen.
Sobald die Anwendung komplexer wird, gerät alles aus den Fugen.
Man muss mit Hunderten oder Tausenden gleichzeitiger Anfragen umgehen.
Viele verschiedene Benutzer.
Mehrere Instanzen Ihrer API laufen gleichzeitig.
Hintergrundaufgaben werden parallel ausgeführt.
Datenbankaufrufe finden ständig statt.
Aufrufe an Drittanbieterdienste.
Fehlerhafte Authentifizierungsversuche.
In diesem Maßstab verliert ein Haufen unstrukturierter Zeichenfolgen, die in eine Konsole ausgegeben werden, den größten Teil ihres Wertes.
Dies führte zu einer Veränderung in der Art und Weise, wie man über Logs denkt.
Ein Logeintrag sollte nicht einfach nur verkünden:
"Etwas ist passiert."
Sondern ein guter Logeintrag sollte es ermöglichen, herauszufinden, was tatsächlich geschehen ist, in welchem Moment, welcher Teil des Systems betroffen war sowie alle relevanten Details, die man benötigt, um das Problem richtig zu analysieren.
Ein nützliches Protokoll enthält Kontext
Betrachten Sie den Unterschied zwischen diesen beiden Ansätzen.
Nicht sehr nützlich
Something went wrong
Verglichen mit:
{
"level": "error",
"message": "Database query failed",
"method": "GET",
"route": "/api/projects",
"requestId": "abc123",
"durationMs": 842
}
Die zweite Version liefert Ihnen sofort weitaus nützlichere Informationen.
Sie können sofort erkennen:
- Was der tatsächliche Fehler war
- Welche Operation ihn ausgelöst hat
- Welche HTTP-Methode verwendet wurde
- Welcher Pfad betroffen war
- Um welche spezifische Anfrage es sich handelt
- Wie lange die Operation gedauert hat
Die konkreten Felder variieren je nach Ihrer Anwendung.
Wichtig ist die Verfügbarkeit von Kontext.
Gestrukturiertes Protokollieren hat meine Sicht auf Protokolle verändert
Ein Konzept, das sich als weitaus wertvoller herausstellte als erwartet, ist das gestrukturierte Protokollieren.
Anstatt ein Ereignis als einfacher, freier Text zu schreiben wie:
User login failed for some reason
Man kann es stattdessen als strukturierte Daten darstellen:
{
"level": "warn",
"event": "login_failed",
"requestId": "abc123",
"ip": "example",
"timestamp": "..."
}
Sobald die Protokolle strukturiert sind, wird das Suchen und Filtern davon erheblich einfacher.
Man kann nach folgenden Dingen abfragen:
level = error
oder:
event = login_failed
oder:
route = /api/auth/login
Dies bringt besonders große Vorteile, wenn die Protokolle in einer zentralisierten Protokollierungsplattform aggregiert werden.
Anstatt endlos durch Rohtexte zu scrollen, kann man präzise und gezielte Fragen stellen.
Protokollieren Sie nicht alles
Das ist eine weitere Falle, in die man leicht geraten kann.
Beim Debuggen scheint es selbstverständlich, mehr Daten als weniger haben zu wollen.
Deshalb protokolliert man am Ende alles, was einem einfällt.
Den Anfragekörper.
Den Antwortkörper.
Das vollständige Benutzerobjekt.
Datenbankergebnisse.
Anfragenzusätze.
Authentifizierungstoken.
Umgebungsdaten.
Interne Fehlerobjekte.
Bald schon hat man es mit einer riesigen Menge an protokollierten Daten zu tun.
Das führt zu zwei separaten Problemen.
Erstens wird es schwieriger, die wirklich wichtigen Informationen zu finden.
Zweitens – und das ist noch gravierender – besteht die Gefahr, versehentlich sensible Daten aufzunehmen.
Dieses zweite Problem verdient eigene Betrachtung.
Behandeln Sie Protokolle niemals als sicheren Ort für Geheimnisse
Dies ist zu einer der wichtigsten Sicherheitshinweise geworden, die man sich einprägen sollte.
Protokolle sind kein geeigneter Ort, um sensible Anmeldeinformationen zu speichern – nur weil es beim Debuggen praktisch erscheint.
Datenkategorien, bei denen in der Regel besondere Vorsicht geboten ist:
- Benutzernamen und Passwörter
- OAuth- oder Token zur Zugriffserlaubnis
- Token zum Erneuern einer Sitzung
- Schlüssel zur Authentifizierung gegenüber APIs
Als Beispiel: Das ist wirklich eine schlechte Idee:
console.log(req.headers.authorization);
Es mag Ihnen ein paar Minuten sparen, während Sie nach einem Fehler suchen.
Aber diese Daten können lange Zeit in Ihren Protokollen verbleiben, nachdem die Fehlersuche beendet ist.
Auch Ihre Protokollierungsinfrastruktur muss gesichert sein.
Falls jemand unbefugten Zugriff auf Ihr Protokollierungssystem erhält, könnte er Zugang zu Daten erhalten, die Sie ursprünglich überhaupt nicht offenzulegen wollten.
Anfragen-IDs sind äußerst nützlich geworden
Stellen Sie sich vor, ein Client sendet eine Anfrage wie folgt:
POST /api/orders
Diese Anfrage durchläuft mehrere Ebenen:
Client
↓
Load Balancer
↓
Node.js API
↓
Database
↓
Payment service
Irgendwann unterwegs fehlschlägt sie.
Stellen Sie sich nun vor, jede Protokollinze, die mit dieser Anfrage verbunden ist, trägt denselben Identifikator:
requestId = 7f91...
Man kann mit diesem Identifikator suchen und die vollständige Abfolge der Ereignisse rekonstruieren.
Zum Beispiel:
request started
↓
authentication successful
↓
database query started
↓
database query completed
↓
payment request started
↓
payment service timeout
↓
request failed
Ohne einen gemeinsamen Anfrageidentifikator können diese einzelnen Protokolleanträge schwer miteinander in Verbindung gebracht werden.
Mit einem solchen Identifikator bilden sie eine zusammenhängende Erzählung anstelle von verstreuten, unverbundenen Ereignissen.
Protokollierung und Debugging sind zwei verschiedene Aktivitäten
Mit der Zeit wurde der Unterschied zwischen diesen beiden deutlicher.
Beim Arbeiten lokal könnte man etwas sehr Spezifisches einfügen, nur um einen Wert zu überprüfen:
console.log(result);
Das ist im Moment hilfreich – für Sie, direkt dort.
Aber Protokollierung für die Produktion erfordert eine andere Art der Gestaltung.
Produktionsprotokolle sollten folgende Eigenschaften haben:
- Konsistent
Eine einfache Debugging-Ausgabe und eine echte Strategie zur Überwachbarkeit sind nicht dasselbe.
Jedes Protokollniveau erfüllt einen eigenen Zweck
Ein gängiges Muster besteht darin, Ebenen wie folgt zu definieren:
debug
info
warn
error
Die genauen Namen unterscheiden sich je nach Tool und Team.
Doch das zugrundeliegende Konzept bleibt bestehen.
Debug
Detaillierte Informationen, die hauptsächlich dann relevant sind, wenn man während der Entwicklung aktiv etwas untersucht oder behebt.
Info
Regelmäßige Ereignisse, die im normalen Betrieb auftreten.
Zum Beispiel:
Server started
Request completed
Background job completed
Warnung
Etwas Ungewöhnliches ist aufgetreten, doch die Anwendung läuft weiterhin einwandfrei.
Zum Beispiel:
External service response was slow
Fehler
Etwas ist kaputtgegangen und erfordert Aufmerksamkeit.
Zum Beispiel:
Database connection failed
Es geht eigentlich nicht darum, sich diese Kategorien einzuprägen.
Wichtig ist vielmehr, sie konsequent anzuwenden.
HTTP-Statuscodes haben ebenfalls einen Wert für die Überwachung
Zu Beginn schienen Statuscodes etwas zu sein, das ausschließlich dem Client zugutekommt.
Tatsächlich bieten sie auch einen nützlichen Einblick in das tatsächliche Verhalten Ihrer API.
Zum Beispiel:
200 → successful request
201 → resource created
400 → invalid request
401 → authentication required/failed
403 → forbidden
404 → resource not found
429 → rate limited
500 → internal server error
Stellen Sie sich vor, Ihre Dashboards zeigen plötzlich einen Anstieg in:
500 responses
Das ist ein Warnsignal, das es wert ist, untersucht zu werden.
Oder stellen Sie sich einen Sprung in:
429 responses
Ein solcher Anstieg kann auf ungewöhnliche Traffic-Muster, übermäßig aggressive Clients oder falsch konfigurierte Rate-Limits hindeuten.
Statuscodes werden zu aussagekräftigen Signalen, sobald Sie sie konsequent in Ihrer API verwenden.
Fehlermeldungen sollten Ihrem Team helfen, nicht Angreifern
Ein weiterer Ausgleich, den man lernen sollte, ist die Grenze zwischen internen Fehlerrichtlinien und dem, was dem Kunden angezeigt wird.
Nehmen wir an, eine Schreiboperation in der Datenbank fehlschlägt.
Intern könnte der rohe Fehler so aussehen:
unique constraint violation on users_email_key
Das ist tatsächlich hilfreich für das Debuggen.
Aber diesen rohen Datenbankfehler direkt an den Endnutzer weiterzugeben, ist in der Regel nicht angemessen.
Stattdessen könnte die API-Antwort so aussehen:
{
"success": false,
"message": "The email address is already registered."
}
Gleichzeitig bleiben die vollständigen technischen Details in den Server-Logs, wo sie hingehören.
Derartig geht es Ihrem Team um die benötigten Informationen, ohne dass interne Implementierungsdetails an diejenigen durchsickern, die die API aufrufen.
Stack-Traces müssen sorgfältig gehandhabt werden
Ein Stack-Trace kann bei einem Fehler äußerst nützlich sein, um herauszufinden, wo etwas schiefgelaufen ist.
Aber es kann auch interne Details darüber offenbaren, wie Ihr System aufgebaut ist.
Deshalb gibt es einen bedeutenden Unterschied darin, wie man dies handhabt:
Entwicklung
Ein vollständiger, detaillierter Fehlerausdruck ist hier in der Regel wirklich nützlich.
Produktion
Kunden sollten in der Regel eine kontrollierte, generische Fehlerantwort erhalten, während die vollständigen diagnostischen Details in sicheren Protokollen verbleiben.
Eine Produktions-API sollte auf keinen Fall ihren gesamten internen Stack-Trace zurückgeben, nur weil eine Anfrage zufällig fehlgeschlagen ist.
Metriken treten anschließend in den Vordergrund
Protokolle beantworten eine Frage wie:
Was ist gerade passiert?
Metriken beantworten eine andere Art von Frage:
Wie oft tritt das auf?
Beispiele sind:
Requests per second
Error rate
Response latency
Database connection count
CPU usage
Memory usage
Stellen Sie sich eine API vor, die in der Regel wie folgt antwortet:
150 ms
und dann steigt die durchschnittliche Latenz auf:
1200 ms
Auch wenn jede Anfrage weiterhin mit 200 zurückkommt, hat sich offensichtlich etwas geändert.
Diese Veränderung ist wichtig.
Die API ist nicht unbedingt kaputt.
Aber sie verhält sich auch nicht mehr wie gewohnt.
Metriken ermöglichen es, solche Veränderungen zu erkennen.
Ausnahmen allein können irreführend sein
Das erwies sich als eine weitere wichtige Lektion.
Nehmen wir an, Ihre API verarbeitet 10.000 Anfragen.
Die meisten davon werden in:
100–200 ms
Aber nur wenige benötigen:
5–10 seconds
Ein Durchschnitt aller Anfragen kann diese Ausreißergruppe leicht verbergen.
Deshalb wird die Latenz oft stattdessen mit Perzentilen gemessen.
Zum Beispiel:
p50
p95
p99
Im Großen und Ganzen:
p50 – wie eine typische Anfrage aussieht.
p95 – was der langsamere Teil der Anfragen erlebt.
p99 – wie der schlimmste Fall im Datenverkehr aussieht.
Ihr müsst nicht immer alle diese Werte verfolgen.
Aber sich ausschließlich auf den Durchschnitt zu verlassen, kann echte Probleme verschleiern.
Logs beschreiben Ereignisse; Metriken zeigen Trends
Diese Unterscheidung erwies sich in der Praxis als wirklich nützlich.
Stellt euch Folgendes vor:
Metric:
API latency increased
Das zeigt Ihnen, etwas hat sich geändert.
Dann prüfen Sie die Logs und finden:
Database query took 4.8 seconds
Nun haben Sie eine plausible Ursache.
Die beiden Datengrundlagen ergänzen sich gegenseitig.
Ihr könnt die Beziehung so darstellen:
Metrics
↓
Something looks wrong
↓
Logs
↓
What happened?
↓
Tracing
↓
Where did it happen?
Das führt natürlich zum nächsten Aspekt der Beobachtbarkeit.
Eine Anfrage von Anfang bis Ende mit Trace-Informationen verfolgen
Mit einem Trace können Sie eine einzelne Anfrage wie eine Geschichte behandeln und ihren Verlauf durch die verschiedenen Komponenten Ihres Systems nachverfolgen.
Zum Beispiel:
HTTP Request
│
├── Authentication
│
├── Controller
│
├── Service
│
├── Database query
│
└── External API
Jeder Schritt auf dem Weg kann eigene Zeitdaten enthalten.
Möglicherweise stellen Sie fest, dass die API-Schicht selbst nur Folgendes benötigt:
200 ms
während ein Aufruf an einen externen Dienst folgende Ressourcen verbraucht:
4 seconds
Ohne Trace-Informationen sehen Sie in der Regel nur eine vage Beschwerde wie:
API is slow
Sobald Trace-Informationen vorhanden sind, können Sie eine viel präzisere Frage stellen:
Welcher Teil dieser Anfrage verursacht tatsächlich die Verlangsamung?
Das ist eine weitaus produktivere Frage für die Fehlersuche.
Logs, Metriken und Trace-Informationen beantworten jeweils unterschiedliche Fragen
Es hilft, diese drei Signalarten auf diese Weise zu betrachten:
SignalCore questionLogs: Was ist passiert? Metrics: Wie häufig oder in welchem Umfang? Traces: Wohin ist die Zeit während der Anfrage tatsächlich verschwunden?
Es gibt einige Überschneidungen zwischen ihnen, aber keines der drei kann das andere vollständig ersetzen.
Ein gut konstruiertes System stützt sich in der Regel auf alle drei zusammen.
Das Kaufen von Tools ist nicht dasselbe wie die Erreichung von Observabilität
Das könnte die wichtigste Erkenntnis aus dem gesamten Prozess sein.
Es ist verlockend anzunehmen:
„Sobald ich ein Tool für die Observabilität installiere, wird meine Anwendung beobachtbar.“
So funktioniert es jedoch nicht ganz.
Tools sind gut darin, Daten zu sammeln.
Aber jemand muss trotzdem Fragen beantworten wie:
- Was muss eigentlich gemessen werden?
- Was lohnt es sich, zu protokollieren?
- Welche Ereignisse sind bedeutsam?
- Was zählt als Fehler?
Die Einrichtung der Tools ist selten der schwierige Teil.
Wichtig ist vielmehr, herauszufinden, welche Informationen tatsächlich von Bedeutung sind.
Welche Rolle spielt OpenTelemetry?
Im Zuge der näheren Untersuchung von Observability tauchte OpenTelemetry immer wieder als ein Konzept auf, das es wert ist, verstanden zu werden.
Sein Wert liegt darin, dass es standardisiert, wie Entwickler mit Telemetriedaten arbeiten, und dabei Aspekte wie folgende abdeckt:
- Spuren
- Metriken
- Protokolle
Anstatt den Ansatz zur Observability an einen einzigen Überwachungsdienst zu binden, bietet OpenTelemetry eine herstellersunabhängige Methode zur Erstellung und Sammlung solcher Telemetriedaten.
Ein kleines Node.js-Projekt benötigt vermutlich von Anfang an keinen vollständigen Observability-Stack.
Trotzdem macht es die frühzeitige Verständnis dieser Konzepte viel einfacher, später etwas zu entwickeln, das sich zu einer ausgereifteren Struktur skalieren lässt.
Telemetrie erfordert dieselbe Datenschutzvorsicht wie Logs
Es gibt hier einen Sicherheitsaspekt, den es zu wiederholen lohnt.
Die Regel, die für das Logging gilt, gilt genauso für Telemetrie:
Das Sammeln mehrerer Daten ist an sich keine Verbesserung.
Man muss sorgfältig entscheiden, was letztendlich aufgenommen wird:
- Logs
- Spuren
- Metriken
- Fehlerverfolgungstools
- Überwachungs-Dashboards
Das Erfassen des gesamten HTTP-Anfragekörpers in einer Spur mag beim Debuggen praktisch erscheinen.
Aber bedenken Sie, was passiert, wenn dieser Payload Folgendes enthält:
password
token
personal information
payment information
Auf einmal ist Ihr Observability-Pipeline zu einem weiteren Speicherort für sensible Daten geworden.
Sicherheitsbedenken beschränken sich nicht auf die Logs – sie betreffen die Observability insgesamt.
Die Ausführlichkeit sollte je nach Umgebung unterschiedlich sein
Beim lokalen Entwickeln möchte man in der Regel sofort so viele Informationen wie möglich erhalten.
Das bedeutet oft, detailliertere Logging-Funktionen zu aktivieren.
In der Produktion rücken folgende Aspekte in den Vordergrund:
- Eine hohe Signal-Rausch-Verhältnis
- Leistungsbelastung
- Sicherheitsrisiken
- Die Suchfähigkeit der Daten
- Speicherrichtlinien
- Verhaltensweisen bei Benachrichtigungen
Eine Logging-Einrichtung, die im Entwicklungsmodus gut funktioniert, ist nicht unbedingt für die Produktion geeignet.
Deshalb ist es wichtig, das Logging je nach Umgebung zu konfigurieren.
Das Verhalten der Datenbank verdient eigene Sichtbarkeit
In den meisten Backend-Systemen gehört die Datenbank in der Regel zu den ersten Stellen, die überprüft werden sollten, wenn eine API langsamer wird.
Stellen Sie sich vor, dieser Endpunkt beginnt plötzlich, mehrere Sekunden zum Antworten zu benötigen:
GET /api/projects
Die Anwendungslogik mag völlig intakt erscheinen.
Doch die zugrunde liegende Abfrage könnte folgende Probleme aufweisen:
- Fehlender benötigter Index
- Auslesen von viel zu vielen Zeilen
- Durchführung unnötiger Join-Operationen
- Mehrfache Ausführungen statt der erwarteten Anzahl
- Blockade durch Wartezeit auf eine andere Operation
- Nutzung eines schlechten Ausführungsplans
Das hängt direkt mit den zuvor genannten Punkten zur guten Gestaltung von PostgreSQL-Schemata zusammen.
Eine solide Datenbankarchitektur und eine gute Überwachbarkeit stärken sich gegenseitig.
Eines allein reicht nicht aus.
Sichtbarkeit macht N+1-Probleme offensichtlich
Nehmen wir ein solches Szenario:
Get 100 projects
↓
Get owner for project 1
Get owner for project 2
Get owner for project 3
...
Get owner for project 100
Funktional gesehen könnte die API einwandfrei funktionieren.
Aber die Gesamtzahl der Anfragen an die Datenbank kann erheblich über das hinaus ansteigen, was man erwarten würde.
Ohne eine Möglichkeit, die Aktivitäten in der Datenbank zu überwachen, kann dieses Problem unbemerkt bleiben, bis der Verkehr so groß wird, dass es sichtbar wird.
Überwachbarkeit löst das N+1-Muster allein nicht.
Doch sie macht es möglich, überhaupt erst zu erkennen, dass dieses Muster existiert.
Warnungen müssen zu konkreten Maßnahmen anregen
Die Erstellung von Warnungen ist ein weiterer Bereich, den man überdenken sollte.
Sobald bereits jede Kleinigkeit eine Warnung auslöst, neigen die Menschen dazu, sie zu ignorieren.
Eine sinnvolle Warnung sollte es ermöglichen, folgende Frage zu beantworten:
Braucht das jetzt meine Aufmerksamkeit?
Etwas in der Art:
Error rate has increased significantly
Es lohnt sich vermutlich, dies zu markieren.
Aber:
One request took 1.2 seconds
Es ist fast sicher nicht notwendig, deswegen jemanden zu stören.
Die genauen Schwellenwerte variieren je nach System.
Die zugrundeliegende Idee bleibt gleich:
Eine Warnung sollte jemanden zur Handlung anregen, nicht nur Lärm verursachen.
Ein wiederholbarer Prozess macht das Debuggen in der Produktion deutlich weniger stressig
Wenn etwas kaputtgeht, führt das Zufällige Überprüfen selten schnell zu einer Lösung. Es hilft eher, sich an einer konsistenten Abfolge zu orientieren.
1. Überprüfen, ob das Problem tatsächlich besteht
Beginnen Sie damit, zu bestätigen, dass tatsächlich etwas schiefgelaufen ist.
Errors increased?
Latency increased?
Specific endpoint affected?
2. Ermitteln, wie weit verbreitet das Problem ist
Ausfindig machen, ob sich das Problem auf Folgendes beschränkt:
one user?
one endpoint?
all users?
one API instance?
database?
external service?
3. Prüfen, was kürzlich geändert wurde
Überprüfen, ob es eine folgende Änderung gab:
deployment?
configuration change?
database migration?
dependency update?
4. Eintauchen in die Protokolle
Suchen Sie nach:
errors
timeouts
failed dependencies
database failures
unexpected status codes
5. Überprüfen der Metriken
Betrachten Sie:
latency
error rate
traffic
CPU
memory
database metrics
6. Die Anfrage nach Möglichkeit verfolgen
Ermitteln Sie genau, wo Zeit verschwendet wurde oder wo das Problem seinen Ursprung hat.
7. Die eigentliche Ursache beseitigen
Gegen den Drang ankämpfen, nur die Symptome zu überdecken.
Ein solcher strukturierter Ansatz ist weitaus zuverlässiger als das Neustarten des Servers in der Hoffnung, dass das Problem von selbst verschwindet.
Das Neustarten kann das eigentliche Problem verbergen
Es lohnt sich, darauf hinzuweisen, denn der Reiz ist groß.
Stellen Sie sich eine Anwendung vor, die extrem langsam arbeitet. Sie starten sie neu – plötzlich scheint alles wieder in Ordnung zu sein. Es wirkt, als wäre das Problem gelöst.
Doch die eigentliche Ursache könnte gewesen sein:
memory leak
Oder erschöpft:
database connections
Oder eine ressourcenintensive Anwendung:
background job
Durch das Neustarten wurden nur die sichtbaren Symptome beseitigt – es wurde nicht unbedingt das eigentliche Problem behoben.
Eine solide Überwachungsfähigkeit ermöglicht es, herauszufinden, warum das Neustarten ursprünglich zu funktionieren schien.
Häufige Fehlerquellen bei Logging und Überwachung
Hier sind Fehler, die man aktiv vermeiden sollte.
1. Einfügen willkürlicher Zeichenketten in die Logs
Dadurch entsteht lediglich Lärm statt nützlicher Informationen.
2. Aufzeichnen sensibler Daten in den Logs
Passwörter, Token, Geheimnisse und personenbezogene Daten erfordern sorgfältige Handhabung, nicht eine nachlässige Behandlung.
3>Übergehen der Abfragekorrelation
Ohne Abfragenummern wird es viel schwieriger, eine einzelne Anfrage über mehrere Schritte hinweg nachzuvollziehen.
4>Nur den Betriebsstatus überwachen
Eine API kann technisch gesehen „aktiv“ sein, obwohl sie äußerst langsam oder teilweise defekt ist.
5. Allein auf die durchschnittliche Latenz verlassen
Basierte Aufschlüsselungen nach Percentilen können Probleme aufdecken, die durch einen einfachen Durchschnitt versteckt bleiben.
6. Die Leistung der Datenbank übersehen
Es ist möglich, eine schnelle Anwendungslogik über einer langsamen Datenbank zu nutzen.
7> Zu viele Warnungen einrichten
Kontinuierliches Warnlärm führt zu echter Erschöpfung und dazu, dass die Nutzer ignorieren.
8. Observabilität erst später hinzufügen
Das Hinzufügen von Überwachungsfunktionen erst nach einem Incident erschwert die spätere Fehlerbehebung.
9. Interne Fehlerrichtlinien an Kunden weitergeben
Detaillierte Diagnoseinformationen gehören in Systeme, die Sie auf der Serverseite kontrollieren, nicht in an Kunden gerichtete Antworten.
10. Werkzeuge ohne klaren Zweck hinzufügen
Ein Dashboard voller Metriken bedeutet nicht automatisch Nutzen.
Eine Checkliste zur Beurteilung der Observabilität
Bevor Sie eine API als ausreichend überwachbar bezeichnen, sollten Sie sich diese Fragen stellen.
Protokollierung
- Werden die wichtigen Anwendungsereignisse protokolliert?
- Ist die Protokollierung strukturiert?
- Wird einheitlich auf verschiedene Protokollierungsstufen zurückgegriffen?
- Gibt es genügend Kontextinformationen, um Fehler zu untersuchen?
- Sind Anfragen mit IDs versehen?
- Wurden sensible Werte entfernt oder ganz ausgeschlossen?
Fehler
- Werden unerwartete Fehler erfasst?
- Sehen die Clients nur sichere, allgemeine Fehlermeldungen?
- Sind detaillierte Diagnoseinformationen intern weiterhin verfügbar?
- Können Fehler auf bestimmte Anfragen zurückgeführt werden?
Metriken
- Kennen Sie das Volumen Ihrer Anfragen?
- Kennen Sie Ihre Fehlerraten?
- Kennen Sie die Latenzzeit Ihrer Antworten?
- Können Sie abnormale Verkehrsmuster erkennen?
Datenbank
- Können Sie langsame Abfragen erkennen?
- Können Sie verbindungsbezogene Probleme sehen?
- Können Sie Endpunkte untersuchen, die stark auf die Datenbank zugreifen?
- Sind Datenbankfehler für Sie sichtbar?
Aufzeichnung
- Können Sie wichtige Anfragen durch das System verfolgen?
- Können Sie langsame Operationen erkennen?
- Können Sie Aufrufe an externe Dienste sehen?
- Können Sie Aktivitäten zwischen verschiedenen Diensten verknüpfen?
Alarmierung
- Lösen wichtige Fehler tatsächlich Alarme aus?
- Sind die Schwellenwerte sinnvoll?
- Sind die Alarmsignale handlungsorientiert?
- Wird der Alarmlärm in Schach gehalten?
Die zentrale Lektion aus all dem
Der größte Perspektivwechsel bestand darin zu erkennen: Eine Anwendung, die korrekt läuft, ist noch lange nicht automatisch eine, mit der man zuverlässig arbeiten kann.
Beim lokalen Entwickeln kann man einfach ein Terminal öffnen und in Echtzeit beobachten, was vor sich geht. In der Produktion funktioniert das nicht so.
Man hat es mit mehreren Benutzern, mehreren Servern, verschiedenen Diensten, Hintergrundaufgaben, Datenbanken, externen APIs sowie Fehlern zu tun, die auf dem eigenen Rechner nie sichtbar werden.
In diesem Umfang wird Sichtbarkeit unerlässlich. Man muss in der Lage sein, Fragen zu beantworten wie: Was ist passiert? Wann ist es passiert? Wer wurde betroffen? Welcher Anfrage ging das voraus? Welcher Teil des Systems ist ausgefallen? Wie lange hat es gedauert? Handelt es sich um ein isoliertes Ereignis oder um ein wiederkehrendes Muster? Und hat sich kurz vor dem Ausbruch etwas geändert?
Deshalb sind Logging und Observability von großer Bedeutung. Es handelt sich dabei nicht nur um Zusatzfunktionen zum Monitoring – sie sind ein Bestandteil der Fähigkeit eines Systems, sich selbst zu erklären.
Was kommt als Nächstes?
Höre ich mich länger mit Backend-Systemen auseinander, wird immer klarer, dass die Produktionsbereitschaft in Wirklichkeit eine Kette miteinander verbundener Entscheidungen ist. Die Authentifizierung schützt davor, dass Unbefugte Zugang erhalten. Die Autorisierung bestimmt, was diese Personen tun dürfen. Das Rate Limiting hält den Datenverkehr unter Kontrolle. Die Datenbankgestaltung schützt die Integrität der Daten. Logging und Observability ermöglichen es Ihnen, zu verstehen, was das System zu jedem beliebigen Zeitpunkt tatsächlich tut.
Der natürliche nächste Schritt ist es, sich genauer mit der Bereitstellungsebene derselben Kette auseinanderzusetzen – Container, Umgebungskonfigurationen, CI/CD-Pipelines, Health Checks, sanfte Herunterfahrensprozesse, Bereitstellungsstrategien sowie alles, was tatsächlich ändert, wenn eine Node.js-Anwendung von einem lokalen Rechner in die Produktion übergeht. Dies ist ein Bereich, den man am besten durch praktische Umsetzung und nicht nur durch Theorie erlernen sollte.
Eine abschließende Gedanke
Es ist leicht anzunehmen, dass das Debuggen hauptsächlich darin besteht, die fehlerhafte Codezeile ausfindig zu machen. In der Praxis geht es jedoch oft mehr darum, über genügend Kontext zu verfügen, um das System um diese Zeile herum zu verstehen.
Gutes Programmieren bleibt weiterhin wichtig. Doch wenn schließlich etwas schiefgeht – und letztendlich wird das immer der Fall sein – ist es genauso wichtig, erkennen zu können, was tatsächlich passiert ist.
Bauen Sie das System auf. Sichern Sie es. Machen Sie es beobachtbar. Konzentrieren Sie sich anschließend darauf, seine Bedienung zu erleichtern.