Partilhar via


Adicionando instrumentação de rastreamento distribuído

Este artigo aplica-se a: ✔️ .NET Core 2.1 e versões ✔️ posteriores .NET Framework 4.5 e versões posteriores

As aplicações .NET podem ser instrumentadas usando a API System.Diagnostics.Activity para a produção de telemetria de rastreamento distribuído. Alguma instrumentação é incorporada em bibliotecas .NET padrão, mas você pode querer adicionar mais para tornar seu código mais facilmente diagnosticável. Neste tutorial, você adicionará uma nova instrumentação de rastreamento distribuído personalizada. Consulte o tutorial da coleção para saber mais sobre como gravar a telemetria produzida por esta instrumentação.

Pré-requisitos

Criar aplicativo inicial

Primeiro, você criará um aplicativo de exemplo que coleta telemetria usando OpenTelemetry, mas ainda não tem nenhuma instrumentação.

dotnet new console

Os aplicativos destinados ao .NET 5 e posteriores já têm as APIs de rastreamento distribuído necessárias incluídas. Para aplicativos destinados a versões mais antigas do .NET, adicione o pacote NuGet System.Diagnostics.DiagnosticSource versão 5 ou superior. Para bibliotecas direcionadas ao netstandard, recomendamos fazer referência à versão mais antiga do pacote, que ainda é suportada e contém as APIs de que sua biblioteca precisa.

dotnet add package System.Diagnostics.DiagnosticSource

Adicione os pacotes OpenTelemetry e OpenTelemetry.Exporter.Console NuGet, que serão usados para coletar a telemetria.

dotnet add package OpenTelemetry
dotnet add package OpenTelemetry.Exporter.Console

Substitua o conteúdo do Program.cs gerado por esta fonte de exemplo:

using OpenTelemetry;
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;
using System;
using System.Threading.Tasks;

namespace Sample.DistributedTracing
{
    class Program
    {
        static async Task Main(string[] args)
        {
            using var tracerProvider = Sdk.CreateTracerProviderBuilder()
                .SetResourceBuilder(ResourceBuilder.CreateDefault().AddService("MySample"))
                .AddSource("Sample.DistributedTracing")
                .AddConsoleExporter()
                .Build();

            await DoSomeWork("banana", 8);
            Console.WriteLine("Example work done");
        }

        // All the functions below simulate doing some arbitrary work
        static async Task DoSomeWork(string foo, int bar)
        {
            await StepOne();
            await StepTwo();
        }

        static async Task StepOne()
        {
            await Task.Delay(500);
        }

        static async Task StepTwo()
        {
            await Task.Delay(1000);
        }
    }
}

O aplicativo ainda não tem instrumentação, portanto, não há informações de rastreamento para exibir:

> dotnet run
Example work done

Melhores práticas

Somente os desenvolvedores de aplicativos precisam fazer referência a uma biblioteca de terceiros opcional para coletar a telemetria de rastreamento distribuída, como OpenTelemetry neste exemplo. Os autores de bibliotecas .NET podem confiar exclusivamente em APIs de System.Diagnostics.DiagnosticSource, que faz parte do runtime .NET. Isso garante que as bibliotecas serão executadas em uma ampla variedade de aplicativos .NET, independentemente das preferências do desenvolvedor do aplicativo sobre qual biblioteca ou fornecedor usar para coletar telemetria.

Adicionar instrumentação básica

Aplicações e bibliotecas adicionam instrumentação de rastreamento distribuído usando as classes System.Diagnostics.ActivitySource e System.Diagnostics.Activity.

ActivitySource

Primeiro, crie uma instância de ActivitySource. ActivitySource fornece APIs para criar e iniciar objetos Activity. Adicione a variável estática ActivitySource acima Main() e using System.Diagnostics; às using diretivas.

using OpenTelemetry;
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;
using System;
using System.Diagnostics;
using System.Threading.Tasks;

namespace Sample.DistributedTracing
{
    class Program
    {
        private static ActivitySource source = new ActivitySource("Sample.DistributedTracing", "1.0.0");

