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 ApiKey em appsettings.json versionado. 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:

  1. Defaults Elven
  2. appsettings.json
  3. Variáveis de ambiente
  4. 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_id e span_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_id e span_id quando 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:

  1. CaptureRequestHeaders ou CaptureResponseHeaders está true
  2. 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=true no 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_id retornado no endpoint
  • Logs com o mesmo trace_id
  • Métricas customizadas
  • Span com error.category
  • Resource attributes como deployment.environment.name, service.namespace e elven.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_KEY no 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 LoggerMessage em caminhos muito chamados
  • Evite spans manuais em loops internos de altíssima frequência
  • Use histogramas para latência de negócio
  • Ajuste SamplingRatio com 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=otlp
  • OTEL_METRICS_EXPORTER=otlp
  • OTEL_LOGS_EXPORTER=otlp
  • ELVEN_API_KEY está configurado
  • ELVEN_TENANT_ID está 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