Microsoft.Extensions.Logging

.NET Core/ASP.NET Core標準のロギング抽象化レイヤー。プロバイダーシステムによりSerilogやNLogなど様々なライブラリへログをルーティング可能。依存性注入との統合、設定ベースのログレベル制御を提供し、.NETエコシステムでの標準的な選択肢。

ロギングライブラリ.NETASP.NET Core依存性注入プロバイダーモデル構造化ログマイクロソフト

ロギングライブラリ

Microsoft.Extensions.Logging

概要

Microsoft.Extensions.Loggingは、.NET/.NET Core用の公式ロギング抽象化ライブラリです。プロバイダーモデルにより、さまざまなログ出力先(コンソール、ファイル、サードパーティライブラリ等)への統一されたAPIを提供します。ASP.NET Coreに標準組み込みされ、**依存性注入(DI)**との深い統合により、モダンな.NETアプリケーションでのロギングのデファクトスタンダードとなっています。

詳細

Microsoft.Extensions.Loggingは、.NET Core 1.0と共に2016年に導入された公式ロギングフレームワークです。既存のサードパーティライブラリ(Serilog、NLog等)を置き換えるのではなく、統一された抽象化レイヤーを提供することで、ロギングライブラリの切り替えや複数ライブラリの併用を可能にしています。

技術的特徴

  • プロバイダーモデル: 複数のログ出力先への同時出力
  • 依存性注入統合: IServiceCollectionとの完全統合
  • 構造化ログ: LoggerMessageとテンプレート機能
  • ログレベル: Trace、Debug、Information、Warning、Error、Critical
  • スコープサポート: BeginScopeによる文脈情報の追加
  • パフォーマンス最適化: コンパイル時最適化とソースジェネレーター

プロバイダー種類

  • 組み込み: Console、Debug、EventSource、EventLog(Windows)
  • ファイル系: File、Rolling File(サードパーティ)
  • 統合系: Serilog、NLog、log4net(各プロバイダー経由)
  • クラウド: Azure Application Insights、AWS CloudWatch等

ログレベル階層

  1. Trace: 最も詳細な情報
  2. Debug: デバッグ情報
  3. Information: 一般的な情報
  4. Warning: 警告メッセージ
  5. Error: エラー情報
  6. Critical: 致命的なエラー

メリット・デメリット

メリット

  • 公式サポート: Microsoftによる公式サポートと保守
  • 標準統合: ASP.NET Coreでの標準的なロギング手法
  • 抽象化: ログライブラリの切り替えが容易
  • パフォーマンス: 高度に最適化された実装
  • 依存性注入: DIコンテナとの完全統合
  • 拡張性: カスタムプロバイダーの作成が容易
  • テスト支援: FakeLoggerによるユニットテスト対応

デメリット

  • 機能限定: 単体では基本的な機能のみ
  • 構造化ログ: ネイティブな構造化ログ機能は限定的
  • 設定複雑性: 高度な設定には複数のプロバイダー必要
  • サードパーティ依存: 本格的な機能にはSerilog等が必要
  • 学習コスト: DIとプロバイダーモデルの理解が必要

参考ページ

書き方の例

基本的な使用方法

using Microsoft.Extensions.Logging;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

// ホストビルダーでの設定
var builder = Host.CreateApplicationBuilder(args);

// ロギング設定
builder.Logging.ClearProviders();
builder.Logging.AddConsole();
builder.Logging.SetMinimumLevel(LogLevel.Debug);

var host = builder.Build();
var logger = host.Services.GetRequiredService<ILogger<Program>>();

// 基本的なログ出力
logger.LogInformation("アプリケーションが開始されました");
logger.LogWarning("これは警告メッセージです");
logger.LogError("エラーが発生しました: {ErrorCode}", 404);

// 構造化ログ
logger.LogInformation("ユーザー {UserId} がログインしました(時刻: {LoginTime})", 
    12345, DateTime.Now);

ASP.NET Coreでの設定

using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Logging;

var builder = WebApplication.CreateBuilder(args);

// ロギング設定
builder.Logging.ClearProviders();
builder.Logging.AddConsole();
builder.Logging.AddDebug();
builder.Logging.AddEventSourceLogger();

// JSONコンソール出力(構造化ログ)
builder.Logging.AddJsonConsole(options =>
{
    options.IncludeScopes = false;
    options.TimestampFormat = "HH:mm:ss ";
    options.JsonWriterOptions = new JsonWriterOptions
    {
        Indented = true
    };
});

var app = builder.Build();

