Rastreio distribuído e correlação através das mensagens do Service Bus

Um dos problemas comuns no desenvolvimento de micro serviços é a capacidade de rastrear a operação de um cliente através de todos os serviços que estão envolvidos no processamento. É útil para depuração, análise de desempenho, testes A/B e outros cenários típicos de diagnóstico. Uma parte desse problema é o rastreamento de peças lógicas de trabalho. Inclui resultados e latência de processamento de mensagens e chamadas de dependência externa. Outra parte é a correlação desses eventos de diagnóstico além dos limites do processo.

Quando um produtor envia uma mensagem através de uma fila, isso normalmente acontece no escopo de alguma outra operação lógica, iniciada por algum outro cliente ou serviço. A mesma operação é continuada pelo consumidor assim que recebe uma mensagem. Tanto o produtor quanto o consumidor (e outros serviços que processam a operação), presumivelmente emitem eventos de telemetria para rastrear o fluxo e o resultado da operação. Para correlacionar esses eventos e rastrear a operação de ponta a ponta, cada serviço que relata a telemetria tem que carimbar cada evento com um contexto de rastreamento.

O sistema de mensagens do Barramento de Serviço do Microsoft Azure definiu propriedades de carga útil que os produtores e consumidores devem usar para passar esse contexto de rastreamento. O protocolo é baseado no W3C Trace-Context.

Nome de Propriedade Descrição
ID de diagnóstico Identificador exclusivo de uma chamada externa do produtor para a fila. Consulte o cabeçalho traceparent Trace-Context do W3C para obter o formato

Rastreamento automático do cliente .NET do Service Bus

A ServiceBusProcessor classe do cliente do Barramento de Serviço de Mensagens do Azure para .NET fornece pontos de instrumentação de rastreamento que podem ser conectados por sistemas de rastreamento ou parte do código do cliente. A instrumentação permite rastrear todas as chamadas para o serviço de mensagens do Service Bus do lado do cliente. Se o processamento de mensagens for feito usando ProcessMessageAsync de (padrão do manipulador de mensagens), o processamento de ServiceBusProcessor mensagens também será instrumentado.

Acompanhamento com o Azure Application Insights

O Microsoft Application Insights fornece recursos avançados de monitoramento de desempenho, incluindo solicitação automágica e rastreamento de dependência.

Dependendo do tipo de projeto, instale o SDK do Application Insights:

Se você usar ProcessMessageAsync de (padrão do manipulador de mensagens) para processar mensagens, o processamento de ServiceBusProcessor mensagens também será instrumentado. Todas as chamadas do Service Bus feitas pelo seu serviço são automaticamente rastreadas e correlacionadas com outros itens de telemetria. Caso contrário, consulte o exemplo a seguir para o rastreamento manual de processamento de mensagens.

Processamento de mensagens de rastreamento

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

Neste exemplo, a telemetria de solicitação é relatada para cada mensagem processada, tendo um carimbo de data/hora, duração e resultado (êxito). A telemetria também tem um conjunto de propriedades de correlação. Rastreamentos aninhados e exceções relatados durante o processamento de mensagens também são marcados com propriedades de correlação que os representam como 'filhos' do RequestTelemetry.

Caso você faça chamadas para componentes externos suportados durante o processamento de mensagens, eles também são automaticamente rastreados e correlacionados. Consulte Controlar operações personalizadas com o SDK do .NET do Application Insights para obter controle manual e correlação.

Se você estiver executando qualquer código externo além do SDK do Application Insights, espere ver uma duração maior ao exibir os logs do Application Insights.

Longer duration in Application Insights log

Isso não significa que houve um atraso no recebimento da mensagem. Nesse cenário, a mensagem já foi recebida desde que a mensagem é passada como um parâmetro para o código SDK. Além disso, a tag de nome nos logs do App Insights (Processo) indica que a mensagem está sendo processada pelo código de processamento de eventos externos. Esse problema não está relacionado ao Azure. Em vez disso, essas métricas se referem à eficiência do seu código externo, dado que a mensagem já foi recebida do Service Bus.

Rastreamento com OpenTelemetry

A biblioteca de cliente .NET do Service Bus versão 7.5.0 e posterior oferece suporte a OpenTelemetry no modo experimental. Para obter mais informações, consulte Rastreamento distribuído no .NET SDK.

Rastreamento sem sistema de rastreamento

Caso seu sistema de rastreamento não ofereça suporte ao rastreamento automático de chamadas do Service Bus, você pode estar pensando em adicionar esse suporte a um sistema de rastreamento ou ao seu aplicativo. Esta seção descreve eventos de diagnóstico enviados pelo cliente .NET do Service Bus.

O Service Bus .NET Client é instrumentado usando primitivos de rastreamento .NET System.Diagnostics.Activity e System.Diagnostics.DiagnosticSource.

