Pourquoi Logging et monitoring .NET ?
Contexte réel : pourquoi un dev a besoin de ça au quotidien
En tant que développeur, vous allez souvent travailler sur des projets qui évoluent rapidement. Vous serez amené à gérer plusieurs environnements (développement, intégration continue, production), à traiter de grandes quantités de données et à assurer un service continu. Logging et monitoring sont essentiels pour vous aider à diagnostiquer les problèmes, à optimiser le rendement et à garantir la qualité du service.
Un cas d'usage concret en 2-3 phrases
Imaginez que vous travaillez sur une application e-commerce qui gère des milliers de commandes par jour. Vous voulez être capable de détecter rapidement si un problème survient, comme une panne de base de données ou une erreur dans le traitement des paiements. En utilisant logging et monitoring, vous pouvez facilement identifier les anomalies et prendre des mesures correctives immédiatement.
Prerequis
Connaissances nécessaires
- C# et .NET Core
- Connaissance de l'architecture d'une application ASP.NET Core
- Familiarité avec le gestionnaire de paquets NuGet
Outils à installer (versions)
- Visual Studio Code
- Installer la version actuelle (https://code.visualstudio.com/download)
- .NET SDK
- Télécharger et installer la dernière version (https://dotnet.microsoft.com/download/dotnet-core)
- Visual Studio Code
Concepts fondamentaux
Logging
Logging est le processus d'enregistrement des informations sur les événements qui se produisent lors de l'exécution d'un programme. Ces informations peuvent être utilisées pour déboguer, analyser et optimiser les performances de l'application.
Schema mental : Logging
+-------------------+
| Application |
| |
| +-------------+ |
| | Code | |
| +-------------+ |
| | LogManager | |
| +-------------+ |
| | Logger | |
| +-------------+ |
| | Output | |
+-------------------+
Logger.cs
using Microsoft.Extensions.Logging;
public class Logger
{
private readonly ILogger _logger;
public Logger(ILoggerFactory loggerFactory)
{
_logger = loggerFactory.CreateLogger<Logger>();
}
public void LogInformation(string message)
{
_logger.LogInformation(message);
}
public void LogWarning(string message)
{
_logger.LogWarning(message);
}
public void LogError(string message)
{
_logger.LogError(message);
}
}
Program.cs
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureServices((hostContext, services) =>
{
services.AddSingleton<Logger>();
})
.ConfigureLogging(logging =>
{
logging.ClearProviders();
logging.AddConsole();
});
}
Monitoring
Monitoring est le processus de surveillance et d'analyse des performances et de l'état d'une application en temps réel. Il permet de détecter les problèmes, de générer des alertes et de prendre des décisions éclairées.
Schema mental : Monitoring
+-------------------+
| Application |
| |
| +-------------+ |
| | Code | |
| +-------------+ |
| | Metrics | |
| +-------------+ |
| | Monitor | |
| +-------------+ |
| | Alert | |
+-------------------+
Metrics.cs
using System.Collections.Generic;
public class Metrics
{
public Dictionary<string, long> GetMetrics()
{
return new Dictionary<string, long>
{
{ "RequestCount", 100 },
{ "ErrorCount", 5 }
};
}
}
Monitor.cs
using Microsoft.Extensions.Logging;
public class Monitor
{
private readonly ILogger _logger;
private readonly Metrics _metrics;
public Monitor(ILogger<Monitor> logger, Metrics metrics)
{
_logger = logger;
_metrics = metrics;
}
public void CheckMetrics()
{
var metrics = _metrics.GetMetrics();
if (metrics["ErrorCount"] > 0)
{
_logger.LogError($"High error count: {metrics["ErrorCount"]}");
}
}
}
Program.cs
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureServices((hostContext, services) =>
{
services.AddSingleton<Metrics>();
services.AddSingleton<Monitor>();
})
.ConfigureLogging(logging =>
{
logging.ClearProviders();
logging.AddConsole();
});
}
Mise en pratique : projet fil rouge
Construisons un gestionnaire de tâches simple (TaskManager)
Créez un nouveau projet ASP.NET Core
dotnet new webapi -n TaskManager cd TaskManagerAjoutez les packages nécessaires
dotnet add package Microsoft.Extensions.Logging.Abstractions dotnet add package Serilog.AspNetCore dotnet add package Serilog.Sinks.ConsoleConfigurez Serilog dans
Program.csusing Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Serilog; public class Program { public static void Main(string[] args) { Log.Logger = new LoggerConfiguration() .ReadFrom.Configuration(Configuration) .Enrich.FromLogContext() .WriteTo.Console() .CreateLogger(); try { Log.Information("Starting web host"); CreateHostBuilder(args).Build().Run(); } catch (Exception ex) { Log.Fatal(ex, "Host terminated unexpectedly"); } finally { Log.CloseAndFlush(); } } public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .UseSerilog() .ConfigureServices((hostContext, services) => { services.AddControllers(); }); }Créez un service pour gérer les tâches
TaskService.cs
using System.Collections.Generic; using Microsoft.Extensions.Logging; public class TaskService { private readonly ILogger<TaskService> _logger; private List<string> _tasks = new List<string>(); public TaskService(ILogger<TaskService> logger) { _logger = logger; } public void AddTask(string task) { _logger.LogInformation($"Adding task: {task}"); _tasks.Add(task); } public List<string> GetTasks() { return _tasks; } }Configurez le service dans
Program.csusing Microsoft.Extensions.DependencyInjection; public class Program { public static void Main(string[] args) { Log.Logger = new LoggerConfiguration() .ReadFrom.Configuration(Configuration) .Enrich.FromLogContext() .WriteTo.Console() .CreateLogger(); try { Log.Information("Starting web host"); CreateHostBuilder(args).Build().Run(); } catch (Exception ex) { Log.Fatal(ex, "Host terminated unexpectedly"); } finally { Log.CloseAndFlush(); } } public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .UseSerilog() .ConfigureServices((hostContext, services) => { services.AddControllers(); services.AddScoped<TaskService>(); }); }Créez un contrôleur pour gérer les tâches
TasksController.cs
using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; [ApiController] [Route("api/[controller]")] public class TasksController : ControllerBase { private readonly TaskService _taskService; private readonly ILogger<TasksController> _logger; public TasksController(TaskService taskService, ILogger<TasksController> logger) { _taskService = taskService; _logger = logger; } [HttpPost] public IActionResult AddTask([FromBody] string task) { if (string.IsNullOrEmpty(task)) { _logger.LogWarning("Task cannot be empty"); return BadRequest("Task cannot be empty"); } _taskService.AddTask(task); _logger.LogInformation($"Task added: {task}"); return Ok(); } [HttpGet] public IActionResult GetTasks() { var tasks = _taskService.GetTasks(); _logger.LogInformation($"Returning {tasks.Count} tasks"); return Ok(tasks); } }Testez l'application
dotnet runOuvrez un navigateur et accédez à
http://localhost:5000/api/tasks. Vous devriez être capable d'ajouter des tâches et de les récupérer.
Erreurs fréquentes et debugging
1. Erreur : Object reference not set to an instance of an object
Code incorrect
public void AddTask(string task)
{
_tasks.Add(task); // _tasks est null
}
Code correct
public void AddTask(string task)
{
if (_tasks == null)
_tasks = new List<string>();
_tasks.Add(task);
}
2. Erreur : System.ArgumentNullException: Value cannot be null.
Code incorrect
[HttpPost]
public IActionResult AddTask([FromBody] string task)
{
_taskService.AddTask(task); // task est null
return Ok();
}
Code correct
[HttpPost]
public IActionResult AddTask([FromBody] string task)
{
if (string.IsNullOrEmpty(task))
{
return BadRequest("Task cannot be empty");
}
_taskService.AddTask(task);
return Ok();
}
3. Erreur : System.InvalidOperationException: Sequence contains no elements
Code incorrect
[HttpGet]
public IActionResult GetTasks()
{
var tasks = _taskService.GetTasks();
var count = tasks.Count; // Si tasks est vide, Count lève une exception
return Ok(tasks);
}
Code correct
[HttpGet]
public IActionResult GetTasks()
{
var tasks = _taskService.GetTasks();
var count = tasks.Count;
if (count == 0)
{
return NotFound("No tasks found");
}
return Ok(tasks);
}
Pour aller plus loin
1. Utilisation de structured logging
Structured logging permet d'enregistrer les informations sous forme structurée, facilitant ainsi l'analyse et le traitement des journaux.
- Liens :
2. Intégration avec Prometheus et Grafana
Prometheus est un système de monitoring open source, tandis que Grafana est une plateforme d'analyse et de visualisation des données.
3. Utilisation de Application Insights
Application Insights est un service de surveillance d'applications Azure qui permet d'analyser les performances, la disponibilité et le comportement des applications.
- Liens :
Défi pratique
Défi :
Créez un service de logging personnalisé qui utilise Azure Log Analytics. Implémentez une fonction pour enregistrer les informations d'erreur et afficher ces journaux dans le portail Azure.