// サービス内でのロガー使用
public class HomeController : ControllerBase
{
    private readonly ILogger<HomeController> _logger;

    public HomeController(ILogger<HomeController> logger)
    {
        _logger = logger;
    }

    [HttpGet]
    public IActionResult Get()
    {
        _logger.LogInformation("GET リクエストを受信しました");
        
        try
        {
            // ビジネスロジック
            return Ok("成功");
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "リクエスト処理中にエラーが発生しました");
            return StatusCode(500);
        }
    }
}

ログレベルとフィルタリング

using Microsoft.Extensions.Logging;

// appsettings.jsonでの設定
{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information",
      "System": "Warning"
    },
    "Console": {
      "LogLevel": {
        "Default": "Debug"
      }
    }
  }
}

// コードでの設定
builder.Logging.AddFilter("Microsoft", LogLevel.Warning);
builder.Logging.AddFilter<ConsoleLoggerProvider>("Microsoft.AspNetCore", LogLevel.Debug);

// 条件付きロギング
public class MyService
{
    private readonly ILogger<MyService> _logger;

    public MyService(ILogger<MyService> logger)
    {
        _logger = logger;
    }

    public void ProcessData(string data)
    {
        // ログレベルチェック(パフォーマンス最適化)
        if (_logger.IsEnabled(LogLevel.Debug))
        {
            _logger.LogDebug("データ処理開始: {Data}", data);
        }

        // 各レベルでのログ出力
        _logger.LogTrace("詳細な追跡情報");
        _logger.LogDebug("デバッグ情報: {Value}", data);
        _logger.LogInformation("処理完了");
        _logger.LogWarning("注意が必要な状況");
        _logger.LogError("エラーが発生しました");
        _logger.LogCritical("致命的なエラー");
    }
}

LoggerMessageによる高性能ログ

using Microsoft.Extensions.Logging;

public static partial class LogMessages
{
    // ソースジェネレーターによる高性能ログメッセージ
    [LoggerMessage(
        EventId = 1001,
        Level = LogLevel.Information,
        Message = "ユーザー {UserId} が {Action} を実行しました")]
    public static partial void UserAction(ILogger logger, int userId, string action);

    [LoggerMessage(
        EventId = 2001,
        Level = LogLevel.Error,
        Message = "データベース接続エラー: {ConnectionString}")]
    public static partial void DatabaseConnectionError(ILogger logger, string connectionString, Exception ex);

    [LoggerMessage(
        EventId = 3001,
        Level = LogLevel.Warning,
        Message = "パフォーマンス警告: 処理時間が {ElapsedMs}ms を超えました")]
    public static partial void PerformanceWarning(ILogger logger, long elapsedMs);
}

// 使用例
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;
        }
    }
}

スコープとコンテキスト情報

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)
    {
        // スコープの開始
        using var scope = _logger.BeginScope("OrderId: {OrderId}, CustomerId: {CustomerId}", 
            orderId, customerId);
        
        _logger.LogInformation("注文処理を開始しました");
        
        try
        {
            // 入れ子スコープ
            using var validationScope = _logger.BeginScope("Validation");
            _logger.LogDebug("注文データを検証中");
            await ValidateOrderAsync(orderId);
            
            // 決済処理スコープ
            using var paymentScope = _logger.BeginScope("Payment");
            _logger.LogInformation("決済処理を実行中");
            await ProcessPaymentAsync(orderId);
            
            _logger.LogInformation("注文処理が正常に完了しました");
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "注文処理中にエラーが発生しました");
            throw;
        }
    }
}

// カスタムスコープ情報
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);
        // このスコープ内でのログには上記情報が自動追加される
    }
}

カスタムプロバイダーとSerilog統合

using Microsoft.Extensions.Logging;
using Serilog;
using Serilog.Extensions.Logging;

// Serilogとの統合
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);

// Microsoft.Extensions.LoggingからSerilogへの統合
builder.Logging.ClearProviders();
builder.Logging.AddSerilog(serilogLogger);

// カスタムプロバイダーの実装例
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);
        
        // カスタムログ処理(データベース、外部API等)
        Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] [{logLevel}] {_categoryName}: {message}");
        
        if (exception != null)
        {
            Console.WriteLine($"Exception: {exception}");
        }
    }
}

// カスタムプロバイダー登録
builder.Logging.AddProvider(new CustomLoggerProvider());

テスト用Fake Logger

using Microsoft.Extensions.Logging;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Diagnostics.Testing;

// テストでのFakeLogger使用
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")));
    }
}

// または個別の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")));
}