Modern applications generate thousands of requests every minute. Without proper monitoring, identifying performance bottlenecks, failed dependencies, or resource exhaustion becomes difficult. Traditional logging helps explain what happened, but it doesn't provide a real-time view of application health.
OpenTelemetry has become the standard for collecting telemetry across applications. It enables developers to capture metrics, traces, and logs using a vendor-neutral API that integrates with popular observability platforms.
In this article, you'll learn how to implement production-ready OpenTelemetry metrics in a .NET 11 application, expose metrics for monitoring, create custom application metrics, and follow best practices for production deployments.
Note: This article focuses on implementation. Dashboard configurations, alert thresholds, and performance measurements should be validated within your own monitoring environment.
What Are OpenTelemetry Metrics?
Metrics are numerical measurements collected over time that describe the health and performance of an application.
Examples include:
HTTP request count
Request duration
CPU usage
Memory usage
Database query duration
Cache hit ratio
Active users
Background job execution time
Unlike logs, metrics are lightweight and ideal for continuous monitoring.
Why Use OpenTelemetry?
OpenTelemetry provides a consistent way to collect telemetry regardless of the monitoring platform.
Benefits include:
Vendor-neutral instrumentation
Standardized metrics collection
Built-in ASP.NET Core instrumentation
Integration with Prometheus, Grafana, Azure Monitor, and other observability platforms
Support for custom business metrics
Project Setup
Create a new Web API.
dotnet new webapi -n OpenTelemetryMetricsDemo
Install the required packages.
dotnet add package OpenTelemetry.Extensions.Hosting
dotnet add package OpenTelemetry.Instrumentation.AspNetCore
dotnet add package OpenTelemetry.Instrumentation.Http
dotnet add package OpenTelemetry.Exporter.OpenTelemetryProtocol
Depending on your monitoring stack, you may also install exporters such as Prometheus or Azure Monitor.
Configure OpenTelemetry
Register OpenTelemetry during application startup.
using OpenTelemetry.Metrics;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddOpenTelemetry()
.WithMetrics(metrics =>
{
metrics
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddRuntimeInstrumentation()
.AddMeter("ProductApi")
.AddOtlpExporter();
});
var app = builder.Build();
app.Run();
Why This Configuration?
Each component serves a specific purpose:
AddAspNetCoreInstrumentation()captures incoming HTTP request metrics.AddHttpClientInstrumentation()records outgoing HTTP calls.AddRuntimeInstrumentation()collects runtime metrics such as garbage collection and thread pool usage.AddMeter()enables custom application metrics.AddOtlpExporter()sends telemetry to an OpenTelemetry-compatible backend.
Creating Custom Metrics
Business metrics are often more valuable than infrastructure metrics.
Create a custom meter.
using System.Diagnostics.Metrics;
public static class MetricsRegistry
{
public static readonly Meter Meter =
new("ProductApi");
public static readonly Counter<int> OrdersCreated =
Meter.CreateCounter<int>("orders.created");
}
Increment the counter whenever an order is created.
public async Task CreateOrderAsync(Order order)
{
// Save order
MetricsRegistry.OrdersCreated.Add(1);
await Task.CompletedTask;
}
This metric can later be visualized on dashboards to monitor application activity.
Measuring Request Duration
Histograms are useful for recording durations.
public static readonly Histogram<double> ProcessingTime =
MetricsRegistry.Meter.CreateHistogram<double>(
"order.processing.time",
unit: "ms");
Record elapsed time.
var stopwatch = Stopwatch.StartNew();
// Business logic
stopwatch.Stop();
MetricsRegistry.ProcessingTime.Record(
stopwatch.Elapsed.TotalMilliseconds);
Histograms allow monitoring platforms to calculate averages, percentiles, and latency distributions.
Recording Active Users
Observable gauges report values collected during metric scraping.
public static int ActiveUsers;
MetricsRegistry.Meter.CreateObservableGauge(
"users.active",
() => ActiveUsers);
The monitoring system reads the latest value whenever metrics are collected.
End-to-End Flow
A typical request follows this sequence:
Client sends an HTTP request.
ASP.NET Core instrumentation records request metrics.
Business logic updates custom counters and histograms.
Runtime instrumentation collects memory and thread metrics.
Metrics are exported through the configured OTLP exporter.
Dashboards visualize the collected data.
This provides a complete view of both application and infrastructure health.
Useful Metrics to Collect
| Category | Example Metrics |
|---|---|
| HTTP | Request count, duration, status codes |
| Runtime | GC collections, memory usage, thread pool |
| Database | Query duration, failed queries |
| Cache | Hit ratio, misses |
| Business | Orders, registrations, payments |
| External APIs | Latency, failures |
Join the conversation! Your thoughts help the community grow.