Partilhar via


Exemplo: Use OpenTelemetry com Prometheus, Grafana e Jaeger

Este exemplo usa Prometheus para coleta de métricas, Grafana para criar um painel e Jaeger para mostrar rastreamento distribuído.

1. Crie o projeto

Crie um projeto de API Web simples usando o modelo ASP.NET Core Empty no Visual Studio ou o seguinte comando da CLI do .NET:

dotnet new web

2. Adicione métricas e definições de atividade

O código a seguir define uma nova métrica (greetings.count) para o número de vezes que a API foi chamada e uma nova fonte de atividade (OtPrGrYa.Example).

// Custom metrics for the application
var greeterMeter = new Meter("OtPrGrYa.Example", "1.0.0");
var countGreetings = greeterMeter.CreateCounter<int>("greetings.count", description: "Counts the number of greetings");

// Custom ActivitySource for the application
var greeterActivitySource = new ActivitySource("OtPrGrJa.Example");

3. Criar um ponto de extremidade da API

app.MapGet("/", SendGreeting);
async Task<String> SendGreeting(ILogger<Program> logger)
{
    // Create a new Activity scoped to the method
    using var activity = greeterActivitySource.StartActivity("GreeterActivity");

    // Log a message
    logger.LogInformation("Sending greeting");

    // Increment the custom counter
    countGreetings.Add(1);

    // Add a tag to the Activity
    activity?.SetTag("greeting", "Hello World!");

    return "Hello World!";
}

Nota

A definição da API não usa nada específico para OpenTelemetry. Ele usa as APIs do .NET para observabilidade.

4. Faça referência aos pacotes OpenTelemetry

Use o Gerenciador de Pacotes NuGet ou a linha de comando para adicionar os seguintes pacotes NuGet:

<ItemGroup>
    <PackageReference Include="OpenTelemetry.Exporter.Console" Version="1.9.0" />
    <PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.9.0" />
    <PackageReference Include="OpenTelemetry.Exporter.Prometheus.AspNetCore" Version="1.9.0-beta.2" />
    <PackageReference Include="OpenTelemetry.Exporter.Zipkin" Version="1.9.0" />
    <PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.9.0" />
    <PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.9.0" />
    <PackageReference Include="OpenTelemetry.Instrumentation.Http" Version="1.9.0" />
</ItemGroup>

Nota

Use as versões mais recentes, pois as APIs OTel estão em constante evolução.

5. Configure o OpenTelemetry com os provedores corretos

var tracingOtlpEndpoint = builder.Configuration["OTLP_ENDPOINT_URL"];
var otel = builder.Services.AddOpenTelemetry();

// Configure OpenTelemetry Resources with the application name
otel.ConfigureResource(resource => resource
    .AddService(serviceName: builder.Environment.ApplicationName));

// Add Metrics for ASP.NET Core and our custom metrics and export to Prometheus
otel.WithMetrics(metrics => metrics
    // Metrics provider from OpenTelemetry
    .AddAspNetCoreInstrumentation()
    .AddMeter(greeterMeter.Name)
    // Metrics provides by ASP.NET Core in .NET 8
    .AddMeter("Microsoft.AspNetCore.Hosting")
    .AddMeter("Microsoft.AspNetCore.Server.Kestrel")
    .AddPrometheusExporter());

// Add Tracing for ASP.NET Core and our custom ActivitySource and export to Jaeger
otel.WithTracing(tracing =>
{
    tracing.AddAspNetCoreInstrumentation();
    tracing.AddHttpClientInstrumentation();
    tracing.AddSource(greeterActivitySource.Name);
    if (tracingOtlpEndpoint != null)
    {
        tracing.AddOtlpExporter(otlpOptions =>
         {
             otlpOptions.Endpoint = new Uri(tracingOtlpEndpoint);
         });
    }
    else
    {
        tracing.AddConsoleExporter();
    }
});

