.NET에서 사용자 지정 로깅 공급자 구현

일반적인 로깅 요구 사항에 사용할 수 있는 다양한 로깅 공급자가 있습니다. 사용 가능한 공급자 중 하나가 애플리케이션 요구 사항에 맞지 않는 경우 사용자 지정 ILoggerProvider를 구현해야 할 수 있습니다. 이 문서에서는 콘솔에서 로그에 색을 지정하는 데 사용할 수 있는 사용자 지정 로깅 공급자를 구현하는 방법을 알아봅니다.

사용자 지정 로깅 공급자 예제 소스 코드는 Docs Github 리포지토리에서 사용할 수 있습니다. 자세한 내용은 GitHub: .NET Docs - 콘솔 사용자 지정 로깅을 참조하세요.

샘플 사용자 지정 로거 구성

샘플에서는 다음 구성 형식을 사용하여 로그 수준 및 이벤트 ID에 따라 다른 색의 콘솔 항목을 만듭니다.

using Microsoft.Extensions.Logging;

public sealed class ColorConsoleLoggerConfiguration
{
    public int EventId { get; set; }

    public Dictionary<LogLevel, ConsoleColor> LogLevelToColorMap { get; set; } = new()
    {
        [LogLevel.Information] = ConsoleColor.Green
    };
}

위의 코드는 기본 수준을 Information으로 설정하고 색을 Green으로 설정하며 EventId는 암시적으로 0입니다.

사용자 지정 로거 만들기

ILogger 구현 범주 이름은 일반적으로 로깅 원본입니다. 예를 들어 다음은 로거를 만드는 형식입니다.

using Microsoft.Extensions.Logging;

public sealed class ColorConsoleLogger(
    string name,
    Func<ColorConsoleLoggerConfiguration> getCurrentConfig) : ILogger
{
    public IDisposable? BeginScope<TState>(TState state) where TState : notnull => default!;

    public bool IsEnabled(LogLevel logLevel) =>
        getCurrentConfig().LogLevelToColorMap.ContainsKey(logLevel);

    public void Log<TState>(
        LogLevel logLevel,
        EventId eventId,
        TState state,
        Exception? exception,
        Func<TState, Exception?, string> formatter)
    {
        if (!IsEnabled(logLevel))
        {
            return;
        }

        ColorConsoleLoggerConfiguration config = getCurrentConfig();
        if (config.EventId == 0 || config.EventId == eventId.Id)
        {
            ConsoleColor originalColor = Console.ForegroundColor;

            Console.ForegroundColor = config.LogLevelToColorMap[logLevel];
            Console.WriteLine($"[{eventId.Id,2}: {logLevel,-12}]");
            
            Console.ForegroundColor = originalColor;
            Console.Write($"     {name} - ");

            Console.ForegroundColor = config.LogLevelToColorMap[logLevel];
            Console.Write($"{formatter(state, exception)}");
            
            Console.ForegroundColor = originalColor;
            Console.WriteLine();
        }
    }
}

앞의 코드가 하는 역할은 다음과 같습니다.

  • 범주 이름별로 로거 인스턴스를 만듭니다.
  • IsEnabled에서 _getCurrentConfig().LogLevelToColorMap.ContainsKey(logLevel)을 확인하므로 각 logLevel에 고유한 로거가 있습니다. 이 구현에서 각 로그 수준에는 로그에 명시적 구성 항목이 필요합니다.

Log는 모든 소비자가 호출할 수 있고 이전에 확인되었다는 보장이 없으므로 ILogger.Log 구현 내에서 ILogger.IsEnabled를 호출하는 것이 좋습니다. IsEnabled 메서드는 대부분의 구현에서 매우 빠릅니다.

TState state,
Exception? exception,

로거는 콜백을 통해 현재 구성을 반환하는 nameFunc<ColorConsoleLoggerConfiguration>을 통해 인스턴스화됩니다. 이는 IOptionsMonitor<TOptions>.OnChange 콜백을 통해 모니터링되는 구성 값으로 업데이트를 처리합니다.

Important

ILogger.Log 구현은 config.EventId 값이 설정되었는지 확인합니다. config.EventId가 설정되지 않았거나 정확하게 logEntry.EventId와 일치하는 경우 로거는 색으로 기록됩니다.

사용자 지정 로거 공급자

ILoggerProvider 개체는 로거 인스턴스를 만들어야 합니다. 범주별로 로거 인스턴스를 만들 필요는 없지만 NLog 또는 log4net과 같은 일부 로거에는 적합합니다. 이 전략을 사용하면 다음 예제와 같이 범주별로 다른 로깅 출력 대상을 선택할 수 있습니다.

using System.Collections.Concurrent;
using System.Runtime.Versioning;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;

[UnsupportedOSPlatform("browser")]
[ProviderAlias("ColorConsole")]
public sealed class ColorConsoleLoggerProvider : ILoggerProvider
{
    private readonly IDisposable? _onChangeToken;
    private ColorConsoleLoggerConfiguration _currentConfig;
    private readonly ConcurrentDictionary<string, ColorConsoleLogger> _loggers =
        new(StringComparer.OrdinalIgnoreCase);

