Instrumentação .NET com Elven Observability
Sumário
- Visão geral
- Pré-requisitos
- Caminho recomendado
- Quick Start - Docker zero-code (recomendado)
- Quick Start - Zero-code no processo
- Pacotes NuGet (para uso programático)
- Quick Start - ASP.NET Core programático
- Quick Start - Worker Service programático
- Quick Start - Console app
- Configuração por variáveis de ambiente
- Configuração via appsettings.json
- Deploy com Docker
- Guia por tipo de aplicação
- Usando logs, traces e métricas manuais
- Instrumentações automáticas
- Mapeamento de erros
- Recursos e metadados
- Configuração avançada
- Native AOT e trimming
- Validação ponta a ponta
- Boas práticas
- Troubleshooting
- FAQ
Visão geral
A instrumentação .NET da Elven é uma distribuição OpenTelemetry pronta para produção. Ela configura o SDK oficial do OpenTelemetry .NET com os padrões da Elven Observability para o stack LGTM:
- Grafana Loki para logs
- Grafana Tempo para traces
- Grafana Mimir para métricas
- Grafana para visualização, correlação e alertas
┌──────────────────────────────────────────────────────────────┐
│ Elven.Observability │
│ │
│ ┌────────────────────┐ ┌────────────────────┐ ┌───────────┐ │
│ │ Traces │ │ Metrics │ │ Logs │ │
│ │ │ │ │ │ │ │
│ │ - ASP.NET Core │ │ - Runtime .NET │ │ - ILogger │ │
│ │ - HttpClient │ │ - Process │ │ - Scopes │ │
│ │ - gRPC │ │ - HTTP/Kestrel │ │ - State │ │
│ │ - SQL/EF/Redis │ │ - HttpClient │ │ - Trace │ │
│ │ - Cloud/Messaging │ │ - Custom meters │ │ link │ │
│ └─────────┬──────────┘ └─────────┬──────────┘ └─────┬─────┘ │
│ │ │ │ │
│ └──────────────────────┼──────────────────┘ │
│ ▼ │
│ OTLP gRPC ou HTTP/protobuf │
└───────────────────────────────────┬──────────────────────────┘
│
▼
OTLP Collector no ambiente do cliente
│
▼
Elven Observability backend
│
┌──────────────────┼──────────────────┐
▼ ▼ ▼
Tempo Mimir Loki
| Componente | O que faz |
|---|---|
Elven.Observability |
Pacote principal. Instale este na maioria dos projetos. |
Elven.Observability.Hosting |
Extensões para IHostApplicationBuilder e IServiceCollection. |
Elven.Observability.AspNetCore |
Extensões ASP.NET Core, middleware de erros e health check. |
Elven.Observability.AutoInstrumentation |
Plugin para zero-code instrumentation com o CLR profiler do OpenTelemetry. |
elvenobservability/dotnet-instrumentation |
Imagem Docker com a camada de auto-instrumentação pronta para uso. |
Importante: em modo Elven, logs, métricas e traces ficam sempre ligados. Configurações que tentam desligar um sinal, como
OTEL_LOGS_EXPORTER=none, são tratadas como erro de configuração.Arquitetura: o Collector OTLP fica sempre do lado do cliente: Kubernetes, ECS, VM, Docker Compose, appliance interno ou outro ambiente gerenciado pelo cliente. A aplicação .NET envia OTLP para esse Collector do cliente, e ele encaminha para os backends Elven.
Pré-requisitos
- .NET 8 LTS, .NET 9 ou .NET 10
- Aplicações ASP.NET Core, Worker Services, Generic Host, console apps, gRPC, serviços em container ou jobs
- Collector OTLP implantado no ambiente do cliente
- Credenciais da Elven Observability:
- Tenant ID
- API Token
- Endpoint OTLP do Collector do cliente
Protocolos suportados
| Protocolo | Valor | Uso recomendado |
|---|---|---|
| OTLP gRPC | grpc |
Padrão recomendado para aplicações .NET. |
| OTLP HTTP/protobuf | http/protobuf |
Útil em ambientes que bloqueiam gRPC ou exigem HTTP. |
Targets suportados
| Target framework | Status |
|---|---|
net8.0 |
Suportado, base mínima LTS. |
net9.0 |
Suportado. |
net10.0 |
Suportado. |
Caminho recomendado
Comece sempre pelo caminho com menor atrito operacional. A ideia é instrumentar primeiro, validar os três sinais no Grafana e só depois decidir se precisa de customização no código.
| Prioridade | Caminho | Quando usar |
|---|---|---|
| 1 | Docker zero-code com env vars | Recomendado para a maioria dos serviços containerizados. Não exige alterar o código. |
| 2 | Zero-code no processo | Bom para VM, systemd, processos dotnet diretos e ambientes sem rebuild da imagem. |
| 3 | Programático ASP.NET Core / Worker | Use quando precisa de spans, métricas, health check e políticas customizadas no código. |
| 4 | Console/manual | Use para jobs curtos, CLIs e aplicações sem Host/DI. |
O endpoint OTLP usado nos exemplos deve apontar para o Collector do cliente, não para um collector compartilhado da Elven.
Quick Start - Docker zero-code (recomendado)
Este é o caminho preferencial para aplicações .NET em container. A aplicação não precisa instalar o pacote NuGet nem alterar Program.cs; a camada elvenobservability/dotnet-instrumentation injeta o CLR profiler do OpenTelemetry e o plugin da Elven.
1. Adicione a camada Elven no Dockerfile
FROM mcr.microsoft.com/dotnet/aspnet:10.0
COPY --from=elvenobservability/dotnet-instrumentation:latest /otel /otel
ENV CORECLR_ENABLE_PROFILING=1 <br>
CORECLR_PROFILER={918728DD-259F-4A6A-AC2B-B85E1B658318} <br>
CORECLR_PROFILER_PATH=/otel/current/OpenTelemetry.AutoInstrumentation.Native.so <br>
DOTNET_ADDITIONAL_DEPS=/otel/AdditionalDeps <br>
DOTNET_SHARED_STORE=/otel/store <br>
DOTNET_STARTUP_HOOKS=/otel/net/OpenTelemetry.AutoInstrumentation.StartupHook.dll <br>
OTEL_DOTNET_AUTO_HOME=/otel <br>
OTEL_DOTNET_AUTO_PLUGINS="Elven.Observability.AutoInstrumentation.ElvenPlugin, Elven.Observability.AutoInstrumentation" <br>
OTEL_TRACES_EXPORTER=otlp <br>
OTEL_METRICS_EXPORTER=otlp <br>
OTEL_LOGS_EXPORTER=otlp <br>
OTEL_EXPORTER_OTLP_PROTOCOL=grpc
WORKDIR /app
COPY . /app
ENTRYPOINT ["dotnet", "/app/MinhaAplicacao.dll"]
2. Configure por variáveis de ambiente
docker run --rm <br>
-e OTEL_SERVICE_NAME="minha-api" <br>
-e OTEL_SERVICE_VERSION="1.0.0" <br>
-e ELVEN_SERVICE_NAMESPACE="checkout" <br>
-e ELVEN_ENVIRONMENT="production" <br>
-e ELVEN_REGION="sa-east-1" <br>
-e OTEL_EXPORTER_OTLP_ENDPOINT="https://otel-collector.sua-infra.com:4317" <br>
-e OTEL_EXPORTER_OTLP_PROTOCOL="grpc" <br>
-e OTEL_TRACES_EXPORTER="otlp" <br>
-e OTEL_METRICS_EXPORTER="otlp" <br>
-e OTEL_LOGS_EXPORTER="otlp" <br>
-e ELVEN_TENANT_ID="seu-tenant-id" <br>
-e ELVEN_API_KEY="seu-api-token" <br>
-p 8080:8080 <br>
minha-api:latest
3. Gere tráfego e valide
curl http://localhost:8080/
No Grafana, procure pelo service.name configurado em OTEL_SERVICE_NAME.
Quick Start - Zero-code no processo
Use este modo em VM, systemd, shells, CI ou qualquer ambiente onde a aplicação roda com dotnet MinhaAplicacao.dll e você não quer alterar o código.
Native AOT não suporta CLR profiler. Para Native AOT, use inicialização programática.
Linux/macOS
curl -sSL https://raw.githubusercontent.com/elven-observability/opentelemetry-instrumentation-dotnet/main/scripts/install.sh | sh
. "$HOME/.otel-dotnet-auto/instrument.sh"
export OTEL_DOTNET_AUTO_PLUGINS="Elven.Observability.AutoInstrumentation.ElvenPlugin, Elven.Observability.AutoInstrumentation"
export OTEL_SERVICE_NAME="minha-api"
export OTEL_SERVICE_VERSION="1.0.0"
export ELVEN_SERVICE_NAMESPACE="checkout"
export ELVEN_ENVIRONMENT="production"
export OTEL_EXPORTER_OTLP_ENDPOINT="https://otel-collector.sua-infra.com:4317"
export OTEL_EXPORTER_OTLP_PROTOCOL="grpc"
export OTEL_TRACES_EXPORTER="otlp"
export OTEL_METRICS_EXPORTER="otlp"
export OTEL_LOGS_EXPORTER="otlp"
export ELVEN_TENANT_ID="seu-tenant-id"
export ELVEN_API_KEY="seu-api-token"
dotnet MinhaAplicacao.dll
Windows PowerShell
iwr https://raw.githubusercontent.com/elven-observability/opentelemetry-instrumentation-dotnet/main/scripts/install.ps1 -OutFile install.ps1
.\install.ps1
$env:CORECLR_ENABLE_PROFILING = "1"
$env:OTEL_DOTNET_AUTO_PLUGINS = "Elven.Observability.AutoInstrumentation.ElvenPlugin, Elven.Observability.AutoInstrumentation"
$env:OTEL_SERVICE_NAME = "minha-api"
$env:OTEL_SERVICE_VERSION = "1.0.0"
$env:ELVEN_SERVICE_NAMESPACE = "checkout"
$env:ELVEN_ENVIRONMENT = "production"
$env:OTEL_EXPORTER_OTLP_ENDPOINT = "https://otel-collector.sua-infra.com:4317"
$env:OTEL_EXPORTER_OTLP_PROTOCOL = "grpc"
$env:OTEL_TRACES_EXPORTER = "otlp"
$env:OTEL_METRICS_EXPORTER = "otlp"
$env:OTEL_LOGS_EXPORTER = "otlp"
$env:ELVEN_TENANT_ID = "seu-tenant-id"
$env:ELVEN_API_KEY = "seu-api-token"
dotnet MinhaAplicacao.dll
Variáveis principais do profiler
Em ambientes automatizados, confirme se as variáveis abaixo estão presentes:
| Variável | Valor Linux comum |
|---|---|
CORECLR_ENABLE_PROFILING |
1 |
CORECLR_PROFILER |
{918728DD-259F-4A6A-AC2B-B85E1B658318} |
CORECLR_PROFILER_PATH |
/otel/current/OpenTelemetry.AutoInstrumentation.Native.so |
DOTNET_ADDITIONAL_DEPS |
/otel/AdditionalDeps |
DOTNET_SHARED_STORE |
/otel/store |
DOTNET_STARTUP_HOOKS |
/otel/net/OpenTelemetry.AutoInstrumentation.StartupHook.dll |
OTEL_DOTNET_AUTO_HOME |
/otel |
OTEL_DOTNET_AUTO_PLUGINS |
Elven.Observability.AutoInstrumentation.ElvenPlugin, Elven.Observability.AutoInstrumentation |
Pacotes NuGet (para uso programático)
Esta etapa só é necessária quando você quer configurar a observabilidade pelo código, por exemplo com builder.AddElvenObservability(...) ou ElvenObservability.Initialize(...). Para Docker zero-code ou profiler zero-code, pule esta seção.
Instalação básica
dotnet add package Elven.Observability
Verificar instalação
dotnet list package | grep Elven.Observability
Pacotes disponíveis
Na maioria dos casos, use apenas Elven.Observability. Os pacotes abaixo existem para cenários avançados, composição manual ou redução explícita de dependências:
| Pacote | Quando usar |
|---|---|
Elven.Observability |
Facade principal para aplicações comuns. |
Elven.Observability.Abstractions |
Contratos, opções e nomes estáveis. |
Elven.Observability.Core |
Resolver de configuração, resource detector, erro e telemetria manual. |
Elven.Observability.Hosting |
Aplicações com Generic Host ou DI. |
Elven.Observability.AspNetCore |
ASP.NET Core, middleware e health check. |
Elven.Observability.Exporters.Otlp |
Defaults Elven para OTLP. |
Elven.Observability.AutoInstrumentation |
Plugin zero-code. |
Elven.Observability.Instrumentation.Sql |
SQL Client. |
Elven.Observability.Instrumentation.EntityFrameworkCore |
Entity Framework Core. |
Elven.Observability.Instrumentation.Redis |
StackExchange.Redis. |
Elven.Observability.Instrumentation.MongoDB |
MongoDB via ActivitySource. |
Elven.Observability.Instrumentation.Messaging |
Kafka, RabbitMQ e MassTransit via sources/meters. |
Elven.Observability.Instrumentation.Cloud |
AWS SDK e Azure SDK via sources/meters. |
Quick Start - ASP.NET Core programático
1. Instale o pacote
dotnet add package Elven.Observability
2. Configure as variáveis de ambiente
export OTEL_SERVICE_NAME="minha-api"
export OTEL_SERVICE_VERSION="1.0.0"
export ELVEN_SERVICE_NAMESPACE="checkout"
export ELVEN_ENVIRONMENT="production"
export ELVEN_REGION="sa-east-1"
export OTEL_EXPORTER_OTLP_ENDPOINT="https://otel-collector.sua-infra.com:4317"
export OTEL_EXPORTER_OTLP_PROTOCOL="grpc"
export OTEL_TRACES_EXPORTER="otlp"
export OTEL_METRICS_EXPORTER="otlp"
export OTEL_LOGS_EXPORTER="otlp"
export ELVEN_TENANT_ID="seu-tenant-id"
export ELVEN_API_KEY="seu-api-token"
3. Adicione a instrumentação no Program.cs
using Elven.Observability;
var builder = WebApplication.CreateBuilder(args);
builder.AddElvenObservability();
var app = builder.Build();
app.UseElvenObservability();
app.MapElvenObservabilityHealthCheck();
app.MapGet("/", (ILogger<Program> logger) =>
{
logger.LogInformation("Request processada com correlação automática.");
return Results.Ok(new { status = "ok" });
});
app.Run();
4. Rode a aplicação
dotnet run
Pronto. A aplicação já envia traces, métricas e logs por OTLP.
Quick Start - Worker Service programático
Use este modo para consumidores, filas, jobs long-running, processadores de eventos e serviços sem HTTP.
using System.Diagnostics.Metrics;
using Elven.Observability;
var builder = Host.CreateApplicationBuilder(args);
builder.AddElvenObservability(options =>
{
options.ServiceName = "worker-pagamentos";
options.ServiceNamespace = "payments";
options.EnvironmentName = "production";
});
builder.Services.AddHostedService<Worker>();
await builder.Build().RunAsync();
internal sealed class Worker(ILogger<Worker> logger) : BackgroundService
{
private readonly Counter<long> processed =
ElvenTelemetry.Meter.CreateCounter<long>("payments.worker.processed");
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
using var activity = ElvenTelemetry.ActivitySource.StartActivity("payment.process");
processed.Add(1);
activity?.SetTag("payment.provider", "acquirer-a");
logger.LogInformation("Pagamento processado pelo worker.");
await Task.Delay(TimeSpan.FromSeconds(5), stoppingToken);
}
}
}
Quick Start - Console app
Use a API estática quando a aplicação não usa DI, Host ou ASP.NET Core.
using Elven.Observability;
using Microsoft.Extensions.Logging;
await using var observability = ElvenObservability.Initialize(options =>
{
options.ServiceName = "job-fechamento";
options.ServiceNamespace = "finance";
options.EnvironmentName = "production";
options.TenantId = "seu-tenant-id";
options.ApiKey = Environment.GetEnvironmentVariable("ELVEN_API_KEY");
options.Endpoint = new Uri("https://otel-collector.sua-infra.com:4317");
});
using (var activity = ElvenTelemetry.ActivitySource.StartActivity("closing.run"))
{
var logger = observability.LoggerFactory.CreateLogger("Closing.Job");
activity?.SetTag("job.type", "daily-closing");
ElvenTelemetry.Meter.CreateCounter<long>("closing.runs").Add(1);
logger.LogInformation("Fechamento iniciado.");
}
await observability.ForceFlushAsync();
Para jobs curtos, chame
ForceFlushAsync()antes de encerrar o processo. Isso evita perder spans, métricas ou logs que ainda estejam em buffer.
Configuração por variáveis de ambiente
A configuração segue os padrões OpenTelemetry e adiciona extensões Elven.
Identidade do serviço
| Variável | Descrição | Exemplo |
|---|---|---|
OTEL_SERVICE_NAME |
Nome do serviço em traces, métricas e logs. | checkout-api |
OTEL_SERVICE_VERSION |
Versão da aplicação. | 1.4.2 |
ELVEN_SERVICE_NAMESPACE |
Namespace lógico do serviço. | payments |
ELVEN_ENVIRONMENT |
Ambiente de deploy. Popula deployment.environment.name. |
production |
ELVEN_REGION |
Região lógica ou cloud region. | sa-east-1 |
OTEL_RESOURCE_ATTRIBUTES |
Atributos adicionais separados por vírgula. | team=core,service.instance.id=pod-1 |
Export OTLP
| Variável | Descrição | Default |
|---|---|---|
OTEL_EXPORTER_OTLP_ENDPOINT |
Endpoint OTLP do Collector do cliente para os três sinais. | Configure explicitamente |
ELVEN_OTLP_ENDPOINT |
Alias Elven para o endpoint OTLP. | - |
OTEL_EXPORTER_OTLP_PROTOCOL |
grpc ou http/protobuf. |
grpc |
OTEL_EXPORTER_OTLP_HEADERS |
Headers extras no formato key=value,key2=value2. |
- |
OTEL_EXPORTER_OTLP_TIMEOUT |
Timeout de export em ms. | 30000 |
OTEL_METRIC_EXPORT_INTERVAL |
Intervalo de export de métricas em ms. | 60000 |
Autenticação e multi-tenancy
| Variável | Mapeamento |
|---|---|
ELVEN_API_KEY |
Enviado como header Authorization: Bearer <token>. |
ELVEN_TENANT_ID |
Enviado como header x-scope-orgid: <tenant>. |
Também é possível usar OTEL_EXPORTER_OTLP_HEADERS, mas prefira ELVEN_API_KEY e ELVEN_TENANT_ID para evitar erro de formatação:
export ELVEN_API_KEY="seu-api-token"
export ELVEN_TENANT_ID="seu-tenant-id"
Política de sinais sempre ligados
| Variável | Valor correto |
|---|---|
OTEL_TRACES_EXPORTER |
otlp |
OTEL_METRICS_EXPORTER |
otlp |
OTEL_LOGS_EXPORTER |
otlp |
Os valores abaixo são inválidos em modo Elven:
OTEL_TRACES_EXPORTER=none
OTEL_METRICS_EXPORTER=none
OTEL_LOGS_EXPORTER=none
OTEL_TRACES_SAMPLER=always_off
Quando isso acontece, a aplicação falha no startup com uma mensagem explícita e o health check de observabilidade fica Unhealthy.
Sampling
| Variável | Descrição | Exemplo |
|---|---|---|
OTEL_TRACES_SAMPLER |
always_on ou sampler compatível. |
always_on |
OTEL_TRACES_SAMPLER_ARG |
Ratio de sampling quando aplicável. | 0.25 |
O SamplingRatio precisa ser maior que 0 e menor ou igual a 1.
Debug
| Variável | Descrição |
|---|---|
ELVEN_DEBUG |
Quando true, 1 ou equivalente, habilita diagnósticos internos mais verbosos. |
Configuração via appsettings.json
A lib lê a seção ElvenObservability. Também aceita Elven:Observability para compatibilidade com padrões hierárquicos.
{
"ElvenObservability": {
"ServiceName": "checkout-api",
"ServiceNamespace": "payments",
"ServiceVersion": "1.4.2",
"EnvironmentName": "production",
"Region": "sa-east-1",
"Endpoint": "https://otel-collector.sua-infra.com:4317",
"Protocol": "Grpc",
"TenantId": "seu-tenant-id",
"SamplingRatio": 1.0,
"MetricExportInterval": "00:01:00",
"ExporterTimeout": "00:00:30",
"RedactDbStatements": true,
"CaptureRequestHeaders": true,
"CaptureResponseHeaders": true,
"AllowedRequestHeaders": [
"x-request-id",
"user-agent"
],
"AllowedResponseHeaders": [
"x-request-id"
],
"ResourceAttributes": {
"team": "checkout",
"cost.center": "payments"
}
}
}
Não grave
ApiKeyemappsettings.jsonversionado. Use secret manager, variável de ambiente, cofre de segredo ou mecanismo equivalente da sua plataforma.
Ordem de precedência
A resolução segue esta ordem:
- Defaults Elven
appsettings.json- Variáveis de ambiente
- Configuração programática passada no
AddElvenObservability(...)
Deploy com Docker
Modo zero-code com camada Elven
Use este modo como padrão para serviços containerizados. Ele não exige alteração no código da aplicação.
FROM mcr.microsoft.com/dotnet/aspnet:10.0
COPY --from=elvenobservability/dotnet-instrumentation:latest /otel /otel
ENV CORECLR_ENABLE_PROFILING=1 <br>
CORECLR_PROFILER={918728DD-259F-4A6A-AC2B-B85E1B658318} <br>
CORECLR_PROFILER_PATH=/otel/current/OpenTelemetry.AutoInstrumentation.Native.so <br>
DOTNET_ADDITIONAL_DEPS=/otel/AdditionalDeps <br>
DOTNET_SHARED_STORE=/otel/store <br>
DOTNET_STARTUP_HOOKS=/otel/net/OpenTelemetry.AutoInstrumentation.StartupHook.dll <br>
OTEL_DOTNET_AUTO_HOME=/otel <br>
OTEL_DOTNET_AUTO_PLUGINS="Elven.Observability.AutoInstrumentation.ElvenPlugin, Elven.Observability.AutoInstrumentation" <br>
OTEL_SERVICE_NAME=minha-api <br>
ELVEN_SERVICE_NAMESPACE=checkout <br>
ELVEN_ENVIRONMENT=production <br>
OTEL_EXPORTER_OTLP_ENDPOINT=https://otel-collector.sua-infra.com:4317 <br>
OTEL_EXPORTER_OTLP_PROTOCOL=grpc <br>
OTEL_TRACES_EXPORTER=otlp <br>
OTEL_METRICS_EXPORTER=otlp <br>
OTEL_LOGS_EXPORTER=otlp <br>
ELVEN_TENANT_ID=seu-tenant-id
WORKDIR /app
COPY . /app
ENTRYPOINT ["dotnet", "/app/MinhaAplicacao.dll"]
Docker Compose zero-code
services:
checkout-api:
image: minha-api:latest
ports:
- "8080:8080"
environment:
OTEL_SERVICE_NAME: checkout-api
OTEL_SERVICE_VERSION: 1.4.2
ELVEN_SERVICE_NAMESPACE: payments
ELVEN_ENVIRONMENT: production
ELVEN_REGION: sa-east-1
OTEL_EXPORTER_OTLP_ENDPOINT: https://otel-collector.sua-infra.com:4317
OTEL_EXPORTER_OTLP_PROTOCOL: grpc
OTEL_TRACES_EXPORTER: otlp
OTEL_METRICS_EXPORTER: otlp
OTEL_LOGS_EXPORTER: otlp
ELVEN_TENANT_ID: seu-tenant-id
ELVEN_API_KEY: ${ELVEN_API_KEY}
Modo programático
Use este modo quando o pacote NuGet está instalado na aplicação e você quer manter a instrumentação no código.
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
WORKDIR /src
COPY . .
RUN dotnet publish -c Release -o /app
FROM mcr.microsoft.com/dotnet/aspnet:10.0
WORKDIR /app
ENV OTEL_SERVICE_NAME=checkout-api <br>
ELVEN_SERVICE_NAMESPACE=payments <br>
ELVEN_ENVIRONMENT=production <br>
OTEL_EXPORTER_OTLP_ENDPOINT=https://otel-collector.sua-infra.com:4317 <br>
OTEL_EXPORTER_OTLP_PROTOCOL=grpc <br>
OTEL_TRACES_EXPORTER=otlp <br>
OTEL_METRICS_EXPORTER=otlp <br>
OTEL_LOGS_EXPORTER=otlp <br>
ELVEN_TENANT_ID=seu-tenant-id
COPY --from=build /app .
ENTRYPOINT ["dotnet", "MinhaAplicacao.dll"]
Passe ELVEN_API_KEY como segredo no runtime, não no Dockerfile:
docker run --rm <br>
-e ELVEN_API_KEY="seu-api-token" <br>
-p 8080:8080 <br>
minha-api:latest
Guia por tipo de aplicação
ASP.NET Core Web API
Use:
builder.AddElvenObservability();
app.UseElvenObservability();
app.MapElvenObservabilityHealthCheck();
O que é coletado:
- Requests HTTP server
- Route template em
http.route - Status code
- Duração
- Exceções
- Logs de
ILogger - Correlação
trace_idespan_id - Métricas ASP.NET Core e Kestrel
Minimal APIs
Funciona sem configuração extra:
app.MapPost("/orders/{id:int}", (int id, ILogger<Program> logger) =>
{
logger.LogInformation("Pedido recebido. order_id={OrderId}", id);
return Results.Accepted();
});
MVC e Controllers
Também funciona sem configuração extra:
[ApiController]
[Route("orders")]
public sealed class OrdersController(ILogger<OrdersController> logger) : ControllerBase
{
[HttpGet("{id:int}")]
public IActionResult Get(int id)
{
logger.LogInformation("Pedido consultado. order_id={OrderId}", id);
return Ok(new { id });
}
}
gRPC
Instale e configure o serviço normalmente:
dotnet add package Grpc.AspNetCore
builder.AddElvenObservability();
builder.Services.AddGrpc();
var app = builder.Build();
app.UseElvenObservability();
app.MapGrpcService<MyGrpcService>();
app.Run();
O client gRPC via Grpc.Net.Client é instrumentado automaticamente quando EnableGrpcClientInstrumentation=true.
Worker Service
Use Host.CreateApplicationBuilder(args) e builder.AddElvenObservability(). Crie spans manuais ao redor de cada unidade de trabalho relevante:
using var activity = ElvenTelemetry.ActivitySource.StartActivity("message.consume");
activity?.SetTag("messaging.system", "rabbitmq");
activity?.SetTag("messaging.destination.name", "orders.created");
Jobs curtos
Sempre finalize com flush:
await observability.ForceFlushAsync();
Lambda e Azure Functions
Para funções com processo .NET moderno, prefira inicialização programática no bootstrap da aplicação ou no host isolado. Em execução curta, garanta flush no final do handler quando possível.
Usando logs, traces e métricas manuais
A instrumentação automática cobre o caminho comum. Para regras de negócio, adicione telemetria manual em pontos relevantes.
Logs estruturados
Use ILogger normalmente:
logger.LogInformation(
"Pedido aprovado. order_id={OrderId} customer_id={CustomerId} amount={Amount}",
orderId,
customerId,
amount);
A lib exporta:
- Mensagem formatada
- Template original
- Propriedades estruturadas
- Scopes
trace_idespan_idquando houver span ativo
Source-generated logging
Para caminhos quentes, prefira LoggerMessage:
internal static partial class OrderLogs
{
[LoggerMessage(
EventId = 1001,
Level = LogLevel.Information,
Message = "Pedido aprovado. order_id={OrderId} amount={Amount}")]
public static partial void OrderApproved(
ILogger logger,
string orderId,
decimal amount);
}
Spans manuais
using var activity = ElvenTelemetry.ActivitySource.StartActivity("order.reserve_inventory");
activity?.SetTag("order.id", orderId);
activity?.SetTag("inventory.sku", sku);
activity?.SetTag("inventory.quantity", quantity);
Eventos no span
activity?.AddEvent(new ActivityEvent(
"inventory.reserved",
tags: new ActivityTagsCollection
{
["inventory.sku"] = sku,
["inventory.quantity"] = quantity
}));
Métricas customizadas
var ordersProcessed =
ElvenTelemetry.Meter.CreateCounter<long>("orders.processed");
var orderDuration =
ElvenTelemetry.Meter.CreateHistogram<double>("orders.duration", unit: "ms");
ordersProcessed.Add(1, new KeyValuePair<string, object?>("status", "approved"));
orderDuration.Record(duration.TotalMilliseconds);
Baggage e propagação
O OpenTelemetry .NET usa W3C TraceContext e Baggage. Para propagar contexto de negócio entre serviços:
Baggage.SetBaggage("tenant.id", tenantId);
Activity.Current?.SetTag("tenant.id", tenantId);
Instrumentações automáticas
Traces
| Área | Cobertura |
|---|---|
| ASP.NET Core | Requests, route, status, duração, exceções. |
| HttpClient | Requests outbound, status, duração, exceções. |
| gRPC client | Chamadas Grpc.Net.Client. |
| SQL Client | Queries, comandos, exceções. |
| Entity Framework Core | Operações EF Core. |
| Redis | StackExchange.Redis, statements opcionais. |
| AWS SDK | ActivitySource oficial do AWS SDK. |
| Azure SDK | ActivitySource dos clients Azure. |
| MongoDB | Source registration para integração via diagnostic sources. |
| Kafka | Source registration para Confluent.Kafka.Extensions.OpenTelemetry. |
| RabbitMQ | Source registration para RabbitMQ.Client. |
| MassTransit | Source e meter registration. |
| Manual | ElvenTelemetry.ActivitySource. |
Métricas
| Área | Cobertura |
|---|---|
| Runtime .NET | GC, heap, allocations, exceptions, threadpool, JIT e runtime. |
| Process | CPU, memória, threads e handles quando disponível. |
| ASP.NET Core/Kestrel | Requests, conexões e métricas do servidor. |
| HttpClient | Duração, DNS, TLS e conexões via meters do .NET. |
| SQL | Métricas do provider quando disponíveis. |
| AWS | Métricas oficiais disponíveis pelo pacote. |
| Custom | ElvenTelemetry.Meter. |
| Internas Elven | Misconfigurações, falhas de exporter e erros classificados. |
Logs
| Fonte | Cobertura |
|---|---|
ILogger |
Logs estruturados, scopes e state values. |
| ASP.NET Core | Logs do pipeline HTTP e endpoints. |
| Aplicação | Logs de negócio com correlação automática. |
| Elven | Logs internos de classificação e diagnóstico. |
Mapeamento de erros
O middleware UseElvenObservability() e o classificador interno adicionam error.category e error.type aos spans.
| Caso | Categoria |
|---|---|
OperationCanceledException |
cancelled |
TimeoutException |
timeout |
SocketError.HostNotFound, NoData, TryAgain |
dns |
SocketError.ConnectionRefused |
connection_refused |
HttpRequestException com certificado/nome remoto suspeito |
dns_poisoning |
DbException, Npgsql, MySql, Mongo, Redis, SqlClient |
database |
UnauthorizedAccessException |
auth |
| FluentValidation/DataAnnotations/ValidationException | validation |
ArgumentException, FormatException, JsonException |
user_input |
| Erro de configuração no startup | configuration |
HTTP 401, 403 |
auth |
HTTP 422 |
validation |
HTTP 400, 404, 405, 409 |
user_input_error |
Outros HTTP 4xx |
client_error |
HTTP 5xx |
server_error |
gRPC CANCELLED |
cancelled |
gRPC DEADLINE_EXCEEDED |
timeout |
gRPC UNAUTHENTICATED, PERMISSION_DENIED |
auth |
gRPC INVALID_ARGUMENT, FAILED_PRECONDITION, OUT_OF_RANGE |
validation |
gRPC UNAVAILABLE |
network |
gRPC INTERNAL, DATA_LOSS, UNKNOWN |
server_error |
Exemplo de endpoint com erro
app.MapGet("/fail/{kind}", (string kind) => kind switch
{
"validation" => Results.ValidationProblem(
new Dictionary<string, string[]> { ["name"] = ["required"] }),
"auth" => Results.Unauthorized(),
"timeout" => throw new TimeoutException("Timeout simulado"),
_ => throw new InvalidOperationException("Falha simulada")
});
Recursos e metadados
A lib popula resource attributes seguindo semantic conventions estáveis.
| Atributo | Origem |
|---|---|
service.name |
OTEL_SERVICE_NAME ou ServiceName. |
service.namespace |
ELVEN_SERVICE_NAMESPACE ou ServiceNamespace. |
service.version |
OTEL_SERVICE_VERSION ou ServiceVersion. |
service.instance.id |
ServiceInstanceId ou OTEL_RESOURCE_ATTRIBUTES. |
deployment.environment.name |
ELVEN_ENVIRONMENT ou EnvironmentName. |
host.name |
Environment.MachineName. |
host.id |
/etc/machine-id, /var/lib/dbus/machine-id ou boot id quando disponível. |
container.id |
Detectado a partir de /proc/self/cgroup quando disponível. |
k8s.namespace.name |
POD_NAMESPACE, KUBERNETES_NAMESPACE ou service account namespace. |
k8s.pod.name |
HOSTNAME. |
k8s.pod.uid |
POD_UID. |
k8s.node.name |
NODE_NAME. |
k8s.cluster.name |
CLUSTER_NAME. |
process.runtime.name |
.NET. |
process.runtime.version |
Versão do runtime. |
telemetry.distro.name |
elven-observability-dotnet. |
telemetry.distro.version |
Versão da lib Elven. |
elven.tenant.id |
ELVEN_TENANT_ID ou TenantId. |
elven.environment |
ELVEN_ENVIRONMENT ou EnvironmentName. |
elven.region |
ELVEN_REGION ou Region. |
Configuração avançada
Configuração programática completa
builder.AddElvenObservability(options =>
{
options.ServiceName = "checkout-api";
options.ServiceNamespace = "payments";
options.ServiceVersion = "1.4.2";
options.ServiceInstanceId = Environment.MachineName;
options.EnvironmentName = "production";
options.Region = "sa-east-1";
options.Endpoint = new Uri("https://otel-collector.sua-infra.com:4317");
options.Protocol = ElvenOtlpProtocol.Grpc;
options.TenantId = "seu-tenant-id";
options.ApiKey = builder.Configuration["ELVEN_API_KEY"];
options.SamplingRatio = 1.0;
options.ExporterTimeout = TimeSpan.FromSeconds(30);
options.MetricExportInterval = TimeSpan.FromSeconds(60);
options.RedactDbStatements = true;
options.RedactHeaders = true;
options.RedactUrlQuery = true;
options.HashUserIdentifiers = true;
options.CaptureRequestHeaders = true;
options.CaptureResponseHeaders = true;
options.AllowedRequestHeaders = ["x-request-id", "user-agent"];
options.AllowedResponseHeaders = ["x-request-id"];
options.ResourceAttributes["team"] = "checkout";
options.ResourceAttributes["cost.center"] = "payments";
});
Opções principais
| Opção | Default | Descrição |
|---|---|---|
ServiceName |
unknown-service |
Nome do serviço. |
ServiceNamespace |
default |
Namespace lógico. |
Endpoint |
Collector do cliente | Endpoint OTLP para envio dos três sinais. |
Protocol |
Grpc |
Grpc ou HttpProtobuf. |
SamplingRatio |
1.0 |
Ratio de traces. Precisa ser maior que 0. |
ExporterTimeout |
30s |
Timeout do exporter. |
MetricExportInterval |
60s |
Intervalo de export de métricas. |
RedactDbStatements |
true |
Redacta statements de banco quando suportado. |
RedactHeaders |
true |
Política de privacidade para headers. |
RedactUrlQuery |
true |
Evita vazamento de query strings sensíveis. |
HashUserIdentifiers |
true |
Política para identificadores de usuário. |
CaptureRequestHeaders |
false |
Captura headers permitidos em spans HTTP. |
CaptureResponseHeaders |
false |
Captura headers permitidos em spans HTTP. |
EnableHealthCheck |
true |
Habilita health check de observabilidade. |
Headers HTTP
Headers só são capturados quando:
CaptureRequestHeadersouCaptureResponseHeadersestátrue- O header está na allowlist correspondente
options.CaptureRequestHeaders = true;
options.AllowedRequestHeaders = ["x-request-id", "user-agent"];
Os nomes são normalizados para atributos OTel:
| Header | Atributo |
|---|---|
x-request-id |
http.request.header.x_request_id |
user-agent |
http.request.header.user_agent |
x-elven-response |
http.response.header.x_elven_response |
HTTP/protobuf
export OTEL_EXPORTER_OTLP_PROTOCOL="http/protobuf"
export OTEL_EXPORTER_OTLP_ENDPOINT="https://otel-collector.sua-infra.com:4318"
Ou programático:
options.Protocol = ElvenOtlpProtocol.HttpProtobuf;
Redação de statements SQL
Por padrão, statements de banco são redigidos.
options.RedactDbStatements = true;
Para diagnóstico temporário:
options.RedactDbStatements = false;
Use statements completos apenas em ambientes controlados. Queries podem conter dados pessoais, tokens ou payloads sensíveis.
Native AOT e trimming
A inicialização programática é compatível com Native AOT e trimming onde o SDK OpenTelemetry e as instrumentações oficiais permitem.
Recomendações para AOT
- Use inicialização programática com
builder.AddElvenObservability() - Evite zero-code instrumentation, pois CLR profiler não se aplica a Native AOT
- Desabilite instrumentações que dependem de reflexão se o publish acusar warnings
- Mantenha
TreatWarningsAsErrors=trueno projeto
Exemplo:
<PropertyGroup>
<PublishAot>true</PublishAot>
<PublishTrimmed>true</PublishTrimmed>
<TrimMode>full</TrimMode>
<InvariantGlobalization>true</InvariantGlobalization>
</PropertyGroup>
Publish:
dotnet publish -c Release -r linux-x64
Ajustes comuns para AOT
builder.AddElvenObservability(options =>
{
options.ServiceName = "native-aot-api";
options.EnableSqlClientInstrumentation = false;
options.EnableEntityFrameworkCoreInstrumentation = false;
options.EnableRedisInstrumentation = false;
});
Validação ponta a ponta
1. Crie um endpoint de teste
app.MapGet("/observability-test/{id:int}", async (
int id,
IHttpClientFactory httpClientFactory,
ILogger<Program> logger) =>
{
using var activity =
ElvenTelemetry.ActivitySource.StartActivity("observability.test");
activity?.SetTag("test.id", id);
activity?.SetTag("db.system", "postgresql");
activity?.SetTag("db.query.text", "select * from orders where id = @id");
ElvenTelemetry.Meter
.CreateCounter<long>("observability.test.requests")
.Add(1);
logger.LogInformation("Teste de observabilidade executado. test_id={TestId}", id);
var client = httpClientFactory.CreateClient();
await client.GetAsync("https://example.com");
return Results.Ok(new
{
id,
traceId = Activity.Current?.TraceId.ToString()
});
});
2. Gere tráfego
curl http://localhost:8080/observability-test/123
3. Gere uma exceção controlada
app.MapGet("/observability-error", () =>
{
throw new InvalidOperationException("Erro sintético para validar traces e logs.");
});
curl -i http://localhost:8080/observability-error
4. Valide no Grafana
No Grafana, procure por:
- Serviço
service.name - Route HTTP
trace_idretornado no endpoint- Logs com o mesmo
trace_id - Métricas customizadas
- Span com
error.category - Resource attributes como
deployment.environment.name,service.namespaceeelven.tenant.id
5. Health check
curl http://localhost:8080/health/observability
Resposta esperada:
{
"status": "Healthy"
}
Boas práticas
Nome de serviço
Use nomes estáveis, curtos e sem ambiente no nome:
OTEL_SERVICE_NAME=checkout-api
ELVEN_SERVICE_NAMESPACE=payments
ELVEN_ENVIRONMENT=production
Evite:
OTEL_SERVICE_NAME=checkout-api-prod-v2-pod-123
Cardinalidade
Não use valores de alta cardinalidade como labels de métricas:
- IDs de usuário
- IDs de pedido
- Emails
- Tokens
- URLs completas com query string
Use esses valores em spans ou logs, com cuidado de privacidade.
Logs
Prefira logs estruturados:
logger.LogInformation("Pedido aprovado. order_id={OrderId}", orderId);
Evite interpolação em logs de caminho quente:
logger.LogInformation($"Pedido aprovado. order_id={orderId}");
Segurança
- Não coloque
ELVEN_API_KEYno código - Não versionar tokens em
appsettings.json - Redactar statements SQL por padrão
- Capturar headers apenas com allowlist
- Evitar payload bruto em spans e logs
Performance
- Use
LoggerMessageem caminhos muito chamados - Evite spans manuais em loops internos de altíssima frequência
- Use histogramas para latência de negócio
- Ajuste
SamplingRatiocom cuidado - Mantenha métricas com baixa cardinalidade
Troubleshooting
Nenhum dado aparece no Grafana
Verifique:
echo $OTEL_SERVICE_NAME
echo $OTEL_EXPORTER_OTLP_ENDPOINT
echo $OTEL_EXPORTER_OTLP_PROTOCOL
echo $OTEL_TRACES_EXPORTER
echo $OTEL_METRICS_EXPORTER
echo $OTEL_LOGS_EXPORTER
echo $ELVEN_TENANT_ID
Confirme que:
OTEL_TRACES_EXPORTER=otlpOTEL_METRICS_EXPORTER=otlpOTEL_LOGS_EXPORTER=otlpELVEN_API_KEYestá configuradoELVEN_TENANT_IDestá correto- A aplicação consegue acessar o endpoint OTLP
Erro de autenticação
ELVEN_API_KEY é enviado como Bearer automaticamente:
export ELVEN_API_KEY="seu-api-token"
Não inclua a palavra Bearer dentro da variável. A lib monta o header final:
Authorization: Bearer seu-api-token
Erro de tenant
Configure:
export ELVEN_TENANT_ID="seu-tenant-id"
A lib monta:
x-scope-orgid: seu-tenant-id
A aplicação falha no startup com exporter none
Em modo Elven, os três sinais são obrigatórios. Remova valores none:
unset OTEL_TRACES_EXPORTER
unset OTEL_METRICS_EXPORTER
unset OTEL_LOGS_EXPORTER
Ou defina explicitamente:
export OTEL_TRACES_EXPORTER=otlp
export OTEL_METRICS_EXPORTER=otlp
export OTEL_LOGS_EXPORTER=otlp
gRPC não conecta
Se o ambiente bloqueia gRPC, use HTTP/protobuf:
export OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf
Confirme também se o endpoint aceita o protocolo escolhido.
Logs aparecem sem trace_id
Logs só recebem correlação quando há Activity.Current. Em ASP.NET Core isso acontece automaticamente dentro de requests. Em Workers e Console apps, crie um span manual:
using var activity = ElvenTelemetry.ActivitySource.StartActivity("job.run");
logger.LogInformation("Rodando job com correlação.");
Headers não aparecem nos spans
Confirme a allowlist:
options.CaptureRequestHeaders = true;
options.AllowedRequestHeaders = ["x-request-id"];
Procure o atributo com _ no lugar de -:
http.request.header.x_request_id
Statements SQL não aparecem
Por padrão, statements são redigidos quando a instrumentação suporta essa opção. Para diagnóstico temporário:
options.RedactDbStatements = false;
Health check unhealthy
Consulte /health/observability. O motivo mais comum é uma misconfiguração no startup, como:
- Endpoint OTLP inválido
- Sampling ratio
0 - Exporter
none OTEL_TRACES_SAMPLER=always_off
Auto-instrumentation não carrega
Verifique:
echo $CORECLR_ENABLE_PROFILING
echo $CORECLR_PROFILER
echo $CORECLR_PROFILER_PATH
echo $DOTNET_STARTUP_HOOKS
echo $OTEL_DOTNET_AUTO_HOME
echo $OTEL_DOTNET_AUTO_PLUGINS
Confirme que o arquivo apontado por CORECLR_PROFILER_PATH existe e que a aplicação não está publicada como Native AOT.
FAQ
Preciso configurar logs, métricas e traces separadamente?
Não. A chamada AddElvenObservability() configura os três sinais.
Posso desligar logs, métricas ou traces?
Não em modo Elven. A plataforma espera os três sinais para correlação completa. Configurações como OTEL_LOGS_EXPORTER=none são tratadas como erro.
O protocolo padrão é qual?
grpc.
Posso usar HTTP/protobuf?
Sim. Configure:
export OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf
O pacote substitui o OpenTelemetry SDK?
Não. Ele configura e padroniza o SDK oficial do OpenTelemetry .NET para a Elven Observability.
Funciona com .NET Framework 4.x?
Não. O foco é .NET moderno: net8.0, net9.0 e net10.0.
Funciona com Native AOT?
Sim para inicialização programática, respeitando as limitações das instrumentações oficiais usadas no projeto. Zero-code via CLR profiler não se aplica a Native AOT.
Como envio dados de negócio?
Use ElvenTelemetry.ActivitySource para spans e ElvenTelemetry.Meter para métricas customizadas. Use ILogger para logs estruturados.
Como correlacionar logs e traces?
Dentro de uma request ASP.NET Core ou de um span manual, ILogger é exportado com contexto de trace e span automaticamente.
Como configuro multi-tenancy?
Use:
export ELVEN_TENANT_ID="seu-tenant-id"
A lib envia esse valor no header x-scope-orgid.
Como configuro autenticação?
Use:
export ELVEN_API_KEY="seu-api-token"
A lib envia esse valor como Authorization: Bearer <token>.
Qual imagem Docker devo usar para zero-code?
Use:
elvenobservability/dotnet-instrumentation:latest
Como vejo se está funcionando?
Faça uma request na aplicação, gere um log e consulte no Grafana pelo service.name. Para ASP.NET Core, também valide:
curl http://localhost:8080/health/observability