Menu

Golang slog und log: strukturiertes Logging in Go

So loggst du in Go: das klassische Paket log mit seinen Flags und log.Fatal und log/slog (Go 1.21) für strukturierte Logs mit Levels, Schlüssel-Wert-Attributen, Text- und JSON-Handlern und Loggern, die per With Kontext mitführen.

Diese Seite enthält ausführbare Editoren - bearbeiten, ausführen und Ausgabe sofort sehen.

slog in einem Beispiel

log/slog (Go 1.21) schreibt strukturierte Datensätze: eine Nachricht, ein Level und Schlüssel-Wert-Attribute.

Jede Zeile kommt als time=... level=INFO msg="user logged in" user=ada attempts=1 heraus. Weil jeder Wert ein eigenes Feld ist, kann ein Log-Collector (Loki, Elasticsearch, CloudWatch, Datadog) nach user=ada oder level=ERROR filtern, ohne reguläre Ausdrücke über Freitext.

Die Beispiele auf dieser Seite schreiben nach os.Stdout, damit die Ausgabe in der richtigen Reihenfolge erscheint. In einem echten Service gehen Logs meist nach os.Stderr, und dorthin schreibt auch der Standard-Logger.

Levels

LevelWertWofür
slog.LevelDebug-4Details für Entwickler, in Produktion aus
slog.LevelInfo0normale Ereignisse: gestartet, Request bedient, Job fertig
slog.LevelWarn4etwas Unerwartetes, das das Programm behandelt hat
slog.LevelError8eine Operation ist fehlgeschlagen

Der Handler verwirft Datensätze unterhalb seines Mindest-Levels, und der Standard ist Info. Deshalb gibt slog.Debug(...) nichts aus, bis du einen Handler mit Level: slog.LevelDebug konfigurierst. Die Lücken zwischen den Werten lassen Platz für eigene Levels wie slog.Level(2).

Um das Level zur Laufzeit zu ändern (per Flag, über einen Admin-Endpoint oder ein Signal), leg eine slog.LevelVar in die Optionen und ruf später Set darauf auf:

var level slog.LevelVar // zero value: Info
logger := slog.New(slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{Level: &level}))
level.Set(slog.LevelDebug) // from now on, debug records are written

Text oder JSON

slog.NewTextHandler schreibt key=value-Paare, gut lesbar im Terminal. slog.NewJSONHandler schreibt ein JSON-Objekt pro Zeile, das Format, das die meisten Log-Pipelines erwarten. Die Logging-Aufrufe bleiben gleich; nur der Handler ändert sich.

Werte behalten ihre Typen: status ist in der JSON-Ausgabe eine Zahl und retry ein Boolean, eine time.Duration erscheint im Text als 42ms und in JSON als Nanosekunden, und ein error gibt seine Meldung aus. ReplaceAttr ist der Hook zum Umschreiben oder Entfernen von Attributen, hier genutzt, um den Zeitstempel wegzulassen, in der Praxis, um Schlüssel umzubenennen (msg zu message) oder Werte zu schwärzen.

Attribute

Die lose typisierte Form wechselt Schlüssel und Werte ab: "user", "ada", "attempts", 3. Sie ist kurz und hat genau eine Fehlerquelle: eine ungerade Anzahl von Argumenten. Der übrige Wert wird unter dem Schlüssel !BADKEY geloggt. go vet findet das:

./main.go:14:2: call to slog.Info missing a final value

Für Typsicherheit und etwas weniger Allokation nimm die Attribut-Konstruktoren und LogAttrs, wenn du in einem heißen Pfad loggst:

logger.Info("order placed",
	slog.Int("order_id", 1017),
	slog.String("currency", "EUR"),
	slog.Float64("total", 59.90),
	slog.Duration("took", elapsed),
)

logger.LogAttrs(ctx, slog.LevelInfo, "order placed", slog.Int("order_id", 1017))

Verwende in der ganzen Codebasis eine Namenskonvention für Schlüssel (überall user_id, nicht userID in einem Paket und uid in einem anderen). Die Abfragen in deinem Log-System hängen davon ab.

With: Logger, die Kontext mitführen

logger.With(attrs...) gibt einen neuen Logger zurück, der diese Attribute jedem Datensatz hinzufügt. Erzeuge einen pro Request oder pro Job, und jede Zeile, die er schreibt, lässt sich zuordnen:

Jede Zeile trägt service, version, request_id und user, ohne dass du sie bei jedem Aufruf wiederholst. slog.Group verschachtelt Attribute; der JSON-Handler schreibt sie als verschachteltes Objekt ("payment":{"amount":25,"currency":"USD"}) und der Text-Handler als Schlüssel mit Punkt (payment.amount=25). logger.WithGroup("db") legt jedes spätere Attribut dieses Loggers unter eine Gruppe.

Reich den Logger für den Request als Parameter oder Struct-Feld nach unten weiter. Ihn in einem context.Context zu speichern ist möglich, verbirgt aber die Abhängigkeit; die Methoden InfoContext(ctx, ...) von slog geben den Context an den Handler weiter, und ein eigener Handler kann daraus Trace-IDs holen.

Geheimnisse mit LogValuer verbergen

Ein Typ kann steuern, wie er geloggt wird, indem er slog.LogValuer implementiert. So bleiben Passwörter und Tokens aus den Logs heraus, egal wer den Wert loggt:

