Logging in C# Applications: Best Practices .NET

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

Logging in C# Applications: Best Practices and Tools (.NET)



Logging is not just writing messages to a file. It is a critical component of any production C# application. Without a quality logging system, you are working blind: errors go unnoticed, performance is difficult to analyze, and debugging becomes guesswork. In this article, we will break down the best logging practices in the .NET ecosystem, review popular libraries (Serilog, NLog), proper logging levels, and a structured approach to data collection.



1. Choosing a Logging Library: Serilog vs NLog



In the .NET world, there are several mature solutions. The two most popular are Serilog and NLog. Both support asynchronous logging, multiple sinks (output targets), and integration with .NET Core/5+ via ILogger.



Serilog

Serilog is known for its philosophy of structured logging. Instead of simple strings, you pass objects, and Serilog serializes them into JSON. This is ideal for systems like Elasticsearch, Seq, or Datadog.



// Serilog Exampleusing Serilog;

Log.Logger = new LoggerConfiguration() .MinimumLevel.Information() .WriteTo.Console() .WriteTo.File("logs/app-.log", rollingInterval: RollingInterval.Day) .CreateLogger();

Log.Information("User {UserId} logged in at {Time}", userId, DateTime.UtcNow);


NLog

NLog is a more traditional, but no less powerful tool. It is well-suited for projects that need flexible configuration via XML or code. It supports file rotation, email sending, and database integration.



// NLog Exampleusing NLog;

private static readonly Logger Logger = LogManager.GetCurrentClassLogger();

Logger.Info("User {UserId} performed an action", userId);


When to choose what? If you are building microservices or using an ELK stack — go with Serilog. If you need simple setup in legacy projects — go with NLog. In any case, both support the standard ILogger from Microsoft.Extensions.Logging.



2. Logging Levels: When and What to Write



One of the most common mistakes is writing everything at the Information level, or conversely, using Debug everywhere. Use the correct levels:



  • Trace (0) — maximally detailed information: method entry/exit, variable values. Used only during debugging.
  • Debug (1) — debug messages useful to a developer, but not an operator.
  • Information (2) — business events: "order created", "user registered". This is the main working level.
  • Warning (3) — an abnormal situation that does not break the application: request timeout, retry connection attempt.
  • Error (4) — handled exceptions, business logic errors, module failures.
  • Critical (5) — fatal errors: database connection loss, out of memory, entire application crash.


// Example of correct level usagepublic async Task<Order> CreateOrderAsync(CreateOrderRequest request){    Log.Debug("Starting order creation for user {UserId}", request.UserId);

try { var order = new Order(request); await _repository.SaveAsync(order); Log.Information("Order {OrderId} created by user {UserId}", order.Id, request.UserId); return order; } catch (DbException ex) { Log.Error(ex, "DB error creating order for user {UserId}", request.UserId); throw; // Re-throw }}


3. Structured Logging: Why Is It Important?



Traditional logging via st

Blogs

Book Recommendations