        static async Task Main(string[] args)
        {
            // ...

Melhores práticas

  • Crie o ActivitySource uma vez, armazene-o em uma variável estática e use essa instância o tempo necessário. Cada biblioteca ou subcomponente de biblioteca pode (e muitas vezes deve) criar sua própria fonte. Considere criar uma nova fonte em vez de reutilizar uma existente se você antecipar que os desenvolvedores de aplicativos gostariam de poder habilitar e desabilitar a telemetria de atividade nas fontes de forma independente.

  • O nome de origem passado para o construtor tem que ser exclusivo para evitar os conflitos com quaisquer outras fontes. Se houver várias fontes dentro do mesmo assembly, use um nome hierárquico que contenha o nome do assembly e, opcionalmente, um nome de componente, por exemplo, Microsoft.AspNetCore.Hosting. Se um assembly estiver a adicionar instrumentação para código noutro assembly independente, o nome deve ser baseado no assembly que define o ActivitySource, não no assembly cujo código está a ser instrumentado.

  • O parâmetro version é opcional. Recomendamos que você forneça a versão caso libere várias versões da biblioteca e faça alterações na telemetria instrumentada.

Observação

OpenTelemetry usa termos alternativos 'Tracer' e 'Span'. No .NET 'ActivitySource' é a implementação do Tracer e Activity é a implementação do 'Span'. . O tipo de atividade da NET é muito anterior à especificação OpenTelemetry e a nomenclatura original do .NET foi preservada para consistência dentro do ecossistema .NET e compatibilidade de aplicativos .NET.

Atividade

Use the ActivitySource object to Start and Stop Activity objects around meaningful units of work. Atualize DoSomeWork() com o código mostrado aqui:

static async Task DoSomeWork(string foo, int bar)
{
    using (Activity activity = source.StartActivity("SomeWork"))
    {
        await StepOne();
        await StepTwo();
    }
}

A execução do aplicativo agora mostra a nova atividade sendo registrada:

> dotnet run
Activity.Id:          00-f443e487a4998c41a6fd6fe88bae644e-5b7253de08ed474f-01
Activity.DisplayName: SomeWork
Activity.Kind:        Internal
Activity.StartTime:   2021-03-18T10:36:51.4720202Z
Activity.Duration:    00:00:01.5025842
Resource associated with Activity:
    service.name: MySample
    service.instance.id: 067f4bb5-a5a8-4898-a288-dec569d6dbef

Observações

  • ActivitySource.StartActivity creates and starts the activity at the same time. The listed code pattern is using the using block, which automatically disposes the created Activity object after executing the block. Descartar o objeto Activity irá pará-lo, de modo que o código não precise chamar Activity.Stop() explicitamente. Isso simplifica o padrão de codificação.

  • ActivitySource.StartActivity determina internamente se há ouvintes gravando a Atividade. Se não houver ouvintes registrados ou se houver ouvintes que não estejam interessados, StartActivity() retornarão null e evitarão criar o objeto Activity. Esta é uma otimização de desempenho para que o padrão de código ainda possa ser usado em funções que são chamadas com freqüência.

Optional: Populate tags

As atividades suportam dados de chave-valor chamados Tags, comumente usados para armazenar quaisquer parâmetros do trabalho que possam ser úteis para diagnósticos. Atualize DoSomeWork() para incluí-los:

static async Task DoSomeWork(string foo, int bar)
{
    using (Activity activity = source.StartActivity("SomeWork"))
    {
        activity?.SetTag("foo", foo);
        activity?.SetTag("bar", bar);
        await StepOne();
        await StepTwo();
    }
}
> dotnet run
Activity.Id:          00-2b56072db8cb5a4496a4bfb69f46aa06-7bc4acda3b9cce4d-01
Activity.DisplayName: SomeWork
Activity.Kind:        Internal
Activity.StartTime:   2021-03-18T10:37:31.4949570Z
Activity.Duration:    00:00:01.5417719
Activity.TagObjects:
    foo: banana
    bar: 8
Resource associated with Activity:
    service.name: MySample
    service.instance.id: 25bbc1c3-2de5-48d9-9333-062377fea49c

Example work done

Melhores práticas

  • Como mencionado acima, activity devolvido por ActivitySource.StartActivity pode ser nulo. The null-coalescing operator ?. in C# is a convenient short-hand to only invoke Activity.SetTag if activity is not null. The behavior is identical to writing:
if(activity != null)
{
    activity.SetTag("foo", foo);
}
  • OpenTelemetry fornece um conjunto de convenções recomendadas para definir Tags em Atividades que representam tipos comuns de trabalho de aplicativo.

  • If you're instrumenting functions with high-performance requirements, Activity.IsAllDataRequested is a hint that indicates whether any of the code listening to Activities intends to read auxiliary information such as Tags. Se nenhum ouvinte o ler, não há necessidade de o código instrumentado passar ciclos de CPU preenchendo-o. Para simplificar, este exemplo não aplica essa otimização.

Opcional: Adicionar eventos

Os eventos são mensagens com carimbo de data/hora que podem anexar um fluxo arbitrário de dados de diagnóstico adicionais às Atividades. Adicione alguns eventos à Atividade:

static async Task DoSomeWork(string foo, int bar)
{
    using (Activity activity = source.StartActivity("SomeWork"))
    {
        activity?.SetTag("foo", foo);
        activity?.SetTag("bar", bar);
        await StepOne();
        activity?.AddEvent(new ActivityEvent("Part way there"));
        await StepTwo();
        activity?.AddEvent(new ActivityEvent("Done now"));
    }
}
> dotnet run
Activity.Id:          00-82cf6ea92661b84d9fd881731741d04e-33fff2835a03c041-01
Activity.DisplayName: SomeWork
Activity.Kind:        Internal
Activity.StartTime:   2021-03-18T10:39:10.6902609Z
Activity.Duration:    00:00:01.5147582
Activity.TagObjects:
    foo: banana
    bar: 8
Activity.Events:
    Part way there [3/18/2021 10:39:11 AM +00:00]
    Done now [3/18/2021 10:39:12 AM +00:00]
Resource associated with Activity:
    service.name: MySample
    service.instance.id: ea7f0fcb-3673-48e0-b6ce-e4af5a86ce4f

Example work done

Melhores práticas

  • Os eventos são armazenados em uma lista na memória até que possam ser transmitidos, o que torna esse mecanismo adequado apenas para registrar um número modesto de eventos. Para um volume grande ou ilimitado de eventos, é melhor usar uma API de log focada nessa tarefa, como o ILogger. O ILogger também garante que as informações de log estarão disponíveis independentemente de o desenvolvedor do aplicativo optar por usar o rastreamento distribuído. O ILogger suporta a captura automática dos IDs de atividade ativos para que as mensagens registradas por meio dessa API ainda possam ser correlacionadas com o rastreamento distribuído.

Opcional: Adicionar status

OpenTelemetry allows each Activity to report a Status that represents the pass/fail result of the work. .NET has a strongly-typed API for this purpose:

Os ActivityStatusCode valores são representados como, Unset, Ok, e Error.

Atualize DoSomeWork() para definir o status:

static async Task DoSomeWork(string foo, int bar)
{
    using (Activity activity = source.StartActivity("SomeWork"))
    {
        activity?.SetTag("foo", foo);
        activity?.SetTag("bar", bar);
        await StepOne();
        activity?.AddEvent(new ActivityEvent("Part way there"));
        await StepTwo();
        activity?.AddEvent(new ActivityEvent("Done now"));

        // Pretend something went wrong
        activity?.SetStatus(ActivityStatusCode.Error, "Use this text give more information about the error");
    }
}

Opcional: Adicionar atividades adicionais

As atividades podem ser encadeadas para descrever partes de uma unidade maior de trabalho. Isso pode ser valioso em torno de partes de código que podem não ser executadas rapidamente ou para localizar melhor falhas provenientes de dependências externas específicas. Embora este exemplo use uma atividade em cada método, isso ocorre apenas porque o código extra foi minimizado. In a larger and more realistic project, using an Activity in every method would produce extremely verbose traces, so it's not recommended.

Atualize o StepOne e o StepTwo para adicionar mais rastreamento em torno destas etapas separadas:

static async Task StepOne()
{
    using (Activity activity = source.StartActivity("StepOne"))
    {
        await Task.Delay(500);
    }
}

static async Task StepTwo()
{
    using (Activity activity = source.StartActivity("StepTwo"))
    {
        await Task.Delay(1000);
    }
}
> dotnet run
Activity.Id:          00-9d5aa439e0df7e49b4abff8d2d5329a9-39cac574e8fda44b-01
Activity.ParentId:    00-9d5aa439e0df7e49b4abff8d2d5329a9-f16529d0b7c49e44-01
Activity.DisplayName: StepOne
Activity.Kind:        Internal
Activity.StartTime:   2021-03-18T10:40:51.4278822Z
Activity.Duration:    00:00:00.5051364
Resource associated with Activity:
    service.name: MySample
    service.instance.id: e0a8c12c-249d-4bdd-8180-8931b9b6e8d0

Activity.Id:          00-9d5aa439e0df7e49b4abff8d2d5329a9-4ccccb6efdc59546-01
Activity.ParentId:    00-9d5aa439e0df7e49b4abff8d2d5329a9-f16529d0b7c49e44-01
Activity.DisplayName: StepTwo
Activity.Kind:        Internal
Activity.StartTime:   2021-03-18T10:40:51.9441095Z
Activity.Duration:    00:00:01.0052729
Resource associated with Activity:
    service.name: MySample
    service.instance.id: e0a8c12c-249d-4bdd-8180-8931b9b6e8d0

Activity.Id:          00-9d5aa439e0df7e49b4abff8d2d5329a9-f16529d0b7c49e44-01
Activity.DisplayName: SomeWork
Activity.Kind:        Internal
Activity.StartTime:   2021-03-18T10:40:51.4256627Z
Activity.Duration:    00:00:01.5286408
Activity.TagObjects:
    foo: banana
    bar: 8
    otel.status_code: ERROR
    otel.status_description: Use this text give more information about the error
Activity.Events:
    Part way there [3/18/2021 10:40:51 AM +00:00]
    Done now [3/18/2021 10:40:52 AM +00:00]
Resource associated with Activity:
    service.name: MySample
    service.instance.id: e0a8c12c-249d-4bdd-8180-8931b9b6e8d0

Example work done

Observe que StepOne e StepTwo incluem um ParentId que se refere a SomeWork. The console is not a great visualization of nested trees of work, but many GUI viewers such as Zipkin can show this as a Gantt chart:

Gráfico de Zipkin Gantt

Opcional: ActivityKind

Activities have an Activity.Kind property, which describes the relationship between the Activity, its parent, and its children. Por padrão, todas as novas Atividades são definidas como Internal, o que é apropriado para Atividades que são uma operação interna dentro de um aplicativo sem pai ou filhos remotos. Outros tipos podem ser definidos usando o parâmetro kind em ActivitySource.StartActivity. Para outras opções, consulte System.Diagnostics.ActivityKind.

Quando o trabalho ocorre em sistemas de processamento em lote, uma única Atividade pode representar o trabalho em nome de muitas solicitações diferentes simultaneamente, cada uma das quais tem o seu próprio ID de rastreamento. Embora a Atividade seja restrita a ter um único pai, pode ser vinculada a IDs de rastreamento adicionais usando System.Diagnostics.ActivityLink. Cada ActivityLink é preenchido com um ActivityContext que armazena informações de ID sobre a Atividade à qual está sendo vinculada. ActivityContext pode ser recuperado de objetos Activity em processo usando Activity.Context ou pode ser analisado a partir de informações de ID serializadas usando ActivityContext.Parse(String, String).

void DoBatchWork(ActivityContext[] requestContexts)
{
    // Assume each context in requestContexts encodes the trace-id that was sent with a request
    using(Activity activity = s_source.StartActivity(name: "BigBatchOfWork",
                                                     kind: ActivityKind.Internal,
                                                     parentContext: default,
                                                     links: requestContexts.Select(ctx => new ActivityLink(ctx))
    {
        // do the batch of work here
    }
}

Ao contrário de eventos e tags que podem ser adicionados sob demanda, os links devem ser adicionados durante StartActivity() e são imutáveis depois.

Important

De acordo com a especificação OpenTelemetry , o limite sugerido para o número de links é 128. No entanto, é importante notar que esse limite não é aplicado.