Acompanhar operações personalizadas com o SDK do .NET do Application Insights

Os SDKs do Application Insights rastreiam automaticamente chamadas e solicitações HTTP recebidas por serviços dependentes, como solicitações HTTP e consultas SQL. O acompanhamento e a correlação de solicitações e dependências fornecem visibilidade sobre a capacidade de resposta e a confiabilidade do aplicativo inteiro em todos os microsserviços que combinados nesse aplicativo.

Há uma classe de padrões de aplicativo que não tem suporte genérico. O monitoramento adequado de tais padrões requer a instrumentação de código manual. Este artigo aborda alguns padrões que podem exigir a instrumentação manual, tais como processamento de fila personalizada e execução de tarefas em segundo plano de longa execução.

Este artigo fornece orientações sobre como rastrear operações personalizadas com o SDK do Application Insights. Esta documentação é relevante para:

  • Application Insights para a .NET (também conhecido como o SDK de Base) versão 2.4+.
  • Application Insights para aplicativos Web (executando ASP.NET) versão 2.4+.
  • Application Insights para ASP.NET Core versão 2.1+.

Observação

A documentação a seguir depende da API clássica do Application Insights. O plano de longo prazo do Application Insights é coletar dados usando o OpenTelemetry. Para obter mais informações, veja Habilitar o OpenTelemetry do Azure Monitor para aplicativos .NET, Node.js, Python e Java.

Visão geral

Uma operação é um trabalho lógico executado por um aplicativo. Ela tem nome, hora de início, duração e resultado, além de um contexto de execução como nome de usuário, propriedades e resultado. Se a operação A tiver sido iniciada pela operação B, então a operação B será definida como pai para A. Uma operação pode ter somente um pai, mas pode ter muitas operações filhas. Para saber mais sobre operações e correlação de telemetria, confira Correlação de telemetria do Application Insights.

No SDK do .NET do Application Insights, a operação é descrita pela classe abstrata OperationTelemetry e seus descendentes RequestTelemetry e DependencyTelemetry.

Acompanhamento de operações de entrada

O SDK da Web do Application Insights coleta automaticamente as solicitações HTTP para aplicativos ASP.NET executados em um pipeline do IIS e para todos os aplicativos do ASP.NET Core. Há soluções com suporte da comunidade para outras plataformas e estruturas. Se o aplicativo não for compatível com nenhuma das soluções padrão ou com suporte da comunidade, instrumente-o manualmente.

Outro exemplo que requer um acompanhamento personalizado é o trabalho que recebe os itens da fila. Para alguns filas, a chamada para adicionar uma mensagem a essa fila é acompanhada como dependência. A operação geral que descreve o processamento de mensagens não é coletada automaticamente.

Vamos ver como essas operações poderiam ser rastreadas.

Em um nível alto, a tarefa é criar RequestTelemetry e definir propriedades conhecidas. Depois que a operação for concluída, você poderá acompanhar a telemetria. O exemplo a seguir demonstra essa tarefa.

Solicitação HTTP no aplicativo autohospedado Owin

Neste exemplo, o contexto de rastreamento é propagado de acordo com o protocolo HTTP para correlação. Espere receber cabeçalhos descritos lá.

public class ApplicationInsightsMiddleware : OwinMiddleware
{
    // You may create a new TelemetryConfiguration instance, reuse one you already have,
    // or fetch the instance created by Application Insights SDK.
    private readonly TelemetryConfiguration telemetryConfiguration = TelemetryConfiguration.CreateDefault();
    private readonly TelemetryClient telemetryClient = new TelemetryClient(telemetryConfiguration);
    
    public ApplicationInsightsMiddleware(OwinMiddleware next) : base(next) {}

