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.
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.
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.
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.
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.
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.
Activity (and ActivitySource) are .NET's own built-in types for representing a unit of work with a start time, end time, and structured tags — this predates widespread OpenTelemetry adoption in .NET. An Activity, in OTel terms, is a span.Meter, Counter<T>, Histogram<T>, and related types are .NET's own built-in metrics instrumentation API — again, a genuine .NET primitive, not something OpenTelemetry invented.Activity, Meter, or ILogger<T> with something new, the OpenTelemetry .NET SDK listens to what these existing APIs already produce and forwards it through exporters using OTel's standard conventions and wire format.HttpClient, and Entity Framework Core each have well-established OpenTelemetry instrumentation packages that already know how to create meaningful spans and metrics for the work each of those subsystems does — because you're not writing that instrumentation code yourself..AddAspNetCoreInstrumentation(), .AddHttpClientInstrumentation(), and .AddEntityFrameworkCoreInstrumentation() attaches listeners to those subsystems' existing internal Activity/Meter sources — no manual span creation required anywhere in your business logic.HttpClient calls, and its EF Core database queries all show up as connected spans in a trace — automatically — because each subsystem is already emitting the right telemetry, and OTel is already listening for it.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.
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.
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.
Activity for every incoming request internally — this is a built-in .NET/ASP.NET Core behavior, independent of OTel.AddAspNetCoreInstrumentation() (and the equivalents for HttpClient/EF Core) subscribes to the relevant ActivitySources that those subsystems already publish to — it's listening to existing signals, not injecting new instrumentation code into the subsystems themselves.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.
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.
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.
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.
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.
System.Diagnostics.Activity for tracing, System.Diagnostics.Metrics for metrics — rather than replacing them.HttpClient, and EF Core produces meaningful, connected spans with essentially no manual instrumentation code, because those subsystems already publish the underlying telemetry OTel listens for.Activity/Meter primitives, nesting naturally inside the automatically-generated structure.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.
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.
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?
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?
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?
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?
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?
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.