Home C# / .NET OpenTelemetry for .NET: Distributed Tracing & Metrics Guide
Advanced 3 min · July 13, 2026

OpenTelemetry for .NET: Distributed Tracing & Metrics Guide

Learn to implement OpenTelemetry in .NET for distributed tracing, metrics, and logging.

N
Naren Founder & Principal Engineer

20+ years shipping production .NET services in enterprise systems. Everything here is grounded in real deployments.

Follow
Production
production tested
July 13, 2026
last updated
2,043
articles · all by Naren
Before you start⏱ 20-25 min read
  • Basic knowledge of C# and ASP.NET Core
  • Visual Studio or .NET CLI installed
  • Familiarity with NuGet packages
 ● Production Incident 🔎 Debug Guide ⚙ Triage Commands
Quick Answer
  • OpenTelemetry is an observability framework for collecting traces, metrics, and logs.
  • In .NET, use the OpenTelemetry SDK and exporter packages.
  • Key signals: traces (spans), metrics (instruments), logs.
  • Export to backends like Jaeger, Prometheus, or Azure Monitor.
  • Instrument ASP.NET Core automatically via AddAspNetCoreInstrumentation().
✦ Definition~90s read
What is OpenTelemetry for .NET Applications?

OpenTelemetry is an open-source observability framework that helps you collect, process, and export traces, metrics, and logs from your .NET applications.

Think of OpenTelemetry as a black box flight recorder for your app.
Plain-English First

Think of OpenTelemetry as a black box flight recorder for your app. It records every request (trace) as a series of steps (spans), measures performance (metrics), and logs errors. When something goes wrong, you can replay the flight to find the issue.

Modern applications are distributed, often spanning multiple services, databases, and cloud infrastructure. When a user reports a slow page load or an error, pinpointing the root cause becomes a detective game. Traditional logging gives you isolated messages, but lacks context across service boundaries. This is where OpenTelemetry shines.

OpenTelemetry is an open-source observability framework that provides a unified way to collect traces, metrics, and logs from your applications. It standardizes how telemetry data is generated, collected, and exported, making it easier to monitor and debug complex systems.

In this tutorial, you'll learn how to instrument a .NET application with OpenTelemetry. We'll cover distributed tracing, metrics, and logs, with practical examples for ASP.NET Core. You'll see how to export data to Jaeger for traces and Prometheus for metrics. By the end, you'll have a production-ready setup that helps you understand your application's behavior and quickly diagnose issues.

What is OpenTelemetry?

OpenTelemetry is a collection of APIs, SDKs, and tools for instrumenting, generating, collecting, and exporting telemetry data (traces, metrics, logs) to help you analyze your software's performance and behavior. It is vendor-agnostic and supports multiple backends like Jaeger, Prometheus, Zipkin, and Azure Monitor.

The three pillars of observability are
  • Traces: Record the path of a request through a system, consisting of spans (individual operations).
  • Metrics: Aggregate measurements (e.g., request count, latency) over time.
  • Logs: Timestamped text records with structured data.

OpenTelemetry standardizes how these signals are generated and exported, allowing you to switch backends without changing your instrumentation code.

Program.csCSHARP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
using OpenTelemetry;
using OpenTelemetry.Trace;
using OpenTelemetry.Metrics;
using OpenTelemetry.Logs;

var builder = WebApplication.CreateBuilder(args);

// Configure OpenTelemetry tracing
builder.Services.AddOpenTelemetry()
    .WithTracing(tracing => tracing
        .AddAspNetCoreInstrumentation()
        .AddHttpClientInstrumentation()
        .AddConsoleExporter())
    .WithMetrics(metrics => metrics
        .AddAspNetCoreInstrumentation()
        .AddConsoleExporter());

// Configure OpenTelemetry logging
builder.Logging.AddOpenTelemetry(logging => logging
    .AddConsoleExporter());

var app = builder.Build();

app.MapGet("/", () => "Hello World!");

app.Run();
🔥Getting Started
📊 Production Insight
In production, avoid using Console exporter due to performance overhead. Use OTLP exporter or backend-specific exporters.
🎯 Key Takeaway
OpenTelemetry provides a unified API for traces, metrics, and logs, making it easy to instrument your application once and export to any backend.

Setting Up Distributed Tracing

