Implementare un provider di registrazione personalizzato in .NET

Esistono molti provider di registrazione disponibili per esigenze di registrazione comuni. Potrebbe essere necessario implementare un ILoggerProvider personalizzato quando uno dei provider disponibili non soddisfa le esigenze dell'applicazione. In questo articolo si apprenderà come implementare un provider di registrazione personalizzato che può essere usato per colorare i log nella console.

Suggerimento

Il codice sorgente di esempio del provider di registrazione personalizzato è disponibile nel repository Docs di GitHub. Per altre informazioni, vedere GitHub: .NET Docs - Registrazioni personalizzate della console.

Configurazione del logger personalizzato di esempio

L'esempio crea voci di console con colori diversi per ogni livello di log e ID evento usando il tipo di configurazione seguente:

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
    };
}

Il codice precedente imposta il livello predefinito su Information, il colore su Green e EventId è implicitamente 0.

Creare il logger personalizzato

Il nome della categoria di implementazione di ILogger è in genere l'origine di registrazione. Ad esempio, il tipo in cui viene creato il logger:

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();
        }
    }
}

Il codice precedente:

  • Crea un'istanza del logger per ogni nome di categoria.
  • Controlla _getCurrentConfig().LogLevelToColorMap.ContainsKey(logLevel) in IsEnabled, in modo che ogni logLevel abbia un logger univoco. In questa implementazione ogni livello di log richiede una voce di configurazione esplicita da registrare.

È consigliabile chiamare ILogger.IsEnabled all'interno di implementazioni di ILogger.Log poiché è possibile chiamare Log da qualsiasi consumer e non esistono garanzie che sia stato eseguito un controllo in precedenza. Il metodo IsEnabled deve essere molto veloce nella maggior parte delle implementazioni.

TState state,
Exception? exception,

Viene creata un'istanza del logger con il name e una Func<ColorConsoleLoggerConfiguration>, che restituisce la configurazione corrente. Ciò consente di gestire gli aggiornamenti ai valori di configurazione in base a quanto monitorato tramite il callback IOptionsMonitor<TOptions>.OnChange.

Importante

L'implementazione di ILogger.Log controlla se è impostato il valore config.EventId. Quando config.EventId non è impostato o quando corrisponde esattamente a logEntry.EventId, il logger esegue la registrazione a colori.

Provider di logger personalizzato

L'oggetto ILoggerProvider è responsabile della creazione di istanze del logger. Non è necessario creare un'istanza del logger per ogni categoria, ma questo approccio è appropriato per alcuni logger, ad esempio NLog o log4net. Questa strategia consente di scegliere diverse destinazioni di output di registrazione per ogni categoria, come nell'esempio seguente:

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();
    }
}

Nel codice precedente CreateLogger crea una singola istanza di ColorConsoleLogger per ogni nome di categoria e la archivia in ConcurrentDictionary<TKey,TValue>. L'interfaccia IOptionsMonitor<TOptions> è inoltre necessaria per aggiornare le modifiche all'oggetto ColorConsoleLoggerConfiguration sottostante.

Per controllare la configurazione di ColorConsoleLogger, definire un alias nel provider:

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

La classe ColorConsoleLoggerProvider definisce due attributi con ambito classe:

La configurazione può essere specificata con qualsiasi provider di configurazione valido. Considerare il file appsettings.json seguente:

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

In questo modo vengono configurati i livelli di log con i valori seguenti:

Il livello di log Information è impostato su DarkGreen, che esegue l'override del valore predefinito impostato nell'oggetto ColorConsoleLoggerConfiguration.

Utilizzo e registrazione del logger personalizzato

Per convenzione, la registrazione dei servizi per l'inserimento delle dipendenze avviene come parte della routine di avvio di un'applicazione. La registrazione si verifica nella classe Program o può essere delegata a una classe Startup. In questo esempio si eseguirà la registrazione direttamente da Program.cs.

Per aggiungere il provider di registrazione personalizzato e il logger corrispondente, aggiungere un ILoggerProvider con ILoggingBuilder da HostingHostBuilderExtensions.ConfigureLogging(IHostBuilder, Action<ILoggingBuilder>):

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 crea una o più istanze di ILogger. Le istanze di ILogger vengono usate dal framework per registrare le informazioni.

La configurazione dal file appsettings.json esegue l'override dei valori seguenti:

Per convenzione, i metodi di estensione su ILoggingBuilder vengono usati per registrare il provider personalizzato:

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;
    }
}

L'esecuzione di questa semplice applicazione eseguirà il rendering dell'output a colori nella finestra della console in modo simile all'immagine seguente:

Color console logger sample output

Vedi anche