Sarıkaya Dev Logo

Deep Observability for .NET 8 Minimal APIs with OpenTelemetry, Prometheus, and Azure Monitor

Mahmut Sarıkaya 4 min read 9 Views 0
Deep Observability for .NET 8 Minimal APIs with OpenTelemetry, Prometheus, and Azure Monitor

Why observability is a bottleneck for Minimal APIs

When a single‑endpoint service starts handling 5,000 requests per second, a missing latency metric can hide a 200 ms spike that costs dollars in cloud spend. .NET 8 Minimal APIs promise ultra‑lightweight endpoints, but without proper tracing and metrics you cannot guarantee SLA compliance.

Recent surveys from the .NET community (2024) show that 62% of teams consider lack of real‑time visibility the top obstacle to adopting Minimal APIs in production. The solution is a layered observability stack that captures traces, metrics, and logs without sacrificing the low‑overhead design of Minimal APIs.

Installing OpenTelemetry in a .NET 8 Minimal API project

Start with the .NET 8 SDK (version 8.0.100 or later) and add the official OpenTelemetry packages. The following command adds tracing, metrics, and the ASP.NET Core instrumentation.

dotnet add package OpenTelemetry.Exporter.Console
dotnet add package OpenTelemetry.Extensions.Hosting
dotnet add package OpenTelemetry.Instrumentation.AspNetCore
dotnet add package OpenTelemetry.Instrumentation.HttpClient

Configure the services in Program.cs. The code below registers a TracerProvider and a MeterProvider that automatically instrument Minimal API handlers.

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddOpenTelemetry().WithTracing(tracerBuilder =>
tracerBuilder
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddConsoleExporter())
.WithMetrics(metricBuilder =>
metricBuilder
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddConsoleExporter());

var app = builder.Build();

app.MapGet("/weather", (HttpContext ctx) =>
{
// Simulate work
Thread.Sleep(50);
return Results.Ok(new { Temperature = 23, Unit = "C" });
});

app.Run();

Notice the use of AddAspNetCoreInstrumentation, which captures incoming HTTP requests, route templates, and response codes automatically—no manual spans are required.

Exporting metrics to Prometheus

Prometheus expects metrics on a plain‑text endpoint. OpenTelemetry provides a Prometheus exporter that can be wired with a single line of code.

builder.Services.AddOpenTelemetry().WithMetrics(metricBuilder =>
metricBuilder
.AddPrometheusExporter());

app.UseOpenTelemetryPrometheusScrapingEndpoint(); // Exposes /metrics

Deploy the API to a Kubernetes pod and annotate the pod with prometheus.io/scrape: "true". Prometheus will poll /metrics every 15 seconds, turning the built‑in http.server.request.duration histogram into a visual latency chart.

Sending traces to Azure Monitor

Azure Monitor’s Application Insights endpoint accepts OpenTelemetry Protocol (OTLP) over gRPC. Install the Azure Monitor exporter and configure the connection string from the Azure portal.

dotnet add package OpenTelemetry.Exporter.AzureMonitor
builder.Services.AddOpenTelemetry().WithTracing(tracerBuilder =>
tracerBuilder
.AddAzureMonitorTraceExporter(opts =>
{
opts.ConnectionString = builder.Configuration["APPLICATIONINSIGHTS_CONNECTION_STRING"];
}));

After deployment, you can explore end‑to‑end request flows in the Azure portal, filter by operation name /weather, and see the exact duration of each dependency call.

Full Minimal API example with all exporters

The snippet below combines console, Prometheus, and Azure Monitor exporters. It demonstrates that a single Minimal API can serve production traffic while feeding three observability back‑ends.

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddOpenTelemetry()
.WithTracing(tracer => tracer
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddConsoleExporter()
.AddAzureMonitorTraceExporter(opts => opts.ConnectionString = builder.Configuration["APPINSIGHTS_CONN"]))
.WithMetrics(metric => metric
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddConsoleExporter()
.AddPrometheusExporter());

var app = builder.Build();

app.UseOpenTelemetryPrometheusScrapingEndpoint();

app.MapGet("/weather", async (HttpClient http) =>
{
var response = await http.GetStringAsync("https://api.weather.gov/gridpoints/BOU/56,71/forecast");
return Results.Ok(new { Source = "NOAA", Data = response.Substring(0, 200) });
});

app.Run();

Run the service with dotnet run, then query http://localhost:5000/metrics for Prometheus data and http://localhost:5000/weather to generate traces.

Practical tips for low‑overhead observability

1. **Sample only critical routes** – OpenTelemetry supports Sampler policies; set AlwaysOnSampler for health checks and ParentBasedSampler for the rest to keep trace volume under control.

2. **Avoid string concatenation in attributes** – Use structured attributes (e.g., http.method, http.route) so exporters can index them efficiently.

3. **Batch export** – Both Prometheus and Azure Monitor exporters batch metrics in 5‑second windows, reducing network chatter.

4. **Monitor exporter health** – Exporter SDKs expose ExportProcessor metrics; expose them via /metrics to catch misconfigurations early.

Conclusion

By integrating OpenTelemetry, Prometheus, and Azure Monitor directly into a .NET 8 Minimal API, you achieve end‑to‑end visibility without compromising the framework’s lightweight promise. The code samples show that a few lines in Program.cs are enough to collect traces, expose Prometheus metrics, and push telemetry to Azure. The key takeaway: observability should be built‑in, not bolted on, and the modern .NET ecosystem provides all the pieces to do it reliably.

Sources

Microsoft Docs – OpenTelemetry for .NET
Prometheus Documentation – Exporters
Azure Monitor – Application Insights OTLP Exporter

Author: Mahmut Sarıkaya — sarikayadev.com

Tags: #.NET 8 #Minimal APIs #OpenTelemetry #Prometheus #Azure Monitor
Share:
M

Written by

Mahmut Sarıkaya

Software Developer

Comments

No comments yet. Be the first to share your thoughts!

Leave a Comment

5 + 2 =