How to Configure OpenTelemetry Instrumentation in ASP.NET Core Applications
To configure OpenTelemetry in ASP.NET Core, install the required NuGet packages, register the SDK in Program.cs via AddOpenTelemetry(), and configure instrumentation for tracing, metrics, and logging with exporters like OTLP.
OpenTelemetry provides a unified observability framework for distributed tracing, metrics, and logging in modern .NET applications. According to the dotnet/skills repository, the recommended approach involves registering the SDK with the dependency injection container and configuring specific instrumentation providers in your application's startup code.
Installing Required NuGet Packages
Begin by adding the core SDK and instrumentation packages to your project. The skill definition file specifies the minimal set required at [lines 35‑43 of plugins/dotnet-aspnetcore/skills/configuring-opentelemetry-dotnet/SKILL.md](https://github.com/dotnet/skills/blob/main/plugins/dotnet-aspnetcore/skills/configuring-opentelemetry-dotnet/SKILL.md#L35‑L43):
dotnet add package OpenTelemetry.Extensions.Hosting
dotnet add package OpenTelemetry.Instrumentation.AspNetCore
dotnet add package OpenTelemetry.Instrumentation.Http
dotnet add package OpenTelemetry.Exporter.OpenTelemetryProtocol # OTLP exporter
Optional packages for Entity Framework Core, gRPC, SQL, and runtime metrics are listed at lines 54‑58 of the same file.
Registering the OpenTelemetry SDK in Program.cs
Register the SDK in your Program.cs file using the AddOpenTelemetry() extension method. The full implementation pattern is demonstrated at lines 61‑111:
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;
using OpenTelemetry.Metrics;
using OpenTelemetry.Logs;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddOpenTelemetry()
.ConfigureResource(r => r.AddService(builder.Environment.ApplicationName))
.WithTracing(t => t
.AddAspNetCoreInstrumentation(o =>
o.Filter = ctx => !ctx.Request.Path.StartsWithSegments("/healthz"))
.AddHttpClientInstrumentation(o => o.RecordException = true)
.AddSource("MyApp.Orders")
.AddSource("MyApp.Payments")
.AddSource("MyApp.Messaging"))
.WithMetrics(m => m
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddMeter("MyApp.Metrics"))
.WithLogging(l => { l.IncludeScopes = true; })
.UseOtlpExporter();
The UseOtlpExporter() method reads the OTEL_EXPORTER_OTLP_ENDPOINT environment variable, defaulting to http://localhost:4317 as noted at lines 107‑110.
Configuring Traces, Metrics, and Logs
Distributed Tracing Configuration
The .WithTracing() builder configures automatic instrumentation for ASP.NET Core and HTTP clients. The example above filters out health check endpoints (/healthz) to reduce noise. You must explicitly register any custom ActivitySource names using .AddSource() so the SDK captures those spans.
Metrics and Log Correlation
The .WithMetrics() builder exposes runtime and custom metrics, while .WithLogging() automatically injects TraceId and SpanId into every ILogger entry, establishing automatic log-trace correlation without additional resource configuration (lines 113‑119).
Creating Custom Spans and Metrics
Custom Activity Sources
To emit custom spans, create a static ActivitySource and match its name to an AddSource() registration. The sample service at lines 124‑178 demonstrates this pattern:
using System.Diagnostics;
using Microsoft.Extensions.Logging;
public class OrderService
{
private static readonly ActivitySource ActivitySource = new("MyApp.Orders");
private readonly ILogger<OrderService> _logger;
public OrderService(ILogger<OrderService> logger) => _logger = logger;
public async Task<Order> ProcessOrderAsync(CreateOrderRequest request)
{
using var activity = ActivitySource.StartActivity("ProcessOrder");
activity?.SetTag("order.customer_id", request.CustomerId);
// Business logic here
return new Order { /* ... */ };
}
}
Custom Metrics with IMeterFactory
For custom metrics, inject IMeterFactory and ensure the meter name matches an AddMeter() registration. The OrderMetrics class at lines 185‑228 shows counters, histograms, and up-down counters:
using System.Diagnostics.Metrics;
public class OrderMetrics
{
private readonly Counter<long> _ordersProcessed;
private readonly Histogram<double> _orderProcessingDuration;
private readonly UpDownCounter<int> _activeOrders;
public OrderMetrics(IMeterFactory meterFactory)
{
var meter = meterFactory.Create("MyApp.Metrics");
_ordersProcessed = meter.CreateCounter<long>("orders.processed", "orders");
_orderProcessingDuration = meter.CreateHistogram<double>("orders.processing_duration", "ms");
_activeOrders = meter.CreateUpDownCounter<int>("orders.active", "orders");
}
public void RecordOrderProcessed(string region, double durationMs)
{
var tags = new TagList { { "region", region }, { "order.type", "standard" } };
_ordersProcessed.Add(1, tags);
_orderProcessingDuration.Record(durationMs, tags);
}
}
Propagating Context Across Non-HTTP Boundaries
For message queues or custom protocols, manually propagate trace context using Propagators.DefaultTextMapPropagator. The implementation at lines 381‑668 shows injection and extraction:
var propagator = Propagators.DefaultTextMapPropagator;
var carrier = new Dictionary<string, string>();
// SEND side - inject current context
propagator.Inject(
new PropagationContext(Activity.Current?.Context ?? default, Baggage.Current),
carrier,
(dict, key, value) => dict[key] = value);
// RECEIVE side - extract parent context
var parentContext = propagator.Extract(default, carrier,
(dict, key) => dict.TryGetValue(key, out var v) ? new[] { v } : Array.Empty<string>());
Baggage.Current = parentContext.Baggage;
using var activity = ActivitySource.StartActivity("ProcessMessage",
ActivityKind.Consumer,
parentContext.ActivityContext);
Avoiding Common Configuration Errors
The skill documentation at lines 781‑889 identifies frequent pitfalls:
- Mismatched ActivitySource names: Ensure the string passed to
AddSource()exactly matches theActivitySourceconstructor name. - Exporter endpoint mismatches: Verify
OTEL_EXPORTER_OTLP_ENDPOINTpoints to the correct collector port (4317 for gRPC, 4318 for HTTP). - High-cardinality tags: Avoid setting unique IDs (like user IDs or request GUIDs) as tag keys, which can overwhelm the telemetry backend.
Summary
- Install the core packages
OpenTelemetry.Extensions.Hosting,OpenTelemetry.Instrumentation.AspNetCore, andOpenTelemetry.Exporter.OpenTelemetryProtocol. - Register the SDK in
Program.csusingbuilder.Services.AddOpenTelemetry()and configure resources, tracing, metrics, and logging. - Use
AddSource()andAddMeter()to register custom telemetry sources, ensuring names match exactly. - Configure
UseOtlpExporter()to export telemetry via the OpenTelemetry Protocol. - Apply manual context propagation for messaging systems and asynchronous processing.
Frequently Asked Questions
How do I filter health check endpoints from OpenTelemetry tracing?
Use the Filter option in AddAspNetCoreInstrumentation() to exclude specific request paths. The recommended pattern excludes /healthz to prevent noise from health probes: o.Filter = ctx => !ctx.Request.Path.StartsWithSegments("/healthz").
What environment variable configures the OTLP exporter endpoint?
The UseOtlpExporter() method reads the OTEL_EXPORTER_OTLP_ENDPOINT environment variable, defaulting to http://localhost:4317 for gRPC communication. For HTTP transport, append /v1/traces or /v1/metrics to the endpoint URL and set the protocol accordingly.
Why aren't my custom spans appearing in the trace viewer?
Your ActivitySource name must exactly match a string passed to AddSource() during SDK configuration. Additionally, ensure you are creating the ActivitySource as a static instance and that you call StartActivity() within a using statement to ensure proper disposal and export.
How do I correlate logs with traces automatically?
Call .WithLogging(l => { l.IncludeScopes = true; }) during SDK configuration. This automatically injects TraceId and SpanId into ILogger output without requiring manual scope creation or separate resource builders.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →