Journalisation dans les applications Go : meilleures pratiques pour débutants
La journalisation ne consiste pas seulement à écrire des messages dans la console. En production, les logs aident à suivre les erreurs, analyser les performances et comprendre le comportement du système. Dans cet article, nous allons examiner les meilleures pratiques de journalisation en Go (Golang) pour débutants : de la bibliothèque standard aux approches structurées modernes.
Pourquoi la journalisation est-elle importante en Go ?
Go est un langage pour créer des applications serveur à forte charge. Sans une journalisation de qualité, vous ne pourrez pas :
- Localiser rapidement les bugs en environnement de production
- Comprendre la séquence des événements en cas de panne
- Effectuer un audit de sécurité
- Optimiser les performances
Une bonne journalisation doit être : structurée, par niveaux (debug, info, warn, error), contextuelle (contenant l'ID de requête, le nom de la fonction) et gérable (rotation, sortie vers fichier).
1. Commencer avec la bibliothèque standard log
Go fournit le package intégré log — simple et suffisant pour les petits projets. Voici comment il fonctionne :
package main
import ( "log" "os")
func main() { // Configuration de base : sortie vers stderr log.SetOutput(os.Stderr) log.SetPrefix("INFO: ") log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile)
log.Println("Application démarrée") log.Printf("Utilisateur %s authentifié", "admin") log.Fatal("Erreur critique") // termine le programme avec le code 1}Fonctionnalités clés de log :
log.Println()— affichage d'une chaînelog.Printf()— affichage formatélog.Fatal()— affichage + fin du programmelog.Panic()— affichage + panique
Mais le logger standard a des inconvénients : pas de niveaux (debug, info, error), pas de sortie structurée (JSON), pas de rotation de fichiers.
2. Ajouter des niveaux de journalisation
Pour les applications de production, il est essentiel de séparer les logs par niveaux. Voici une implémentation minimale utilisant le package standard :
package main
import ( "log" "os")
type Logger struct { debug *log.Logger info *log.Logger warn *log.Logger error *log.Logger}
func NewLogger() *Logger { flags := log.Ldate | log.Ltime | log.Lshortfile return &Logger{ debug: log.New(os.Stdout, "DEBUG: ", flags), info: log.New(os.Stdout, "INFO: ", flags), warn: log.New(os.Stderr, "WARN: ", flags), error: log.New(os.Stderr, "ERROR: ", flags), }}
func (l *Logger) Debug(msg string, args ...interface{}) { l.debug.Printf(msg, args...)}
func (l *Logger) Info(msg string, args ...interface{}) { l.info.Printf(msg, args...)}
func (l *Logger) Error(msg string, args ...interface{}) { l.error.Printf(msg, args...)}
func main() { log := NewLogger() log.Info("Serveur démarré sur le port %d", 8080) log.Debug("Requête de %s", "192.168.1.1") log.Error("Impossible de se connecter à la BDD : %v", "timeout")}Conseil : En production, utilisez os.Stderr pour les erreurs et os.Stdout pour les logs normaux — cela simplifie la collecte des logs via systemd ou Docker.
3. Journalisation structurée (JSON)
Dans les systèmes modernes, les logs sont souvent collectés dans des stockages centralisés (Elasticsearch, Loki). Pour cela, une sortie structurée est nécessaire, par exemple en JSON. Bibliothèques populaires :
- logrus — flexible, avec prise en charge des formateurs
- zap — ultra-rapide, par Uber
- zerolog — léger et rapide
Exemple avec logrus :
package main
import ( "os" "gi