Seguimiento y correlación distribuidos del servicio de mensajería de Service Bus

Uno de los problemas comunes en el desarrollo de microservicios es la capacidad de realizar un seguimiento de las operaciones de un cliente a través de todos los servicios que intervienen en el proceso. Es útil para la realizar depuraciones, análisis de rendimiento, pruebas A/B y otros escenarios de diagnóstico típicos. Una parte de este problema es el seguimiento de elementos de trabajo lógicos. Asimismo, incluye los resultados y latencia del procesamiento de mensajes y las llamadas de dependencia externas. Otra parte del problema es la correlación de estos eventos de diagnóstico más allá de los límites del proceso.

Cuando un productor envía un mensaje a través de una cola, normalmente se produce en el ámbito de cualquier otra operación lógica que inició algún otro cliente o servicio. Igualmente, el consumidor continúa con la misma operación después de recibir un mensaje. Tanto el productor como el consumidor (y otros servicios que procesan la operación) emiten eventos de telemetría para hacer un seguimiento del flujo de la operación y su resultado. Para poder correlacionar estos eventos y realizar un seguimiento de la operación de un extremo a otro, cada servicio que notifica la telemetría debe marcar todos los eventos con un contexto de seguimiento.

El servicio de mensajería de Microsoft Azure Service Bus ha definido las propiedades de carga que deben usar los productores y consumidores para poder pasar estos contextos de seguimiento. El protocolo se basa en el contexto de seguimiento de W3C.

Nombre de la propiedad Descripción
Diagnostic-Id Identificador único de una llamada externa del productor a la cola. Consulte Encabezado transparente de contexto de seguimiento de W3C para ver el formato.

Seguimiento automático del cliente .NET de Service Bus

La clase ServiceBusProcessor del cliente de Azure Messaging Service Bus para .NET proporciona varios puntos de instrumentación de seguimiento que se pueden enlazar mediante los sistemas de seguimiento o los fragmentos de código de cliente. La instrumentación permite realizar un seguimiento de todas las llamadas al servicio de mensajería de Service Bus desde el lado del cliente. Si el procesamiento de mensajes se realiza con ProcessMessageAsync de ServiceBusProcessor (patrón del controlador de mensajes), el procesamiento de mensajes también se instrumentará.

Realizar un seguimiento con Azure Application Insights

Microsoft Application Insights proporciona funcionalidades de supervisión de alto rendimiento entre las que se incluyen las solicitudes automágicas o el seguimiento de dependencias.

Dependiendo del tipo de proyecto, instale el SDK de Application Insights:

  • ASP.NET: instale la versión 2.5-beta2 o superior.
  • ASP.NET Core: instale la versión 2.2.0-beta2 o superior. Estos vínculos proporcionan detalles sobre la instalación del SDK, la creación de recursos y la configuración del SDK (si fuera necesario). Para aplicaciones que no sean de ASP.NET, consulte el artículo Azure Application Insights for Console Applications (Azure Application Insights para las aplicaciones de consola).

Si usa ProcessMessageAsync de ServiceBusProcessor (patrón del controlador de mensajes) para procesar los mensajes, el procesamiento de mensajes también se instrumentará. Se realizará el seguimiento automático de todas las llamadas de Service Bus que se hayan hecho mediante el servicio y se correlacionarán con otros elementos de telemetría. En caso contrario, consulte el siguiente ejemplo para saber cómo realizar el seguimiento manual del procesamiento de mensajes.

Seguimiento del procesamiento de mensajes

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");
        }
    }
}

En este ejemplo, la telemetría de solicitudes se notifica en cada mensaje procesado e indica la marca de tiempo, la duración y el resultado (correcto). La telemetría también tiene un conjunto de propiedades de correlación. Igualmente, los seguimientos anidados y las excepciones que se notifican durante el procesamiento de mensajes también se marcan con propiedades de correlación que se representan como "elementos secundarios" de RequestTelemetry.

Si realiza llamadas a componentes externos compatibles durante el procesamiento de mensajes, también se seguirán y correlacionarán de manera automática. Consulte Seguimiento de las operaciones personalizadas con el SDK de .NET para Application Insights, si quiere obtener más información sobre el seguimiento y la correlación manuales.

Si ejecuta código externo además del SDK de Application Insights, tenga previsto que la duración será mayor al ver registros de Application Insights.

Longer duration in Application Insights log

Esto no significa que haya un retraso en la recepción del mensaje. En este escenario, el mensaje ya se ha recibido porque se ha pasado como un parámetro al código del SDK. Además, la etiqueta name de los registros de App Insights (Process) indica que el código de procesamiento de eventos externos está procesando el mensaje. Este problema no está relacionado con Azure. estas métricas ponen de manifiesto más bien la eficacia del código externo, dado que el mensaje ya se ha recibido de Service Bus.

Seguimiento con OpenTelemetry

La versión 7.5.0 y posteriores de la biblioteca cliente .NET de Service Bus admite OpenTelemetry en modo experimental. Para obtener más información, consulte Seguimiento distribuido en el SDK de .NET.

Realizar seguimientos sin sistema de seguimiento

Si el sistema de seguimiento no admite el seguimiento automático de llamadas de Service Bus, puede examinar la posibilidad de agregar dicha compatibilidad a un sistema de seguimiento o a su aplicación. En esta sección se describen los eventos de diagnóstico que envió el cliente .NET de Service Bus.

El cliente .NET de Service Bus se instrumentó mediante las primitivas de seguimiento de .NET System.Diagnostics.Activity y System.Diagnostics.DiagnosticSource.

Activity actúa como un contexto de seguimiento mientras que DiagnosticSource es un mecanismo de notificación.

Si no hay ningún agente de escucha para los eventos DiagnosticSource, la instrumentación estará desactivada y el costo de instrumentación será cero. DiagnosticSource proporciona todo el control al agente de escucha:

  • El agente de escucha se encarga de controlar qué orígenes y eventos hay que escuchar.
  • El agente de escucha controla el muestreo y la velocidad de los eventos.
  • Los eventos se envían con una carga que proporciona un contexto completo para que pueda obtener acceso y modificar el objeto de mensaje durante el evento.

Familiarícese con DiagnosticSource User Guide (Guía de usuario de DiagnosticSource) antes de continuar con la implementación.

Vamos a crear un agente de escucha de eventos de Service Bus en la aplicación de ASP.NET Core que escriba registros con Microsoft.Extension.Logger. Este usa la biblioteca System.Reactive.Core para suscribirse a DiagnosticSource (aunque también es fácil suscribirse a DiagnosticSource sin este elemento).

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();
    });
}

En este ejemplo, el agente de escucha registra la duración, el resultado, el identificador único y la hora de inicio de cada operación de Service Bus.

Eventos

Todos los eventos tendrán las propiedades siguientes que se ajustan a la especificación de telemetría abierta: https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/trace/api.md.

  • message_bus.destination: ruta de acceso a la cola, el tema o la suscripción.
  • peer.address: espacio de nombres completo.
  • kind: productor, consumidor o cliente. El productor se usa al enviar mensajes, el consumidor al recibirlos y el cliente al liquidarlos.
  • componentservicebus

Todos los eventos tienen también las propiedades Entity y Endpoint.

  • Entity: nombre de la entidad (cola, tema, etc.).
  • Endpoint: dirección URL del punto de conexión de Service Bus

Operaciones instrumentadas

Esta es la lista completa de operaciones instrumentadas:

Nombre de la operación API de seguimiento
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 Devolución de llamada del procesador establecida en ServiceBusProcessor. Propiedad ProcessMessageAsync
ServiceBusSessionProcessor.ProcessSessionMessage Devolución de llamada del procesador establecida en ServiceBusSessionProcessor. Propiedad ProcessMessageAsync

Filtrado y muestreo

En algunos casos, es recomendable registrar parte de los eventos para reducir el consumo del almacenamiento o la sobrecarga del rendimiento. Para ello, puede registrar solo los eventos "Stop" (tal como se muestra en el ejemplo anterior) o un porcentaje del muestreo de los eventos. DiagnosticSource proporciona la forma de lograrlo mediante el predicado IsEnabled. Para obtener más información, consulte Context-Based Filtering in DiagnosticSource (Filtrado basado en contexto en DiagnosticSource).

IsEnabled puede recibir varias llamadas de una sola operación para así minimizar el impacto en el rendimiento.

IsEnabled se llama en la siguiente secuencia:

  1. IsEnabled(<OperationName>, string entity, null) por ejemplo, IsEnabled("ServiceBusSender.Send", "MyQueue1"). Tenga en cuenta que no hay ningún evento "Start" o "Stop" al final. Use esta opción para filtrar operaciones o colas determinadas. Si el método de devolución de llamada devuelve false, no se envían los eventos de la operación.

    • También recibirá una devolución de llamada IsEnabled(<OperationName>, string entity, Activity activity) para las operaciones "Process" y "ProcessSession". Úsela para filtrar eventos basados en activity.Id o en propiedades de etiquetas.
  2. IsEnabled(<OperationName>.Start) por ejemplo, IsEnabled("ServiceBusSender.Send.Start"). Comprueba si se deben activar eventos "Start". El resultado solo afecta a los eventos "Start", pero la instrumentación adicional no depende de él.

No hay ningún elemento IsEnabled para el evento "Stop".

Si algún resultado de la operación es una excepción, se llama a IsEnabled("ServiceBusSender.Send.Exception"). Solo puede suscribirse a eventos "Exception" y evitar el resto de la instrumentación. En este caso, tendrá que administrar dichas excepciones. Como el resto de la instrumentación está deshabilitada, no espere que el contexto del seguimiento fluya con los mensajes de consumidor a productor.

Puede usar IsEnabled y también implementar estrategias de muestreo. Gracias al muestreo basado en Activity.Id o Activity.RootId, puede obtener resultados de muestreo coherentes en todos los elementos (siempre y cuando se propague mediante el seguimiento del sistema o nuestro propio código).

Cuando hay varios clientes de escucha DiagnosticSource en el mismo origen, un solo cliente de escucha no es suficiente para aceptar el evento, por lo que no se puede garantizar que llame a IsEnabled.

Pasos siguientes