    public override async Task Invoke(IOwinContext context)
    {
        // Let's create and start RequestTelemetry.
        var requestTelemetry = new RequestTelemetry
        {
            Name = $"{context.Request.Method} {context.Request.Uri.GetLeftPart(UriPartial.Path)}"
        };

        // If there is a Request-Id received from the upstream service, set the telemetry context accordingly.
        if (context.Request.Headers.ContainsKey("Request-Id"))
        {
            var requestId = context.Request.Headers.Get("Request-Id");
            // Get the operation ID from the Request-Id (if you follow the HTTP Protocol for Correlation).
            requestTelemetry.Context.Operation.Id = GetOperationId(requestId);
            requestTelemetry.Context.Operation.ParentId = requestId;
        }

        // StartOperation is a helper method that allows correlation of 
        // current operations with nested operations/telemetry
        // and initializes start time and duration on telemetry items.
        var operation = telemetryClient.StartOperation(requestTelemetry);

        // Process the request.
        try
        {
            await Next.Invoke(context);
        }
        catch (Exception e)
        {
            requestTelemetry.Success = false;
            requestTelemetry.ResponseCode;
            telemetryClient.TrackException(e);
            throw;
        }
        finally
        {
            // Update status code and success as appropriate.
            if (context.Response != null)
            {
                requestTelemetry.ResponseCode = context.Response.StatusCode.ToString();
                requestTelemetry.Success = context.Response.StatusCode >= 200 && context.Response.StatusCode <= 299;
            }
            else
            {
                requestTelemetry.Success = false;
            }

            // Now it's time to stop the operation (and track telemetry).
            telemetryClient.StopOperation(operation);
        }
    }
    
    public static string GetOperationId(string id)
    {
        // Returns the root ID from the '|' to the first '.' if any.
        int rootEnd = id.IndexOf('.');
        if (rootEnd < 0)
            rootEnd = id.Length;

        int rootStart = id[0] == '|' ? 1 : 0;
        return id.Substring(rootStart, rootEnd - rootStart);
    }
}

O protocolo HTTP para correlação também declara o cabeçalho Correlation-Context. Ela é omitida aqui para fins de simplificação.

Instrumentação de fila

O W3C Trace Context e HTTP Protocol for Correlation transmitem detalhes de correlação com solicitações HTTP, mas cada protocolo de fila precisa definir como os mesmos detalhes são transmitidos pela mensagem de fila. Alguns protocolos de fila, como AMQP, permitem a transmissão de mais metadados. Outros, como a Fila do Armazenamento do Azure, exigem que o contexto seja codificado no conteúdo da mensagem.

Observação

O rastreamento entre componentes ainda não é compatível com filas.

Com HTTP, se o produtor e o consumidor enviarem telemetria para diferentes recursos do Application Insights, a experiência de diagnóstico de transação e o Mapa do Aplicativo mostrarão transações e farão o mapeamento de ponta a ponta. No caso das filas, esse recurso ainda não tem suporte.

Fila do Barramento de Serviço

Para saber mais sobre o rastreamento, confira Rastreamento distribuído e correlação por meio de mensagens do Barramento de Serviço do Azure.

Fila de Armazenamento do Azure

O exemplo a seguir mostra como acompanhar operações da fila de Armazenamento do Azure e correlacionar telemetria entre o produtor, o consumidor e o Armazenamento do Azure.

A fila de Armazenamento tem uma API HTTP. Todas as chamadas à fila são rastreadas pelo coletor de dependência do Application Insights para solicitações HTTP. Ele é configurado por padrão em aplicativos ASP.NET e ASP.NET Core. Com outros tipos de aplicativos, confira a Documentação de aplicativos do console.

Também convém correlacionar a ID da operação do Application Insights à ID de solicitação de Armazenamento. Para obter informações sobre como definir e obter um cliente de solicitação de Armazenamento e uma ID de solicitação do servidor, consulte Monitorar, diagnosticar e solucionar problemas do Armazenamento do Azure.

Enfileirar

Como as filas de Armazenamento do Azure dão suporte a API HTTP, todas as operações com a fila automaticamente são acompanhadas pelo Application Insights. Em muitos casos, essa instrumentação deve ser suficiente. Para correlacionar rastreamentos do consumidor com rastreamentos do produtor, transmita algum contexto de correlação de maneira semelhante a feita em HTTP Protocol for Correlation.

Este exemplo mostra como controlar a operação Enqueue. Você pode:

  • Correlacionar novas tentativas (se houver): todas têm um pai comum que é a operação Enqueue. Caso contrário, elas são acompanhadas como filhos da solicitação de entrada. Se houver várias solicitações lógicas para a fila, pode ser difícil descobrir qual chamada resultou em novas tentativas.
  • Correlacione os logs do Armazenamento (se e quando necessário): são correlacionados à telemetria do Application Insights.

