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

Os SDKs do Application Insights rastreiam automaticamente solicitações HTTP de entrada e chamadas para serviços dependentes, como solicitações HTTP e consultas SQL. O rastreamento e a correlação de solicitações e dependências oferecem visibilidade sobre a capacidade de resposta e a confiabilidade de todo o aplicativo em todos os microsserviços que combinam esse aplicativo.

Há uma classe de padrões de aplicativos que não podem ser suportados genericamente. O monitoramento adequado de tais padrões requer instrumentação manual de código. Este artigo aborda alguns padrões que podem exigir instrumentação manual, como processamento de fila personalizado e execução de tarefas em segundo plano de longa execução.

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

  • Application Insights para .NET (também conhecido como SDK Base) versão 2.4+.
  • Application Insights para aplicações Web (em execução ASP.NET) versão 2.4+.
  • Application Insights para ASP.NET Core versão 2.1+.

Nota

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

Descrição geral

Uma operação é uma parte lógica do trabalho executado por um aplicativo. Ele tem um nome, hora de início, duração, resultado e um contexto de execução como nome de usuário, propriedades e resultado. Se a operação A foi iniciada pela operação B, então a operação B é definida como pai para A. Uma operação pode ter apenas um dos pais, mas pode ter muitas operações filhas. Para obter mais informações sobre operações e correlação de telemetria, consulte Correlação de telemetria do Application Insights.

No SDK do Application Insights .NET, 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 solicitações HTTP para aplicativos ASP.NET executados em um pipeline do IIS e todos os aplicativos ASP.NET Core. Existem soluções suportadas pela comunidade para outras plataformas e estruturas. Se o aplicativo não for suportado por nenhuma das soluções padrão ou suportadas pela comunidade, você poderá instrumentá-lo manualmente.

Outro exemplo que requer acompanhamento personalizado é o trabalhador que recebe itens da fila. Para algumas filas, a chamada para adicionar uma mensagem a essa fila é rastreada como uma dependência. A operação de alto nível que descreve o processamento de mensagens não é coletada automaticamente.

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

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

Solicitação HTTP no aplicativo auto-hospedado Owin

Neste exemplo, o contexto de rastreamento é propagado de acordo com o protocolo HTTP para correlação. Você deve esperar 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 Correlation-Context cabeçalho. É omitido aqui por simplicidade.

Instrumentação de fila

O Contexto de Rastreamento do W3C e o Protocolo HTTP para Correlação passam detalhes de correlação com solicitações HTTP, mas cada protocolo de fila precisa definir como os mesmos detalhes são passados ao longo da mensagem de fila. Alguns protocolos de fila, como o AMQP, permitem a passagem de mais metadados. Outros protocolos, como a Fila de Armazenamento do Azure, exigem que o contexto seja codificado na carga útil da mensagem.

Nota

O rastreamento entre componentes ainda não é suportado para filas.

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

Fila do Service Bus

Para obter informações de rastreamento, consulte 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 controlar as operações de fila do Armazenamento do Azure e correlacionar a telemetria entre o produtor, o consumidor e o Armazenamento do Azure.

A fila de armazenamento tem uma API HTTP. Todas as chamadas para a fila são rastreadas pelo Application Insights Dependency Collector para solicitações HTTP. Ele é configurado por padrão em aplicativos ASP.NET e ASP.NET Core. Com outros tipos de aplicativos, consulte a documentação de aplicativos de console.

Você também pode querer correlacionar o ID da operação do Application Insights com o ID da 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 de servidor, consulte Monitorar, diagnosticar e solucionar problemas do Armazenamento do Azure.

Enfileiramento

Como as filas de armazenamento suportam a API HTTP, todas as operações com a fila são rastreadas automaticamente pelo Application Insights. Em muitos casos, esta instrumentação deve ser suficiente. Para correlacionar rastreamentos do lado do consumidor com rastreamentos do produtor, você deve passar algum contexto de correlação de forma semelhante à forma como fazemos isso no Protocolo HTTP para Correlação.

Este exemplo mostra como controlar a Enqueue operação. Pode:

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

A Enqueue operação é filha de uma operação pai. Um exemplo é uma solicitação HTTP de entrada. A chamada de dependência HTTP é filha da Enqueue operação e neta 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 seu aplicativo relata ou se você não quiser acompanhar a operação por outros motivos, use a EnqueueActivity API diretamente:

  • Crie (e inicie) um novo Activity em vez de iniciar a operação do Application Insights. Não é necessário atribuir nenhuma propriedade a ele, exceto o nome da operação.
  • Serialize yourActivity.Id na carga útil da mensagem em vez de operation.Telemetry.Id. Você também pode usar Activity.Current.Ido .

Remover da fila

