Aller au contenu principal

Journalisation

Journaliser les évènements dans des fichiers .log

Notions théoriques

Pourquoi logger ?

Console.WriteLine s'affiche à l'écran mais disparaît dès que le terminal se ferme. En production (serveur, service Windows, conteneur Docker), il n'y a pas de terminal. Le logging (journalisation) permet d'enregistrer des événements dans un fichier ou un service centralisé, avec horodatage et niveau de gravité.

// MAUVAIS en production — disparaît avec le terminal
Console.WriteLine("Utilisateur connecté");

// BON — horodaté, nivelé, enregistré dans un fichier
logger.LogInformation("Utilisateur {Nom} connecté à {Heure}", nom, DateTime.Now);

Les 6 niveaux de log

Du moins grave au plus grave :

NiveauMéthodeUsage
TraceLogTraceDétails très fins (débogage intensif)
DebugLogDebugInformations de débogage
InformationLogInformationÉvénements normaux importants
WarningLogWarningSituation anormale mais récupérable
ErrorLogErrorErreur — une opération a échoué
CriticalLogCriticalDéfaillance grave — application compromise

Microsoft.Extensions.Logging — logging intégré .NET

Le package Microsoft.Extensions.Logging.Console est le plus courant pour les applications console. Il est préinstallé dans les projets ASP.NET Core, mais pour une application console simple il faut l'ajouter :

dotnet add package Microsoft.Extensions.Logging
dotnet add package Microsoft.Extensions.Logging.Console
using Microsoft.Extensions.Logging;

// Créer le logger
using ILoggerFactory factory = LoggerFactory.Create(builder =>
{
builder
.AddConsole()
.SetMinimumLevel(LogLevel.Debug); // affiche Debug et au-dessus
});

ILogger logger = factory.CreateLogger("MonApplication");

// Utilisation
logger.LogInformation("Application démarrée");
logger.LogDebug("Connexion à la base de données sur {Host}", "localhost");
logger.LogWarning("Tentative de connexion échouée pour {Utilisateur}", "alice");
logger.LogError("Impossible de lire le fichier {Chemin}", "/data/config.json");

Paramètres structurés {NomParam} vs interpolation $"..."

string utilisateur = "alice";
int tentatives = 3;

// MAUVAIS — interpolation : le message est une chaîne plate, on perd les données
logger.LogWarning($"Échec de connexion pour {utilisateur} après {tentatives} essais");

// BON — paramètres structurés : les valeurs sont indexées séparément
logger.LogWarning("Échec de connexion pour {Utilisateur} après {Tentatives} essais",
utilisateur, tentatives);
Pourquoi les paramètres structurés ?

Avec les paramètres structurés, les systèmes de logging avancés (Elasticsearch, Seq, Azure Monitor) peuvent filtrer et rechercher par valeur : "montrer toutes les erreurs pour l'utilisateur alice". Avec l'interpolation, on obtient une chaîne plate impossible à filtrer.

Logger une exception avec sa pile d'appels

try
{
File.ReadAllText("donnees.txt");
}
catch (IOException ex)
{
// Passer l'exception EN PREMIER pour inclure la pile d'appels dans le log
logger.LogError(ex, "Impossible de lire le fichier {Chemin}", "donnees.txt");
}
L'exception en premier paramètre

Pour inclure la pile d'appels complète dans le log, passez l'exception comme premier argument de LogError. Si vous l'omettez ou la passez après le message, la pile d'appels n'est pas journalisée.

Serilog — logger populaire et flexible

Serilog est un package NuGet très utilisé qui offre plus de flexibilité que le logger intégré .NET, notamment pour écrire dans plusieurs destinations (console + fichier simultanément) :

Sur Windows (PowerShell) et Linux (Bash) — même commande :

dotnet add package Serilog
dotnet add package Serilog.Sinks.Console
dotnet add package Serilog.Sinks.File
using Serilog;

Log.Logger = new LoggerConfiguration()
.MinimumLevel.Debug()
.WriteTo.Console()
.WriteTo.File("logs/journal.txt",
rollingInterval: RollingInterval.Day, // nouveau fichier chaque jour
retainedFileCountLimit: 7) // garder 7 jours
.CreateLogger();

