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
ActivitySourcena 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:
StartActivitypode retornarnullse 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.idcustomer.idtenant.id(se multi-tenant)http.status_codedb.system
✅ Em erro:
SetStatus(Error)RecordException(ex)
✅ Evite:
- baggage com dados sensíveis
db.statementcom dados pessoais (PII)- spans gigantes (quebre em passos)