Esse código usa a instrumentação ASP.NET Core para obter métricas e atividades do ASP.NET Core. Ele também registra os Metrics e ActivitySource provedores para métricas e rastreamento, respectivamente.

O código usa o exportador Prometheus para métricas, que usa ASP.NET Core para hospedar o ponto de extremidade, então você também precisa adicionar:

// Configure the Prometheus scraping endpoint
app.MapPrometheusScrapingEndpoint();

6. Execute o projeto

Execute o projeto e, em seguida, acesse a API com o navegador ou curl.

curl -k http://localhost:7275

Cada vez que você solicitar a página, ela aumentará a contagem para o número de saudações que foram feitas. Você pode acessar o ponto de extremidade de métricas usando a mesma url base, com o caminho /metrics.

6.1 Saída de log

As instruções de log do código são saídas usando ILogger. Por padrão, o Provedor de Console é habilitado para que a saída seja direcionada para o console.

Há algumas opções de como os logs podem ser enviados do .NET:

  • stdout e stderr a saída é redirecionada para arquivos de log por sistemas de contêiner, como o Kubernetes.
  • Usando bibliotecas de log que se integrarão ao ILogger, elas incluem Serilog ou NLog.
  • Usando provedores de log para OTel, como OTLP ou o exportador do Azure Monitor mostrado mais abaixo.

6.2 Acesse as métricas

Você pode acessar as métricas usando o /metrics endpoint.

curl -k https://localhost:7275/
Hello World!

curl -k https://localhost:7275/metrics
# TYPE greetings_count counter
# HELP greetings_count Counts the number of greetings
greetings_count 1 1686894204856

# TYPE current_connections gauge
# HELP current_connections Number of connections that are currently active on the server.
current_connections{endpoint="127.0.0.1:7275"} 1 1686894204856
current_connections{endpoint="[::1]:7275"} 0 1686894204856
current_connections{endpoint="[::1]:5212"} 1 1686894204856
...

A saída de métricas é um instantâneo das métricas no momento em que o ponto de extremidade é solicitado. Os resultados são fornecidos em formato de exposição Prometheus, que é legível por humanos, mas melhor compreendido por Prometheus. Esse tema é abordado na próxima etapa.

6.3 Aceder ao rastreio

Se você olhar para o console para o servidor, verá a saída do exportador de rastreamento de console, que produz as informações em um formato legível por humanos. Isso deve mostrar duas atividades, uma do seu costume ActivitySourcee outra do ASP.NET Core:

Activity.TraceId:            2e00dd5e258d33fe691b965607b91d18
Activity.SpanId:             3b7a891f55b97f1a
Activity.TraceFlags:         Recorded
Activity.ParentSpanId:       645071fd0011faac
Activity.ActivitySourceName: OtPrGrYa.Example
Activity.DisplayName:        GreeterActivity
Activity.Kind:               Internal
Activity.StartTime:          2023-06-16T04:50:26.7675469Z
Activity.Duration:           00:00:00.0023974
Activity.Tags:
    greeting: Hello World!
Resource associated with Activity:
    service.name: OTel-Prometheus-Grafana-Jaeger
    service.instance.id: e1afb619-bc32-48d8-b71f-ee196dc2a76a
    telemetry.sdk.name: opentelemetry
    telemetry.sdk.language: dotnet
    telemetry.sdk.version: 1.5.0

Activity.TraceId:            2e00dd5e258d33fe691b965607b91d18
Activity.SpanId:             645071fd0011faac
Activity.TraceFlags:         Recorded
Activity.ActivitySourceName: Microsoft.AspNetCore
Activity.DisplayName:        /
Activity.Kind:               Server
Activity.StartTime:          2023-06-16T04:50:26.7672615Z
Activity.Duration:           00:00:00.0121259
Activity.Tags:
    net.host.name: localhost
    net.host.port: 7275
    http.method: GET
    http.scheme: https
    http.target: /
    http.url: https://localhost:7275/
    http.flavor: 1.1
    http.user_agent: curl/8.0.1
    http.status_code: 200