Distributed tracing tracks a single request as it travels across multiple services. Each service creates spans that are linked together via a trace ID. In .NET, you can automatically instrument ASP.NET Core requests, HttpClient calls, and database queries.

To set up tracing, add the OpenTelemetry SDK and configure the tracer provider. Use AddAspNetCoreInstrumentation() to capture incoming HTTP requests, and AddHttpClientInstrumentation() to capture outgoing HTTP calls. You can also add custom spans for business logic.

Program.csCSHARP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
using OpenTelemetry.Trace;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddOpenTelemetry()
    .WithTracing(tracing => tracing
        .AddAspNetCoreInstrumentation()
        .AddHttpClientInstrumentation()
        .AddEntityFrameworkCoreInstrumentation() // if using EF Core
        .AddOtlpExporter(options => options.Endpoint = new Uri("http://localhost:4317")));

var app = builder.Build();

app.MapGet("/order/{id}", async (int id, HttpClient client) =>
{
    // Custom span
    using var activity = Activity.Current?.Source.StartActivity("ProcessOrder");
    activity?.SetTag("order.id", id);
    
    var response = await client.GetAsync($"http://payment-service/process/{id}");
    return await response.Content.ReadAsStringAsync();
});

app.Run();
💡Custom Spans
📊 Production Insight
Set appropriate sampling rates to control volume. Use head-based sampling (e.g., TraceIdRatioBasedSampler) to sample a percentage of requests.
🎯 Key Takeaway
Automatic instrumentation covers most common libraries, but you can also create custom spans for deeper visibility.

Collecting Metrics

Metrics are numerical measurements collected over time, such as request count, latency, and error rate. OpenTelemetry provides instruments like Counter, Histogram, and Gauge. ASP.NET Core instrumentation automatically captures HTTP server metrics (request duration, active requests). You can also create custom metrics.

To export metrics, configure a metric reader and exporter. Prometheus is a popular backend; use AddPrometheusExporter() to expose a /metrics endpoint.

Program.csCSHARP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
using OpenTelemetry.Metrics;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddOpenTelemetry()
    .WithMetrics(metrics => metrics
        .AddAspNetCoreInstrumentation()
        .AddPrometheusExporter());

var app = builder.Build();

app.MapGet("/", () => "Hello World!");

// Expose metrics endpoint
app.UseOpenTelemetryPrometheusScrapingEndpoint();

app.Run();
🔥Custom Metrics
📊 Production Insight
Use histogram metrics for latency distributions. Configure explicit bucket boundaries to match your SLOs.
🎯 Key Takeaway
Metrics give you aggregated views of system health and performance, essential for dashboards and alerts.

Integrating Logs with OpenTelemetry

OpenTelemetry can also collect logs, providing a structured and correlated logging experience. By integrating with .NET's ILogger, you can enrich logs with trace and span IDs, making it easy to correlate logs with traces.

To enable OpenTelemetry logging, add the OpenTelemetry.Extensions.Logging package and configure the logger factory.

Program.csCSHARP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
using OpenTelemetry.Logs;

var builder = WebApplication.CreateBuilder(args);

builder.Logging.AddOpenTelemetry(logging => logging
    .AddOtlpExporter(options => options.Endpoint = new Uri("http://localhost:4317")));

var app = builder.Build();

app.MapGet("/", (ILogger<Program> logger) =>
{
    logger.LogInformation("Handling request");
    return "Hello World!";
});

app.Run();
⚠ Log Volume
📊 Production Insight
Use structured logging with semantic properties to enable powerful querying in your log backend.
🎯 Key Takeaway
OpenTelemetry logs are automatically correlated with traces, providing full context for debugging.

Exporting Telemetry to Backends

OpenTelemetry supports multiple exporters: Console, OTLP (OpenTelemetry Protocol), Jaeger, Zipkin, Prometheus, Azure Monitor, and more. The OTLP exporter is the recommended standard, as it can send traces, metrics, and logs to any OTLP-compatible backend.

To export to Jaeger, use the OpenTelemetry.Exporter.Jaeger package. For Prometheus, use OpenTelemetry.Exporter.Prometheus.AspNetCore. For Azure Monitor, use Azure.Monitor.OpenTelemetry.AspNetCore.

