
Un log en texte libre comme 2026/07/08 erreur sur la requête de alice ne se filtre ni ne s'agrège : votre stack d'observabilité n'en tire rien. Depuis Go 1.21, la bibliothèque standard répond avec log/slog, qui produit des logs structurés faits d'un message et d'attributs clé-valeur, en texte ou en JSON. Ce guide montre comment émettre ces logs, y attacher des attributs, choisir le format et régler les niveaux. Public visé : intermédiaire en Go. Tous les exemples sont testés avec Go 1.26.
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »- Émettre des logs avec
log/slog, sans dépendance externe. - Attacher des attributs structurés à chaque log.
- Choisir un handler texte ou JSON.
- Régler les niveaux et créer un logger contextuel.
Pourquoi structurer les logs
Section intitulée « Pourquoi structurer les logs »Comparez les deux formes du même événement. En texte libre, la valeur est noyée dans la phrase et il faut une expression régulière pour l'extraire. En structuré, chaque donnée est un champ nommé, directement filtrable dans Loki, Elasticsearch ou un SIEM.
| Approche | Exemple | Exploitable par un outil ? |
|---|---|---|
| Texte libre | échec upstream pour alice (504) | Non, il faut parser |
| Structuré (JSON) | {"msg":"échec upstream","user":"alice","status":504} | Oui, champ par champ |
Un premier log structuré
Section intitulée « Un premier log structuré »slog fonctionne autour d'un logger et d'un handler qui décide du format et de la destination. Le handler JSON écrit sur la sortie de votre choix. Un log se compose d'un message et d'attributs, créés avec slog.String, slog.Int, etc.
package main
import ( "log/slog" "os")
func main() { // Handler JSON écrivant sur la sortie standard. logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
logger.Info("démarrage du service", slog.String("service", "collector"), slog.Int("port", 8080), )}go run .{"time":"2026-07-08T05:27:29Z","level":"INFO","msg":"démarrage du service","service":"collector","port":8080}Le message reste lisible, et service comme port sont des champs à part entière, prêts pour un filtre port=8080.
Les niveaux de log
Section intitulée « Les niveaux de log »slog définit quatre niveaux : Debug, Info, Warn, Error. Le handler porte un seuil : tout log en dessous est ignoré. On règle ce seuil via HandlerOptions.
logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{ Level: slog.LevelInfo, // Debug est filtré}))En production, on reste souvent à Info pour limiter le volume, en gardant la possibilité de basculer à Debug via une variable d'environnement.
Le logger contextuel avec With
Section intitulée « Le logger contextuel avec With »C'est l'atout décisif de slog. With crée un logger dérivé qui porte des attributs communs, réémis à chaque log. Plus besoin de répéter l'identifiant de requête ou l'utilisateur sur chaque ligne.
// Ce logger ajoute request_id et user à tous ses logs.reqLog := logger.With( slog.String("request_id", "req-4213"), slog.String("user", "alice"),)reqLog.Info("requête reçue", slog.String("path", "/api/repos"))reqLog.Warn("quota bientôt atteint", slog.Int("remaining", 5))reqLog.Debug("détail interne ignoré") // sous le seuil Info : rienreqLog.Error("échec upstream", slog.String("err", "timeout"), slog.Int("status", 504))La sortie confirme que request_id et user sont présents sur chaque ligne, et que le Debug a bien été filtré :
{"time":"...","level":"INFO","msg":"requête reçue","request_id":"req-4213","user":"alice","path":"/api/repos"}{"time":"...","level":"WARN","msg":"quota bientôt atteint","request_id":"req-4213","user":"alice","remaining":5}{"time":"...","level":"ERROR","msg":"échec upstream","request_id":"req-4213","user":"alice","err":"timeout","status":504}Texte en développement, JSON en production
Section intitulée « Texte en développement, JSON en production »Le même code peut sortir en texte lisible pour le développement ou en JSON pour la production, en changeant simplement de handler. slog.NewTextHandler produit une ligne clé=valeur agréable à l'œil en local ; slog.NewJSONHandler produit le JSON attendu par les collecteurs.
var handler slog.Handlerif production { handler = slog.NewJSONHandler(os.Stdout, opts)} else { handler = slog.NewTextHandler(os.Stdout, opts) // clé=valeur lisible}logger := slog.New(handler)Bonnes pratiques
Section intitulée « Bonnes pratiques »- Loggez en JSON en production : c'est ce que consomment Loki, Elasticsearch et les SIEM.
- Attachez les identifiants avec
Withplutôt que de les répéter à chaque appel. - Nommez les attributs de façon stable (
user,status,request_id) : vos filtres en dépendent. - Ne mettez jamais de secret dans un log, même en Debug : il finit dans votre agrégateur.
- Réglez le niveau par configuration, pas en dur, pour basculer en Debug sans recompiler.
À retenir
Section intitulée « À retenir »log/slog(Go 1.21+) fournit le logging structuré dans la bibliothèque standard.- Un log = un message plus des attributs clé-valeur, filtrables par un outil.
- Le handler choisit le format (texte ou JSON) et la destination ; son niveau filtre les logs.
Withcrée un logger contextuel qui réémet des attributs communs.slogest le bon défaut ; zap reste utile pour un débit extrême.