Skip to main content

ActivitySource | Observabilidade | CSharp

  • ActivitySource | Observabilidade | CSharp

ActivitySource (.NET) — Guia direto com exemplos

O que é ActivitySource?

ActivitySource é a API do .NET para criar spans (atividades) de distributed tracing seguindo o padrão do OpenTelemetry.

  • Você cria um ActivitySource na sua lib/app
  • Você inicia uma Activity (span) ao entrar numa operação
  • Você adiciona tags/eventos/erros
  • Um listener/exporter (ex: OpenTelemetry + Jaeger/Zipkin/OTLP) coleta tudo

Instalação (ASP.NET Core + OpenTelemetry)

No seu projeto:

dotnet add package OpenTelemetry.Extensions.Hosting
dotnet add package OpenTelemetry.Instrumentation.AspNetCore
dotnet add package OpenTelemetry.Instrumentation.Http
dotnet add package OpenTelemetry.Exporter.Console

Configuração mínima (Program.cs)

Exportando para console (pra validar rápido)

using OpenTelemetry.Trace;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddOpenTelemetry()
.WithTracing(tracing =>
{
tracing
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddSource("MinhaApp") // nome do seu ActivitySource
.AddConsoleExporter();
});

var app = builder.Build();

app.MapGet("/", () => "OK");

app.Run();

1) Criando um ActivitySource padrão

Crie um arquivo Telemetry.cs:

using System.Diagnostics;

public static class Telemetry
{
public static readonly ActivitySource ActivitySource = new("MinhaApp");
}

✅ Use o mesmo nome do .AddSource("MinhaApp").


2) Criando um span manual (o uso mais comum)

using System.Diagnostics;

public class OrderService
{
public void ProcessOrder(string orderId)
{
using var activity = Telemetry.ActivitySource.StartActivity(
"OrderService.ProcessOrder",
ActivityKind.Internal);

activity?.SetTag("order.id", orderId);
activity?.SetTag("app.feature", "orders");

// ... lógica
}
}

Pontos importantes:

  • StartActivity pode retornar null se não houver listener ativo
  • Use activity?. pra não quebrar

3) Span com status OK / Error (tratando exceções)

using System.Diagnostics;

public async Task ChargeAsync(string orderId)
{
using var activity = Telemetry.ActivitySource.StartActivity("Payment.Charge");

try
{
activity?.SetTag("order.id", orderId);

// simula falha
throw new InvalidOperationException("Cartão recusado");
}
catch (Exception ex)
{
activity?.SetStatus(ActivityStatusCode.Error, ex.Message);
activity?.RecordException(ex);
throw;
}
}

RecordException(ex) é o padrão OpenTelemetry.


4) Adicionando eventos (marcos dentro do span)

using System.Diagnostics;

using var activity = Telemetry.ActivitySource.StartActivity("Order.Pipeline");

activity?.AddEvent(new ActivityEvent("validacao.iniciada"));

await Task.Delay(50);

activity?.AddEvent(new ActivityEvent(
"validacao.finalizada",
tags: new ActivityTagsCollection
{
["validacao.ok"] = true
}));

Use eventos para coisas tipo:

  • cache hit/miss
  • fila publish ok
  • passo de validação concluído

5) Tags vs Baggage (diferença prática)

Tags: ficam no span atual

activity?.SetTag("customer.id", "123");

Baggage: propaga para spans filhos/serviços downstream

using System.Diagnostics;

Baggage.SetBaggage("tenant.id", "empresaA");

Use baggage com cuidado (propaga pra todo mundo).


6) Criando spans filhos (aninhados)

using var parent = Telemetry.ActivitySource.StartActivity("Pedido.Criar");

using (var child1 = Telemetry.ActivitySource.StartActivity("Banco.Salvar"))
{
// ...
}

using (var child2 = Telemetry.ActivitySource.StartActivity("Fila.Publicar"))
{
// ...
}

✅ Se existir Activity.Current, o filho herda automaticamente.


7) ActivityKind: escolha certa

Telemetry.ActivitySource.StartActivity("HTTP GET /users", ActivityKind.Server);
Telemetry.ActivitySource.StartActivity("POST Payment Gateway", ActivityKind.Client);
Telemetry.ActivitySource.StartActivity("Consume Kafka", ActivityKind.Consumer);
Telemetry.ActivitySource.StartActivity("Publish Kafka", ActivityKind.Producer);
Telemetry.ActivitySource.StartActivity("DoWork", ActivityKind.Internal);

Regras rápidas:

  • Server: request entrando
  • Client: chamada HTTP/DB saindo
  • Producer/Consumer: mensageria
  • Internal: qualquer operação interna

8) Links (quando um span tem “pais” múltiplos)

Caso típico: consumir mensagem de fila e correlacionar com trace original.

using System.Diagnostics;

var links = new List<ActivityLink>
{
new ActivityLink(new ActivityContext(
traceId: ActivityTraceId.CreateRandom(),
spanId: ActivitySpanId.CreateRandom(),
traceFlags: ActivityTraceFlags.Recorded))
};

using var activity = Telemetry.ActivitySource.StartActivity(
"ProcessarMensagem",
ActivityKind.Consumer,
parentContext: default,
tags: null,
links: links);

✅ Links ≠ parent/child. É correlação.


9) Adicionando atributos de HTTP / DB (boa prática)

HTTP

activity?.SetTag("http.method", "GET");
activity?.SetTag("http.url", "https://api.exemplo.com/users");
activity?.SetTag("http.status_code", 200);

Banco

activity?.SetTag("db.system", "postgresql");
activity?.SetTag("db.name", "appdb");
activity?.SetTag("db.statement", "SELECT * FROM users WHERE id = @id");

10) Exemplo completo (API endpoint com tracing)

app.MapGet("/orders/{id}", async (string id, HttpClient http) =>
{
using var activity = Telemetry.ActivitySource.StartActivity("GET /orders/{id}", ActivityKind.Internal);

activity?.SetTag("order.id", id);

// Simula chamada externa
var response = await http.GetAsync("https://example.org");
activity?.SetTag("external.status", (int)response.StatusCode);

return Results.Ok(new { id, status = "ok" });
});

11) Performance: como evitar overhead

✅ Padrão recomendado:

if (Telemetry.ActivitySource.HasListeners())
{
using var activity = Telemetry.ActivitySource.StartActivity("OperacaoPesada");
activity?.SetTag("x", 1);

// ...
}

HasListeners() evita trabalho quando tracing está desligado.


12) Debug rápido: escutando Activities sem OpenTelemetry

Se você só quer ver eventos no console via ActivityListener:

using System.Diagnostics;

var listener = new ActivityListener
{
ShouldListenTo = source => source.Name == "MinhaApp",
Sample = (ref ActivityCreationOptions<ActivityContext> options) => ActivitySamplingResult.AllDataAndRecorded,
ActivityStarted = activity => Console.WriteLine($"START {activity.DisplayName}"),
ActivityStopped = activity => Console.WriteLine($"STOP {activity.DisplayName} Duration={activity.Duration}")
};

ActivitySource.AddActivityListener(listener);

Checklist de boas práticas

✅ Use nomes consistentes:

  • "OrderService.ProcessOrder"
  • "DB Query"
  • "PaymentGateway.Charge"

✅ Sempre coloque tags úteis:

  • order.id
  • customer.id
  • tenant.id (se multi-tenant)
  • http.status_code
  • db.system

✅ Em erro:

  • SetStatus(Error)
  • RecordException(ex)

✅ Evite:

  • baggage com dados sensíveis
  • db.statement com dados pessoais (PII)
  • spans gigantes (quebre em passos)