Resource associated with Activity:
    service.name: OTel-Prometheus-Grafana-Jaeger
    service.instance.id: e1afb619-bc32-48d8-b71f-ee196dc2a76a
    telemetry.sdk.name: opentelemetry
    telemetry.sdk.language: dotnet
    telemetry.sdk.version: 1.5.0

A primeira é a atividade personalizada interna que você criou. O segundo é criado por ASP.NET para a solicitação e inclui tags para as propriedades da solicitação HTTP. Você verá que ambos têm o mesmo TraceId, que identifica uma única transação e em um sistema distribuído pode ser usado para correlacionar os rastreamentos de cada serviço envolvido em uma transação. Os IDs são transmitidos como cabeçalhos HTTP. ASP.NET Core atribui um TraceId se nenhum estiver presente quando recebe uma solicitação. HttpClient Inclui os cabeçalhos por padrão em solicitações de saída. Cada atividade tem um SpanId, que é a combinação de TraceId e SpanId que identificam de forma única cada atividade. A Greeter atividade é parentada à atividade HTTP por meio de seu ParentSpanId, que mapeia para a SpanId atividade HTTP.

Em um estágio posterior, você alimentará esses dados no Jaeger para visualizar os rastreamentos distribuídos.

7. Colete métricas com Prometheus

Prometheus é um sistema de banco de dados de coleta de métricas, agregação e séries temporais. Você o configura com os pontos de extremidade métricos para cada serviço e ele periodicamente raspa os valores e os armazena em seu banco de dados de séries temporais. Em seguida, você pode analisá-los e processá-los conforme necessário.

Os dados de métricas expostos no formato Prometheus são um instantâneo point-in-time das métricas do processo. Cada vez que uma solicitação é feita para o ponto de extremidade de métricas, ele relatará os valores atuais. Embora os valores atuais sejam interessantes, eles se tornam mais valiosos quando comparados aos valores históricos para ver tendências e detetar se os valores são anômalos. Comumente, os serviços têm picos de uso com base na hora do dia ou eventos mundiais, como uma onda de compras de fim de ano. Ao comparar os valores com as tendências históricas, você pode detetar se elas são anormais ou se uma métrica está piorando lentamente ao longo do tempo.

O processo não armazena nenhum histórico desses instantâneos métricos. Adicionar essa capacidade ao processo pode exigir muitos recursos. Além disso, em um sistema distribuído, você geralmente tem várias instâncias de cada nó, portanto, deseja ser capaz de coletar as métricas de todas elas e, em seguida, agregar e comparar com seus valores históricos.

7.1 Instalar e configurar o Prometheus

Faça o download do https://prometheus.io/download/ Prometheus para a sua plataforma e extraia o conteúdo do download.

Observe a parte superior da saída do seu servidor em execução para obter o número da porta para o ponto de extremidade http . Por exemplo:

info: Microsoft.Hosting.Lifetime[14]
      Now listening on: https://localhost:7275
info: Microsoft.Hosting.Lifetime[14]
      Now listening on: http://localhost:5212

Modifique o arquivo de configuração do Prometheus YAML para especificar a porta para seu ponto de extremidade de raspagem HTTP e defina um intervalo de raspagem menor. Por exemplo:

  scrape_configs:
  # The job name is added as a label `job=<job_name>` to any timeseries scraped from this config.
  - job_name: "prometheus"

    # metrics_path defaults to '/metrics'
    # scheme defaults to 'http'.

    scrape_interval: 1s # poll very quickly for a more responsive demo
    static_configs:
      - targets: ["localhost:5212"]

Inicie o Prometheus e procure na saída a porta em que ele está sendo executado, normalmente 9090:

>prometheus.exe
...
ts=2023-06-16T05:29:02.789Z caller=web.go:562 level=info component=web msg="Start listening for connections" address=0.0.0.0:9090

