slog en un exemple
log/slog (Go 1.21) écrit des enregistrements structurés : un message, un niveau et des attributs clé-valeur.
Chaque ligne sort sous la forme time=... level=INFO msg="user logged in" user=ada attempts=1. Comme chaque valeur est un champ séparé, un collecteur de logs (Loki, Elasticsearch, CloudWatch, Datadog) peut filtrer sur user=ada ou level=ERROR sans expressions régulières sur du texte libre.
Les exemples de cette page écrivent sur os.Stdout pour que la sortie apparaisse dans l'ordre. Dans un vrai service, les logs vont généralement sur os.Stderr, qui est aussi là où écrit le logger par défaut.
Niveaux
| Niveau | Valeur | À utiliser pour |
|---|---|---|
slog.LevelDebug | -4 | des détails pour les développeurs, désactivés en production |
slog.LevelInfo | 0 | les événements normaux : démarrage, requête servie, tâche terminée |
slog.LevelWarn | 4 | quelque chose d'inattendu que le programme a géré |
slog.LevelError | 8 | une opération a échoué |
Le handler écarte les enregistrements sous son niveau minimal, et le minimum par défaut est Info. C'est pourquoi slog.Debug(...) n'affiche rien tant que vous n'avez pas configuré un handler avec Level: slog.LevelDebug. Les écarts entre les valeurs laissent de la place pour des niveaux personnalisés comme slog.Level(2).
Pour changer le niveau à l'exécution (depuis un flag, un endpoint d'administration ou un signal), placez un slog.LevelVar dans les options et appelez Set dessus plus tard :
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
Texte ou JSON
slog.NewTextHandler écrit des paires key=value, faciles à lire dans un terminal. slog.NewJSONHandler écrit un objet JSON par ligne, le format qu'attendent la plupart des pipelines de logs. Les appels de journalisation restent les mêmes ; seul le handler change.
Les valeurs gardent leur type : status est un nombre dans la sortie JSON et retry un booléen, un time.Duration s'affiche 42ms en texte et en nanosecondes en JSON, et une error affiche son message. ReplaceAttr est le point d'accroche pour réécrire ou supprimer des attributs, utilisé ici pour retirer l'horodatage, et en pratique pour renommer des clés (msg en message) ou masquer des valeurs.
Attributs
La forme faiblement typée alterne clés et valeurs : "user", "ada", "attempts", 3. Elle est courte, et elle a un seul mode d'échec : un nombre impair d'arguments. La valeur restante est journalisée sous la clé !BADKEY. go vet le détecte :
./main.go:14:2: call to slog.Info missing a final value
Pour la sécurité du typage et un peu moins d'allocations, utilisez les constructeurs d'attributs, et LogAttrs pour journaliser dans un chemin critique :
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))
Utilisez une seule convention de nommage des clés dans toute la base de code (user_id partout, pas userID dans un package et uid dans un autre). Les requêtes dans votre système de logs en dépendent.
With : des loggers qui transportent du contexte
logger.With(attrs...) renvoie un nouveau logger qui ajoute ces attributs à chaque enregistrement. Créez-en un par requête ou par tâche, et toutes les lignes qu'il écrit peuvent être reliées entre elles :
Chaque ligne porte service, version, request_id et user sans les répéter à chaque appel. slog.Group imbrique des attributs, que le handler JSON écrit comme un objet imbriqué ("payment":{"amount":25,"currency":"USD"}) et le handler texte comme des clés pointées (payment.amount=25). logger.WithGroup("db") place tous les attributs suivants de ce logger sous un groupe.
Transmettez le logger lié à la requête en paramètre ou dans un champ de struct. Le stocker dans un context.Context est possible mais masque la dépendance ; les méthodes InfoContext(ctx, ...) de slog transmettent le context au handler, qu'un handler personnalisé peut utiliser pour en extraire des identifiants de trace.
Masquer les secrets avec LogValuer
Un type peut contrôler la façon dont il est journalisé en implémentant slog.LogValuer. Cela garde les mots de passe et les jetons hors des logs, quel que soit celui qui journalise la valeur :
User ne journalise que son identifiant et son e-mail, et un Token journalisé seul affiche REDACTED. Le handler n'appelle LogValue que lorsque l'enregistrement est réellement écrit, donc cela fonctionne aussi pour des valeurs coûteuses à calculer.
Le package log classique
log est antérieur à slog et convient toujours aux petits programmes et aux scripts. Il écrit des lignes sur la sortie d'erreur standard avec un préfixe de date et d'heure :
| Flag | Ajoute |
|---|---|
log.LstdFlags (par défaut) | la date et l'heure 2009/11/10 23:00:00 |
log.Lmicroseconds | les microsecondes sur l'heure |
log.LUTC | l'heure en UTC |
log.Lshortfile / log.Llongfile | main.go:14 / le chemin complet |
log.Lmsgprefix | place le préfixe avant le message au lieu du début de la ligne |
Trois familles de fonctions quittent ou provoquent un panic, et la différence compte :
log.Fatal,log.Fatalf,log.Fatallnaffichent puis appellentos.Exit(1). Les appels différés ne s'exécutent pas. Utilisez-les dansmainpour les échecs au démarrage, jamais dans du code de bibliothèque ni dans des handlers de requêtes.log.Panicet ses variantes affichent puis provoquent un panic, donc les appels différés s'exécutent et le panic peut être récupéré.- Toutes les autres fonctions écrivent simplement une ligne.
Pour journaliser dans un fichier, ouvrez-le et passez-le à log.New ou log.SetOutput ; io.MultiWriter(os.Stderr, f) écrit dans les deux.
log et slog ensemble
slog.SetDefault(logger) fait de logger le logger par défaut des fonctions de haut niveau slog.Info, et fait aussi passer la sortie du package log par lui. Les appels log.Printf existants dans votre code ou vos dépendances sortent alors sous forme d'enregistrements structurés au niveau Info :
slog.SetDefault(slog.New(slog.NewJSONHandler(os.Stderr, nil)))
log.Printf("legacy message") // {"time":"...","level":"INFO","msg":"legacy message"}
Avant SetDefault, le logger slog par défaut écrit via le package log, c'est pourquoi un simple slog.Info("hi") affiche 2026/09/23 14:30:00 INFO hi.
Règles pratiques
- Journalisez ou renvoyez une erreur, pas les deux. Une fonction qui journalise une erreur et la renvoie fait journaliser le même échec à chaque niveau de la pile d'appels. Remontez les erreurs avec du contexte, et journalisez une fois là où elles sont traitées.
- Mettez les données variables dans les attributs, pas dans le message.
logger.Info("user created", "user_id", id)se regroupe bien dans un système de logs ;logger.Info(fmt.Sprintf("user %d created", id))crée un message différent pour chaque utilisateur. - Ne journalisez jamais de secrets ni de bodies de requête complets. Utilisez
LogValuerouReplaceAttrpour masquer. - Utilisez JSON en production, texte en développement. Choisissez le handler au démarrage à partir d'un flag ou d'une variable d'environnement.
- Choisissez les niveaux délibérément. Si tout est journalisé en Error, les alertes sur les erreurs deviennent du bruit.
Questions fréquentes
Qu'est-ce que slog en Go ?
log/slog est le package de logs structurés ajouté à la bibliothèque standard avec Go 1.21. Au lieu de chaînes formatées, chaque enregistrement a un message, un niveau (Debug, Info, Warn, Error) et des attributs clé-valeur, et un handler l'écrit sous forme de texte key=value ou de JSON : slog.Info("login", "user", "ada", "attempts", 3).
Comment activer les logs de débogage dans slog ?
Le niveau minimal par défaut est Info, donc slog.Debug n'affiche rien. Créez un handler avec un niveau plus bas et faites-en le handler par défaut : slog.SetDefault(slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: slog.LevelDebug}))). Utilisez un slog.LevelVar au lieu d'une constante si vous voulez changer le niveau pendant l'exécution du programme.
Quelle est la différence entre log et slog en Go ?
log écrit des lignes libres avec un préfixe d'horodatage facultatif et n'a pas de niveaux. slog écrit des enregistrements avec des niveaux et des attributs clé-valeur typés que les collecteurs de logs peuvent analyser et filtrer. Les deux sont dans la bibliothèque standard ; slog.SetDefault redirige aussi la sortie du package log vers le handler slog.
log.Fatal exécute-t-il les fonctions différées ?
Non. log.Fatal et log.Fatalf affichent le message et appellent os.Exit(1), ce qui saute tous les appels différés. Ne les utilisez que dans main ou dans du code d'initialisation où il n'y a rien à nettoyer. log.Panic provoque plutôt un panic, donc les appels différés s'exécutent.