From a52c08ec326543d13f374dc2a65e3bcecb251f59 Mon Sep 17 00:00:00 2001 From: mirage <119869686+ClementBobin@users.noreply.github.com> Date: Tue, 31 Mar 2026 15:32:19 +0200 Subject: [PATCH 1/4] feat: incrementer le nombre de lectures et de likes dans TitreController --- Webzine.WebApplication/Controllers/TitreController.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Webzine.WebApplication/Controllers/TitreController.cs b/Webzine.WebApplication/Controllers/TitreController.cs index 01b1239..e4c0cd7 100644 --- a/Webzine.WebApplication/Controllers/TitreController.cs +++ b/Webzine.WebApplication/Controllers/TitreController.cs @@ -53,6 +53,8 @@ namespace Webzine.WebApplication.Controllers return this.RedirectToAction("Index"); } + this.titreRepository.IncrementNbLectures(titre); + var vm = new TitreDetail { Details = new TitreContent @@ -113,10 +115,11 @@ namespace Webzine.WebApplication.Controllers if (titre == null) { this.logger.LogWarning("Impossible d'ajouter un like. Titre ID {Id} introuvable.", model.IdTitre); - return this.RedirectToAction("Index"); } - - titre.NbLikes++; + else + { + this.titreRepository.IncrementNbLikes(titre); + } return this.RedirectToAction("Details", new { id = model.IdTitre }); } From 83eae661bf714bfa1c55c2885051a70d07df45b0 Mon Sep 17 00:00:00 2001 From: mirage <119869686+ClementBobin@users.noreply.github.com> Date: Thu, 2 Apr 2026 10:50:19 +0200 Subject: [PATCH 2/4] =?UTF-8?q?feat=C2=A0:=20ajout=20d'un=20intercepteur?= =?UTF-8?q?=20de=20requ=C3=AAtes=20lentes=20EF=20Core=20et=20une=20configu?= =?UTF-8?q?ration=20des=20options=20de=20performance?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Webzine.Business/Webzine.Business.csproj | 1 + .../Configuration/Middlewares.cs | 13 ++ .../Interceptors/EfSlowQueryInterceptor.cs | 146 ++++++++++++++++++ Webzine.WebApplication/Program.cs | 23 ++- Webzine.WebApplication/appsettings.json | 5 +- 5 files changed, 183 insertions(+), 5 deletions(-) create mode 100644 Webzine.WebApplication/Configuration/Middlewares.cs create mode 100644 Webzine.WebApplication/Interceptors/EfSlowQueryInterceptor.cs diff --git a/Webzine.Business/Webzine.Business.csproj b/Webzine.Business/Webzine.Business.csproj index 6e5a6aa..a2efab2 100644 --- a/Webzine.Business/Webzine.Business.csproj +++ b/Webzine.Business/Webzine.Business.csproj @@ -23,6 +23,7 @@ + diff --git a/Webzine.WebApplication/Configuration/Middlewares.cs b/Webzine.WebApplication/Configuration/Middlewares.cs new file mode 100644 index 0000000..bbfe61e --- /dev/null +++ b/Webzine.WebApplication/Configuration/Middlewares.cs @@ -0,0 +1,13 @@ +namespace Webzine.WebApplication.Configuration; + +/// +/// Options de seuil pour la détection des opérations EF Core lentes. +/// +public class EfPerformanceOptions +{ + /// + /// Obtient ou définit le seuil en millisecondes au-delà duquel une commande SQL est journalisée. + /// Valeur par défaut : 200 ms. + /// + public int SeuilMs { get; set; } = 200; +} diff --git a/Webzine.WebApplication/Interceptors/EfSlowQueryInterceptor.cs b/Webzine.WebApplication/Interceptors/EfSlowQueryInterceptor.cs new file mode 100644 index 0000000..5e629a8 --- /dev/null +++ b/Webzine.WebApplication/Interceptors/EfSlowQueryInterceptor.cs @@ -0,0 +1,146 @@ +namespace Webzine.WebApplication.Interceptors; + +using System.Data.Common; +using System.Diagnostics; + +using Configuration; + +using Microsoft.EntityFrameworkCore.Diagnostics; +using Microsoft.Extensions.Options; + +/// +/// Intercepteur EF Core qui journalise uniquement les commandes SQL dépassant le seuil configuré. +/// Remonte la pile d'appels pour identifier la méthode repository (Webzine.Repository.*) à l'origine de la requête. +/// +/// +/// +/// Références : +/// +/// +/// EF Core interceptors (doc officielle) : +/// +/// +/// +/// API : +/// +/// +/// +/// Exemple de slow-query interceptor (SO) : +/// +/// +/// +/// pour remonter l'appelant : +/// +/// +/// +/// Enregistrement via AddInterceptors : +/// +/// +/// +/// +/// +public class EfSlowQueryInterceptor : DbCommandInterceptor +{ + private readonly ILogger logger; + private readonly int seuilMs; + + /// + /// Initializes a new instance of the class. + /// + /// Le service de journalisation injecté pour suivre les opérations de l'intercepteur. + /// Les options de performance EF injectées pour récupérer le seuil de lenteur configuré. + public EfSlowQueryInterceptor(ILogger logger, IOptions options) + { + this.logger = logger; + this.seuilMs = options.Value.SeuilMs; + + this.logger.LogDebug("[EfSlowQueryInterceptor] Constructeur appelé — seuil : {SeuilMs} ms.", this.seuilMs); + } + + /// + public override DbDataReader ReaderExecuted(DbCommand command, CommandExecutedEventData eventData, DbDataReader result) + { + this.JournaliserSiLent(eventData.Duration); + return result; + } + + /// + public override ValueTask ReaderExecutedAsync(DbCommand command, CommandExecutedEventData eventData, DbDataReader result, CancellationToken cancellationToken = default) + { + this.JournaliserSiLent(eventData.Duration); + return ValueTask.FromResult(result); + } + + /// + public override int NonQueryExecuted(DbCommand command, CommandExecutedEventData eventData, int result) + { + this.JournaliserSiLent(eventData.Duration); + return result; + } + + /// + public override ValueTask NonQueryExecutedAsync(DbCommand command, CommandExecutedEventData eventData, int result, CancellationToken cancellationToken = default) + { + this.JournaliserSiLent(eventData.Duration); + return ValueTask.FromResult(result); + } + + /// + public override object? ScalarExecuted(DbCommand command, CommandExecutedEventData eventData, object? result) + { + this.JournaliserSiLent(eventData.Duration); + return result; + } + + /// + public override ValueTask ScalarExecutedAsync(DbCommand command, CommandExecutedEventData eventData, object? result, CancellationToken cancellationToken = default) + { + this.JournaliserSiLent(eventData.Duration); + return ValueTask.FromResult(result); + } + + /// + /// Remonte la pile d'appels pour trouver la première méthode dans Webzine.Repository. + /// Toutes les requêtes EF Core du projet transitent par ce namespace, ce qui garantit + /// un résultat pertinent sans parcourir l'intégralité de la stack. + /// + /// Chaîne Classe.Méthode ou "inconnu" si rien trouvé. + /// + /// est instancié uniquement quand le seuil est dépassé, + /// ce qui évite tout impact sur le chemin nominal. + /// Ref : . + /// + private static string TrouverAppelantRepository() + { + // skipFrames: 1 pour sauter TrouverAppelantRepository elle-même + // fNeedFileInfo: false — on ne veut pas les numéros de ligne (coût supplémentaire inutile) + var frames = new StackTrace(skipFrames: 1, fNeedFileInfo: false).GetFrames(); + + foreach (var frame in frames) + { + var methode = frame.GetMethod(); + if (methode?.DeclaringType?.Namespace?.StartsWith("Webzine.Repository", StringComparison.Ordinal) == true) + { + return $"{methode.DeclaringType.Name}.{methode.Name}"; + } + } + + return "inconnu"; + } + + private void JournaliserSiLent(TimeSpan duree) + { + if (duree.TotalMilliseconds > this.seuilMs) + { + var appelant = TrouverAppelantRepository(); + + this.logger.LogWarning( + "[EfSlowQueryInterceptor] Opération EF Core lente détectée — durée réelle : {DureeMs} ms — seuil : {SeuilMs} ms — dépassement : +{Depassement} ms.{NewLine}Appelant : {Appelant}", + duree.TotalMilliseconds.ToString("F2"), + this.seuilMs, + (duree.TotalMilliseconds - this.seuilMs).ToString("F2"), + Environment.NewLine, + appelant); + } + } +} \ No newline at end of file diff --git a/Webzine.WebApplication/Program.cs b/Webzine.WebApplication/Program.cs index d118c67..1e17c43 100644 --- a/Webzine.WebApplication/Program.cs +++ b/Webzine.WebApplication/Program.cs @@ -15,6 +15,7 @@ using Webzine.Repository; using Webzine.Repository.Contracts; using Webzine.WebApplication.Configuration; using Webzine.WebApplication.Extensions; +using Webzine.WebApplication.Interceptors; // Initiation du logger NLog pour la classe courante afin de pouvoir l'utiliser pour logger des messages d'information, d'erreur, etc avant la construction de l'application. var logger = LogManager.Setup().LoadConfigurationFromAppSettings().GetCurrentClassLogger(); @@ -37,6 +38,12 @@ try builder.Logging.ClearProviders(); builder.Host.UseNLog(); + builder.Services.Configure(options => + { + options.SeuilMs = builder.Configuration.GetValue("EfPerformance:SeuilMs"); + }); + builder.Services.AddSingleton(); + // En fonction de la configuration, utilise soit les repositories basés sur une base de données, soit les repositories basés sur des listes locales. var repositoryType = builder.Configuration.GetValue("Repository"); var seederType = builder.Configuration.GetValue("Seeder"); @@ -45,13 +52,21 @@ try { if (builder.Environment.IsProduction()) { - builder.Services.AddDbContext(options => - options.UseNpgsql(builder.Configuration.GetConnectionString("PostGreSQLConnection"))); + builder.Services.AddDbContext((serviceProvider, options) => + { + options + .UseNpgsql(builder.Configuration.GetConnectionString("PostGreSQLConnection")) + .AddInterceptors(serviceProvider.GetRequiredService()); + }); } else { - builder.Services.AddDbContext(options => - options.UseSqlite(builder.Configuration.GetConnectionString("SqliteConnection"))); + builder.Services.AddDbContext((serviceProvider, options) => + { + options + .UseSqlite(builder.Configuration.GetConnectionString("SqliteConnection")) + .AddInterceptors(serviceProvider.GetRequiredService()); + }); } builder.Services.AddScoped(); diff --git a/Webzine.WebApplication/appsettings.json b/Webzine.WebApplication/appsettings.json index 20bd894..46fd8b0 100644 --- a/Webzine.WebApplication/appsettings.json +++ b/Webzine.WebApplication/appsettings.json @@ -13,5 +13,8 @@ "SqliteConnection": "Data Source=Data/webzine.sqlite", "PostGreSQLConnection": "Host=localhost;Port=5432;Username=admin;Password=admin123;Database=webzine_db" }, - "AllowedHosts": "*" + "AllowedHosts": "*", + "EfPerformance": { + "SeuilMs": 10 + } } \ No newline at end of file From 6c79d6df187bb48858f6c89e8818a44871a2692e Mon Sep 17 00:00:00 2001 From: mirage <119869686+ClementBobin@users.noreply.github.com> Date: Thu, 2 Apr 2026 11:11:24 +0200 Subject: [PATCH 3/4] =?UTF-8?q?feat:=20mettre=20=C3=A0=20jour=20la=20confi?= =?UTF-8?q?guration=20de=20journalisation=20et=20renommer=20la=20cible=20d?= =?UTF-8?q?e=20la=20console=20pour=20plus=20de=20clart=C3=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Webzine.WebApplication/appsettings.json | 6 ------ Webzine.WebApplication/nlog.config | 2 +- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/Webzine.WebApplication/appsettings.json b/Webzine.WebApplication/appsettings.json index 46fd8b0..5f66872 100644 --- a/Webzine.WebApplication/appsettings.json +++ b/Webzine.WebApplication/appsettings.json @@ -1,10 +1,4 @@ { - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft.AspNetCore": "Warning" - } - }, "Webzine": { "NombreDerniereChronique": 3, "NombreDeTopTitres": 3 diff --git a/Webzine.WebApplication/nlog.config b/Webzine.WebApplication/nlog.config index 2fc8c32..1b64237 100644 --- a/Webzine.WebApplication/nlog.config +++ b/Webzine.WebApplication/nlog.config @@ -22,7 +22,7 @@ layout="${longdate}|${level:uppercase=true}|${logger}|${message} ${exception:format=tostring}|${aspnet-request-url:whenEmpty=NoRequest}" /> - From bc4e05e3a8a797b48e9d6005c9a6a1c8c6f74650 Mon Sep 17 00:00:00 2001 From: mirage <119869686+ClementBobin@users.noreply.github.com> Date: Thu, 2 Apr 2026 11:11:47 +0200 Subject: [PATCH 4/4] =?UTF-8?q?feat:=20ajouter=20la=20configuration=20de?= =?UTF-8?q?=20journalisation=20pour=20am=C3=A9liorer=20la=20gestion=20des?= =?UTF-8?q?=20niveaux=20de=20log?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Webzine.WebApplication/appsettings.json | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Webzine.WebApplication/appsettings.json b/Webzine.WebApplication/appsettings.json index 5f66872..46fd8b0 100644 --- a/Webzine.WebApplication/appsettings.json +++ b/Webzine.WebApplication/appsettings.json @@ -1,4 +1,10 @@ { + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, "Webzine": { "NombreDerniereChronique": 3, "NombreDeTopTitres": 3