A operação Enqueue é filho de uma operação pai. Um exemplo é uma solicitação HTTP de entrada. A chamada de dependência HTTP é filho da operação Enqueue e neto da solicitação de entrada.

public async Task Enqueue(CloudQueue queue, string message)
{
    var operation = telemetryClient.StartOperation<DependencyTelemetry>("enqueue " + queue.Name);
    operation.Telemetry.Type = "Azure queue";
    operation.Telemetry.Data = "Enqueue " + queue.Name;

    // MessagePayload represents your custom message and also serializes correlation identifiers into payload.
    // For example, if you choose to pass payload serialized to JSON, it might look like
    // {'RootId' : 'some-id', 'ParentId' : '|some-id.1.2.3.', 'message' : 'your message to process'}
    var jsonPayload = JsonConvert.SerializeObject(new MessagePayload
    {
        RootId = operation.Telemetry.Context.Operation.Id,
        ParentId = operation.Telemetry.Id,
        Payload = message
    });
    
    CloudQueueMessage queueMessage = new CloudQueueMessage(jsonPayload);

    // Add operation.Telemetry.Id to the OperationContext to correlate Storage logs and Application Insights telemetry.
    OperationContext context = new OperationContext { ClientRequestID = operation.Telemetry.Id};

    try
    {
        await queue.AddMessageAsync(queueMessage, null, null, new QueueRequestOptions(), context);
    }
    catch (StorageException e)
    {
        operation.Telemetry.Properties.Add("AzureServiceRequestID", e.RequestInformation.ServiceRequestID);
        operation.Telemetry.Success = false;
        operation.Telemetry.ResultCode = e.RequestInformation.HttpStatusCode.ToString();
        telemetryClient.TrackException(e);
    }
    finally
    {
        // Update status code and success as appropriate.
        telemetryClient.StopOperation(operation);
    }
}  

Para reduzir a quantidade de telemetria que o seu aplicativo relata ou se você não quiser acompanhar a operação Enqueue por outros motivos, use a API Activity diretamente:

  • Crie (e inicie) um novo Activity em vez de iniciar a operação do Application Insights. Você não precisa atribuir nenhuma propriedade a ele, exceto o nome da operação.
  • Serializar yourActivity.Id para o conteúdo da mensagem, em vez de operation.Telemetry.Id. Também é possível usar Activity.Current.Id.

Remover da fila

Da mesma forma que Enqueue, a solicitação HTTP real para a fila de Armazenamento é acompanhada automaticamente pelo Application Insights. A operação Enqueue presumivelmente ocorre no contexto do pai, como no contexto de uma solicitação de entrada. Os SDKs do Application Insights correlacionam automaticamente essa operação e sua parte HTTP com a solicitação pai e outras telemetrias relatadas no mesmo escopo.

A operação Dequeue é complicada. O SDK do Application Insights acompanha automaticamente as solicitações HTTP. No entanto, o contexto de correlação permanece desconhecido até a conclusão da análise da mensagem. Não é possível correlacionar a solicitação HTTP para obter a mensagem com o restante da telemetria, principalmente quando mais de uma mensagem é recebida.

public async Task<MessagePayload> Dequeue(CloudQueue queue)
{
    var operation = telemetryClient.StartOperation<DependencyTelemetry>("dequeue " + queue.Name);
    operation.Telemetry.Type = "Azure queue";
    operation.Telemetry.Data = "Dequeue " + queue.Name;
    
    try
    {
        var message = await queue.GetMessageAsync();
    }
    catch (StorageException e)
    {
        operation.telemetry.Properties.Add("AzureServiceRequestID", e.RequestInformation.ServiceRequestID);
        operation.telemetry.Success = false;
        operation.telemetry.ResultCode = e.RequestInformation.HttpStatusCode.ToString();
        telemetryClient.TrackException(e);
    }
    finally
    {
        // Update status code and success as appropriate.
        telemetryClient.StopOperation(operation);
    }

    return null;
}

Processo

