Menu

slog e log in Golang: logging strutturato in Go

Come fare logging in Go: il classico package log con i suoi flag e log.Fatal, e log/slog (Go 1.21) per log strutturati con livelli, attributi chiave-valore, handler testuali e JSON e logger che portano con sé il contesto tramite With.

Questa pagina include editor eseguibili: modifica, esegui e vedi subito l'output.

slog in un esempio

log/slog (Go 1.21) scrive record strutturati: un messaggio, un livello e attributi chiave-valore.

Ogni riga esce come time=... level=INFO msg="user logged in" user=ada attempts=1. Siccome ogni valore è un campo separato, un sistema di raccolta log (Loki, Elasticsearch, CloudWatch, Datadog) può filtrare su user=ada o level=ERROR senza espressioni regolari sul testo libero.

Gli esempi di questa pagina scrivono su os.Stdout così l'output compare in ordine. In un servizio reale, i log di solito vanno su os.Stderr, che è anche dove scrive il logger predefinito.

Livelli

LivelloValoreDa usare per
slog.LevelDebug-4dettagli per chi sviluppa, disattivati in produzione
slog.LevelInfo0eventi normali: avvio, richiesta servita, job terminato
slog.LevelWarn4qualcosa di inatteso che il programma ha gestito
slog.LevelError8un'operazione è fallita

L'handler scarta i record sotto il suo livello minimo, e il minimo predefinito è Info. Per questo slog.Debug(...) non stampa niente finché non configuri un handler con Level: slog.LevelDebug. Gli spazi tra i valori lasciano posto a livelli personalizzati come slog.Level(2).

Per cambiare il livello a runtime (da un flag, da un endpoint di amministrazione o da un segnale), metti uno slog.LevelVar nelle opzioni e chiama Set su di esso più avanti:

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

Testo o JSON

slog.NewTextHandler scrive coppie key=value, facili da leggere in un terminale. slog.NewJSONHandler scrive un oggetto JSON per riga, il formato che si aspetta la maggior parte delle pipeline di log. Le chiamate di logging restano uguali; cambia solo l'handler.

I valori mantengono il loro tipo: status è un numero nell'output JSON e retry un booleano, un time.Duration si stampa come 42ms nel testo e in nanosecondi nel JSON, e un error stampa il suo messaggio. ReplaceAttr è il punto di aggancio per riscrivere o rimuovere attributi, usato qui per togliere il timestamp, e in pratica per rinominare le chiavi (msg in message) o oscurare valori.

Attributi

La forma a tipizzazione libera alterna chiavi e valori: "user", "ada", "attempts", 3. È breve, e ha un solo modo di sbagliare: un numero dispari di argomenti. Il valore avanzato viene registrato sotto la chiave !BADKEY. go vet lo intercetta:

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

Per la sicurezza dei tipi e qualche allocazione in meno, usa i costruttori di attributi, e LogAttrs quando fai logging in un percorso critico per le prestazioni:

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))

Usa una sola convenzione di nomi per le chiavi in tutto il codice (user_id ovunque, non userID in un package e uid in un altro). Le query nel tuo sistema di log dipendono da questo.

With: logger che portano il contesto

logger.With(attrs...) restituisce un nuovo logger che aggiunge quegli attributi a ogni record. Creane uno per ogni richiesta o per ogni job, e ogni riga che scrive si può collegare alle altre:

Ogni riga porta service, version, request_id e user senza ripeterli a ogni chiamata. slog.Group annida gli attributi, che l'handler JSON scrive come oggetto annidato ("payment":{"amount":25,"currency":"USD"}) e l'handler testuale come chiavi con il punto (payment.amount=25). logger.WithGroup("db") mette sotto un gruppo ogni attributo successivo di quel logger.

Passa il logger legato alla richiesta verso il basso come parametro o come campo di una struct. Salvarlo in un context.Context è possibile ma nasconde la dipendenza; i metodi InfoContext(ctx, ...) di slog passano il context all'handler, e un handler personalizzato può usarlo per estrarre gli ID di trace.

Nascondere i segreti con LogValuer

Un tipo può controllare come viene registrato nei log implementando slog.LogValuer. Così password e token restano fuori dai log, chiunque sia a registrare il valore:

