OpenTelemetry for ASP.NET Core: Trace, Measure, and Debug Production Requests
When an API becomes slow, a log line saying “request failed” is rarely enough. You need to see which dependency consumed the time, whether latency is climbing, and which release changed the behavior. OpenTelemetry gives an ASP.NET Core service a vendor-neutral way to emit traces, metrics, and logs that answer those questions.
This guide builds a small, useful baseline. It does not promise a magic dashboard: it shows how to create signals you can query, keep sensitive data out, and validate that a request remains correlated across an outbound call.
Start with questions, not exporters
Pick a few decisions an on-call engineer must make: Is latency in our service or a dependency? Are failures isolated to one route? Did a database or outbound HTTP call dominate a trace? Microsoft describes logs, metrics, and distributed traces as complementary signals; use the three together rather than treating one as a replacement for the others.
For a service that calls another API, a practical baseline is request duration and error rate by route, a trace for slow or failed requests, and structured logs that include the trace identifier. The HttpClient resilience guide is the natural companion when a trace reveals an unstable outbound dependency.
Add tracing and metrics deliberately
The example uses the ASP.NET Core and HttpClient instrumentations plus an OTLP exporter. Point the exporter at your collector through configuration; do not hard-code a vendor endpoint or secret in source control. The custom activity source records business work that framework instrumentation cannot infer.
using OpenTelemetry.Metrics;
using OpenTelemetry.Trace;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddOpenTelemetry()
.WithTracing(tracing => tracing
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddSource("Orders.Api")
.AddOtlpExporter())
.WithMetrics(metrics => metrics
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddRuntimeInstrumentation()
.AddOtlpExporter());
builder.Services.AddHttpClient<InventoryClient>();
var app = builder.Build();
app.Run();
Package versions and exporter options change, so confirm the current package documentation and your collector’s authentication model before deployment. .NET’s built-in logging, metrics, and activity APIs are the foundation that OpenTelemetry integrates with; that means library instrumentation and your own ActivitySource can participate in one trace.
Make application work visible
Name spans for stable operations, not for unbounded identifiers. An order ID belongs in a carefully governed attribute or log field only if your privacy policy permits it; it should not become the span name. Record exceptions so failures have context without serializing whole request bodies.
using System.Diagnostics;
public sealed class OrderService(InventoryClient inventory)
{
private static readonly ActivitySource ActivitySource = new("Orders.Api");
public async Task<bool> ReserveAsync(string sku, int quantity, CancellationToken cancellationToken)
{
using var activity = ActivitySource.StartActivity("inventory.reserve");
activity?.SetTag("app.inventory.sku", sku);
try
{
return await inventory.ReserveAsync(sku, quantity, cancellationToken);
}
catch (Exception exception)
{
activity?.SetStatus(ActivityStatusCode.Error, exception.Message);
activity?.RecordException(exception);
throw;
}
}
}
Control cardinality and sensitive data
Telemetry has a cost. A tag such as user ID, raw URL query, email address, or order ID can create an enormous number of distinct metric series and may expose private data. Keep metric dimensions bounded: route template, HTTP method, status class, region, and a small set of dependency names are usually safer. Sample successful traces when volume is high, but retain enough failed and slow traces to investigate incidents.
Also treat request and response bodies, authorization headers, and connection strings as prohibited telemetry by default. Review collector processors, export destinations, retention, and access controls alongside application code. If the signal cannot be safely retained, do not emit it.
Validate the signal path before an incident
- Run a request that makes an outbound HTTP call and confirm one trace contains both server and client spans.
- Force a safe dependency failure in a non-production environment; verify error status and exception data are useful but not sensitive.
- Compare a metric’s route label cardinality before and after a load test.
- Confirm a log record can be pivoted to the relevant trace in your backend.
- Test collector outage behavior: telemetry should degrade without taking down request handling.
Production checklist
- Set a stable service name, environment, and version through resource attributes.
- Use bounded attributes and a sampling policy that preserves failure investigation.
- Protect exporter credentials outside the image and rotate them.
- Alert on user-facing symptoms such as error rate and latency, not on every log line.
- Pair traces with readiness checks; see ASP.NET Core health checks for the operational boundary.
Conclusion
OpenTelemetry is valuable when it shortens the path from symptom to evidence. Begin with a small signal contract, trace one real request end to end, and govern what leaves the process. For the broader delivery context, the .NET and ASP.NET Core learning track connects testing, containers, deployment, and production operations.