Menu

slog et log en Golang : les logs structurés en Go

Comment journaliser en Go : le package log classique avec ses flags et log.Fatal, et log/slog (Go 1.21) pour des logs structurés avec niveaux, attributs clé-valeur, handlers texte et JSON, et des loggers qui transportent du contexte avec With.

Cette page contient des éditeurs exécutables - modifiez, exécutez et voyez la sortie instantanément.

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

NiveauValeurÀ utiliser pour
slog.LevelDebug-4des détails pour les développeurs, désactivés en production
slog.LevelInfo0les événements normaux : démarrage, requête servie, tâche terminée
slog.LevelWarn4quelque chose d'inattendu que le programme a géré
slog.LevelError8une 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 :

FlagAjoute
log.LstdFlags (par défaut)la date et l'heure 2009/11/10 23:00:00
log.Lmicrosecondsles microsecondes sur l'heure
log.LUTCl'heure en UTC
log.Lshortfile / log.Llongfilemain.go:14 / le chemin complet
log.Lmsgprefixplace 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.Fatalln affichent puis appellent os.Exit(1). Les appels différés ne s'exécutent pas. Utilisez-les dans main pour les échecs au démarrage, jamais dans du code de bibliothèque ni dans des handlers de requêtes.
  • log.Panic et 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 LogValuer ou ReplaceAttr pour 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.

Coddy programming languages illustration

Apprendre à coder avec Coddy

COMMENCER