Monitoring C# (.NET) Applications: A Complete Guide to OpenTelemetry and Prometheus
Modern .NET applications require constant monitoring of performance, errors, and state. Without monitoring, you risk losing control over production. In this article, we will cover how to set up effective monitoring for C# (.NET) applications using the OpenTelemetry (telemetry collection) and Prometheus + Grafana (visualization and alerting) stack.
Why OpenTelemetry?
OpenTelemetry (OTel) is the industry standard for collecting metrics, traces, and logs. For .NET, it provides official SDKs and automatic instrumentation for popular libraries (ASP.NET Core, gRPC, Entity Framework).
Installing and Configuring OpenTelemetry in a C# Project
Add NuGet packages to your project:
dotnet add package OpenTelemetrydotnet add package OpenTelemetry.Extensions.Hostingdotnet add package OpenTelemetry.Instrumentation.AspNetCoredotnet add package OpenTelemetry.Exporter.Prometheus.AspNetCoreBasic Configuration in Program.cs
Configure metric collection and export to Prometheus:
using OpenTelemetry.Metrics;using OpenTelemetry.Resources;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddOpenTelemetry() .ConfigureResource(resource => resource .AddService("MyApp", serviceVersion: "1.0.0")) .WithMetrics(metrics => metrics .AddAspNetCoreInstrumentation() .AddHttpClientInstrumentation() .AddMeter("Microsoft.AspNetCore.Hosting") .AddMeter("Microsoft.AspNetCore.Server.Kestrel") .AddPrometheusExporter());
var app = builder.Build();
// Endpoint for Prometheus (default /metrics)app.MapPrometheusScrapingEndpoint();
app.Run();Creating Custom Metrics
You can add your own counters, histograms, and gauges:
using System.Diagnostics.Metrics;
public class OrderMetrics{ private readonly Counter<int> _ordersCreated; private readonly Histogram<double> _orderProcessingTime;
public OrderMetrics(IMeterFactory meterFactory) { var meter = meterFactory.Create("MyApp.Orders"); _ordersCreated = meter.CreateCounter<int>("orders.created", description: "Number of orders created"); _orderProcessingTime = meter.CreateHistogram<double>("orders.processing_time", unit: "ms"); }
public void RecordOrderCreated() => _ordersCreated.Add(1); public void RecordProcessingTime(double ms) => _orderProcessingTime.Record(ms);}Distributed Tracing
To track call chains, add tracing:
using OpenTelemetry.Trace;
builder.Services.AddOpenTelemetry() .WithTracing(tracing => tracing .AddAspNetCoreInstrumentation() .AddHttpClientInstrumentation() .AddEntityFrameworkCoreInstrumentation() .AddConsoleExporter()); // For debugging, in production — Jaeger or ZipkinLogging via OpenTelemetry
Connect the logger to OTel:
using OpenTelemetry.Logs;
builder.Logging.AddOpenTelemetry(logging => logging .AddConsoleExporter() .AddOtlpExporter(options => options.Endpoint = new Uri("http://localhost:4317")));Configuring Prometheus for Metric Collection
In the prometheus.yml file, specify your endpoint:
scrape_configs: - job_name: 'dotnet-app' scrape_interval: 5s static_configs: - targets: ['localhost:5000'] # Your application's portVisualization in Grafana
After connecting Prometheus to Grafana, create a dashboard with key metrics:
- http_server_duration_ms — server response time
- http_server_request_count — request count
- process_cpu_usage — CPU usage
- process_memory_working_set — memory consumption
Conclusion
Using OpenTelemetry with Prometheus and Grafana provides a complete monitoring solution for .NET applications. This approach allows you to quickly identify bottlenecks, track errors, and ensure stable operation of your services in production.