    public ColorConsoleLoggerProvider(
        IOptionsMonitor<ColorConsoleLoggerConfiguration> config)
    {
        _currentConfig = config.CurrentValue;
        _onChangeToken = config.OnChange(updatedConfig => _currentConfig = updatedConfig);
    }

    public ILogger CreateLogger(string categoryName) =>
        _loggers.GetOrAdd(categoryName, name => new ColorConsoleLogger(name, GetCurrentConfig));

    private ColorConsoleLoggerConfiguration GetCurrentConfig() => _currentConfig;

    public void Dispose()
    {
        _loggers.Clear();
        _onChangeToken?.Dispose();
    }
}

위의 코드에서 CreateLogger는 범주 이름별로 ColorConsoleLogger의 단일 인스턴스를 만들고 ConcurrentDictionary<TKey,TValue>에 저장합니다. 또한 IOptionsMonitor<TOptions> 인터페이스는 기본 ColorConsoleLoggerConfiguration 개체의 변경 내용을 업데이트하는 데 필요합니다.

ColorConsoleLogger의 구성을 제어하려면 해당 공급자에 별칭을 정의합니다.

[UnsupportedOSPlatform("browser")]
[ProviderAlias("ColorConsole")]
public sealed class ColorConsoleLoggerProvider : ILoggerProvider

ColorConsoleLoggerProvider 클래스는 두 클래스 범위 특성을 정의합니다.

모든 유효한 구성 공급자를 사용하여 구성을 지정할 수 있습니다. 다음 appsettings.json 파일을 고려하세요.

{
    "Logging": {
        "ColorConsole": {
            "LogLevelToColorMap": {
                "Information": "DarkGreen",
                "Warning": "Cyan",
                "Error": "Red"
            }
        }
    }
}

그러면 로그 수준이 다음 값으로 구성됩니다.

Information 로그 수준은 ColorConsoleLoggerConfiguration 개체에 설정된 기본값을 재정의하는 DarkGreen으로 설정됩니다.

사용자 지정 로거의 사용 및 등록

관례적으로 종속성 주입을 위한 서비스 등록은 애플리케이션 시작 루틴의 일부로 수행됩니다. 등록은 Program 클래스에서 발생하거나 Startup 클래스에 위임될 수 있습니다. 이 예제에서는 Program.cs에서 직접 등록합니다.

사용자 지정 로깅 공급자와 해당 로거를 추가하려면 HostingHostBuilderExtensions.ConfigureLogging(IHostBuilder, Action<ILoggingBuilder>)에서 ILoggingBuilder와 함께 ILoggerProvider를 추가합니다.

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

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);

builder.Logging.ClearProviders();
builder.Logging.AddColorConsoleLogger(configuration =>
{
    // Replace warning value from appsettings.json of "Cyan"
    configuration.LogLevelToColorMap[LogLevel.Warning] = ConsoleColor.DarkCyan;
    // Replace warning value from appsettings.json of "Red"
    configuration.LogLevelToColorMap[LogLevel.Error] = ConsoleColor.DarkRed;
});

using IHost host = builder.Build();

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

logger.LogDebug(1, "Does this line get hit?");    // Not logged
logger.LogInformation(3, "Nothing to see here."); // Logs in ConsoleColor.DarkGreen
logger.LogWarning(5, "Warning... that was odd."); // Logs in ConsoleColor.DarkCyan
logger.LogError(7, "Oops, there was an error.");  // Logs in ConsoleColor.DarkRed
logger.LogTrace(5, "== 120.");                    // Not logged

await host.RunAsync();

ILoggingBuilder는 하나 이상의 ILogger 인스턴스를 만듭니다. ILogger 인스턴스는 프레임워크에서 정보를 로그하는 데 사용됩니다.

appsettings.json 파일의 구성은 다음 값을 재정의합니다.

규칙에 따라, ILoggingBuilder의 확장 메서드는 사용자 지정 공급자를 등록하는 데 사용됩니다.

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Configuration;

public static class ColorConsoleLoggerExtensions
{
    public static ILoggingBuilder AddColorConsoleLogger(
        this ILoggingBuilder builder)
    {
        builder.AddConfiguration();

        builder.Services.TryAddEnumerable(
            ServiceDescriptor.Singleton<ILoggerProvider, ColorConsoleLoggerProvider>());

        LoggerProviderOptions.RegisterProviderOptions
            <ColorConsoleLoggerConfiguration, ColorConsoleLoggerProvider>(builder.Services);

        return builder;
    }

    public static ILoggingBuilder AddColorConsoleLogger(
        this ILoggingBuilder builder,
        Action<ColorConsoleLoggerConfiguration> configure)
    {
        builder.AddColorConsoleLogger();
        builder.Services.Configure(configure);

        return builder;
    }
}

이 간단한 애플리케이션을 실행하면 다음 이미지와 유사한 색 출력이 콘솔 창에 렌더링됩니다.

Color console logger sample output

참고 항목