원클릭으로
opentelemetry
OpenTelemetry SDK for .NET — tracing, custom spans, metrics, .NET Aspire integration, and OTLP exporter configuration.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
OpenTelemetry SDK for .NET — tracing, custom spans, metrics, .NET Aspire integration, and OTLP exporter configuration.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
SOC 직업 분류 기준
| name | opentelemetry |
| description | OpenTelemetry SDK for .NET — tracing, custom spans, metrics, .NET Aspire integration, and OTLP exporter configuration. |
Reference material for adding distributed tracing and metrics to ASP.NET Core services. Used by dnp-architect and dnp-tdd-developer-hard.
<!-- Core -->
<PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.*" />
<!-- Tracing instrumentation -->
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.*" />
<PackageReference Include="OpenTelemetry.Instrumentation.Http" Version="1.*" />
<PackageReference Include="OpenTelemetry.Instrumentation.EntityFrameworkCore" Version="1.*" />
<PackageReference Include="OpenTelemetry.Instrumentation.Runtime" Version="1.*" />
<!-- Exporter -->
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.*" />
builder.Services.AddOpenTelemetry()
.WithTracing(tracing => tracing
.AddAspNetCoreInstrumentation(options =>
{
options.RecordException = true;
options.Filter = ctx => ctx.Request.Path != "/health"; // exclude health checks
})
.AddEntityFrameworkCoreInstrumentation(options =>
{
options.SetDbStatementForText = true; // include SQL in spans (dev only)
})
.AddHttpClientInstrumentation()
.AddSource("MyApp.Orders") // register custom ActivitySource names
.AddOtlpExporter())
.WithMetrics(metrics => metrics
.AddAspNetCoreInstrumentation()
.AddRuntimeInstrumentation()
.AddMeter("MyApp.Orders") // register custom Meter names
.AddOtlpExporter());
SetDbStatementForText = true captures SQL — disable in production to avoid leaking query parameters containing PII.
ActivitySource is .NET's API for creating custom spans. One source per logical module.
// Register as singleton in DI:
builder.Services.AddSingleton(new ActivitySource("MyApp.Orders"));
// Usage in a handler or service:
public class ProcessOrderHandler(IOrderRepository orders, ActivitySource activitySource)
{
public async Task<Order> HandleAsync(ProcessOrderCommand cmd, CancellationToken ct)
{
using var activity = activitySource.StartActivity("ProcessOrder");
activity?.SetTag("order.id", cmd.OrderId.ToString());
activity?.SetTag("order.customer_id", cmd.CustomerId.ToString());
try
{
var order = await orders.GetByIdAsync(cmd.OrderId, ct)
?? throw new NotFoundException($"Order {cmd.OrderId} not found.");
activity?.SetTag("order.status", order.Status.ToString());
// business logic...
return order;
}
catch (Exception ex)
{
activity?.SetStatus(ActivityStatusCode.Error, ex.Message);
activity?.RecordException(ex);
throw;
}
}
}
Use OpenTelemetry semantic conventions where they exist. For business attributes, use <domain>.<attribute> format:
| Attribute | Example value |
|---|---|
order.id | "ord_abc123" |
order.status | "Pending" |
customer.tier | "Premium" |
payment.method | "CreditCard" |
Do not tag spans with PII (email, name, card number, SSN).
using var parent = activitySource.StartActivity("CreateOrder");
using var validationSpan = activitySource.StartActivity("ValidateOrder", ActivityKind.Internal);
// validationSpan is automatically a child of parent due to Activity.Current
Use IMeterFactory for DI-friendly meter creation. One Meter per logical module.
// Register metrics class as singleton:
builder.Services.AddSingleton<OrderMetrics>();
public class OrderMetrics(IMeterFactory factory)
{
private readonly Meter _meter = factory.Create(new MeterOptions("MyApp.Orders") { Version = "1.0" });
private readonly Counter<long> _ordersCreated;
private readonly Histogram<double> _orderProcessingDuration;
private readonly UpDownCounter<long> _pendingOrders;
public OrderMetrics(IMeterFactory factory)
{
_meter = factory.Create(new MeterOptions("MyApp.Orders") { Version = "1.0" });
_ordersCreated = _meter.CreateCounter<long>("orders.created", "orders", "Total orders created");
_orderProcessingDuration = _meter.CreateHistogram<double>("orders.processing_duration", "ms", "Order processing time");
_pendingOrders = _meter.CreateUpDownCounter<long>("orders.pending", "orders", "Current pending orders");
}
public void RecordOrderCreated(string tier)
=> _ordersCreated.Add(1, new TagList { { "customer.tier", tier } });
public void RecordProcessingDuration(double ms, string status)
=> _orderProcessingDuration.Record(ms, new TagList { { "order.status", status } });
public void IncrementPending() => _pendingOrders.Add(1);
public void DecrementPending() => _pendingOrders.Add(-1);
}
When using .NET Aspire, OTEL is pre-configured via AddServiceDefaults() in the ServiceDefaults project. Do not manually call AddOpenTelemetry() in projects that call AddServiceDefaults() — it's already wired.
// AppHost project — resource registration
var api = builder.AddProject<Projects.MyApp_Api>("myapp-api");
// API project — ServiceDefaults handles OTEL, health checks, service discovery
builder.AddServiceDefaults(); // replaces manual AddOpenTelemetry() setup
app.MapDefaultEndpoints(); // maps /health and /alive
ServiceDefaults reads OTEL_EXPORTER_OTLP_ENDPOINT and OTEL_SERVICE_NAME from the environment. Aspire injects these automatically when running via the AppHost.
You still need to register custom ActivitySource and Meter names:
// In ServiceDefaults ConfigureOpenTelemetry or in the service itself:
.AddSource("MyApp.Orders")
.AddMeter("MyApp.Orders")
Via environment variables (preferred — works with Aspire and Docker):
{
"OTEL_EXPORTER_OTLP_ENDPOINT": "http://localhost:4317",
"OTEL_SERVICE_NAME": "myapp-api",
"OTEL_SERVICE_VERSION": "1.0.0"
}
Via code (for explicit control):
.AddOtlpExporter(options =>
{
options.Endpoint = new Uri("http://localhost:4317");
options.Protocol = OtlpExportProtocol.Grpc; // or HttpProtobuf
})
Add Serilog.Enrichers.Span to include TraceId and SpanId in every log entry, enabling log↔trace correlation in your observability platform:
<PackageReference Include="Serilog.Enrichers.Span" Version="3.*" />
builder.Host.UseSerilog((ctx, cfg) =>
cfg.Enrich.WithSpan() // adds TraceId and SpanId to log properties
...);
| Do | Don't |
|---|---|
Register ActivitySource as singleton | Create new ActivitySource(...) per request |
| Tag spans with business-meaningful IDs (order ID, user tier) | Tag spans with PII |
Use SetStatus(Error) + RecordException in catch blocks | Swallow exceptions without marking the span failed |
| Exclude health check endpoints from tracing | Trace /health and /alive — creates noise |
| Disable SQL statement capture in production | Leave SetDbStatementForText = true in prod |
Use ActivityKind.Internal for internal spans | Leave ActivityKind unset on child spans |
skills/logging/SKILL.md — Serilog setup and correlation with spans via TraceIdAddServiceDefaults() source in ServiceDefaults/Extensions.csJWT bearer auth, ASP.NET Identity, OIDC, and policy-based authorization patterns for ASP.NET Core APIs.
HybridCache (.NET 9+), output caching, cache-aside pattern, and IMemoryCache — registration, usage, invalidation, and key strategy.
Protocol for detecting and replicating existing project conventions before generating new code — naming, folder structure, DI registration, test framework, DTOs, and error handling.
Domain-Driven Design patterns for .NET — aggregates, value objects, strongly-typed IDs, domain events, repositories, and layer rules.
Result pattern, ProblemDetails (RFC 7807), global exception boundaries, and typed error records for .NET APIs.
Serilog structured logging for ASP.NET Core — setup, message templates, LogContext enrichment, request logging middleware, and log level guidelines.