Microsoft.Extensions.Logging
Standard logging abstraction layer for .NET Core/ASP.NET Core. Enables routing logs to various libraries like Serilog or NLog through provider system. Provides dependency injection integration and configuration-based log level control, standard choice in .NET ecosystem.
Logging Library
Microsoft.Extensions.Logging
Overview
Microsoft.Extensions.Logging is the official logging abstraction library for .NET/.NET Core. Through the provider model, it provides a unified API for various log outputs (console, file, third-party libraries, etc.). It's built into ASP.NET Core by default and has become the de facto standard for logging in modern .NET applications through deep integration with dependency injection (DI).
Details
Microsoft.Extensions.Logging was introduced with .NET Core 1.0 in 2016 as an official logging framework. Rather than replacing existing third-party libraries (Serilog, NLog, etc.), it provides a unified abstraction layer that enables switching between logging libraries and using multiple libraries simultaneously.
Technical Features
- Provider Model: Simultaneous output to multiple log destinations
- Dependency Injection Integration: Full integration with IServiceCollection
- Structured Logging: LoggerMessage and template functionality
- Log Levels: Trace, Debug, Information, Warning, Error, Critical
- Scope Support: Context information addition via BeginScope
- Performance Optimization: Compile-time optimization and source generators
Provider Types
- Built-in: Console, Debug, EventSource, EventLog (Windows)
- File-based: File, Rolling File (third-party)
- Integration: Serilog, NLog, log4net (via respective providers)
- Cloud: Azure Application Insights, AWS CloudWatch, etc.
Log Level Hierarchy
- Trace: Most detailed information
- Debug: Debug information
- Information: General information
- Warning: Warning messages
- Error: Error information
- Critical: Critical errors
Pros and Cons
Pros
- Official Support: Official support and maintenance by Microsoft
- Standard Integration: Standard logging approach in ASP.NET Core
- Abstraction: Easy switching between logging libraries
- Performance: Highly optimized implementation
- Dependency Injection: Full integration with DI container
- Extensibility: Easy creation of custom providers
- Test Support: Unit testing support with FakeLogger
Cons
- Limited Features: Basic functionality only when used standalone
- Structured Logging: Limited native structured logging capabilities
- Configuration Complexity: Advanced configuration requires multiple providers
- Third-party Dependencies: Requires Serilog, etc. for full-featured logging
- Learning Curve: Understanding of DI and provider model required
Reference Links
- Official Documentation: https://docs.microsoft.com/aspnet/core/fundamentals/logging/
- API Reference: https://docs.microsoft.com/dotnet/api/microsoft.extensions.logging
- GitHub Repository: https://github.com/dotnet/extensions
- Provider List: https://docs.microsoft.com/dotnet/core/extensions/logging-providers
- Performance Guide: https://docs.microsoft.com/dotnet/core/extensions/high-performance-logging
Usage Examples
Basic Usage
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
// Configuration with host builder
var builder = Host.CreateApplicationBuilder(args);
// Logging configuration
builder.Logging.ClearProviders();
builder.Logging.AddConsole();
builder.Logging.SetMinimumLevel(LogLevel.Debug);
var host = builder.Build();
var logger = host.Services.GetRequiredService<ILogger<Program>>();
// Basic log output
logger.LogInformation("Application started");
logger.LogWarning("This is a warning message");
logger.LogError("An error occurred: {ErrorCode}", 404);
// Structured logging
logger.LogInformation("User {UserId} logged in at {LoginTime}",
12345, DateTime.Now);
ASP.NET Core Configuration
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Logging;
var builder = WebApplication.CreateBuilder(args);
// Logging configuration
builder.Logging.ClearProviders();
builder.Logging.AddConsole();
builder.Logging.AddDebug();
builder.Logging.AddEventSourceLogger();
// JSON console output (structured logging)
builder.Logging.AddJsonConsole(options =>
{
options.IncludeScopes = false;
options.TimestampFormat = "HH:mm:ss ";
options.JsonWriterOptions = new JsonWriterOptions
{
Indented = true
};
});
var app = builder.Build();
// Logger usage in services
public class HomeController : ControllerBase
{
private readonly ILogger<HomeController> _logger;
public HomeController(ILogger<HomeController> logger)
{
_logger = logger;
}
[HttpGet]
public IActionResult Get()
{
_logger.LogInformation("GET request received");
try
{
// Business logic
return Ok("Success");
}
catch (Exception ex)
{
_logger.LogError(ex, "Error occurred during request processing");
return StatusCode(500);
}
}
}
Log Levels and Filtering
using Microsoft.Extensions.Logging;
// Configuration in appsettings.json
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information",
"System": "Warning"
},
"Console": {
"LogLevel": {
"Default": "Debug"
}
}
}
}
// Code-based configuration
builder.Logging.AddFilter("Microsoft", LogLevel.Warning);
builder.Logging.AddFilter<ConsoleLoggerProvider>("Microsoft.AspNetCore", LogLevel.Debug);
// Conditional logging
public class MyService
{
private readonly ILogger<MyService> _logger;
public MyService(ILogger<MyService> logger)
{
_logger = logger;
}
public void ProcessData(string data)
{
// Log level check (performance optimization)
if (_logger.IsEnabled(LogLevel.Debug))
{
_logger.LogDebug("Data processing started: {Data}", data);
}
// Log output at various levels
_logger.LogTrace("Detailed trace information");
_logger.LogDebug("Debug information: {Value}", data);
_logger.LogInformation("Processing completed");
_logger.LogWarning("Situation requiring attention");
_logger.LogError("An error occurred");
_logger.LogCritical("Critical error");
}
}
High-Performance Logging with LoggerMessage
using Microsoft.Extensions.Logging;
public static partial class LogMessages
{
// High-performance log messages via source generator
[LoggerMessage(
EventId = 1001,
Level = LogLevel.Information,
Message = "User {UserId} performed {Action}")]
public static partial void UserAction(ILogger logger, int userId, string action);
[LoggerMessage(
EventId = 2001,
Level = LogLevel.Error,
Message = "Database connection error: {ConnectionString}")]
public static partial void DatabaseConnectionError(ILogger logger, string connectionString, Exception ex);
[LoggerMessage(
EventId = 3001,
Level = LogLevel.Warning,
Message = "Performance warning: Processing time exceeded {ElapsedMs}ms")]
public static partial void PerformanceWarning(ILogger logger, long elapsedMs);
}
// Usage example
public class UserService
{
private readonly ILogger<UserService> _logger;
public UserService(ILogger<UserService> logger)
{
_logger = logger;
}
public async Task<User> GetUserAsync(int userId)
{
var stopwatch = Stopwatch.StartNew();
try
{
LogMessages.UserAction(_logger, userId, "GetUser");
var user = await _repository.GetUserAsync(userId);
stopwatch.Stop();
if (stopwatch.ElapsedMilliseconds > 1000)
{
LogMessages.PerformanceWarning(_logger, stopwatch.ElapsedMilliseconds);
}
return user;
}
catch (Exception ex)
{
LogMessages.DatabaseConnectionError(_logger, _connectionString, ex);
throw;
}
}
}
Scopes and Context Information
using Microsoft.Extensions.Logging;
public class OrderService
{
private readonly ILogger<OrderService> _logger;
public OrderService(ILogger<OrderService> logger)
{
_logger = logger;
}
public async Task ProcessOrderAsync(int orderId, int customerId)
{
// Begin scope
using var scope = _logger.BeginScope("OrderId: {OrderId}, CustomerId: {CustomerId}",
orderId, customerId);
_logger.LogInformation("Order processing started");
try
{
// Nested scope
using var validationScope = _logger.BeginScope("Validation");
_logger.LogDebug("Validating order data");
await ValidateOrderAsync(orderId);
// Payment processing scope
using var paymentScope = _logger.BeginScope("Payment");
_logger.LogInformation("Processing payment");
await ProcessPaymentAsync(orderId);
_logger.LogInformation("Order processing completed successfully");
}
catch (Exception ex)
{
_logger.LogError(ex, "Error occurred during order processing");
throw;
}
}
}
// Custom scope information
public class RequestContextEnricher
{
public void EnrichLogs(HttpContext context, ILogger logger)
{
var enrichedScope = new Dictionary<string, object?>
{
["TraceId"] = context.TraceIdentifier,
["UserId"] = context.User?.Identity?.Name,
["IPAddress"] = context.Connection.RemoteIpAddress?.ToString(),
["UserAgent"] = context.Request.Headers["User-Agent"].ToString()
};
using var scope = logger.BeginScope(enrichedScope);
// Logs within this scope will automatically include the above information
}
}
Custom Providers and Serilog Integration
using Microsoft.Extensions.Logging;
using Serilog;
using Serilog.Extensions.Logging;
// Serilog integration
var serilogLogger = new LoggerConfiguration()
.WriteTo.Console()
.WriteTo.File("logs/app-.txt", rollingInterval: RollingInterval.Day)
.WriteTo.Seq("http://localhost:5341")
.CreateLogger();
var builder = WebApplication.CreateBuilder(args);
// Integration from Microsoft.Extensions.Logging to Serilog
builder.Logging.ClearProviders();
builder.Logging.AddSerilog(serilogLogger);
// Custom provider implementation example
public class CustomLoggerProvider : ILoggerProvider
{
public ILogger CreateLogger(string categoryName)
{
return new CustomLogger(categoryName);
}
public void Dispose() { }
}
public class CustomLogger : ILogger
{
private readonly string _categoryName;
public CustomLogger(string categoryName)
{
_categoryName = categoryName;
}
public IDisposable BeginScope<TState>(TState state) => null!;
public bool IsEnabled(LogLevel logLevel) => logLevel >= LogLevel.Information;
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state,
Exception? exception, Func<TState, Exception?, string> formatter)
{
if (!IsEnabled(logLevel)) return;
var message = formatter(state, exception);
// Custom log processing (database, external API, etc.)
Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] [{logLevel}] {_categoryName}: {message}");
if (exception != null)
{
Console.WriteLine($"Exception: {exception}");
}
}
}
// Custom provider registration
builder.Logging.AddProvider(new CustomLoggerProvider());
Testing with Fake Logger
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Diagnostics.Testing;
// Using FakeLogger in tests
public class UserServiceTests
{
[Test]
public void UserService_ShouldLogInformation_WhenUserCreated()
{
// Arrange
var services = new ServiceCollection();
services.AddFakeLogging();
services.AddTransient<UserService>();
var provider = services.BuildServiceProvider();
var userService = provider.GetRequiredService<UserService>();
var fakeLogCollector = provider.GetFakeLogCollector();
// Act
userService.CreateUser("testuser");
// Assert
var logs = fakeLogCollector.GetSnapshot();
Assert.Single(logs.Where(l =>
l.Level == LogLevel.Information &&
l.Message.Contains("testuser")));
}
}
// Or using individual FakeLogger
[Test]
public void TestWithFakeLogger()
{
var fakeLogger = new FakeLogger<MyService>();
var service = new MyService(fakeLogger);
service.DoWork();
var logs = fakeLogger.Collector.GetSnapshot();
Assert.True(logs.Any(l => l.Message.Contains("DoWork completed")));
}