User loggt nur seine ID und E-Mail, und ein einzeln geloggter Token erscheint als REDACTED. Der Handler ruft LogValue erst auf, wenn der Datensatz tatsächlich geschrieben wird, also funktioniert es auch für Werte, deren Berechnung teuer ist.

Das klassische Paket log

log ist älter als slog und für kleine Programme und Skripte nach wie vor in Ordnung. Es schreibt Zeilen mit einem Präfix aus Datum und Uhrzeit auf die Standardfehlerausgabe:

FlagFügt hinzu
log.LstdFlags (der Standard)Datum und Uhrzeit 2009/11/10 23:00:00
log.LmicrosecondsMikrosekunden an der Uhrzeit
log.LUTCUhrzeit in UTC
log.Lshortfile / log.Llongfilemain.go:14 / den vollen Pfad
log.Lmsgprefixsetzt das Präfix vor die Nachricht statt an den Zeilenanfang

Drei Funktionsgruppen beenden das Programm oder lösen eine Panic aus, und der Unterschied zählt:

  • log.Fatal, log.Fatalf, log.Fatalln geben aus und rufen dann os.Exit(1) auf. Per defer eingeplante Aufrufe laufen nicht. Nutz sie in main für Fehler beim Start, nie in Bibliothekscode oder Request-Handlern.
  • log.Panic und Verwandte geben aus und lösen dann eine Panic aus, also laufen die defer-Aufrufe, und die Panic lässt sich abfangen.
  • Jede andere Funktion schreibt einfach eine Zeile.

Um in eine Datei zu loggen, öffne sie und übergib sie an log.New oder log.SetOutput; io.MultiWriter(os.Stderr, f) schreibt in beide.

log und slog zusammen

slog.SetDefault(logger) macht logger zum Standard für die Funktionen slog.Info auf oberster Ebene und leitet auch die Ausgabe des Pakets log durch ihn. Vorhandene Aufrufe von log.Printf in deinem Code oder in Abhängigkeiten kommen dann als strukturierte Datensätze auf Level Info heraus:

slog.SetDefault(slog.New(slog.NewJSONHandler(os.Stderr, nil)))
log.Printf("legacy message") // {"time":"...","level":"INFO","msg":"legacy message"}

Vor SetDefault schreibt der Standard-Logger von slog über das Paket log, deshalb gibt ein schlichtes slog.Info("hi") 2026/09/23 14:30:00 INFO hi aus.

Praktische Regeln

  • Einen Fehler loggen oder zurückgeben, nicht beides. Eine Funktion, die einen Fehler loggt und zurückgibt, sorgt dafür, dass derselbe Fehlschlag auf jeder Ebene des Aufrufstapels geloggt wird. Gib Fehler mit Kontext nach oben zurück und logge sie einmal dort, wo sie behandelt werden.
  • Variable Daten in Attribute, nicht in die Nachricht. logger.Info("user created", "user_id", id) lässt sich im Log-System gut gruppieren; logger.Info(fmt.Sprintf("user %d created", id)) erzeugt für jeden Nutzer eine andere Nachricht.
  • Nie Geheimnisse oder vollständige Request-Bodys loggen. Schwärze mit LogValuer oder ReplaceAttr.
  • JSON in Produktion, Text in der Entwicklung. Wähl den Handler beim Start per Flag oder Umgebungsvariable.
  • Levels bewusst wählen. Wird alles auf Error geloggt, werden Alarme bei Fehlern zu Rauschen.

Häufig gestellte Fragen

Was ist slog in Go?

log/slog ist das Paket für strukturiertes Logging, das mit Go 1.21 in die Standardbibliothek kam. Statt formatierter Strings hat jeder Datensatz eine Nachricht, ein Level (Debug, Info, Warn, Error) und Schlüssel-Wert-Attribute, und ein Handler schreibt ihn als key=value-Text oder als JSON: slog.Info("login", "user", "ada", "attempts", 3).

Wie aktiviere ich Debug-Logs in slog?

Das minimale Standard-Level ist Info, also gibt slog.Debug nichts aus. Erzeuge einen Handler mit niedrigerem Level und mach ihn zum Standard: slog.SetDefault(slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: slog.LevelDebug}))). Nimm eine slog.LevelVar statt einer Konstanten, wenn du das Level während der Laufzeit ändern willst.

Was ist der Unterschied zwischen log und slog in Go?

log schreibt frei formatierte Zeilen mit optionalem Zeitstempel-Präfix und kennt keine Levels. slog schreibt Datensätze mit Levels und typisierten Schlüssel-Wert-Attributen, die Log-Collectoren parsen und filtern können. Beide gehören zur Standardbibliothek; slog.SetDefault leitet auch die Ausgabe des Pakets log durch den slog-Handler.

Führt log.Fatal defer-Funktionen aus?

Nein. log.Fatal und log.Fatalf geben die Nachricht aus und rufen os.Exit(1) auf, was alle per defer eingeplanten Aufrufe überspringt. Nutz sie nur in main oder in Setup-Code, in dem es nichts aufzuräumen gibt. log.Panic löst stattdessen eine Panic aus, also laufen die defer-Aufrufe.

Coddy programming languages illustration

Lerne mit Coddy zu programmieren

LOS GEHT'S