Abra este URL no seu navegador. Na interface do usuário do Prometheus, agora você deve ser capaz de consultar suas métricas. Use o botão realçado na imagem a seguir para abrir o explorador de métricas, que mostra todas as métricas disponíveis.

Explorador de métricas Prometheus

Selecione a greetings_count métrica para ver um gráfico de valores.

Gráfico de greetings_count

8. Use o Grafana para criar um painel de métricas

Grafana é um produto de dashboarding que pode criar dashboards e alertas com base no Prometheus ou noutras fontes de dados.

Baixe e instale a versão OSS do Grafana https://grafana.com/oss/grafana/ seguindo as instruções para sua plataforma. Uma vez instalado, o Grafana é normalmente executado na porta 3000, por isso abra http://localhost:3000 no seu navegador. Terá de iniciar sessão; O nome de usuário e a senha padrão são ambos admin.

No menu hambúrguer, escolha conexões e insira o texto prometheus para selecionar o tipo de ponto final. Selecione Criar uma fonte de dados Prometheus para adicionar uma nova fonte de dados.

Ligação de grafana a Prometheus

Você precisa definir as seguintes propriedades:

  • URL do servidor Prometheus: http://localhost:9090/ alterando a porta conforme aplicável

Selecione Save & Test para verificar a configuração.

Depois de receber uma mensagem de sucesso, você pode configurar um painel. Clique no link de criação de um painel mostrado no pop-up para a mensagem de sucesso.

Selecione Adicionar uma visualização e, em seguida, escolha a fonte de dados Prometheus que você acabou de adicionar como a fonte de dados.

O designer do painel do painel deve aparecer. Na metade inferior da tela, você pode definir a consulta.

Consulta Grafana usando greetings_count

Selecione a greetings_count métrica e, em seguida, selecione Executar consultas para ver os resultados.

Com o Grafana, você pode projetar painéis sofisticados que rastrearão qualquer número de métricas.

Cada métrica no .NET pode ter dimensões adicionais, que são pares chave-valor que podem ser usados para particionar os dados. Todas as métricas ASP.NET apresentam uma série de dimensões aplicáveis ao contador. Por exemplo, o current-requests contador de Microsoft.AspNetCore.Hosting tem as seguintes dimensões:

Atributo Tipo Description Exemplos Presença
method string Método de solicitação HTTP. GET; POST; HEAD Sempre
scheme string O esquema de URI que identifica o protocolo usado. http; https Sempre
host string Nome do servidor HTTP local que recebeu a solicitação. localhost Sempre
port int Porta do servidor HTTP local que recebeu a solicitação. 8080 Adicionado se não for padrão (80 para http ou 443 para https)

Os gráficos em Grafana são geralmente particionados com base em cada combinação única de dimensões. As dimensões podem ser usadas nas consultas Grafana para filtrar ou agregar os dados. Por exemplo, se você criar um gráfico current_requests, verá valores particionados com base em cada combinação de dimensões. Para filtrar com base apenas no host, adicione uma operação de e use host como o valor do Sum rótulo.

Grafana current_requests por anfitrião

9. Rastreamento distribuído com Jaeger

Na etapa 6, você viu que as informações de rastreamento distribuído estavam sendo expostas ao console. Esta informação rastreia unidades de trabalho com atividades. Algumas atividades são criadas automaticamente pela plataforma, como a de ASP.NET para representar o tratamento de uma solicitação, e bibliotecas e código do aplicativo também podem criar atividades. O exemplo das saudações tem uma Greeter atividade. As atividades são correlacionadas usando as TraceIdtags , SpanId, e ParentId .

Cada processo em um sistema distribuído produz seu próprio fluxo de informações de atividade e, como métricas, você precisa de um sistema para coletar, armazenar e correlacionar as atividades para poder visualizar o trabalho feito para cada transação. Jaeger é um projeto de código aberto para permitir essa coleção e visualização.

