1. Structured-Logging-Formate
Wenn man zum ersten Mal einen JSON-Log sieht, scheint es einfach: "Na ja, JSON und JSON, was macht das für einen Unterschied?" Der Unterschied zeigt sich genau in dem Moment, wenn man versteht, dass JSON nur ein Container ist, und die Bedeutung durch Feldnamen und deren Struktur bestimmt wird. Der eine erwartet das Feld @timestamp, der andere timestamp, der dritte versteckt das Level nicht in level, sondern in log.level. Letztlich ist das Format ein "Dialekt" desselben Log-Ereignisses, und ohne Kenntnis des Dialekts liest man Logs wie ein Tourist, der nur das Wort "Hallo" kennt.
Spring Boot 4 bietet drei eingebaute Dialekte für strukturiertes Logging: ECS, GELF und Logstash JSON. Ziel dieser Vorlesung ist nicht, dass Sie sie "auswendig wie das Einmaleins" lernen (ich will auch nicht so leben), sondern dass Sie lernen, das Format schnell zu erkennen und darin die wichtigsten Bedeutungsteile zu finden: Zeit, Level, Quelle, Nachricht.
Bedeutungskarte in JSON-Logs
Bevor wir ECS/GELF/Logstash im Detail besprechen, ist es sinnvoll, eine grundlegende Lesekompetenz zu vereinbaren: Was auch immer im JSON steht, Sie suchen im Wesentlichen immer denselben Satz an Bedeutungen. Es ist wie beim Lesen eines Kassenzettels im Geschäft: Es ist Ihnen egal, wo genau das Wort "Summe" steht; Hauptsache, Sie finden den Betrag, nicht dass Sie die Schriftarten genießen.
Unten sehen Sie eine "Bedeutungskarte" und wie sie in verschiedenen Formaten typischerweise aussehen. Betrachten Sie dies nicht als strikte Spezifikation "immer so", sondern als Kompass: Wo liegt das Gesuchte meistens?
| Bedeutung (was verstehen?) | ECS (häufig) | GELF (häufig) | Logstash JSON (häufig) |
|---|---|---|---|
| Wann passiert | @timestamp | timestamp (Zahl, Epochensekunden) | @timestamp |
| Level | log.level | level (Zahl) und manchmal |
level |
| Wer hat gemeldet (Logger) | log.logger | normalerweise im zusätzlichen Feld |
logger_name |
| Nachrichtentext | message | short_message | message |
| Welcher Dienst | service.name | manchmal oder ähnlich |
service/ (abhängig von der Implementierung) |
| Prozess-PID | process.pid | kann in sein |
process_id/ (abhängig von der Implementierung) |
| Thread | process.thread.name oder |
kann in sein |
thread_name |
| Ausnahme | error.* / (variabel) |
full_message (oft wird Stacktrace abgelegt) | stack_trace/ (variabel) |
Und hier ein praktischer Life-Hack, der den Schmerz stark lindert: Lesen Sie einen strukturierten Log nicht "von oben nach unten", sondern als Objekt. Wenn Sie in der IDE/Konsole markieren und suchen können, suchen Sie einfach nach den Schlüsseln message, level, @timestamp, short_message, log.level. Das ist normal. Das ist kein "Cheaten". Das ist das Erwachsenenleben.
2. Format ECS: Wo suche ich das Wichtigste
ECS (Elastic Common Schema) wird oft als "etwas für Leute, die im Elasticsearch leben" wahrgenommen, aber für den Entwickler ist es wegen seiner Disziplin interessant: Die Felder sind nicht zufällig, sondern in logischen "Boxen" angeordnet. Daher kann ECS beim ersten Kennenlernen "zu verschachtelt" erscheinen, dafür gewöhnt sich das Gehirn später daran, und das Lesen wird einfacher. Es ist wie anfangs ordentliche Ordner auf der Festplatte zu hassen, um sie dann plötzlich zu lieben, wenn man die Datei in 3 Sekunden findet.
Ein typischer ECS-Log sieht etwa so aus (Beispiel gekürzt, um nicht zu "Krieg und Frieden" zu werden, aber die Form ist aussagekräftig):
{
"@timestamp": "2026-03-18T10:15:00Z",
"message": "Catalog started",
"log": {
"level": "INFO",
"logger": "com.example.catalogservice.CatalogServiceApplication"
},
"service": {
"name": "catalog-service"
}
}
Hier gibt es einige Dinge, die Sie beachten sollten, auch wenn Sie noch nicht "schematisch denken" können.
Erstens: @timestamp ist fast immer in UTC und endet mit Z. Das ist nicht der Versuch, Ihnen das Leben schwer zu machen, sondern der Versuch, Logs zwischen Maschinen und Umgebungen vergleichbar zu machen. Wenn Logs an einer Stelle in "lokaler Zeit" und an einer anderen in UTC vorliegen, suchen Sie nach Fehlern in der Zeit und nicht im Code. Und Bugs in der Zeit sind eine eigene Art von Leid.
Zweitens: Level und Logger liegen nicht auf der Wurzelebene, sondern im Objekt log. Das ist der ECS-Stil: Felder nach Bedeutung gruppieren. Für das Lesen bedeutet das eine einfache Regel: Wenn Sie das Level wollen, suchen Sie log.level; wenn Sie die Quelle wollen, log.logger.
Drittens: service.name ist ein sehr wichtiges Feld, wenn Sie mehr als einen Dienst haben (auch wenn wir jetzt nur einen catalog-service haben). Es erlaubt zu unterscheiden: "Das hat unser Dienst geschrieben" von "Das hat ein Nachbardienst geschrieben". Im Lehrprojekt catalog-service ist es eine gute Angewohnheit, spring.application.name zu setzen, damit Boot es in strukturierten Logs verwenden kann.
Minimale Konfiguration (wir haben das in der vorherigen Vorlesung aktiviert, hier erinnere ich nur an den Kontext):
spring:
application:
# Der Anwendungsname wird in strukturierten Logs als service.name (in ECS) und analog in anderen Formaten erscheinen.
name: catalog-service
logging:
structured:
format:
# Format der strukturierten Logs für die Konsolenausgabe.
console: ecs
3. Format GELF: Schlüsselfelder
GELF (Graylog Extended Log Format) fühlt sich anders an: Es ist "protokollartiger". Dieses Format ist historisch darauf ausgerichtet, Log-Nachrichten an Systeme wie Graylog zu liefern, und daher gibt es Anforderungen an Pflichtfelder und daran, wie zusätzliche Felder aussehen. Wenn ECS versucht, ein "universelles Ereignisschema" zu sein, ähnelt GELF eher einer "Nachricht an einen Log-Server".
Eine typische GELF-Form sieht normalerweise so aus:
{
"version": "1.1",
"host": "my-host",
"timestamp": 1773828900.123,
"short_message": "Catalog started",
"level": 6,
"_level_name": "INFO",
"_log_logger": "com.example.catalogservice.CatalogServiceApplication"
}
Das erste, was ins Auge fällt: short_message. In GELF ist das die Schlüsselidee – eine kurze Nachricht (im Wesentlichen unsere message), die leicht zu indizieren und in der Benutzeroberfläche anzuzeigen ist. Manchmal gibt es in GELF auch full_message – dort wird oft erweiterte Informationen abgelegt, z.B. ein Stacktrace. Im Rahmen der heutigen Besprechung reicht es zu wissen, dass Sie die "kurze" fast immer lesen und die "vollständige" dann heranziehen, wenn Sie die Fehlerdetails verstehen müssen.
Zweiter Punkt: level ist hier eine Zahl. Das ist normal. Das ist nicht "komplizierter", sondern einfach ein anderer Standard. Normalerweise sind das Syslog-Level (je größer die Zahl, desto weniger kritisch; DEBUG ist am "lautesten"). Um nicht zu raten, behalten Sie folgende Tabelle im Kopf:
| GELF level | Bezeichnung (häufig) | Beispiel "einfach erklärt" |
|---|---|---|
| 0 | EMERGENCY | "alles brennt, ruft die Feuerwehr" |
| 1 | ALERT | "sehr schlecht, muss reagiert werden" |
| 2 | CRITICAL | "Kritisches ist kaputt" |
| 3 | ERROR | "Fehler" |
| 4 | WARNING | "verdächtig, aber wir leben noch" |
| 5 | NOTICE | "wichtige Benachrichtigung" |
| 6 | INFO | "normales Leben des Dienstes" |
| 7 | DEBUG | "unter dem Mikroskop" |
Dritter Punkt: Felder mit Unterstrich, z.B. _level_name und _log_logger. In GELF gibt es die Regel: Alle zusätzlichen (nicht standardmäßigen) Felder haben das Präfix _. Das ist der Grund für die "Unterstriche": nicht der Stil des Log-Autors, sondern die Formatspezifikation. Für das Lesen bedeutet das, dass nützliche Dinge sowohl in Standardfeldern (short_message, timestamp, level) als auch in zusätzlichen (_...) liegen können.
Im Lehrkontext des catalog-service ist es wichtig, eine einfache Sache zu können: sehen Sie level: 6 – dann ist es INFO; sehen Sie short_message – dann ist das die "Hauptnachricht"; sehen Sie _log_logger – dann ist das "wer hat geschrieben".
4. Format Logstash JSON: Schlüsselfelder
Logstash JSON sieht oft aus wie "na ja, das verstehe ich, normales flaches JSON". Und das stimmt: Es ist normalerweise weniger verschachtelt als ECS und roher menschenlesbarer. Deshalb wird es in Situationen geliebt, in denen man den Log nicht nur maschinell parsen, sondern auch schnell mit den Augen scannen möchte, ohne einen speziellen Viewer zu öffnen.
Ein typisches Beispiel im Logstash-Stil:
{
"@timestamp": "2026-03-18T10:15:00Z",
"@version": "1",
"message": "Catalog started",
"logger_name": "com.example.catalogservice.CatalogServiceApplication",
"level": "INFO"
}
Hier ist fast alles intuitiv. @timestamp haben Sie bereits gesehen. message – Text. level – als Zeichenfolge. logger_name – Quelle.
Was ist @version? Meistens ist das einfach ein Dienstfeld, das historisch im Logstash-Ökosystem verwendet wird. Für Ihre Diagnose innerhalb des catalog-service ist es fast nie der "Schlüssel zur Lösung", also verschwenden Sie nicht zu viel Aufmerksamkeit darauf: Es ist nützlicher für die Maschine/Pipeline als für den Menschen.
Der Hauptvorteil dieses Formats für den Anfänger liegt in der Lesegeschwindigkeit. Sie sehen message, Sie sehen level, Sie sehen logger_name – schon können Sie verstehen, was passiert ist. Aber für die Einfachheit zahlt man oft mit einer geringeren "Normalisierung" der Struktur im Vergleich zu ECS. Das ist weder schlecht noch gut, sondern einfach ein Kompromiss.
5. Schnelles Erkennen des Formats
Wenn Sie einen Log zum ersten Mal sehen (besonders wenn er von einem fremden Team/fremden Dienst kommt), haben Sie genau 2 Sekunden für die Diagnose: "Was ist das überhaupt?" In diesem Moment ist es nützlich, einige visuelle Anker zu kennen. Nicht die Spezifikationen auswendig lernen, sondern "am Gesicht erkennen".
Hier ist eine "Erkennungstabelle" anhand mehrerer charakteristischer Merkmale:
| Merkmal | ECS | GELF | Logstash JSON |
|---|---|---|---|
| Hat verschachtelte Objekte log, service | Sehr häufig | Fast nie | Normalerweise nicht |
| Nachricht liegt in message | Ja | Nein (short_message) | Ja |
| Nachricht liegt in short_message | Nein | Ja | Nein |
| Level – Zahl | Normalerweise nicht | Ja (level: 6) | Normalerweise nicht |
| Viele Felder mit _-Präfix | Normalerweise nicht | Ja (zusätzliche Felder) | Normalerweise nicht |
| Gibt es @version | Normalerweise nicht | Nein | Ja |
Und noch eine Beobachtung aus der Praxis: Wenn das Format flach ist und "wie ein Tutorial aussieht", ist das noch keine Garantie für Logstash. Aber @version und logger_name sind ziemlich starke Signale. Wenn Sie short_message und ein numerisches Level sehen, ist das fast sicher GELF.
6. Ein Signal in drei Formaten
Damit nicht der Eindruck entsteht, "das sind irgendwelche theoretischen JSONs aus dem Internet", wollen wir einen sehr wichtigen Gedanken festigen: Der Logging-Aufruf in Java bleibt derselbe, nur das Ausgabeformat ändert sich. Wir nehmen ein kleines Signal aus dem catalog-service und spielen es gedanklich in verschiedenen Formaten durch.
Angenommen, wir haben ein solches Stück in einer der Startkomponenten (z.B. in unserem StartupSummaryRunner oder einer ähnlichen Klasse). Der Code ist maximal gewöhnlich:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class StartupSummaryRunner {
// Logger ist normalerweise static final: er ist einmal pro Klasse und wird nicht für jedes Objekt erstellt.
private static final Logger log = LoggerFactory.getLogger(StartupSummaryRunner.class);
public void logCatalogLoaded(int courseCount) {
// {} ist die SLF4J-Parametrisierung: Der Wert wird lazy und performancesicher eingesetzt.
log.info("Catalog loaded: {} courses", courseCount);
}
}
Wenn courseCount = 12 ist, sehen Sie im Klartext etwa "Catalog loaded: 12 courses". Und in strukturierten Formaten sieht es ungefähr so aus.
ECS-Stil (gekürzt):
{
"@timestamp": "2026-03-18T10:15:00Z",
"message": "Catalog loaded: 12 courses",
"log": { "level": "INFO", "logger": "com.example.catalogservice.catalog.bootstrap.StartupSummaryRunner" },
"service": { "name": "catalog-service" }
}
GELF-Stil (gekürzt):
{
"version": "1.1",
"timestamp": 1773828900.123,
"short_message": "Catalog loaded: 12 courses",
"level": 6,
"_log_logger": "com.example.catalogservice.catalog.bootstrap.StartupSummaryRunner"
}
Logstash-Stil (gekürzt):
{
"@timestamp": "2026-03-18T10:15:00Z",
"@version": "1",
"message": "Catalog loaded: 12 courses",
"logger_name": "com.example.catalogservice.catalog.bootstrap.StartupSummaryRunner",
"level": "INFO"
}
Jetzt die wichtige Schlussfolgerung, die Sie sich gut als "Muskelgedächtnis" merken sollten: Wenn Sie verschiedene strukturierte Formate lesen, lernen Sie keine neue "Logik des Loggens". Sie lernen eine neue "Rechtschreibung". Der Sinn ist derselbe, die Markup ist unterschiedlich.
7. Algorithmus zum Lesen strukturierter Logs
Wenn Sie anfangen, kann ein strukturierter Log psychologisch durch seinen Umfang erdrücken: "Viele Felder, also muss ich alles verstehen." Das ist eine Falle. In normalem Text müssen Sie ja auch nicht jeden Buchstaben lesen, um den Sinn zu verstehen. Ein strukturierter Log wird genauso gelesen: zuerst die Essenz, dann Details bei Bedarf. Und das ist keine Schwäche – es ist einfach normale Arbeit mit Daten.
Praktisch können Sie sich diese Lesereihenfolge merken (sie ist für ECS/GELF/Logstash gleich, nur die Feldnamen ändern sich). Zuerst finden Sie "was passiert ist": message oder short_message. Dann bestätigen Sie "wie ernst ist das": log.level, level oder level-Zahl. Dann klären Sie "wer hat es gesagt": log.logger, logger_name oder _log_logger. Danach können Sie sich die Dienstfelder ansehen: welcher Dienst (service.name), welche PID, welcher Thread, welche Umgebungsparameter, und erst dann – seltene spezifische Felder.
Das ist besonders nützlich im catalog-service, da unser Dienst klein und lehrreich ist: Die Hauptfragen sind normalerweise einfach. "Warum startet er nicht auf dem richtigen Port?", "Warum wird die Kursliste nicht geladen?", "Warum verhält sich der Filter seltsam?" Bei solchen Fragen reichen Ihnen fast immer Level, Logger und Nachrichtentext. Die restlichen Felder sind ein Bonus, der hilft, wenn das Problem komplexer ist.
8. Typische Fehler beim Lesen von ECS, GELF und Logstash
Fehler Nr. 1: ECS/GELF/Logstash als verschiedene "Log-Level" wahrnehmen.
Level – das sind INFO/DEBUG/WARN und ihre Bedeutung ("wie laut schreien wir"). Formate – das ist "in welchem Umschlag der Brief verschickt wurde". Man kann auf ERROR-Level sowohl in ECS als auch in GELF und in Logstash schreien – nur die Felder heißen anders.
Fehler Nr. 2: Erwarten, dass Feldnamen in allen Formaten gleich sind.
Eine sehr menschliche Reaktion: Sie haben sich gemerkt, dass es message gibt, und suchen message in GELF. Aber da ist short_message, und für einen Moment fühlen Sie sich ungerecht behandelt. Das ist eine normale Lernstufe: Die Formate ähneln sich in der Bedeutung, unterscheiden sich aber in der "Lexik".
Fehler Nr. 3: Das Format nach "schöner Abkürzung" auswählen, nicht nach Lesbarkeit und erwarteter Struktur.
In einem Lehrprojekt schaltet man leicht das ein, was modisch aussieht, und leidet dann, weil es schwer zu lesen ist. In echten Teams wird das Format normalerweise danach gewählt, was die Log-Infrastruktur "erwartet". In unserem Kurs ist die Schlüsselkompetenz, erkennen und lesen zu können, und nicht einen Abkürzungswettbewerb zu veranstalten.
Fehler Nr. 4: Versuchen, alle Felder zu merken und im ersten Dienst sofort einen "idealen JSON" zu tunen.
Structured Logging zieht schnell in eine endlose Konfiguration: "Lass uns Feld X hinzufügen, und auch Y, und das ganze Umgebungsbaum aufschlüsseln." In der Praxis wächst der Nutzen nicht mit der Anzahl der Felder, sondern mit der Qualität der Signale. Wenn die Nachrichten vage sind, wird auch hundert Felder hinzugefügt nur ein vager JSON sein.
Fehler Nr. 5: Denken, dass man nur weil das Format JSON ist, Objekte gedankenlos komplett loggen kann.
Das JSON-Format hebt den gesunden Menschenverstand nicht auf. Das gesamte "Kurs-Objekt mit allen Beschreibungen" zu loggen, ist kein structured logging, sondern structured Rauschen. Selbst in JSON ist das Ziel dasselbe: ein kurzes diagnostisches Signal festzuhalten, das hilft, die Frage zu beantworten "Was ist passiert und wo soll ich nach der Ursache suchen".
GO TO FULL VERSION