Aller au contenu
Développement medium

Logging structuré en Go avec slog

6 min de lecture

Go

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.

  • É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.

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.

ApprocheExempleExploitable 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

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.

main.go
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),
)
}
Fenêtre de terminal
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.

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.

main.go
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.

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.

main.go
// 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 : rien
reqLog.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}

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.Handler
if production {
handler = slog.NewJSONHandler(os.Stdout, opts)
} else {
handler = slog.NewTextHandler(os.Stdout, opts) // clé=valeur lisible
}
logger := slog.New(handler)
  • Loggez en JSON en production : c'est ce que consomment Loki, Elasticsearch et les SIEM.
  • Attachez les identifiants avec With plutô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.
  • 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.
  • With crée un logger contextuel qui réémet des attributs communs.
  • slog est le bon défaut ; zap reste utile pour un débit extrême.

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracking. Un soutien, même symbolique, m'aide à couvrir l'hébergement et à garder ces ressources gratuites. Merci pour votre appui.

Le formulaire ne s'affiche pas ? Ouvrir Ko-fi dans un onglet.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn