Suivi distribué et corrélation via la messagerie Service Bus

L’un des défis que pose couramment le développement de microservices est la capacité à tracer l’opération d’un client à travers tous les services impliqués dans le traitement. Le traçage facilite le débogage, l’analyse des performances, les tests A/B et d’autres diagnostics standard. L’un des aspects du problème a trait au suivi d’éléments de travail logiques. Ceux-ci comptent notamment le résultat et la latence du traitement des messages, et les appels de dépendances externes. Un autre aspect du problème est la mise en corrélation de ces événements de diagnostic au-delà des limites de traitement.

Quand un producteur envoie un message par le biais d’une file d’attente, cela se produit généralement dans l’étendue d’une autre opération logique lancée par un autre client ou service. Le consommateur continue la même opération quand il reçoit un message. Le producteur et le consommateur (et les autres services qui traitent l’opération) émettent probablement des événements de télémétrie pour tracer le flux et le résultat de l’opération. Pour mettre en corrélation de tels événements et tracer l’opération de bout en bout, chaque service qui signale des données de télémétrie doit horodater les événements avec un contexte de trace.

La messagerie Microsoft Azure Service Bus définit des propriétés de charge utile que les producteurs et les consommateurs doivent utiliser pour passer ce contexte de trace. Le protocole est basé sur la spécification Trace-Context de W3C.

Nom de la propriété Description
Diagnostic-Id Identificateur unique d’un appel externe du producteur à la file d’attente. Reportez-vous à l’en-tête traceparent de la spécification Trace-Context de W3C pour le format.

Traçage automatique du client .NET Service Bus

La classe ServiceBusProcessor du client Service Bus d’Azure Messaging pour .NET fournit des points d’instrumentation de traçage qui peuvent être interceptés par des systèmes de traçage ou des éléments de code client. L’instrumentation permet le suivi de tous les appels au service de messagerie Service Bus du côté client. Si le traitement des messages est effectué avec ProcessMessageAsync de ServiceBusProcessor (modèle de gestionnaire de messages), le traitement des messages est également instrumenté.

Suivi avec Azure Application Insights

Microsoft Application Insights fournit des fonctionnalités riches de monitoring des performances, notamment le suivi automagique des requêtes et des dépendances.

En fonction de votre type de projet, installez le SDK Application Insights :

  • ASP.NET : installez la version 2.5 (bêta 2) ou ultérieure.
  • ASP.NET Core : installez la version 2.2.0 (bêta 2) ou ultérieure. Ces liens fournissent des détails sur l’installation du SDK, la création de ressources et la configuration du SDK (le cas échéant). Pour les applications non-ASP.NET, consultez l’article Azure Application Insights pour les applications console.

Si vous utilisez ProcessMessageAsync de ServiceBusProcessor (modèle de gestionnaire de messages) pour traiter les messages, le traitement des messages est également instrumenté. Tous les appels Service Bus effectués par votre service sont automatiquement suivis et mis en corrélation avec d’autres éléments de télémétrie. Sinon, passez en revue l’exemple suivant qui illustre le suivi manuel du traitement des messages.

Tracer le traitement des messages

async Task ProcessAsync(ProcessMessageEventArgs args)
{
    ServiceBusReceivedMessage message = args.Message;
    if (message.ApplicationProperties.TryGetValue("Diagnostic-Id", out var objectId) && objectId is string diagnosticId)
    {
        var activity = new Activity("ServiceBusProcessor.ProcessMessage");
        activity.SetParentId(diagnosticId);
        // If you're using Microsoft.ApplicationInsights package version 2.6-beta or higher, you should call StartOperation<RequestTelemetry>(activity) instead
        using (var operation = telemetryClient.StartOperation<RequestTelemetry>("Process", activity.RootId, activity.ParentId))
        {
            telemetryClient.TrackTrace("Received message");
            try 
            {
            // process message
            }
            catch (Exception ex)
            {
                telemetryClient.TrackException(ex);
                operation.Telemetry.Success = false;
                throw;
            }

            telemetryClient.TrackTrace("Done");
        }
    }
}

Dans cet exemple, la télémétrie des demandes est signalée pour chaque message traité, avec un horodateur, une durée et un résultat (réussite). Les données de télémétrie comprennent également un jeu de propriétés de corrélation. Les traces et exceptions imbriquées qui sont signalées durant le traitement des messages sont également horodatées avec des propriétés de corrélation qui les représentent comme des « enfants » de RequestTelemetry.

Si vous appelez des composants externes pris en charge durant le traitement des messages, ils sont également suivis et mis en corrélation automatiquement. Pour effectuer manuellement le suivi et la mise en corrélation, consultez Suivi des opérations personnalisées avec le kit SDK .NET d’Application Insights.

Si vous exécutez un code externe en plus du SDK Application Insights, attendez-vous à constater une durée plus longue lors de l’affichage des journaux Application Insights.

Longer duration in Application Insights log

Cela ne signifie pas qu’il y a eu un retard lors de la réception du message. Dans ce scénario, le message a déjà été reçu, car il est passé en tant que paramètre au code du SDK. De plus, la balise name dans les journaux App Insights (Process) indique que le message est en cours de traitement par votre code de traitement d’événement externe. Ce problème n’est pas lié à Azure. Au lieu de cela, ces métriques font référence à l’efficacité de votre code externe, étant donné que le message a déjà été reçu de Service Bus.

Suivi avec OpenTelemetry

La version 7.5.0 de la bibliothèque de client Service Bus .NET et les versions ultérieures prennent en charge OpenTelemetry en mode expérimental. Pour plus d’informations, consultez Suivi distribué dans le SDK .NET.

Suivi sans un système de traçage

Si votre système de traçage ne prend pas en charge le suivi automatique des appels Service Bus, songez à l’ajouter dans un système de traçage ou dans votre application. Cette section décrit les événements de diagnostic envoyés par Service Bus .NET Client.

Service Bus .NET Client est instrumenté à l’aide des primitives de traçage .NET System.Diagnostics.Activity et System.Diagnostics.DiagnosticSource.

Activity sert de contexte de trace, tandis que DiagnosticSource est un mécanisme de notification.

S’il n’y a aucun écouteur pour les événements DiagnosticSource, l’instrumentation est désactivée et n’occasionne pas de frais. DiagnosticSource donne tout le contrôle à l’écouteur :

  • L’écouteur contrôle les sources et les événements qu’il écoute
  • L’écouteur contrôle l’échantillonnage et la fréquence des événements
  • Les événements sont envoyés avec une charge utile qui fournit un contexte complet, ce qui vous permet d’accéder à l’objet Message durant l’événement et de le modifier

Avant de passer à l’implémentation, passez en revue le guide de l’utilisateur de DiagnosticSource.

Nous allons créer un écouteur pour les événements Service Bus dans une application ASP.NET Core qui écrit des journaux d’activité avec Microsoft.Extension.Logger. L’abonnement à DiagnosticSource s’effectue à l’aide de la bibliothèque System.Reactive.Core (vous pouvez aussi vous abonner facilement à DiagnosticSource sans cette bibliothèque).

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory factory, IApplicationLifetime applicationLifetime)
{
    // configuration...

    var serviceBusLogger = factory.CreateLogger("Azure.Messaging.ServiceBus");

    IDisposable innerSubscription = null;
    IDisposable outerSubscription = DiagnosticListener.AllListeners.Subscribe(delegate (DiagnosticListener listener)
    {
        // subscribe to the Service Bus DiagnosticSource
        if (listener.Name == "Azure.Messaging.ServiceBus")
        {
            // receive event from Service Bus DiagnosticSource
            innerSubscription = listener.Subscribe(delegate (KeyValuePair<string, object> evnt)
            {
                // Log operation details once it's done
                if (evnt.Key.EndsWith("Stop"))
                {
                    Activity currentActivity = Activity.Current;
                    serviceBusLogger.LogInformation($"Operation {currentActivity.OperationName} is finished, Duration={currentActivity.Duration}, Id={currentActivity.Id}, StartTime={currentActivity.StartTimeUtc}");
                }
            });
        }
    });

    applicationLifetime.ApplicationStopping.Register(() =>
    {
        outerSubscription?.Dispose();
        innerSubscription?.Dispose();
    });
}

Dans cet exemple, l’écouteur journalise la durée, le résultat, l’identificateur unique et l’heure de début de chaque opération Service Bus.

Événements

Tous les événements ont les propriétés suivantes conformes à la spécification de télémétrie ouverte : https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/trace/api.md.

  • message_bus.destination : chemin d’accès de file d’attente/rubrique/abonnement
  • peer.address : espace de noms complet
  • kind : producteur, consommateur ou client. Le producteur est utilisé lors de l’envoi de messages, consommateur lors de la réception et client lors du règlement.
  • componentservicebus

Tous les événements ont également des propriétés Entity et Endpoint.

  • Entity : nom de l’entité (file d’attente, rubrique, etc.)
  • Endpoint : URL du point de terminaison Service Bus

Opérations instrumentées

Voici la liste complète des opérations instrumentées :

Nom d’opération API suivie
ServiceBusSender.Send ServiceBusSender.SendMessageAsync
ServiceBusSender.SendMessagesAsync
ServiceBusSender.Schedule ServiceBusSender.ScheduleMessageAsync
ServiceBusSender.ScheduleMessagesAsync
ServiceBusSender.Cancel ServiceBusSender.CancelScheduledMessageAsync
ServiceBusSender.CancelScheduledMessagesAsync
ServiceBusReceiver.Receive ServiceBusReceiver.ReceiveMessageAsync
ServiceBusReceiver.ReceiveMessagesAsync
ServiceBusReceiver.ReceiveDeferred ServiceBusReceiver.ReceiveDeferredMessagesAsync
ServiceBusReceiver.Peek ServiceBusReceiver.PeekMessageAsync
ServiceBusReceiver.PeekMessagesAsync
ServiceBusReceiver.Abandon ServiceBusReceiver.AbandonMessagesAsync
ServiceBusReceiver.Complete ServiceBusReceiver.CompleteMessagesAsync
ServiceBusReceiver.DeadLetter ServiceBusReceiver.DeadLetterMessagesAsync
ServiceBusReceiver.Defer ServiceBusReceiver.DeferMessagesAsync
ServiceBusReceiver.RenewMessageLock ServiceBusReceiver.RenewMessageLockAsync
ServiceBusSessionReceiver.RenewSessionLock ServiceBusSessionReceiver.RenewSessionLockAsync
ServiceBusSessionReceiver.GetSessionState ServiceBusSessionReceiver.GetSessionStateAsync
ServiceBusSessionReceiver.SetSessionState ServiceBusSessionReceiver.SetSessionStateAsync
ServiceBusProcessor.ProcessMessage Rappel de processeur défini sur ServiceBusProcessor. Propriété ProcessMessageAsync
ServiceBusSessionProcessor.ProcessSessionMessage Rappel de processeur défini sur ServiceBusSessionProcessor. Propriété ProcessMessageAsync

Filtrage et échantillonnage

Dans certains cas, il est souhaitable de journaliser uniquement une partie des événements pour réduire la surcharge des performances ou la consommation du stockage. Vous pouvez journaliser uniquement les événements « Stop » (comme dans l’exemple précédent) ou échantillonner un pourcentage des événements. DiagnosticSource offre un moyen d’y parvenir avec le prédicat IsEnabled. Pour plus d’informations, consultez Filtrage basé sur le contexte dans DiagnosticSource.

IsEnabled peut être appelé plusieurs fois pour une opération unique afin de minimiser l’impact sur les performances.

IsEnabled est appelé dans la séquence suivante :

  1. IsEnabled(<OperationName>, string entity, null), par exemple IsEnabled("ServiceBusSender.Send", "MyQueue1"). Notez l’absence de « Start » ou de « Stop » à la fin. Utilisez-le pour exclure des opérations ou des files d’attente particulières. Si la méthode de rappel retourne false, les événements de l’opération ne sont pas envoyés.

    • Pour les opérations « Process » et « ProcessSession », vous recevez également le rappel IsEnabled(<OperationName>, string entity, Activity activity). Utilisez-le pour filtrer des événements en fonction des propriétés des balises ou de activity.Id.
  2. IsEnabled(<OperationName>.Start), par exemple IsEnabled("ServiceBusSender.Send.Start"). Vérifie si l’événement « Start » doit être déclenché. Le résultat affecte uniquement l’événement « Start », mais l’instrumentation supplémentaire n’en dépend pas.

Il n’y a pas de IsEnabled pour l’événement « Stop ».

Si le résultat d’une opération est une exception, IsEnabled("ServiceBusSender.Send.Exception") est appelé. Vous pouvez uniquement vous abonner aux événements « Exception » et empêcher le reste de l’instrumentation. Dans ce cas, vous devez encore gérer ces exceptions. Toute autre instrumentation étant désactivée, ne vous attendez pas à ce que le contexte de trace circule avec les messages du consommateur au producteur.

Vous pouvez également utiliser IsEnabled pour implémenter des stratégies d’échantillonnage. L’échantillonnage basé sur Activity.Id ou Activity.RootId garantit des résultats cohérents (à condition qu’il soit propagé par le système de suivi ou par votre propre code).

En présence de plusieurs écouteurs DiagnosticSource pour la même source, il suffit qu’un seul écouteur accepte l’événement. L’appel de IsEnabled n’est donc pas garanti.

Étapes suivantes