User registra solo il suo ID e l'email, e un Token registrato da solo stampa REDACTED. L'handler chiama LogValue solo quando il record viene effettivamente scritto, quindi funziona anche per valori costosi da calcolare.

Il classico package log

log è nato prima di slog e va ancora bene per piccoli programmi e script. Scrive righe sullo standard error con un prefisso di data e ora:

FlagAggiunge
log.LstdFlags (il predefinito)data e ora 2009/11/10 23:00:00
log.Lmicrosecondsi microsecondi sull'ora
log.LUTCl'ora in UTC
log.Lshortfile / log.Llongfilemain.go:14 / il percorso completo
log.Lmsgprefixmette il prefisso prima del messaggio invece che all'inizio della riga

Tre funzioni terminano il programma o vanno in panic, e la differenza conta:

  • log.Fatal, log.Fatalf, log.Fatalln stampano e poi chiamano os.Exit(1). Le chiamate in defer non vengono eseguite. Usale in main per gli errori di avvio, mai nel codice di una libreria o negli handler delle richieste.
  • log.Panic e simili stampano e poi vanno in panic, quindi le chiamate in defer vengono eseguite e il panic si può recuperare.
  • Tutte le altre funzioni scrivono solo una riga.

Per scrivere il log su un file, aprilo e passalo a log.New o a log.SetOutput; io.MultiWriter(os.Stderr, f) scrive su entrambi.

log e slog insieme

slog.SetDefault(logger) rende logger quello predefinito per le funzioni di primo livello come slog.Info, e fa passare attraverso di esso anche l'output del package log. Le chiamate log.Printf già presenti nel tuo codice o nelle dipendenze escono allora come record strutturati a livello Info:

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

Prima di SetDefault, il logger slog predefinito scrive attraverso il package log, ed è per questo che un semplice slog.Info("hi") stampa 2026/09/23 14:30:00 INFO hi.

Regole pratiche

  • Registra un errore o restituiscilo, non entrambe le cose. Una funzione che registra un errore e lo restituisce fa sì che lo stesso fallimento finisca nei log a ogni livello dello stack di chiamate. Restituisci gli errori verso l'alto con contesto, e registrali una volta sola dove vengono gestiti.
  • Metti i dati variabili negli attributi, non nel messaggio. logger.Info("user created", "user_id", id) si raggruppa bene in un sistema di log; logger.Info(fmt.Sprintf("user %d created", id)) crea un messaggio diverso per ogni utente.
  • Non registrare mai segreti o body completi delle richieste. Usa LogValuer o ReplaceAttr per oscurarli.
  • Usa JSON in produzione, testo in sviluppo. Scegli l'handler all'avvio da un flag o da una variabile d'ambiente.
  • Scegli i livelli con cura. Se tutto viene registrato come Error, gli allarmi sugli errori diventano rumore.

Domande frequenti

Cos'è slog in Go?

log/slog è il package di logging strutturato aggiunto alla libreria standard in Go 1.21. Invece di stringhe formattate, ogni record ha un messaggio, un livello (Debug, Info, Warn, Error) e attributi chiave-valore, e un handler lo scrive come testo key=value o come JSON: slog.Info("login", "user", "ada", "attempts", 3).

Come attivo i log di debug in slog?

Il livello minimo predefinito è Info, quindi slog.Debug non stampa niente. Crea un handler con un livello più basso e rendilo quello predefinito: slog.SetDefault(slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: slog.LevelDebug}))). Usa uno slog.LevelVar invece di una costante se vuoi cambiare il livello mentre il programma gira.

Qual è la differenza tra log e slog in Go?

log scrive righe libere con un prefisso facoltativo di data e ora e non ha livelli. slog scrive record con livelli e attributi chiave-valore tipizzati che i sistemi di raccolta log possono analizzare e filtrare. Sono entrambi nella libreria standard; slog.SetDefault reindirizza anche l'output del package log attraverso l'handler di slog.

log.Fatal esegue le funzioni in defer?

No. log.Fatal e log.Fatalf stampano il messaggio e chiamano os.Exit(1), che salta tutte le chiamate in defer. Usali solo in main o nel codice di avvio dove non c'è niente da ripulire. log.Panic invece va in panic, quindi le chiamate in defer vengono eseguite.

Illustrazione dei linguaggi di programmazione di Coddy

Impara a programmare con Coddy

INIZIA