Exemplo: usar o OpenTelemetry com o Prometheus, o Grafana e o Jaeger
Este exemplo usa o Prometheus para coletar métricas, o Grafana para criar um dashboard e o Jaeger para mostrar o rastreamento distribuído.
1. Criar o projeto
Crie um projeto simples de API Web usando o modelo ASP.NET Core Vazio no Visual Studio ou o seguinte comando da CLI do .NET:
dotnet new web
2. Adicionar definições de métricas e atividades
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 de 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!";
}
Observação
A definição de API não usa nada específico do OpenTelemetry. Usa apenas as APIs do .NET para observabilidade.
4. Referenciar os pacotes do 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>
Observação
Use as versões mais recentes, pois as APIs do OTel estão em constante evolução.
5. Configurar 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 do ASP.NET Core para obter métricas e atividades do ASP.NET Core. Ele também registra os provedores Metrics
e ActivitySource
para métricas e rastreamento, respectivamente.
O código usa o exportador Prometheus para métricas, que usa o ASP.NET Core para hospedar o ponto de extremidade, portanto, você também precisa adicionar:
// Configure the Prometheus scraping endpoint
app.MapPrometheusScrapingEndpoint();
6. Executar o projeto
Execute o projeto e acesse a API com o navegador ou curl.
curl -k http://localhost:7275
Sempre que você solicitar a página, ela incrementará a contagem do 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 dos logs
As instruções de registro em log do código são geradas usando ILogger
. Por padrão, o Provedor de Console está habilitado para que a saída seja direcionada para o console.
Há algumas opções de como os logs podem sair do .NET:
- As saídas
stdout
estderr
são redirecionadas para arquivos de log por sistemas de contêiner, como o Kubernetes. - Usando bibliotecas de log que serão integradas ao ILogger, elas incluem Serilog ou NLog.
- Usando provedores de log para o OTel, como o OTLP ou o exportador do Azure Monitor, mostrado mais abaixo.
6.2 Acessar as métricas
Você pode acessar as métricas usando o ponto de extremidade /metrics
.
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 no formato de exposição Prometheus, que é legível por humanos, mas melhor compreendido pelo Prometheus. Esse tópico é abordado na próxima fase.
6.3 Acessar o rastreamento
Se você analisar o console do servidor, verá a saída do exportador de rastreamento do console, que gera as informações em um formato legível por humanos. Isso deve mostrar duas atividades, uma da ActivitySource
personalizada e 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. A segunda é criada pelo ASP.NET para a solicitação e inclui tags para as propriedades da solicitação HTTP. Você verá que ambos têm a mesma TraceId
, que identifica uma única transação e, em um sistema distribuído, pode ser usada para correlacionar os rastreamentos de cada serviço envolvido em uma transação. As IDs são transmitidas como cabeçalhos HTTP. O ASP.NET Core atribui uma TraceId
se nenhuma estiver presente quando receber uma solicitação. HttpClient
inclui os cabeçalhos por padrão em solicitações de saída. Cada atividade tem uma SpanId
, que é a combinação de TraceId
e SpanId
que identificam exclusivamente cada atividade. A atividade Greeter
é associada à atividade HTTP por meio da sua ParentSpanId
, que mapeia para a SpanId
da atividade HTTP.
Em um estágio posterior, você alimentará esses dados no Jaeger para visualizar os rastreamentos distribuídos.
7. Coletar métricas com o Prometheus
O Prometheus é um sistema de banco de dados de coleta, agregação e série temporal de métricas. Você o configura com os pontos de extremidade de métricas para cada serviço e ele periodicamente coleta os valores e os armazena no 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 uma instantâneo pontual das métricas do processo. Quando uma solicitação for feita ao ponto de extremidade de métricas, ele informará 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 detectar se os valores são anômalos. Normalmente, os serviços têm picos de uso com base na hora do dia ou em eventos mundiais, como uma maratona de compras de fim de ano. Comparando os valores com tendências históricas, você pode detectar se eles 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 de métrica. Adicionar essa funcionalidade ao processo pode ser um recurso intensivo. Além disso, em um sistema distribuído, é comum ter várias instâncias de cada nó, portanto, você precisa coletar as métricas de todos eles e, em seguida, agregar e comparar com seus valores históricos.
7.1 Instalar e configurar o Prometheus
Baixe o Prometheus na sua plataforma acessando https://prometheus.io/download/ e extraia os conteúdos do download.
Veja a parte superior da saída do servidor em execução para obter o número da porta do 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 o ponto de extremidade de extração HTTP e defina um intervalo de extração mais baixo. 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 analise a saída da porta em que está sendo executada, 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 essa URL no navegador. Agora você poderá consultar suas métricas na interface do usuário do Prometheus. Use o botão destacado na imagem a seguir para abrir o explorador de métricas, que mostra todas as métricas disponíveis.
Selecione a métrica greetings_count
para ver um grafo dos valores.
8. Usar o Grafana para criar um dashboard de métricas
O Grafana é um produto que pode criar dashboards e alertas com base no Prometheus ou em outras fontes de dados.
Baixe e instale a versão de software de código aberto do Grafana seguindo as instruções em https://grafana.com/oss/grafana/ para sua plataforma. Depois de instalado, o Grafana normalmente é executado na porta 3000, portanto, abra http://localhost:3000
no seu navegador. Você precisará fazer login; o nome de usuário e a senha padrão são ambos admin
.
No menu de hambúrguer, escolha conexões e, em seguida, insira o texto prometheus
para selecionar o tipo de ponto de extremidade. Selecione Criar uma fonte de dados do Prometheus para adicionar uma nova fonte de dados.
Você precisa definir as seguintes propriedades:
- URL do servidor Prometheus:
http://localhost:9090/
alterando a porta conforme aplicável
Selecione Salvar e Testar para verificar a configuração.
Depois de receber uma mensagem de aprovação, você poderá configurar um dashboard. Clique no link criar um dashboard mostrado no pop-up da mensagem de aprovação.
Selecione Adicionar uma visualização e, em seguida, escolha a fonte de dados do Prometheus que você acabou de adicionar como fonte de dados.
O designer de painel dashboard deve aparecer. Na metade inferior da tela, você pode definir a consulta.
Selecione a métrica greetings_count
e, em seguida, selecione Executar Consultas para ver os resultados.
Com o Grafana, você pode criar dashboards sofisticados que acompanharã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. As métricas ASP.NET apresentam várias dimensões aplicáveis ao contador. Por exemplo, o contador current-requests
do Microsoft.AspNetCore.Hosting
tem as seguintes dimensões:
Atributo | Tipo | Descrição | 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 grafos no Grafana geralmente são particionados com base em cada combinação exclusiva de dimensões. As dimensões podem ser usadas nas consultas do Grafana para filtrar ou agregar os dados. Por exemplo, se você grafar 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 Sum
e use o host
como o valor do rótulo.
9. Rastreamento distribuído com Jaeger
Na etapa 6, você viu que as informações de rastreamento distribuído estavam sendo expostas ao console. Essas informações acompanham unidades de trabalho com atividades. Algumas atividades são criadas automaticamente pela plataforma, como a do ASP.NET para representar o tratamento de uma solicitação, sendo que as bibliotecas e o código do aplicativo também podem criar atividades. O exemplo de saudações tem uma atividade Greeter
. As atividades são correlacionadas usando as tags TraceId
, 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 realizado em cada transação. O Jaeger é um projeto de código aberto para habilitar essa coleção e visualização.
Faça o download do arquivo de distribuição binária mais recente do Jaeger para sua plataforma de https://www.jaegertracing.io/download/.
Em seguida, extraia o download para um local fácil de acessar. Abra o executável jaeger-all-in-one(.exe):
./jaeger-all-in-one --collector.otlp.enabled
Examine a saída do console para localizar a porta em que ela está escutando o tráfego OTLP por meio do 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á escutando em 0.0.0.0:4317
, para que você possa configurar essa porta como o destino do exportador OTLP.
Abra o arquivo AppSettings.json
do nosso projeto e adicione a linha a seguir, alterando a porta, se aplicável.
"OTLP_ENDPOINT_URL" : "http://localhost:4317/"
Reinicie o processo de saudação para que ele possa pegar a alteração de propriedade e começar a direcionar as informações de rastreamento para o Jaeger.
Agora, você será capaz de ver a interface do usuário do Jaeger em http://localhost:16686/
por meio de um navegador.
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.
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 deixar seu aplicativo um pouco mais interessante permitindo que ele faça chamadas HTTP para si mesmo.
Adicionar um alocador
HttpClient
ao aplicativobuilder.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 podem ser rastreadas. Nesse caso, ele chama de volta para si mesmo em um loop artificial (somente aplicável em 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 grafo mais interessante em forma de pirâmide para as solicitações, pois cada nível aguarda a resposta da chamada anterior.