No exemplo a seguir, uma mensagem de entrada é rastreada de maneira semelhante a uma solicitação HTTP de entrada:

public async Task Process(MessagePayload message)
{
    // After the message is dequeued from the queue, create RequestTelemetry to track its processing.
    RequestTelemetry requestTelemetry = new RequestTelemetry { Name = "process " + queueName };
    
    // It might also make sense to get the name from the message.
    requestTelemetry.Context.Operation.Id = message.RootId;
    requestTelemetry.Context.Operation.ParentId = message.ParentId;

    var operation = telemetryClient.StartOperation(requestTelemetry);

    try
    {
        await ProcessMessage();
    }
    catch (Exception e)
    {
        telemetryClient.TrackException(e);
        throw;
    }
    finally
    {
        // Update status code and success as appropriate.
        telemetryClient.StopOperation(operation);
    }
}

Da mesma forma, outras operações de fila podem ser instrumentadas. Uma operação de espiar deve ser instrumentada da mesma maneira que uma operação de remoção da fila. A instrumentação de operações de gerenciamento de fila não é necessária. O Application Insights acompanha operações como HTTP e, na maioria dos casos, isso é suficiente.

Ao instrumentar a exclusão de mensagem, verifique se você definiu os identificadores da operação (correlação). Como alternativa, você pode usar a API Activity. Assim, você não precisa definir identificadores de operação nos itens de telemetria porque o SDK do Application Insights faz isso para você:

  • Crie um novo Activity depois que tiver obtido um item da fila.
  • Use Activity.SetParentId(message.ParentId) para correlacionar os logs de produtor e consumidor.
  • Inicie o Activity.
  • Acompanhe as operações de remoção da fila, processamento e exclusão usando auxiliares Start/StopOperation. Faça isso do mesmo fluxo de controle assíncrono (contexto de execução). Dessa forma, elas são correlacionadas corretamente.
  • Pare o Activity.
  • Use Start/StopOperation ou chame a telemetria Track manualmente.

Tipos de dependência

Application Insights usa o tipo de dependência para personalizar experiências de interface do usuário. Para filas, os seguintes tipos de DependencyTelemetry que melhoram a experiência do diagnóstico de transação são reconhecidos:

  • Azure queue para filas do Armazenamento do Azure
  • Azure Event Hubs para Hubs de Eventos do Azure
  • Azure Service Bus para Barramento de Serviço do Azure

Processamento em lotes

Com algumas filas, você pode remover da fila várias mensagens com uma solicitação. O processamento dessas mensagens é supostamente independente e pertence a diferentes operações lógicas. Não é possível correlacionar a Dequeue operação a uma mensagem específica que está sendo processada.

Cada mensagem deve ser processada no seu próprio fluxo de controle assíncrono. Para obter mais informações, consulte a seção Acompanhamento de dependências de saída.

Tarefas em segundo plano de execução longa

Alguns aplicativos iniciam operações de longa execução que podem ser causadas por solicitações de usuário. Da perspectiva do rastreamento/instrumentação, isso não é diferente da instrumentação de solicitação ou de dependência:

async Task BackgroundTask()
{
    var operation = telemetryClient.StartOperation<DependencyTelemetry>(taskName);
    operation.Telemetry.Type = "Background";
    try
    {
        int progress = 0;
        while (progress < 100)
        {
            // Process the task.
            telemetryClient.TrackTrace($"done {progress++}%");
        }
        // Update status code and success as appropriate.
    }
    catch (Exception e)
    {
        telemetryClient.TrackException(e);
        // Update status code and success as appropriate.
        throw;
    }
    finally
    {
        telemetryClient.StopOperation(operation);
    }
}

Neste exemplo, o telemetryClient.StartOperation cria DependencyTelemetry e preenche o contexto de correlação. Digamos que você tem uma operação pai criada por solicitações de entrada que agendaram a operação. Desde que BackgroundTask inicie no mesmo fluxo de controle assíncrono que uma solicitação de entrada, ela será correlacionada com essa operação pai. BackgroundTask e todos os itens de telemetria aninhados são automaticamente correlacionados com a solicitação a causou, mesmo após o término da solicitação.

