← Open in the full interactive course (progress tracking, search & more)

One instrumentation API. Any backend you want. That's the entire point.

Imagine instrumenting your entire application — every log line, every metric, every trace span — specifically for one vendor's monitoring product. It works great, until finance asks you to switch vendors to cut costs, or a client mandates a different tool, or the vendor you picked gets acquired and quietly sunsets the product you built your whole observability story on top of. Now you're not just switching a configuration setting — you're re-instrumenting your entire codebase, because your telemetry code was written directly against one vendor's proprietary SDK.

OpenTelemetry exists specifically to prevent that trap. You instrument your code once, against one open, vendor-neutral standard — and where that telemetry actually goes becomes a configuration decision, not a rewrite.

This is the capstone lesson of Part VII. You'll learn precisely what OpenTelemetry is (and, just as importantly, what it isn't), how it builds on .NET's own existing tracing and metrics primitives rather than replacing them, how to wire it into an ASP.NET Core app with automatic instrumentation for the HTTP, HttpClient, and EF Core calls you already know deeply — and, closing out this whole Part, how the last six lessons connect into one complete, production-ready ASP.NET Core story.

What Is It?

The Simple Explanation

OpenTelemetry (often shortened to "OTel") is an open, vendor-neutral standard for producing and exporting observability data — the logs, metrics, and traces from the previous lesson — in one common, agreed-upon format that many different tools and backends know how to consume. Crucially, OpenTelemetry itself is not a product, a dashboard, or a vendor — it doesn't store your telemetry, visualize it, or alert on it. It's the common language your app speaks, so that where the telemetry ends up is a choice you make separately, and can change later without touching your instrumentation code.

The Technical Definition

OpenTelemetry is a Cloud Native Computing Foundation (CNCF) project defining a standard API, SDK, and wire protocol for telemetry. In .NET, the OpenTelemetry SDK doesn't invent a whole new instrumentation mechanism from scratch — it builds directly on primitives .NET already had before OpenTelemetry adoption became standard practice: System.Diagnostics.Activity for distributed tracing, and System.Diagnostics.Metrics for metrics. The SDK's job is to collect what those built-in APIs already produce and route it, via configurable exporters, to whichever backend(s) you've chosen — Jaeger, Prometheus, Application Insights, Grafana, and many others, including several at once if you want.

What OpenTelemetry Is NOT

What OpenTelemetry Actually Is

Why Does It Exist?

The Problem — Every Vendor Wanted Its Own Instrumentation SDK

Before OpenTelemetry, observability vendors typically shipped their own proprietary instrumentation SDKs. Wanted distributed tracing from Vendor A? You instrumented your code with Vendor A's specific library, calling their specific APIs, in their specific way. Wanted to add metrics from Vendor B, or later switch tracing to Vendor C? You instrumented — and maintained — an entirely separate set of code for each one, scattered through your application. This is exactly the trap from the hook: your telemetry code became permanently coupled to whichever vendor you picked first, making switching (or even just running two backends side by side, which teams often genuinely want during a migration) expensive and disruptive.

The Solution — One Standard API, Many Possible Destinations

OpenTelemetry decouples producing telemetry from consuming it. Your application code instruments against one open, standard API — genuinely vendor-neutral, backed by essentially the entire observability industry as a shared standard, not just one company's product. Where that telemetry actually goes — which backend, or several at once — is purely an exporter configuration choice, made independently of your instrumentation code. Switch backends, and in the common case you change configuration, not application code.

Big Picture

                    YOUR APPLICATION CODE
                            │
        ┌───────────────────┼───────────────────┐
        │                   │                   │
   ILogger<T>          Activity            Meter / Instrument
   (Logs)               (Traces)            (Metrics)
        │                   │                   │
        └───────────────────┼───────────────────┘
                            │
                OpenTelemetry SDK for .NET
           (collects, batches, unifies the three)
                            │
                     EXPORTER (pluggable)
                            │
        ┌───────────────────┼───────────────────┐
        │                   │                   │
    Jaeger /            Prometheus /       Application Insights /
    Grafana Tempo        Grafana            Datadog / others...
      (traces)           (metrics)          (all three, vendor-hosted)

  ↑ Swap or add a backend here — WITHOUT touching your instrumentation code above.

The critical property this diagram is showing: everything above the "OpenTelemetry SDK" line is yours, standard, and stable. Everything below it — the exporter and destination — is configuration, and can change independently.

How It Works

.NET Already Had the Primitives — OTel Wires Them Up

BUILT-IN .NET PRIMITIVES BEHIND OPENTELEMETRY
1. TRACING — System.Diagnostics.Activity
2. METRICS — System.Diagnostics.Metrics
3. LOGGING — Microsoft.Extensions.Logging (ILogger<T>)
4. THE OPENTELEMETRY SDK CONNECTS ALL THREE TO THE OTEL ECOSYSTEM