Da mesma forma que Enqueue, uma solicitação HTTP real para a fila de armazenamento é rastreada automaticamente pelo Application Insights. A Enqueue operação presumivelmente acontece no contexto pai, como um contexto de solicitação de entrada. Os SDKs do Application Insights correlacionam automaticamente essa operação, e sua parte HTTP, com a solicitação pai e outra telemetria relatada no mesmo escopo.

A Dequeue operação é complicada. O SDK do Application Insights rastreia automaticamente as solicitações HTTP. Mas não sabe o contexto da correlação até que a mensagem seja analisada. Não é possível correlacionar a solicitação HTTP para obter a mensagem com o resto da telemetria, especialmente 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 visualização deve ser instrumentada de forma semelhante a uma operação de retirada de fila. Não é necessário instrumentar operações de gerenciamento de filas. O Application Insights rastreia operações como HTTP e, na maioria dos casos, é suficiente.

Ao instrumentar a exclusão de mensagens, certifique-se de definir os identificadores de operação (correlação). Como alternativa, você pode usar a Activity API. Em seguida, você não precisa definir identificadores de operação nos itens de telemetria porque o SDK do Application Insights faz isso por você:

  • Crie um novo Activity depois de ter um item da fila.
  • Use Activity.SetParentId(message.ParentId) para correlacionar logs de consumidores e produtores.
  • Inicie o Activityarquivo .
  • Rastreie operações de dequeue, processe e delete usando Start/StopOperation auxiliares. Faça isso a partir do mesmo fluxo de controle assíncrono (contexto de execução). Desta forma, eles são correlacionados corretamente.
  • Pare o Activityarquivo .
  • Use Start/StopOperation ou chame Track a telemetria manualmente.

Tipos de dependência

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

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

Processamento em lotes

Com algumas filas, você pode retirar várias mensagens da fila com uma solicitação. O processamento dessas mensagens é presumivelmente independente e pertence às 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 em seu próprio fluxo de controle assíncrono. Para obter mais informações, consulte a seção Controle de dependências de saída.

Tarefas em segundo plano de longa execução

Alguns aplicativos iniciam operações de longa duração que podem ser causadas por solicitações do usuário. Do ponto de vista do traçado/instrumentação, não é diferente da instrumentação de solicitação ou 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, telemetryClient.StartOperation cria DependencyTelemetry e preenche o contexto de correlação. Digamos que você tenha uma operação pai que foi criada por solicitações de entrada que agendaram a operação. Desde que BackgroundTask seja iniciado no mesmo fluxo de controle assíncrono de uma solicitação de entrada, ele está correlacionado com essa operação pai. BackgroundTask e todos os itens de telemetria aninhados são automaticamente correlacionados com a solicitação que a causou, mesmo após o término da solicitação.

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

Rastreamento de dependências de saída

Você pode rastrear seu próprio tipo de dependência ou uma operação que não é suportada pelo Application Insights.

O Enqueue método na fila do Service Bus ou na fila de armazenamento pode servir como exemplos para esse rastreamento personalizado.

A abordagem geral para o rastreamento de dependência personalizada é:

  • Chame o método (extensão) que preenche as propriedades necessárias para correlação DependencyTelemetry e algumas outras propriedades, como início, carimbo TelemetryClient.StartOperation de data/hora e duração.
  • Defina outras propriedades personalizadas no DependencyTelemetry, como o nome e qualquer outro contexto necessário.
  • Faça uma chamada de dependência e aguarde.
  • Pare a operação quando StopOperation ela terminar.
  • Lidar com 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.
        }
    }
}

Eliminar uma operação faz com que a operação pare, pelo que poderá fazê-lo em vez de chamar StopOperation.

Aviso

Em alguns casos, uma exceção não entregue pode impedirfinally que seja chamada, portanto, as operações podem não ser rastreadas.

Processamento e rastreamento de operações paralelas

A chamada StopOperation apenas interrompe a operação que foi iniciada. Se a operação em execução atual não corresponder àquela que você deseja parar, StopOperation não fará nada. Essa situação pode acontecer se você iniciar várias 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;

Certifique-se de sempre chamar StartOperation e processar a operação no mesmo método assíncrono para isolar as operações em execução em paralelo. Se a operação for síncrona (ou não assíncrona), envolva o processo e acompanhe 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 ApplicationInsights vs. System.Diagnostics.Activity

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

As atividades são cidadãos de primeira classe em Application Insights. A dependência automática e a coleta de solicitações dependem muito deles, juntamente com DiagnosticSource os eventos. Se você criou Activity em seu aplicativo, isso não resultaria na criação da telemetria do Application Insights. O Application Insights precisa receber DiagnosticSource eventos e conhecer os nomes e cargas úteis dos eventos para traduzir Activity em telemetria.

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

Próximos passos