Program.csCSHARP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// OTLP exporter (traces, metrics, logs)
builder.Services.AddOpenTelemetry()
    .WithTracing(tracing => tracing.AddOtlpExporter())
    .WithMetrics(metrics => metrics.AddOtlpExporter());

// Jaeger exporter (traces only)
builder.Services.AddOpenTelemetry()
    .WithTracing(tracing => tracing
        .AddJaegerExporter(options =>
        {
            options.AgentHost = "localhost";
            options.AgentPort = 6831;
        }));

// Prometheus exporter (metrics only)
builder.Services.AddOpenTelemetry()
    .WithMetrics(metrics => metrics
        .AddPrometheusExporter());
💡Choosing an Exporter
📊 Production Insight
Always configure batching and retries for exporters to handle transient network issues.
🎯 Key Takeaway
Exporters send telemetry to backends. OTLP is the standard protocol, but specific exporters exist for popular backends.

Sampling Strategies

In high-traffic systems, collecting every trace can be expensive. Sampling allows you to collect a representative subset. OpenTelemetry supports head-based sampling (decision made at the start of a trace) and tail-based sampling (decision made after the trace is complete).

Head-based sampling is simpler and can be configured via the sampler. For example, TraceIdRatioBasedSampler samples a fixed percentage of traces.

Program.csCSHARP
1
2
3
4
5
6
7
using OpenTelemetry.Trace;

builder.Services.AddOpenTelemetry()
    .WithTracing(tracing => tracing
        .SetSampler(new TraceIdRatioBasedSampler(0.1)) // 10% sampling
        .AddAspNetCoreInstrumentation()
        .AddOtlpExporter());
🔥Tail-Based Sampling
📊 Production Insight
Start with 100% sampling in development and adjust to 1-10% in production. Use adaptive sampling based on error rate.
🎯 Key Takeaway
Sampling reduces data volume while retaining representative traces. Choose a strategy that balances cost and visibility.

Security and Best Practices

When instrumenting your application, be mindful of security. Avoid capturing sensitive data in span attributes or log messages. Use attribute filtering to redact personal information.

Also, ensure that your OpenTelemetry endpoints are secured. If using OTLP over gRPC, consider TLS. For Prometheus scraping, protect the /metrics endpoint with authentication if it exposes internal metrics.

Best practices
  • Use semantic conventions for attribute names.
  • Set appropriate resource attributes (service name, version, environment).
  • Monitor the overhead of OpenTelemetry itself (CPU, memory).
  • Regularly review and update exporter configurations.
Program.csCSHARP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Redact sensitive data from spans
builder.Services.AddOpenTelemetry()
    .WithTracing(tracing => tracing
        .AddAspNetCoreInstrumentation(options =>
        {
            options.Filter = (httpContext) =>
            {
                // Exclude health check endpoints
                return !httpContext.Request.Path.StartsWithSegments("/health");
            };
            options.EnrichWithHttpRequest = (activity, request) =>
            {
                // Remove query string parameters that may contain PII
                activity.SetTag("http.url", request.Scheme + "://" + request.Host + request.Path);
            };
        }));
⚠ Data Privacy
📊 Production Insight
Implement a data redaction middleware that runs before OpenTelemetry instrumentation to scrub sensitive fields.
🎯 Key Takeaway
Security is paramount. Always sanitize telemetry data and secure exporter endpoints.
● Production incidentPOST-MORTEMseverity: high

The Silent Database Timeout

Symptom
Users reported occasional slow page loads, but average response times looked fine.
Assumption
Developers assumed the issue was network latency or a third-party API.
Root cause
A database query that timed out after 5 seconds on certain rows due to missing index, causing a retry loop.
Fix
Added an index and implemented a circuit breaker to fail fast.
Key lesson
  • Average metrics can hide outliers; use percentiles and traces to catch slow requests.
  • Distributed tracing reveals the exact span where time is spent.
  • Always instrument database calls with OpenTelemetry to see query performance.
  • Set up alerts on span duration percentiles (e.g., p99).
  • Use span attributes to capture query text and parameters for debugging.