Faça o download do arquivo de distribuição binária mais recente do Jaeger para sua plataforma em https://www.jaegertracing.io/download/.

Em seguida, extraia o download para um local de fácil acesso. Execute o executável jaeger-all-in-one(.exe ):

./jaeger-all-in-one --collector.otlp.enabled

Examine a saída do console para encontrar a porta onde ele está ouvindo o tráfego OTLP via gRPC. Por exemplo:

{"level":"info","ts":1686963686.3854616,"caller":"otlpreceiver@v0.78.2/otlp.go:83","msg":"Starting GRPC server","endpoint":"0.0.0.0:4317"}

Essa saída informa que está ouvindo no 0.0.0.0:4317, para que você possa configurar essa porta como o destino para seu exportador OTLP.

Abra o AppSettings.json arquivo para o nosso projeto e adicione a seguinte linha, alterando a porta, se aplicável.

"OTLP_ENDPOINT_URL" :  "http://localhost:4317/"

Reinicie o processo de rececionista para que ele possa pegar a alteração de propriedade e começar a direcionar as informações de rastreamento para Jaeger.

Agora, você deve ser capaz de ver a interface do usuário do Jaeger em http://localhost:16686/ a partir de um navegador da web.

Consulta Jaeger para rastreamentos

Para ver uma lista de rastreamentos, selecione OTel-Prometheus-grafana-Jaeger na lista suspensa Serviço . A seleção de um rastreamento deve mostrar um gráfico de gantt das atividades como parte desse rastreamento. Clicar em cada uma das operações mostra mais detalhes sobre a atividade.

Detalhes da operação Jaeger

Em um sistema distribuído, você deseja enviar rastreamentos de todos os processos para a mesma instalação do Jaeger para que ele possa correlacionar as transações em todo o sistema.

Você pode tornar seu aplicativo um pouco mais interessante fazendo com que ele faça chamadas HTTP para si mesmo.

  • Adicionar uma HttpClient fábrica ao aplicativo

    builder.Services.AddHttpClient();
    
  • Adicionar um novo ponto de extremidade para fazer chamadas de saudação aninhadas

    app.MapGet("/NestedGreeting", SendNestedGreeting);
    
  • Implemente o ponto de extremidade para que ele faça chamadas HTTP que também possam ser rastreadas. Neste caso, ele chama de volta para si mesmo em um loop artificial (realmente aplicável apenas a cenários de demonstração).

    async Task SendNestedGreeting(int nestlevel, ILogger<Program> logger, HttpContext context, IHttpClientFactory clientFactory)
    {
        // Create a new Activity scoped to the method
        using var activity = greeterActivitySource.StartActivity("GreeterActivity");
    
        if (nestlevel <= 5)
        {
            // Log a message
            logger.LogInformation("Sending greeting, level {nestlevel}", nestlevel);
    
            // Increment the custom counter
            countGreetings.Add(1);
    
            // Add a tag to the Activity
            activity?.SetTag("nest-level", nestlevel);
    
            await context.Response.WriteAsync($"Nested Greeting, level: {nestlevel}\r\n");
    
            if (nestlevel > 0)
            {
                var request = context.Request;
                var url = new Uri($"{request.Scheme}://{request.Host}{request.Path}?nestlevel={nestlevel - 1}");
    
                // Makes an http call passing the activity information as http headers
                var nestedResult = await clientFactory.CreateClient().GetStringAsync(url);
                await context.Response.WriteAsync(nestedResult);
            }
        }
        else
        {
            // Log a message
            logger.LogError("Greeting nest level {nestlevel} too high", nestlevel);
            await context.Response.WriteAsync("Nest level too high, max is 5");
        }
    }
    

Isso resulta em um gráfico mais interessante com uma forma de pirâmide para as solicitações, já que cada nível aguarda a resposta da chamada anterior.

Resultados de dependência aninhados de Jaeger