Xircuit Blog

Produkt-Updates, Entwickler-Logs und Neuigkeiten aus der Entwicklung von Xircuit.

Ein Logging-Standard, der nie PII verrät cover image

Ein Logging-Standard, der nie PII verrät


Ein Logging-Standard, der nie PII verrät

Code und Logzeilen auf einem dunklen Monitor Foto: Negative Space · StockSnap (CC0 1.0), via Openverse

Es gibt einen Moment in jedem Projekt, in dem man um drei Uhr nachts in einer Log-Datei sitzt und versucht zu rekonstruieren, warum ein bestimmter Nutzer sich nicht anmelden konnte. In diesem Moment merkt man sehr schnell, ob die eigenen Logs etwas taugen. Entweder man findet in Sekunden die relevante Zeile, oder man scrollt durch Zehntausende Einträge aus Freitext, halb formatierten Ausnahmen und Zeilen, die niemand mehr zuordnen kann.

Xircuit verarbeitet Gesundheitsdaten. Das verschärft das Problem in beide Richtungen: Ich brauche Logs, die aussagekräftig genug sind, um echte Probleme zu diagnostizieren, und gleichzeitig darf in diesen Logs unter keinen Umständen etwas landen, das eine Person identifizierbar oder ihre Gesundheit ablesbar macht. Diese Woche geht es darum, wie wir diese beiden Anforderungen in einen einzigen, verbindlichen Logging-Standard gegossen haben.

Warum Freitext-Logs nicht skalieren

Am Anfang loggt jeder so, wie es ihm gerade sinnvoll erscheint. Der eine schreibt "User logged in", der nächste "login ok for account", ein dritter packt eine ganze Stack-Trace in eine Info-Meldung. Solange ein einzelner Dienst läuft, funktioniert das. Sobald man aber mehrere Dienste, Worker und eine App auf verschiedenen Plattformen betreibt, wird die Suche zum Ratespiel. Man weiß nie genau, nach welcher Zeichenkette man eigentlich filtern soll.

Das eigentliche Problem ist nicht die Menge der Logs, sondern ihre fehlende Struktur. Ein Log-Eintrag, den man nur über Volltextsuche findet, ist im Zweifel gar nicht auffindbar. Und in einem verteilten System, das über Aspire orchestriert wird und dessen Telemetrie in einer zentralen Observability-Pipeline zusammenläuft, ist genau diese Auffindbarkeit der entscheidende Faktor. Wir wollten deshalb keine besseren Log-Texte, sondern eine Konvention, die jede einzelne Zeile ohne Nachdenken zuordenbar macht.

Ein Tag-Schema: Domain.Process.Layer

Der Kern des Standards ist ein strukturiertes Tag, das jede Log-Zeile mit sich trägt. Es folgt dem Schema <Domain>.<Process>.<Layer>. Die Domäne beschreibt den fachlichen Bereich, der Prozess den konkreten Ablauf, die Schicht die technische Ebene, auf der der Eintrag entsteht. Ein Tag wie Auth.Login.Service sagt sofort, worum es geht, ohne dass ich auch nur ein Wort Freitext lesen muss.

Der Gewinn liegt darin, dass die Zuordnung nicht mehr aus dem Nachrichtentext erraten werden muss. Statt nach vagen Formulierungen zu suchen, filtere ich nach einem stabilen, vorhersagbaren Präfix. Alle Zeilen einer Domäne, alle Zeilen eines Prozesses quer über mehrere Dienste, alle Zeilen einer bestimmten Schicht — jede dieser Ansichten ist nur ein Filter entfernt. In einer strukturierten Logging-Welt wie der von .NET fügt sich das nahtlos ein: Das Tag ist ein eigenes, indizierbares Feld, kein interpoliertes Stück Text.

Diese Disziplin klingt kleinlich, aber sie verändert das Debugging grundlegend. Man hört auf, Logs zu lesen, und fängt an, sie abzufragen.

Das harte Prinzip: niemals PII loggen

Die zweite Säule ist eine Regel, bei der es kein Ermessen gibt: In Xircuit-Logs landet niemals personenbezogene oder gesundheitsbezogene Information. Keine E-Mail-Adressen, keine Namen, keine Messwerte, keine Gesundheitsdaten irgendeiner Art. Diese Regel ist bewusst absolut formuliert, weil jede Ausnahme sofort zur Grauzone wird und Grauzonen bei Gesundheitsdaten inakzeptabel sind.

