Observability in C#: Tracing, Metrics, and Logs in Practice

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

How to stop guessing and start seeing your code through?



Have you ever sat at night in front of your monitor, looking at logs that scream about an error but remain silent about the cause? Or tried to understand why your C# microservice responds in 10 seconds, when yesterday everything was flying? Sound familiar? Welcome to the world where without Observability, your code is a black box.



In this article, we won't be giving boring lectures. We'll break down the three pillars of observability: tracing, metrics, and logs — and show how to make them work together with C#. You'll learn how to stop putting out fires and start preventing them. Let's go!



What is Observability and why is it not just "logging"?



Many people confuse Observability with simple log collection. But the difference is colossal. Observability is a property of a system that allows you to ask any questions about its state without adding new code. The three pillars on which everything rests:

  • Logs — discrete events. "User clicked a button", "Connection error". This is "what happened?".
  • Metrics — aggregated data. "Requests per second", "Memory consumption". This is "how much and how often?".
  • Traces — the path of a request through the system. "Request came in, went to the DB, then to the cache, returned". This is "why is it so slow?".


In the C# .NET ecosystem, these three pillars are combined using libraries like OpenTelemetry. And now we'll break down what this looks like in code.



Setting up an experiment: writing an application with Observability



Imagine we have a simple ASP.NET Core service that processes orders. We want to see:

  1. Each step of order processing (tracing).
  2. How many orders were processed and with what error (metrics).
  3. Details of exceptions (logs).


Step 1: Setting up logging with Serilog



Let's start with the most understandable part — logs. We'll use Serilog (but NLog is also an option). It can write structured logs that are easy to parse later.



// NuGet: Serilog, Serilog.AspNetCore, Serilog.Sinks.Consoleusing Serilog;

// Setup in Program.csLog.Logger = new LoggerConfiguration() .WriteTo.Console() .WriteTo.File("logs/app.log", rollingInterval: RollingInterval.Day) .Enrich.WithProperty("Application", "OrderService") .CreateLogger();

var builder = WebApplication.CreateBuilder(args);builder.Host.UseSerilog(); // Replace the default logger

// Example usage in a controller[ApiController][Route("api/[controller]")]public class OrderController : ControllerBase{ private readonly ILogger<OrderController> _logger;

public OrderController(ILogger<OrderController> logger) => _logger = logger;

[HttpPost] public IActionResult CreateOrder(Order order) { _logger.LogInformation("Creating order {OrderId} for user {User}", order.Id, order.UserId); // ... logic ... _logger.LogWarning("Order {OrderId} is delayed due to stock issues", order.Id); return Ok(); }}


Notice: we use named placeholders ({OrderId}). This is not just text — it's structured data. In Elasticsearch or Grafana Loki, you'll be able to search all logs by a specific OrderId.



Step 2: Metrics with Prometheus and .NET



Metrics are responsible for the "numbers". How many requests per second? How many 500 errors? In C#, we use Meters from System.Diagnostics.Metrics or the prometheus-net library.



Let's create a simple counter for processed orders:



// NuGet: prometheus-net.AspNetCoreusing Prometheus;

// In Program.cs add metricsvar builder = WebApplication.CreateBuilder(args);

Blogs

Book Recommendations