Startseite / Artikel

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.

Verlag: REACTAPP.TOP17 Min. Lesezeit4023 Wörter

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
  • Geheimnisse im Zusammenhang mit der Sitzungskontrolle
  • Anmeldedaten, die zur Verbindung mit einer Datenbank verwendet werden
  • Cookies, die zur Authentifizierung einer Sitzung dienen
  • Jegliche personenbezogenen oder privaten Benutzerdaten
  • 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
  • Aufrufbar durch Suchfunktion
  • Gestrukturiert
  • Nützlich
  • Sicher
  • In angemessener Menge
  • 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.

    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?
  • Welche Bedingungen rechtfertigen eine Benachrichtigung?
  • Welche Felder sind sensibel?
  • Wie lange sollten diese Daten aufbewahrt werden?
  • Wie verknüpft man zusammenhängende Anfragen miteinander?
  • 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?
  • Können Sie die wichtigen Abhängigkeiten überwachen?
  • 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.