Quando a tarefa inicia do thread em segundo plano que não tem nenhuma operação (Activity) associada a ele, BackgroundTask não tem nenhum pai. No entanto, ela pode ter operações aninhadas. Todos os itens de telemetria relatados da tarefa estão correlacionados à DependencyTelemetry criada na BackgroundTask.

Acompanhamento de dependências de saída

Você pode controlar sua própria variante de dependência ou uma operação sem suporte pelo Application Insights.

O método Enqueue na fila do Barramento de Serviço ou fila de Armazenamento pode servir como exemplos de tal acompanhamento personalizado.

A abordagem geral ao acompanhamento de dependência personalizado é:

  • Chame o método TelemetryClient.StartOperation (extensão) que preenche as propriedades DependencyTelemetry necessárias para correlação e algumas outras, como início, carimbo de data/hora e duração.
  • Definir outras propriedades personalizadas no DependencyTelemetry: tais como nome e qualquer outro contexto necessário.
  • Fazer uma chamada de dependência e esperar por ela.
  • Interromper a operação com StopOperation quando concluída.
  • Tratar exceções.
public async Task RunMyTaskAsync()
{
    using (var operation = telemetryClient.StartOperation<DependencyTelemetry>("task 1"))
    {
        try 
        {
            var myTask = await StartMyTaskAsync();
            // Update status code and success as appropriate.
        }
        catch(...) 
        {
            // Update status code and success as appropriate.
        }
    }
}

Como descartar uma operação faz com que ela seja interrompida, é possível fazer isso em vez de chamar StopOperation.

Aviso

Em alguns casos, uma exceção não tratada pode impedir a chamada de finally, impedindo o rastreamento das operações.

Rastreamento e processamento de operações paralelas

A chamada de StopOperation só interrompe a operação iniciada. Se a operação de execução atual não corresponder à que você deseja interromper, StopOperation não fará nada. Essa situação pode acontecer ao iniciar diversas operações em paralelo no mesmo contexto de execução.

var firstOperation = telemetryClient.StartOperation<DependencyTelemetry>("task 1");
var firstTask = RunMyTaskAsync();

var secondOperation = telemetryClient.StartOperation<DependencyTelemetry>("task 2");
var secondTask = RunMyTaskAsync();

await firstTask;

// FAILURE!!! This will do nothing and will not report telemetry for the first operation
// as currently secondOperation is active.
telemetryClient.StopOperation(firstOperation); 

await secondTask;

Sempre chame StartOperation e processe a operação no mesmo método assíncrono para isolar as operações executadas em paralelo. Se a operação for síncrona (ou não assíncrona), envolva o processo e faça o rastreamento com Task.Run.

public void RunMyTask(string name)
{
    using (var operation = telemetryClient.StartOperation<DependencyTelemetry>(name))
    {
        Process();
        // Update status code and success as appropriate.
    }
}

public async Task RunAllTasks()
{
    var task1 = Task.Run(() => RunMyTask("task 1"));
    var task2 = Task.Run(() => RunMyTask("task 2"));
    
    await Task.WhenAll(task1, task2);
}

Operações do Application Insights vs. System.Diagnostics.Activity

System.Diagnostics.Activity representa o contexto de rastreamento distribuído e é usado por estruturas e bibliotecas para criar e propagar o contexto dentro e fora do processo e correlacionar os itens de telemetria. O Activity trabalha com o System.Diagnostics.DiagnosticSource como o mecanismo de notificação entre a estrutura e a biblioteca para notificar sobre eventos interessantes, como solicitações e exceções recebidas ou enviadas.

As atividades são a prioridade do Application Insights. A dependência automática e a coleta de solicitações dependem muito delas e dos eventos DiagnosticSource. Se você criou o Activity em seu aplicativo, isso não cria a telemetria do Application Insights. O Application Insights precisa receber eventos DiagnosticSource e saber os nomes e os conteúdos deles para converter Activity em telemetria.

Cada operação do Application Insights (solicitação ou dependência) envolve Activity. Quando StartOperation é chamado, ele cria Activity. StartOperation é a maneira recomendada para controlar a telemetrias de solicitação ou dependência manualmente e garantir que tudo esteja correlacionado.

Próximas etapas