Log.Information("Application démarrée");
Log.Warning("Stock faible pour {Produit} : {Stock} unité(s)", "Clavier", 2);

try
{
throw new InvalidOperationException("Base de données non disponible");
}
catch (Exception ex)
{
Log.Error(ex, "Erreur critique lors de l'initialisation");
}
finally
{
Log.CloseAndFlush(); // important : vider le tampon avant de quitter
}

Exemple pratique

using Microsoft.Extensions.Logging;

using ILoggerFactory factory = LoggerFactory.Create(builder =>
builder.AddConsole().SetMinimumLevel(LogLevel.Debug));

ILogger logger = factory.CreateLogger("GestionStock");

// Simulation d'un système de gestion de stock
var stock = new Dictionary<string, int>
{
["Clavier"] = 5,
["Souris"] = 0,
["Écran"] = 2,
};

logger.LogInformation("=== Démarrage de la vérification du stock ===");

foreach (var (produit, quantite) in stock)
{
if (quantite == 0)
{
logger.LogError("Rupture de stock détectée pour {Produit}", produit);
}
else if (quantite < 3)
{
logger.LogWarning("Stock faible pour {Produit} : {Quantite} unité(s)", produit, quantite);
}
else
{
logger.LogDebug("Stock OK pour {Produit} : {Quantite} unité(s)", produit, quantite);
}
}

// Simulation d'une opération qui peut échouer
try
{
logger.LogInformation("Lecture du fichier de configuration...");
if (!File.Exists("config.json"))
throw new FileNotFoundException("Fichier de configuration manquant.", "config.json");

logger.LogInformation("Configuration chargée avec succès");
}
catch (FileNotFoundException ex)
{
logger.LogError(ex, "Impossible de charger la configuration : {Fichier}", ex.FileName);
logger.LogWarning("Utilisation des valeurs par défaut");
}

logger.LogInformation("=== Vérification terminée ===");

Test de mémorisation/compréhension


Dans quel ordre sont les 6 niveaux de log, du moins grave au plus grave ?


Pourquoi préférer `logger.LogWarning("Utilisateur {Nom} connecté", nom)` plutôt que `logger.LogWarning($"Utilisateur {nom} connecté")` ?


Comment inclure la pile d'appels complète d'une exception dans un `LogError` ?


Quelle méthode Serilog doit-on appeler avant de quitter l'application ?


Quel niveau de log convient pour un événement normal important (ex: 'utilisateur connecté') ?


TP pour réfléchir et résoudre des problèmes

Vous allez créer un journal d'activité pour un système de gestion de notes.

Étape 1 — Configurer le logger

Créez et configurez un logger qui affiche les niveaux Information et au-dessus.


Bonne pratique - Niveau minimum adapté à l'environnement

En développement, utilisez LogLevel.Debug pour voir tous les détails. En production, utilisez LogLevel.Information ou LogLevel.Warning pour éviter de noyer les logs importants dans du bruit. Le niveau minimum se configure idéalement dans appsettings.json et non dans le code, pour pouvoir le changer sans recompiler.

Étape 2 — Logger les opérations

Loggez les ajouts de notes avec le niveau approprié selon la note.


Bonne pratique - Choisir le bon niveau de log

Utilisez le niveau adapté à la gravité de l'événement : Information pour les événements normaux, Warning pour les situations préoccupantes mais gérées, Error pour les erreurs qui empêchent une opération de réussir, Critical pour les défaillances qui compromettent toute l'application. Un log Error pour chaque note en dessous de 10 ferait croire à une panne alors qu'il s'agit juste d'un résultat médiocre.

Étape 3 — Logger les exceptions

Capturez une exception et loggez-la avec sa pile d'appels.


Bonne pratique - Toujours passer l'exception à LogError

Passer l'exception comme premier argument de LogError inclut automatiquement la pile d'appels complète dans le log. C'est indispensable pour diagnostiquer les problèmes en production. Sans la pile d'appels, vous savez qu'une erreur s'est produite, mais pas où dans le code elle a été déclenchée.

📌 Une solution