Auto-Instrumentation — Telemetry "For Free"

WHAT AUTO-INSTRUMENTATION MEANS IN PRACTICE
1. LIBRARIES SHIP OFFICIAL OTEL INSTRUMENTATION PACKAGES
2. YOU ENABLE THEM WITH ONE LINE EACH, AT STARTUP
3. RESULT — MEANINGFUL TRACES WITH NO PER-METHOD INSTRUMENTATION CODE

Simple Example

Wiring OpenTelemetry into an ASP.NET Core app, with auto-instrumentation enabled for the three subsystems you already know well:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddOpenTelemetry()
    .ConfigureResource(resource => resource.AddService("OrderApi"))
    .WithTracing(tracing =>
    {
        tracing
            .AddAspNetCoreInstrumentation()   // incoming HTTP requests — automatically
            .AddHttpClientInstrumentation()   // outbound HttpClient calls — automatically
            .AddEntityFrameworkCoreInstrumentation() // EF Core queries — automatically
            .AddOtlpExporter();               // send traces via the OTLP protocol
    })
    .WithMetrics(metrics =>
    {
        metrics
            .AddAspNetCoreInstrumentation()
            .AddHttpClientInstrumentation()
            .AddRuntimeInstrumentation()      // GC, thread pool, and other runtime metrics
            .AddOtlpExporter();
    });

var app = builder.Build();
app.Run();

Nothing in your controllers, endpoint handlers, or services needed to change at all. The moment a request comes in, calls a downstream API via a typed HttpClient, and queries the database via EF Core, three connected spans are created automatically — one for the inbound request, one for the outbound HTTP call, one for the database query — each carrying real timing data, with no manual Activity creation anywhere in that code path.

Real-World Example

Continuing the checkout scenario from the previous lesson: OrderService receives a request, calls PaymentGatewayClient (a typed HttpClient, exactly like the one from Intermediate lesson 130), and queries the database via EF Core to persist the order. With auto-instrumentation wired in as above, this single request automatically produces a trace like:

Trace: POST /api/orders                              340ms
  ├─ ASP.NET Core span (the whole request)             340ms
  │    ├─ HttpClient span → PaymentGatewayClient        210ms  ← the resilience-wrapped call from lesson 267
  │    │      (tags: http.status_code=200, retry attempts, etc.)
  │    └─ EF Core span → INSERT INTO Orders...           45ms
  │           (tags: db.statement, db.system=sqlserver)
  └─ (ASP.NET Core middleware/filter overhead — the rest)

You can add a custom span of your own for a specific piece of business logic worth naming explicitly, using the same Activity primitive OTel already builds on:

private static readonly ActivitySource ActivitySource = new("OrderApi.OrderService");

public async Task<Order> PlaceOrderAsync(OrderRequest request)
{
    using var activity = ActivitySource.StartActivity("CalculateOrderTotal");
    activity?.SetTag("order.itemCount", request.Items.Count);

    var total = CalculateTotal(request.Items); // your own business logic

    activity?.SetTag("order.total", total);
    // ... proceed with the order ...
}

This custom span nests naturally inside the automatically-generated request span — because it's built on the exact same underlying Activity mechanism the auto-instrumentation packages use. Auto-instrumentation gives you the surrounding structure "for free"; manual spans like this let you name and enrich the specific parts of your own business logic worth calling out individually.

Analogy

A Universal Power Adapter

Imagine every appliance manufacturer required its own uniquely shaped wall outlet. Buy a different brand of appliance, rewire your wall. That's instrumenting directly against one vendor's proprietary SDK — you're locked to whatever "outlet shape" that vendor requires.

OpenTelemetry is like a universal, standard plug shape that every appliance (your application code) uses, plugged into a universal socket (the OTel SDK). What's actually on the other side of that socket — which specific power company, which specific grid — is a choice made independently of the plug shape. Swap providers, and your appliances don't need to change at all; only what's plugged in behind the wall changes.

Under the Hood

FROM AN Activity TO A REMOTE BACKEND
1. ASP.NET CORE ALREADY STARTS AN Activity PER REQUEST
2. THE OTEL SDK REGISTERS AS A LISTENER
3. COMPLETED SPANS ARE BATCHED AND SENT VIA AN EXPORTER
4. A COLLECTOR OR BACKEND RECEIVES AND STORES IT

Common Confusion

"OpenTelemetry is a monitoring tool, like a dashboard product" — it's the plumbing, not the faucet

