Logging in C# Applications: Best Practices in Practice
Logging is not just about writing messages to a file. It is a crucial tool for monitoring, debugging, and auditing any production C# application. Properly configured logging allows you to quickly find the causes of failures, analyze system behavior, and prevent incidents. In this article, we will cover the best logging practices in .NET that you can immediately apply in your projects.
1. Choosing a Library: Serilog vs NLog vs Built-in ILogger
In modern .NET, there are three main approaches to logging. Let's look at their strengths.
- Built-in ILogger (Microsoft.Extensions.Logging) — a basic abstract interface. It is built into ASP.NET Core and is minimally configurable. Suitable for simple projects, but requires third-party providers for advanced scenarios.
- Serilog — the de facto industry standard. Supports structured logging out of the box, a rich ecosystem of sinks, and simple configuration via configuration files.
- NLog — a mature framework with flexible routing and support for legacy systems. It is known for high performance and detailed rule configuration.
Recommendation: for new projects, use Serilog — it integrates best with modern practices (ELK, Application Insights). For high-load systems where performance is critical, consider NLog.
// Example of minimal Serilog configuration in Program.csusing Serilog;
Log.Logger = new LoggerConfiguration() .MinimumLevel.Information() .WriteTo.Console() .WriteTo.File("logs/app-.log", rollingInterval: RollingInterval.Day) .CreateLogger();
try{ Log.Information("Application started"); // your code}catch (Exception ex){ Log.Fatal(ex, "Critical error on startup");}finally{ Log.CloseAndFlush();}2. Logging Levels: When and What to Write
Choosing the wrong logging level is one of the most common mistakes. Let's break down each level and its purpose.
- Trace / Verbose — for developer debugging only. Never enable in production without extreme necessity.
- Debug — diagnostic information for development. Can be temporarily enabled to find a problem.
- Information — normal application operation: requests, successful operations, business events. The main level in production.
- Warning — a potential problem: low disk space, retries, deprecated API.
- Error — exceptions and failures that the application handled, but the user noticed a problem.
- Fatal / Critical — unrecoverable errors requiring immediate administrator intervention.
// Example of correct level usagepublic class OrderService{ private readonly ILogger<OrderService> _logger;
public OrderService(ILogger<OrderService> logger) { _logger = logger; }
public async Task<Order> CreateOrderAsync(CreateOrderRequest request) { _logger.LogInformation("Creating order for user {UserId}", request.UserId);
try { var order = await _repository.SaveAsync(request); _logger.LogInformation("Order {OrderId} successfully created", order.Id); return order; } catch (Exception ex) { _logger.LogError(ex, "Error creating order for user {UserId}", request.UserId); throw; } }}3. Structured Logging: Why It Matters
Traditional text logging (throug