Logging Configuration for .NET
Overview
This skill configures structured logging following Microsoft best practices:
- ILogger<T> - Inject into services for category-based logging
- ILoggerFactory - Create loggers dynamically when needed
- Structured Logging - Preserve log properties for analysis
- Source Generators - High-performance compile-time logging
- Serilog Integration - Enhanced sinks and enrichers
Quick Reference
| Interface | Lifetime | Use Case |
|---|---|---|
ILogger<T> | Singleton | Standard service logging |
ILoggerFactory | Singleton | Create loggers dynamically |
ILogger | - | Non-generic (avoid in DI) |
Logging Structure
/Infrastructure/Logging/
├── LoggerConfiguration.cs
├── LogEvents.cs # LoggerMessage source generators
├── LogEnrichers/
│ ├── UserContextEnricher.cs
│ └── CorrelationIdEnricher.cs
└── Sinks/
└── CustomSink.cs
Template: Service with ILogger<T>
// src/{name}.application/Services/OrderService.cs
using Microsoft.Extensions.Logging;
namespace {name}.application.services;
/// <summary>
/// Services should inject ILogger<T> for category-based logging.
/// The category is automatically set to the full type name.
/// </summary>
public sealed class OrderService : IOrderService
{
private readonly ILogger<OrderService> _logger;
private readonly IOrderRepository _orderRepository;
public OrderService(
ILogger<OrderService> logger,
IOrderRepository orderRepository)
{
_logger = logger;
_orderRepository = orderRepository;
}
public async Task<Result<Order>> ProcessOrderAsync(
Guid orderId,
CancellationToken cancellationToken)
{
// ═══════════════════════════════════════════════════════════════
// STRUCTURED LOGGING - Use placeholders, not string interpolation
// ═══════════════════════════════════════════════════════════════
_logger.LogInformation(
"Processing order {OrderId}",
orderId);
try
{
var order = await _orderRepository.GetByIdAsync(orderId, cancellationToken);
if (order is null)
{
_logger.LogWarning(
"Order {OrderId} not found",
orderId);
return Result.Failure<Order>(OrderErrors.NotFound(orderId));
}
// Log with multiple properties
_logger.LogInformation(
"Order {OrderId} retrieved. Status: {Status}, Total: {Total}",
orderId,
order.Status,
order.Total);
return Result.Success(order);
}
catch (Exception ex)
{
// Always log exceptions with the exception parameter first
_logger.LogError(
ex,
"Error processing order {OrderId}",
orderId);
throw;
}
}
}
Template: ILoggerFactory Usage
// src/{name}.infrastructure/Services/DynamicLoggerService.cs
using Microsoft.Extensions.Logging;
namespace {name}.infrastructure.services;
/// <summary>
/// Use ILoggerFactory when you need to create loggers dynamically.
/// Common use cases:
/// - Factory classes that create multiple types
/// - Plugin systems where type isn't known at compile time
/// - Base classes that want child-specific categories
/// </summary>
public sealed class PluginManager
{
private readonly ILoggerFactory _loggerFactory;
public PluginManager(ILoggerFactory loggerFactory)
{
_loggerFactory = loggerFactory;
}
public IPlugin LoadPlugin(string pluginName, Type pluginType)
{
// Create a logger with a dynamic category
var logger = _loggerFactory.CreateLogger(pluginType);
logger.LogInformation(
"Loading plugin {PluginName} of type {PluginType}",
pluginName,
pluginType.Name);
// Or with a string category
var customLogger = _loggerFactory.CreateLogger($"Plugins.{pluginName}");
customLogger.LogDebug(
"Plugin {PluginName} initialized",
pluginName);
return CreatePlugin(pluginType, customLogger);
}
}
Template: High-Performance Logging with Source Generators
// src/{name}.application/Logging/LogEvents.cs
using Microsoft.Extensions.Logging;
namespace {name}.application.logging;
/// <summary>
/// LoggerMessage source generators provide the best logging performance.
/// Benefits:
/// - Zero allocation for disabled log levels
/// - Compile-time validation of message templates
/// - Strongly typed parameters
/// </summary>
public static partial class LogEvents
{
// ═══════════════════════════════════════════════════════════════
// INFORMATION LEVEL
// ═══════════════════════════════════════════════════════════════
[LoggerMessage(
EventId = 1000,
Level = LogLevel.Information,
Message = "Processing request {RequestName} with ID {RequestId}")]
public static partial void LogRequestProcessing(
this ILogger logger,
string requestName,
Guid requestId);
[LoggerMessage(
EventId = 1001,
Level = LogLevel.Information,
Message = "Request {RequestName} completed in {ElapsedMs}ms")]
public static partial void LogRequestCompleted(
this ILogger logger,
string requestName,
long elapsedMs);
[LoggerMessage(
EventId = 1002,
Level = LogLevel.Information,
Message = "User {UserId} authenticated successfully")]
public static partial void LogUserAuthenticated(
this ILogger logger,
Guid userId);
// ═══════════════════════════════════════════════════════════════
// WARNING LEVEL
// ═══════════════════════════════════════════════════════════════
[LoggerMessage(
EventId = 2000,
Level = LogLevel.Warning,
Message = "Slow request detected: {RequestName} took {ElapsedMs}ms")]
public static partial void LogSlowRequest(
this ILogger logger,
string requestName,
long elapsedMs);
[LoggerMessage(
EventId = 2001,
Level = LogLevel.Warning,
Message = "Cache miss for key {CacheKey}")]
public static partial void LogCacheMiss(
this ILogger logger,
string cacheKey);
[LoggerMessage(
EventId = 2002,
Level = LogLevel.Warning,
Message = "Rate limit exceeded for client {ClientId}")]
public static partial void LogRateLimitExceeded(
this ILogger logger,
string clientId);
// ═══════════════════════════════════════════════════════════════
// ERROR LEVEL
// ═══════════════════════════════════════════════════════════════
[LoggerMessage(
EventId = 3000,
Level = LogLevel.Error,
Message = "Error processing request {RequestName}")]
public static partial void LogRequestError(
this ILogger logger,
Exception exception,
string requestName);
[LoggerMessage(
EventId = 3001,
Level = LogLevel.Error,
Message = "Database operation failed for entity {EntityType} with ID {EntityId}")]
public static partial void LogDatabaseError(
this ILogger logger,
Exception exception,
string entityType,
Guid entityId);
[LoggerMessage(
EventId = 3002,
Level = LogLevel.Error,
Message = "External service {ServiceName} returned error: {ErrorCode}")]
public static partial void LogExternalServiceError(
this ILogger logger,
string serviceName,
string errorCode);
// ═══════════════════════════════════════════════════════════════
// DEBUG LEVEL
// ═══════════════════════════════════════════════════════════════
[LoggerMessage(