This is the single most common misunderstanding, and it's worth being precise about: OpenTelemetry does not give you a UI to look at, does not store your telemetry long-term, and does not alert you when something's wrong. It's the standard that gets your telemetry produced and exported correctly — you still need to point it at an actual backend (open-source or vendor-provided) to actually store, query, visualize, and alert on that data. The vendor-neutrality is precisely what makes this genuinely valuable: the same instrumented application can export to a completely different backend without any code changes.

"OpenTelemetry replaces Activity and Meter" — it's built directly on top of them

Because OTel is often introduced alongside these APIs, it's easy to assume OpenTelemetry invented them or requires you to abandon them for something new. In .NET specifically, the opposite is true: Activity and Meter are .NET's own, pre-existing primitives, and OpenTelemetry's .NET SDK deliberately builds on top of them rather than replacing them — which is exactly why so much telemetry (from ASP.NET Core itself, for instance) is already available "for free" the moment you wire up the SDK, without waiting for every library author to write brand-new, OTel-specific instrumentation from scratch.

Common Mistakes

Mistake 1 — Manually instrumenting everything before checking what auto-instrumentation already covers

Hand-writing custom spans around every ASP.NET Core action, every HttpClient call, and every EF Core query — duplicating work the official instrumentation packages already do correctly.

Enable auto-instrumentation for the well-supported subsystems first (ASP.NET Core, HttpClient, EF Core), and reserve manual Activity/Meter usage for genuinely custom business logic worth naming explicitly.

Mistake 2 — Treating OpenTelemetry setup as "done" with no exporter configured

Adding AddOpenTelemetry() and the instrumentation calls but never configuring an actual exporter — the SDK is faithfully collecting telemetry with nowhere to send it.

Always pair instrumentation with a concrete exporter (OTLP being the standard, portable default) pointed at a real backend, even if that backend is a local development collector initially.

Mistake 3 — Assuming vendor-neutrality means zero migration effort, always

Assuming that because OTel is vendor-neutral, switching backends is always a zero-effort configuration change, regardless of any vendor-specific dashboards, alert rules, or custom attributes built on top of the old backend.

Understand vendor-neutrality accurately: the instrumentation and telemetry format itself is portable; anything you built specifically inside a particular backend's UI (dashboards, alert definitions) generally still needs to be recreated for a new one — the win is avoiding re-instrumentation, not avoiding all migration work everywhere.

When Should I Use It?

Mental Model

OpenTelemetry = the standard, vendor-neutral way to produce and export logs, metrics, and traces
Activity / Meter = .NET's own built-in primitives OTel is built on top of, not replacing
Exporter = the pluggable piece that decides where telemetry actually goes
Auto-instrumentation = ASP.NET Core, HttpClient, and EF Core spans, generated for you

Remember: OpenTelemetry is the plumbing that gets telemetry produced in a standard form — you still choose, separately, where it flows.

Key Takeaway


Closing Part VII — Production ASP.NET Core, Start to Finish

This lesson is the capstone not just of this six-lesson cluster, but effectively of this entire Part's journey through what it actually takes to run an ASP.NET Core API in real production, at real scale, with real users depending on it. It's worth stepping back and seeing the whole arc, because no single lesson in this Part tells the complete story on its own — together, they do.

THE FULL ARC OF PART VII
1. YOU BUILT THE REQUEST PIPELINE
2. YOU SECURED IT
3. YOU MADE IT PERFORM WELL AND SURVIVE FAILURE, UNDER LOAD
4. YOU MADE IT FULLY OBSERVABLE

Put together, that's a complete, coherent story: a request enters a well-structured pipeline, is authenticated and authorized correctly, is served efficiently thanks to caching, survives the inevitable hiccup from an external dependency thanks to resilience, and — whether it succeeds or fails — leaves behind exactly the telemetry needed to understand what happened, automatically, without anyone needing to have predicted the specific failure in advance. That's what "production-ready" actually means in practice: not one clever trick, but every one of these concerns handled deliberately, together.

Looking further ahead: A later part of this Advanced tier covers cloud-native .NET, including .NET Aspire — a tool that builds further on exactly this OpenTelemetry-based observability foundation, making multi-service local development and deployment considerably easier to reason about. You don't need to know anything about it yet; just know that everything you learned in this lesson is the groundwork it builds on.

Check Your Understanding

You've reached the capstone of Part VII. Let's confirm you understand both OpenTelemetry itself, and how it closes out everything this Part built.

1. Which statement most accurately characterizes what OpenTelemetry actually is?

Show answer

Correct: B

Why B is correct: This is the precise, correct characterization emphasized throughout the lesson — OpenTelemetry standardizes how telemetry is produced and exported, remaining neutral about which backend actually stores or visualizes it.

Why A is incorrect: OpenTelemetry has no dashboard or UI of its own — you still need a separate backend to actually view and query the data it produces.

Why C is incorrect: OpenTelemetry's .NET SDK is built directly on top of Activity and System.Diagnostics.Metrics, not a replacement for them.

