Aller au contenu

Mesurer la durée d'exécution des requêtes HTTP

Introduction

Dans ASP.NET Core, les middlewares sont des composants intervenant dans la pipeline de traitement des requêtes HTTP. Le choix du mécanisme de mesure de l'exécution dépend de ce que l'on souhaite mesurer :

  • Un middleware permet de mesurer la durée de traitement d'une requête HTTP à travers les différents composants de la pipeline situés après lui. Il est donc adapté si nous souhaitons connaître le temps nécessaire au traitement d'une requête, par une partie du pipeline de l'application.

  • Cependant, si nous souhaitons mesurer plus précisément le temps d'exécution d'une action d'un contrôleur, l'utilisation d'un filtre d'action est plus appropriée. Un filtre d'action s'exécute au niveau de la pipeline, autour de l'exécution de l'action, et permet ainsi d'isoler davantage le temps consacré au traitement réalisé par le contrôleur.

  • Dans cet article, je vous propose d'implémenter un middleware personnalisé, permettant de mesurer le temps nécessaire au traitement d'une requête, et d'enregistrer cette durée dans les logs de l'application.

Dans cet article, je vous propose d'implémenter un middleware personnalisé, permettant de mesurer le temps nécessaire au traitement d'une requête, et d'enregistrer cette durée dans les logs de l'application.

Présentation des middlewares

Un middleware est un composant intégré à la pipeline ASP.NET qui participe au traitement des requêtes et des réponses HTTP. Chaque middleware peut effectuer un traitement avant de transmettre la requête au composant suivant avec l'instruction next(), puis effectuer un nouveau traitement lorsque la réponse remonte la pipeline. Cette organisation permet notamment de gérer des préoccupations transversales telles que les erreurs, l'authentification, les fichiers statiques ou, dans notre cas, la mesure du temps d'exécution.

Pipeline de middlewares

Implémentation du middleware

Pour implémenter ce middleware :

  • Lors du traitement d’une requête HTTP, un chronomètre implémenté avec un Stopwatch est démarré. Le middleware laisse ensuite les composants suivants de la pipeline traiter la requête et la réponse.

  • Lorsque ce traitement est terminé, le chronomètre est arrêté. La durée écoulée depuis le démarrage du chronomètre représente le temps passé dans les composants, situés après ce middleware dans la pipeline, y compris le temps passé dans les actions du contrôleur.

Ce middleware peut être implémenté de la manière suivante :

using System.Diagnostics;

public class RequestExecutionTimeMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger<RequestExecutionTimeMiddleware> _logger;

    public RequestExecutionTimeMiddleware(
        RequestDelegate next,
        ILogger<RequestExecutionTimeMiddleware> logger)
    {
        _next = next;
        _logger = logger;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        var stopwatch = Stopwatch.StartNew();

        try
        {
            // Exécution du middleware suivant dans la pipeline.
            await _next(context);
        }
        finally
        {
            stopwatch.Stop();

            _logger.LogInformation(
                "Durée d'exécution de {Method} {Path} : {ElapsedMilliseconds} ms",
                context.Request.Method,
                context.Request.Path,
                stopwatch.Elapsed.TotalMilliseconds);
        }
    }
}

public static class RequestExecutionTimeMiddlewareExtensions
{
    public static IApplicationBuilder UseRequestExecutionTime(this IApplicationBuilder app)
    {
        return app.UseMiddleware<RequestExecutionTimeMiddleware>();
    }
}

L'utilisation du bloc try ... finally ... garantit que la durée d'exécution est enregistrée dans les logs, même lorsqu'une exception survient pendant le traitement de la requête.

L'instruction await _next(context); transmet la requête au middleware suivant dans la pipeline. Cette exécution reprend lorsque les composants suivants ont terminé leur traitement, ce qui permet alors d'arrêter le chronomètre et de calculer la durée écoulée.

Utilisation du middleware

Puis, enregistrons ce middleware dans la pipeline en utilisant la méthode d'extension implémentée précédemment :

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllersWithViews();

var app = builder.Build(); 

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Home/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

// Mesure du temps d'exécution.
app.UseRequestExecutionTime();

app.UseRouting();

app.UseAuthorization();

app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");

app.Run();

La position de notre middleware dans la pipeline est importante, car la mesure inclut le temps passé dans les composants placés après celui-ci. En le positionnant après le middleware chargé de servir les fichiers statiques, mais avant ceux du routage et d'autorisation, nous mesurons le traitement applicatif des requêtes. Les requêtes directement traitées par le middleware des fichiers statiques ne sont ainsi pas prises en compte.

Aussi, il serait possible de déplacer notre middleware dans la pipeline, afin d'inclure ou d'exclure certains traitements. Sa position doit donc être choisie en fonction de ce que nous souhaitons mesurer.