Activity serve como um contexto de rastreamento, enquanto DiagnosticSource é um mecanismo de notificação.

Se não houver um ouvinte para os eventos DiagnosticSource, a instrumentação estará desativada, mantendo zero custos de instrumentação. DiagnosticSource dá todo o controle ao ouvinte:

  • O ouvinte controla quais fontes e eventos ouvir
  • O ouvinte controla a taxa de eventos e a amostragem
  • os eventos são enviados com uma carga que fornece contexto completo para que você possa acessar e modificar o objeto Message durante o evento

Familiarize-se com o Guia do Usuário do DiagnosticSource antes de prosseguir com a implementação.

Vamos criar um ouvinte para eventos do Service Bus no aplicativo ASP.NET Core que grava logs com Microsoft.Extension.Logger. Ele usa a biblioteca System.Reative.Core para assinar o DiagnosticSource (também é fácil assinar o DiagnosticSource sem ele)

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

Neste exemplo, o ouvinte registra a duração, o resultado, o identificador exclusivo e a hora de início de cada operação do Service Bus.

Eventos

Todos os eventos terão as seguintes propriedades que estão em conformidade com a especificação de telemetria aberta: https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/trace/api.md.

  • message_bus.destination – caminho da fila/tópico/subscrição
  • peer.address – namespace totalmente qualificado
  • kind – produtor, consumidor ou cliente. O produtor é usado ao enviar mensagens, o consumidor ao receber e o cliente ao se acomodar.
  • componentservicebus

Todos os eventos também têm Entity e Endpoint propriedades.

  • Entity - - Nome da entidade (fila, tópico e assim por diante.)
  • Endpoint - URL do ponto de extremidade do Service Bus

Operações instrumentadas

Aqui está a lista completa de operações instrumentadas:

Nome da Operação API rastreada
ServiceBusSender.Send ServiceBusSender.SendMessageAsync
ServiceBusSender.SendMessagesAsync
ServiceBusSender.Schedule ServiceBusSender.ScheduleMessageAsync
ServiceBusSender.ScheduleMessagesAsync
ServiceBusSender.Cancelar ServiceBusSender.CancelScheduledMessageAsync
ServiceBusSender.CancelScheduledMessagesAsync
ServiceBusReceiver.Receber 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 Retorno de chamada do processador definido no ServiceBusProcessor. Propriedade ProcessMessageAsync
ServiceBusSessionProcessor.ProcessSessionMessage Retorno de chamada do processador definido em ServiceBusSessionProcessor. Propriedade ProcessMessageAsync

Filtragem e amostragem

Em alguns casos, é desejável registrar apenas parte dos eventos para reduzir a sobrecarga de desempenho ou o consumo de armazenamento. Você pode registrar apenas eventos 'Stop' (como no exemplo anterior) ou porcentagem de amostra dos eventos. DiagnosticSource fornecer maneira de alcançá-lo com IsEnabled predicado. Para obter mais informações, consulte Filtragem baseada em contexto em DiagnosticSource.

IsEnabled pode ser chamado várias vezes para uma única operação para minimizar o impacto no desempenho.

IsEnabled é chamado na seguinte sequência:

  1. IsEnabled(<OperationName>, string entity, null) por exemplo, IsEnabled("ServiceBusSender.Send", "MyQueue1"). Observe que não há 'Iniciar' ou 'Parar' no final. Use-o para filtrar operações ou filas específicas. Se o método de retorno de chamada retornar false, os eventos para a operação não serão enviados.

    • Para as operações 'Process' e 'ProcessSession', você também recebe IsEnabled(<OperationName>, string entity, Activity activity) retorno de chamada. Use-o para filtrar eventos com base nas activity.Id propriedades ou Tags.
  2. IsEnabled(<OperationName>.Start) por exemplo, IsEnabled("ServiceBusSender.Send.Start"). Verifica se o evento 'Start' deve ser acionado. O resultado afeta apenas o evento 'Start', mas a instrumentação adicional não depende dele.

Não há para o IsEnabled evento 'Stop'.

Se algum resultado da operação for exceção, IsEnabled("ServiceBusSender.Send.Exception") será chamado. Você só pode se inscrever em eventos de "Exceção" e impedir o resto da instrumentação. Neste caso, você ainda tem que lidar com essas exceções. Como outra instrumentação está desativada, você não deve esperar que o contexto de rastreamento flua com as mensagens do consumidor para o produtor.

Você também pode usar IsEnabled implementar estratégias de amostragem. A amostragem com base no ou garante uma Activity.Id amostragem consistente em todos os pneus (desde que seja propagada pelo sistema de rastreamento ou Activity.RootId pelo seu próprio código).

Na presença de vários DiagnosticSource ouvintes para a mesma fonte, basta que apenas um ouvinte aceite o evento, portanto, não há garantia de que IsEnabled seja chamado.

Próximos passos