Why D is incorrect: OpenTelemetry unifies all three pillars — logs, metrics, and traces — not just tracing alone.

Reinforcement: OpenTelemetry's defining, genuinely important property is vendor-neutrality — one standard instrumentation layer, many possible destinations.

2. In .NET, what is the relationship between OpenTelemetry and System.Diagnostics.Activity?

Show answer

Correct: B

Why B is correct: Activity predates widespread OpenTelemetry adoption in .NET and is a genuine built-in .NET primitive. The OpenTelemetry SDK is built to listen to and export what Activity (and Meter) already produce — this is exactly why so much telemetry, like ASP.NET Core's own request spans, is available with minimal setup.

Why A is incorrect: This reverses the actual history and relationship — Activity is a .NET primitive that OpenTelemetry builds on, not something OTel created.

Why C is incorrect: They're complementary, not competing — OpenTelemetry's entire .NET tracing story depends on Activity underneath it.

Why D is incorrect: Activity is part of the .NET base class library and works independently of whether OpenTelemetry is installed at all — OTel simply listens to it when present.

Reinforcement: OpenTelemetry's .NET integration is about wiring existing primitives into a broader ecosystem, not inventing new ones from scratch.

3. After adding .AddAspNetCoreInstrumentation(), .AddHttpClientInstrumentation(), and .AddEntityFrameworkCoreInstrumentation(), what should a developer expect regarding manual instrumentation code?

Show answer

Correct: B

Why B is correct: This is precisely the value of auto-instrumentation — these three packages already know how to create meaningful spans for their respective subsystems, so you get real tracing structure without hand-writing it, and reserve manual instrumentation for the specific business-logic detail auto-instrumentation can't know about.

Why A is incorrect: This is exactly backwards — auto-instrumentation exists specifically to avoid needing manual spans for these well-understood subsystems.

Why C is incorrect: These specific calls are tracing (and, in the case of ASP.NET Core/HttpClient, metrics) instrumentation additions — logging is configured separately, typically through the existing ILogger<T> pipeline.

Why D is incorrect: Instrumentation and exporting are separate concerns — you still need to configure an exporter (like OTLP) for the collected telemetry to actually go anywhere.

Reinforcement: Auto-instrumentation is the mechanism behind "a meaningful amount of tracing for free" — real structure with minimal manual effort.

4. Looking back across this entire six-lesson cluster (265–270), which best describes how caching, resilience, health checks, and observability fit together as part of one coherent production story?

Show answer

Correct: B

Why B is correct: This is exactly the closing narrative of the lesson — each piece of this cluster addresses a genuinely different production concern (performance/staleness, fault tolerance, automated instance health management, and after-the-fact understanding), and together with the earlier lessons on pipeline structure and security, they form one coherent, complete production-ready story.

Why A is incorrect: These topics are deliberately sequenced and connected — each solves a distinct, necessary piece of running a real production system, not isolated trivia.

Why C is incorrect: Every piece covered in this cluster has real production impact — dismissing caching, resilience, or health checks as optional misses why each lesson exists.

Why D is incorrect: Health checks answer "is this instance okay to route traffic to right now," a narrow, automated infrastructure signal; observability is the much broader practice of understanding overall system behavior after the fact — related, but genuinely distinct.

Reinforcement: Production-readiness isn't one technique — it's caching, resilience, health checks, and observability, each covering a different real risk, working together.

5. A team switches their OpenTelemetry-instrumented ASP.NET Core application from exporting to one observability backend to exporting to a completely different one. What, per this lesson, is the most accurate expectation for this change?

Show answer

Correct: B

Why B is correct: This reflects the accurate, nuanced expectation the lesson sets: vendor-neutrality means the instrumentation code itself typically doesn't need to change, but anything built specifically inside the old backend's UI (dashboards, alert rules) isn't automatically portable and still needs separate work.

Why A is incorrect: This is exactly the trap OpenTelemetry is designed to avoid — full re-instrumentation is the old, pre-OTel pain point, not the expected outcome here.

Why C is incorrect: OpenTelemetry explicitly supports pluggable, swappable, even multiple simultaneous exporters — it isn't locked to one backend.

Why D is incorrect: Activity and Meter usage stays exactly the same regardless of which exporter/backend is configured — that's precisely the point of building on these stable primitives.

Reinforcement: Vendor-neutrality is a real, practical benefit, but it's specifically about instrumentation portability — not a claim that literally everything about a backend migration is automatically free.

You've completed Part VII — Production ASP.NET Core. From request pipeline, to security, to performance and resilience, to full observability with OpenTelemetry — you now understand what it genuinely takes to run an ASP.NET Core API in real production, end to end.


dotnetmadeeasy.com — Learn C# and .NET, the right way.