Introduction: Why Monitor C# Applications
Modern C# applications on the .NET platform are complex distributed systems operating under high loads. Without quality monitoring, you risk missing performance degradation, memory leaks, or critical failures. Monitoring allows not only to detect problems but also to predict them, responding to anomalies in a timely manner.
In this article, we will cover how to set up metrics collection, logging, and an alerting system for C# (.NET) applications using modern tools: Prometheus, Grafana, Serilog, and built-in Health Checks. You will get ready-to-use code examples and practical recommendations.
Key Metrics for C# (.NET) Applications
Monitoring begins with defining key indicators. For .NET applications, they can be divided into three categories:
- Infrastructure metrics — CPU, memory, disk, and network usage.
- Application metrics — API response time, request count, errors (4xx/5xx), queue sizes.
- .NET-specific metrics — GC heap size, thread count, garbage collection time, database connection count.
Collecting Metrics via Prometheus and .NET
To export metrics to Prometheus, use the prometheus-net library. Install it via NuGet:
dotnet add package prometheus-net.AspNetCoreAdd the middleware in Program.cs:
var builder = WebApplication.CreateBuilder(args);var app = builder.Build();
// Enable Prometheus metricsapp.UseHttpMetrics(); // HTTP metricsapp.UseMetricServer(); // /metrics endpoint
app.MapGet("/", () => "Hello World!");app.Run();Now standard metrics will be available at the /metrics endpoint: request count, duration, errors. Let's add a custom metric — a counter for endpoint hits:
using Prometheus;
var requestCounter = Metrics.CreateCounter( "myapp_requests_total", "Total requests to the endpoint");
app.MapGet("/api/data", () => { requestCounter.Inc(); return new { Message = "Data loaded" };});Health Checks: Basic State Verification
The built-in Health Checks mechanism in .NET allows you to quickly assess the application's health. Add a database connection check:
using Microsoft.Extensions.Diagnostics.HealthChecks;
builder.Services.AddHealthChecks() .AddDbContextCheck<AppDbContext>() .AddUrlGroup(new Uri("https://external-api.com"), "External API");
app.MapHealthChecks("/health");Health Checks return a status of Healthy, Degraded, or Unhealthy — this is an ideal source for alerts.
Logging with Serilog: Structured Logs
Logs are a critical source of data for diagnostics. Serilog allows you to send logs to Elasticsearch, Seq, or simply to a file in a structured format.
Installation:
dotnet add package Serilog.AspNetCoredotnet add package Serilog.Sinks.SeqConfiguration in Program.cs:
Log.Logger = new LoggerConfiguration() .MinimumLevel.Information() .WriteTo.Console() .WriteTo.Seq("http://localhost:5341") .Enrich.WithProperty("Application", "MyApp") .CreateLogger();
builder.Host.UseSerilog();Now all logs will contain contextual information: RequestId, UserId, execution time. Example of logging an error:
try { // dangerous code} catch (Exception ex) { Log.Error(ex, "Error processing order {OrderId}", orderId);}Structured logs are easy to analyze in Grafana or Kibana, build