Why Distributed Tracing Matters in .NET 8 Microservices
When a single request hops across five or six services, pinpointing the latency source becomes a guessing game. A 2023 survey by the Cloud Native Computing Foundation reported that 68% of developers consider observability the biggest blocker for scaling micro‑architectures. Distributed tracing turns that chaos into a visual map, showing each span, its duration, and the exact point of failure.
Prerequisites and System Requirements
Before you start, ensure your development machine runs .NET 8 SDK (released November 2023) and Docker 20.10+. Your target environment should have access to a Jaeger instance (local or Azure‑hosted) and an Azure Monitor workspace with a valid connection string. A typical CI pipeline will need the OpenTelemetry .NET Collector package version 1.6.0 or later.
Adding OpenTelemetry SDK to a .NET 8 Service
The first step is to register the OpenTelemetry services in the host builder. The SDK automatically captures ASP.NET Core requests, outgoing HTTP calls, and custom activities. Below is a minimal configuration for an "OrderService" microservice.
using Microsoft.Extensions.DependencyInjection;
using OpenTelemetry.Trace;
using OpenTelemetry.Resources;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddOpenTelemetryTracing(ot =>
{
ot.SetResourceBuilder(ResourceBuilder.CreateDefault().AddService("OrderService"))
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddJaegerExporter(jaegerOptions =>
{
jaegerOptions.AgentHost = "localhost";
jaegerOptions.AgentPort = 6831;
})
.AddAzureMonitorTraceExporter(azureOptions =>
{
azureOptions.ConnectionString = \"InstrumentationKey=YOUR_KEY\";
});
});
var app = builder.Build();
app.MapGet("/health", () => "OK");
app.Run();Notice the use of ResourceBuilder to tag all spans with the service name. The AddJaegerExporter and AddAzureMonitorTraceExporter calls can coexist, allowing you to send data to both back‑ends simultaneously.
Exporting Traces to Jaeger
Jaeger provides an open‑source UI that visualizes trace graphs without additional licensing. Run it locally with Docker to validate your instrumentation before pushing to production.
docker run -d --name jaeger \
-e COLLECTOR_ZIPKIN_HOST_PORT=:9411 \
-p 6831:6831/udp \
-p 16686:16686 \
jaegertracing/all-in-one:1.53After the container is up, open http://localhost:16686 and search for the "OrderService" traces. If you see spans with proper parent‑child relationships, the OpenTelemetry pipeline is working.
Integrating Azure Monitor as a Secondary Exporter
Azure Monitor adds powerful aggregation, alerting, and cost‑analysis features. To enable it, create a Log Analytics workspace, copy its connection string, and paste it into the ConnectionString field shown earlier. The exporter batches spans in 10‑second intervals, minimizing network overhead.
For production environments, you may want to enable adaptive sampling to keep ingestion costs predictable. The SDK supports TraceIdRatioBasedSampler with a configurable ratio (e.g., 0.2 for 20% of requests).
ot.SetSampler(new TraceIdRatioBasedSampler(0.2));Correlating Logs and Metrics with Trace Context
One of the hidden benefits of OpenTelemetry is automatic propagation of the trace context into ILogger entries. Configure the logger like this:
builder.Logging.AddOpenTelemetry(options =>
{
options.IncludeScopes = true;
options.ParseStateValues = true;
});Now every log line contains trace_id and span_id fields, which you can filter in Azure Monitor or Jaeger’s log view. This correlation makes root‑cause analysis dramatically faster.
Testing the End‑to‑End Flow
Use a simple HTTP client to trigger a request chain: OrderService → InventoryService → PaymentService. Each service should have the same OpenTelemetry setup. After a few minutes, you will see a single trace spanning three services, with each span displaying latency and any exceptions.
Validate the data in both Jaeger UI and Azure Monitor’s “Traces” blade. If a span is missing, check that the corresponding service has the AddAspNetCoreInstrumentation call and that the environment variable OTEL_EXPORTER_JAEGER_ENDPOINT (if used) points to the correct collector.
Performance Considerations and Sampling
Collecting every request can increase CPU usage by 3‑5% and network traffic by up to 15 MB/s in a 100 RPS service. Apply head‑based sampling early in the pipeline to reduce overhead. The ParentBasedSampler respects upstream sampling decisions, preserving a consistent view across services.
Finally, enable the OpenTelemetry .NET “Instrumentation” environment variables to fine‑tune what gets captured. For example, OTEL_DOTNET_EXPERIMENTAL_HTTP_CLIENT_ENABLED=true adds detailed request/response headers without code changes.
Sources
- OpenTelemetry .NET Documentation
- Jaeger Tracing Official Site
- Azure Monitor Documentation
Author: Mahmut Sarıkaya — sarikayadev.com