Konkret bedeutet das, dass wir statt der eigentlichen Werte durchgängig mit undurchsichtigen Kennungen arbeiten. Ich logge nicht, dass eine bestimmte Person einen bestimmten Blutzuckerwert erfasst hat, sondern dass ein Vorgang für eine anonyme Kennung stattgefunden hat. Für die Diagnose reicht das fast immer aus, denn was ich zur Fehlersuche brauche, ist der Ablauf und sein Ausgang, nicht der Inhalt der Daten. Der Wert selbst gehört in die Datenbank, hinter Zugriffskontrolle und Verschlüsselung, und niemals in eine Log-Zeile, die potenziell durch mehrere Systeme wandert.

Eine Checkliste als Sinnbild für den Audit-Trail Foto: Rawpixel (CC0 1.0), via Openverse

AuthLog.Forbid: Verweigerungen nachvollziehbar machen

Ein Bereich, in dem sich die Anforderung nach Aussagekraft und die Anforderung nach Datensparsamkeit besonders reiben, ist die Autorisierung. Wenn ein Zugriff verweigert wird, will ich später verstehen können, warum — aber gerade an dieser Stelle ist die Versuchung groß, genau die Identität und den Kontext zu loggen, die man auf keinen Fall in ein Log schreiben sollte.

Dafür gibt es in Xircuit einen dedizierten Helfer: AuthLog.Forbid. Er ist der zentrale Punkt, über den jede Autorisierungs-Verweigerung protokolliert wird. Er hält fest, welche Regel gegriffen hat und in welchem Kontext die Entscheidung fiel, aber er tut das grundsätzlich ohne PII. Weil alle Verweigerungen durch denselben Helfer laufen, sind sie einheitlich formatiert, einheitlich getaggt und lassen sich als Gruppe auswerten. Wenn plötzlich eine Reihe von Zugriffen an derselben Stelle scheitert, sehe ich das Muster, ohne eine einzige Zeile lesen zu müssen, die einen Rückschluss auf eine reale Person zuließe.

Der eigentliche Wert liegt darin, dass eine Verweigerung nicht mehr stumm ist. Ein „Zugriff verweigert" ohne nachvollziehbaren Grund ist im Betrieb genauso frustrierend wie ein stiller Fehler. AuthLog.Forbid verwandelt jede Ablehnung in einen erklärbaren, aber datenschutzkonformen Eintrag.

Boundary-Logging an den Auth-Grenzen

Anmeldeprobleme sind die undankbarste Art von Fehler, weil sie den Nutzer genau an der Tür treffen und weil so viele bewegliche Teile beteiligt sind. Deshalb behandeln wir die Grenzen des Authentifizierungsflusses gesondert. Login, Token-Refresh und Konto-Wiederherstellung haben jeweils ein eigenes Boundary-Logging, das den Übergang über diese Grenzen diagnostizierbar macht.

Der Gedanke dahinter ist, dass an einer Grenze immer die Frage steht: Ist die Anfrage sauber hereingekommen, und ist eine saubere Antwort herausgegangen? Wenn beide Enden protokolliert sind, lässt sich ein fehlgeschlagener Login präzise verorten — bricht er beim Eintritt ab, irgendwo in der Mitte, oder erst beim Ausstellen des neuen Tokens. Gerade beim Zusammenspiel mit einem externen Identity-Provider ist dieses Verorten Gold wert, denn ohne saubere Grenzen verbringt man Stunden damit, herauszufinden, auf welcher Seite der Grenze das Problem überhaupt liegt. Und auch hier gilt kompromisslos, dass die eigentlichen Anmeldedaten nichts in den Logs zu suchen haben; protokolliert wird der Verlauf, nicht der Inhalt.

Ein Standard, den jeder neue Dienst von Anfang an erbt

Das Beste an alldem wäre wertlos, wenn es nur in meinem Kopf existierte. Deshalb ist der Logging-Standard als Konvention dokumentiert und nicht als lose Sammlung von Gewohnheiten. Wer einen neuen Dienst aufsetzt, hält sich vom ersten Commit an daran, statt später mühsam nachzurüsten.

Diese Entscheidung ist bewusst getroffen. Logging-Disziplin lässt sich nicht nachträglich in ein gewachsenes System einziehen, ohne dass es schmerzt — die Tags fehlen an tausend Stellen, die PII-Lecks sind längst passiert, und niemand traut sich mehr, alte Zeilen anzufassen. Indem der Standard von Beginn an gilt, wird das richtige Verhalten zum Weg des geringsten Widerstands. Neue Dienste erben die Konvention, statt sie neu zu erfinden, und die gesamte Observability-Pipeline bleibt konsistent, egal wie viele Bausteine noch dazukommen.

Was kommt als Nächstes?

Nächste Woche: datenbankgestützte Feature-Flags, mit denen wir Funktionen zur Laufzeit schalten und Worker in Sekunden pausieren.


Kommentare

Noch keine Kommentare. Teilen Sie als Erste(r) Ihre Gedanken!

Kommentar hinterlassen