Écoute des événements d'application via System.Diagnostics.DiagnosticListener

Le namespace System.Diagnostics fournit un ensemble d'API de bas niveau pour interagir avec le système, les journaux d'événements et les compteurs de performances. Dans les environnements .NET Core, il inclut des bibliothèques comme System.Diagnostics.DiagnosticSource, qui permet une instrumentation avancée des applications.

Présentation de DiagnosticSource et DiagnosticListener

DiagnosticSource est une classe abstraite représantant une source de données diagnostiques. Pour l'utiliser, on instancie typiquement un DiagnosticListener. Contrairement à EventSource, qui sérialise les données pour des processus externes, DiagnosticSource est conçu pour un traitement interne au processus, offrant ainsi des données plus riches et la prise en charge d'objets non sérialisables.

DiagnosticListener implémente IObservable<KeyValuePair<string, object>>, ce qui en fait un sujet observable. Voici une définition simplifiée des classes pertinentes :


// Classe de base pour les sources diagnostiques
public abstract class DiagnosticSource
{
    public abstract bool EstActive(string nom);
    public abstract void Ecrire(string nom, object valeur);
    // Autres méthodes omises pour la concision
}

// Implémentation concrète
public class DiagnosticListener : DiagnosticSource, IObservable<KeyValuePair<string, object>>
{
    public string Nom { get; }
    public IDisposable SAbonner(IObserver<KeyValuePair<string, object>> observateur);
    // Autres membres
}

Pour créer une source observable, on suit généralement ces étapes : instancier un DiagnosticListener, puis y abonner des observateurs. Cependant, dans la plupart des cas, on utilise DiagnosticListener comme observateur pour capturer des événements déjà instrumentés dans les bibliothèques standard.

Surveillance des événements dans SqlClient

La bibliothèque System.Data.SqlClient inclut des points de diagnostic prédéfinis. Voici un exemple d'observateur pour capturer les événements SQL :


// Observateur générique personnalisé
public class ObservateurGenerique<T> : IObserver<T>
{
    private readonly Action<T> _traitementSuivant;

    public ObservateurGenerique(Action<T> traitement)
    {
        _traitementSuivant = traitement;
    }

    public void OnCompleted() { }
    public void OnError(Exception erreur) { }
    public void OnNext(T valeur) => _traitementSuivant(valeur);
}

// Observateur pour SqlClient avec DiagnosticAdapter
public class ObservateurSql : IObserver<DiagnosticListener>
{
    private readonly List<IDisposable> _abonnements = new List<IDisposable>();
    private readonly AsyncLocal<Stopwatch> _chrono = new AsyncLocal<Stopwatch>();

    public void OnNext(DiagnosticListener source)
    {
        if (source.Nom == "SqlClientDiagnosticListener")
        {
            var abonnement = source.AbonnerAvecAdaptateur(this);
            _abonnements.Add(abonnement);
        }
    }

    [DiagnosticName("System.Data.SqlClient.WriteCommandBefore")]
    public void AvantCommande() => _chrono.Value = Stopwatch.StartNew();

    [DiagnosticName("System.Data.SqlClient.WriteCommandAfter")]
    public void ApresCommande(DbCommand commande)
    {
        var chrono = _chrono.Value;
        chrono.Stop();
        Console.WriteLine($"Texte de commande : {commande.CommandText}");
        Console.WriteLine($"Temps écoulé : {chrono.Elapsed}");
    }
}

// Exemple d'utilisation
public static int ExecuterRequete()
{
    using var connexion = new SqlConnection(chaîneConnexion);
    return connexion.QuerySingle<int>("SELECT 42;");
}

Pour initialiser la surveillance :


static void Main(string[] args)
{
    // S'abonner à tous les listeners pour explorer les événements disponibles
    DiagnosticListener.TousLesListeners.SAbonner(new ObservateurGenerique<DiagnosticListener>(listener =>
    {
        Console.WriteLine(listener.Nom);
    }));

    // S'abonner à l'observateur SQL spécifique
    DiagnosticListener.TousLesListeners.SAbonner(new ObservateurSql());

    Console.WriteLine(ExecuterRequete());
}

Interception des requêtes HttpClient

Des points de diagnostic similaires existent pour HttpClient. Voici comment les exploiter :


public static int ObtenirReponseHttp()
{
    using var client = new HttpClient();
    var reponse = client.GetAsync("https://example.com").Result;
    return reponse.Content.ReadAsStringAsync().Result.Length;
}

// Observateur pour HttpClient
public class ObservateurHttp : IObserver<DiagnosticListener>
{
    private readonly List<IDisposable> _abonnements = new List<IDisposable>();

    public void OnNext(DiagnosticListener source)
    {
        if (source.Nom == "HttpHandlerDiagnosticListener")
        {
            var abonnement = source.AbonnerAvecAdaptateur(this);
            _abonnements.Add(abonnement);
        }
    }

    [DiagnosticName("System.Net.Http.Request")]
    public void CapturerRequete(HttpRequestMessage requete)
    {
        Console.WriteLine($"URL : {requete.RequestUri.AbsoluteUri}");
        Console.WriteLine($"Méthode : {requete.Method}");
    }

    [DiagnosticName("System.Net.Http.Response")]
    public void CapturerReponse(HttpResponseMessage reponse)
    {
        Console.WriteLine($"Code de statut : {reponse.StatusCode}");
        Console.WriteLine($"Version : {reponse.Version}");
    }

    [DiagnosticName("System.Net.Http.Exception")]
    public void CapturerException(HttpRequestMessage requete, Exception exception)
    {
        Console.WriteLine($"Erreur : {exception.Message}");
    }
}

Enregistrement de l'observateur HTTP dans le point d'entrée :


static void Main(string[] args)
{
    DiagnosticListener.TousLesListeners.SAbonner(new ObservateurHttp());
    Console.WriteLine(ObtenirReponseHttp());
}

Étiquettes: System.Diagnostics DiagnosticListener DiagnosticSource SqlClient HttpClient

Publié le 12 juin à 23h02