Implementación de un proveedor de registro personalizado en .NET

Hay muchos proveedores de registro disponibles para las necesidades de registro habituales. Puede que tenga que implementar un ILoggerProvider personalizado cuando uno de los proveedores disponibles no se ajuste a las necesidades de su aplicación. En este artículo, aprenderá a implementar un proveedor de registro personalizado que se puede usar para colorear los registros en la consola.

Sugerencia

El código fuente de ejemplo del proveedor de registro personalizado está disponible en el repositorio de GitHub de Docs. Para obtener más información, consulte GitHub: Documentos de .NET: Registro personalizado de consola.

Configuración de registrador personalizado de ejemplo

En el ejemplo se crean diferentes entradas de consola de color por nivel de registro e identificador de evento con el siguiente tipo de configuración:

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

El código anterior establece el nivel predeterminado en Information, el color en Green y el EventId en 0 de forma implícita.

Creación del registrador personalizado

El nombre de la categoría de implementación ILogger es normalmente el origen del registro. Por ejemplo, el tipo en el que se crea el registrador:

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

El código anterior:

  • Crea una instancia del registrador por nombre de categoría.
  • Comprueba _getCurrentConfig().LogLevelToColorMap.ContainsKey(logLevel) en IsEnabled, por lo que cada logLevel tiene un registrador único. En esta implementación, cada nivel de registro requiere una entrada de configuración explícita para el registro.

Se recomienda llamar a ILogger.IsEnabled dentro de implementaciones de ILogger.Log, ya que cualquier consumidor puede llamar a Log y no hay ninguna garantía de que se haya comprobado previamente. El método IsEnabled debería ser muy rápido en la mayoría de las implementaciones.

TState state,
Exception? exception,

El registrador es instanciado con el name y un Func<ColorConsoleLoggerConfiguration>, que devuelve la configuración actual-esto maneja las actualizaciones de los valores de configuración como monitoreado a través de la devolución de llamada IOptionsMonitor<TOptions>.OnChange.

Importante

La implementación de ILogger.Log comprueba si se establece el valor de config.EventId. Cuando no se establece config.EventId o cuando coincide con el valor logEntry.EventId exacto, el registrador realiza el registro en color.

Proveedor de registrador personalizado

El objeto ILoggerProvider es responsable de crear las instancias de registrador. No es necesario crear una instancia del registrador por categoría, pero esto tiene sentido para algunos registradores, como NLog o log4net. Esta estrategia le permite elegir diferentes destinos de salida de registro por categoría, como en el ejemplo siguiente:

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

En el código anterior, CreateLogger crea una única instancia de ColorConsoleLogger por nombre de categoría y la almacena en ConcurrentDictionary<TKey,TValue>. Además, es necesario que la interfaz IOptionsMonitor<TOptions> actualice los cambios en el objeto ColorConsoleLoggerConfiguration subyacente.

Para controlar la configuración de ColorConsoleLogger, defina un alias en su proveedor:

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

La clase ColorConsoleLoggerProvider define dos atributos con ámbito de clase:

La configuración se puede especificar con cualquier proveedor de configuración válido. Fíjese en el siguiente archivo appsettings.json:

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

Esto configura los niveles de registro en los valores siguientes:

El nivel de registro Information se establece en DarkGreen, que reemplaza el valor predeterminado establecido en el objeto ColorConsoleLoggerConfiguration.

Uso y registro del registrador personalizado

Por convención, el registro de servicios para la inserción de dependencias se produce como parte de la rutina de inicio de una aplicación. El registro se produce en la clase Program o se podría delegar en una clase Startup. En este ejemplo, se registrará directamente desde Program.cs.

Para agregar el proveedor de registro personalizado y el registrador correspondiente, agregue un ILoggerProvider con ILoggingBuilder desde 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 más instancias de ILogger. El marco de trabajo usa las instancias de ILogger para registrar la información.

La configuración del archivo appsettings.json reemplaza los siguientes valores:

Por convención, los métodos de extensión en ILoggingBuilder se usan para registrar el proveedor personalizado:

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

Al ejecutar esta aplicación sencilla se representará la salida de color en la ventana de la consola, similar a la siguiente imagen:

Color console logger sample output

Consulte también