Production debug guideSymptom to Action4 entries
Symptom · 01
Traces not appearing in backend
Fix
Check exporter configuration, network connectivity, and sampling rate.
Symptom · 02
Spans missing for certain requests
Fix
Verify instrumentation is added for all libraries (e.g., HttpClient, EF Core).
Symptom · 03
Metrics show no data
Fix
Ensure metric instruments are created and recorded; check exporter endpoint.
Symptom · 04
High overhead in production
Fix
Adjust sampling rate (e.g., use head-based sampling for traces).
★ Quick Debug Cheat SheetCommon OpenTelemetry issues and immediate fixes.
No traces exported
Immediate action
Check exporter endpoint and API key.
Commands
dotnet add package OpenTelemetry.Exporter.Console
Add UseConsoleExporter() to see traces in console.
Fix now
Temporarily switch to Console exporter to verify data generation.
Spans not correlating across services+
Immediate action
Ensure consistent TraceId propagation via HTTP headers.
Commands
Check if HttpClient instrumentation is added.
Verify propagation format (W3C TraceContext).
Fix now
Add AddHttpClientInstrumentation() and ensure downstream services also use OpenTelemetry.
Metrics not showing in Prometheus+
Immediate action
Check Prometheus scrape configuration and endpoint.
Commands
Verify /metrics endpoint returns data.
Check if metric exporter is registered.
Fix now
Add AddPrometheusExporter() and ensure Prometheus is scraping the correct port.
FeatureOpenTelemetryApplication Insights SDK
Vendor AgnosticYesNo (Azure only)
TracesYesYes
MetricsYesYes
LogsYesYes (via ILogger)
SamplingConfigurableFixed rate
Custom InstrumentationEasy via ActivitySourceCustom telemetry client
Open SourceYesNo
⚙ Quick Reference
5 commands from this guide
FileCommand / CodePurpose
Program.csusing OpenTelemetry;What is OpenTelemetry?
Program.csusing OpenTelemetry.Trace;Setting Up Distributed Tracing
Program.csusing OpenTelemetry.Metrics;Collecting Metrics
Program.csusing OpenTelemetry.Logs;Integrating Logs with OpenTelemetry
Program.csbuilder.Services.AddOpenTelemetry()Exporting Telemetry to Backends

Key takeaways

1
OpenTelemetry provides a unified API for traces, metrics, and logs, enabling comprehensive observability.
2
Automatic instrumentation in .NET covers ASP.NET Core, HttpClient, and EF Core, but you can also add custom spans.
3
Sampling is essential in production to manage data volume and cost.
4
Always sanitize telemetry data to avoid leaking sensitive information.
5
Use the OpenTelemetry Collector for production-grade batching, retries, and processing.

Common mistakes to avoid

5 patterns
×

Not setting up a sampler and exporting 100% of traces in production.

×

Forgetting to add instrumentation for HttpClient or database calls.

×

Using Console exporter in production.

×

Not handling exceptions in custom spans.

×

Exposing sensitive data in span attributes or logs.

INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01SENIOR
Explain the three pillars of observability and how OpenTelemetry address...
Q02SENIOR
How does OpenTelemetry handle context propagation across services?
Q03SENIOR
What is the difference between head-based and tail-based sampling?
Q04JUNIOR
How can you add custom attributes to a span in .NET?
Q05SENIOR
What is the role of the OpenTelemetry Collector?
Q01 of 05SENIOR

Explain the three pillars of observability and how OpenTelemetry addresses them.

ANSWER
The three pillars are traces, metrics, and logs. OpenTelemetry provides APIs and SDKs to generate and export all three signals in a unified manner. Traces track request flow, metrics aggregate measurements, and logs record events. OpenTelemetry standardizes the collection and export, enabling correlation between signals.
FAQ · 5 QUESTIONS

Frequently Asked Questions

01
What is the difference between OpenTelemetry and Application Insights?
02
How do I correlate logs with traces?
03
Can I use OpenTelemetry with .NET Framework?
04
How do I handle high cardinality metrics?
05
What is the OpenTelemetry Collector?
N
Naren Founder & Principal Engineer

20+ years shipping production .NET services in enterprise systems. Everything here is grounded in real deployments.

Follow
Verified
production tested
July 13, 2026
last updated
2,043
articles · all by Naren
🔥

That's ASP.NET. Mark it forged?

3 min read · try the examples if you haven't

Previous
FluentValidation in ASP.NET Core
28 / 35 · ASP.NET
Next
CI/CD for .NET with GitHub Actions