Search code examples
c#dependency-injection.net-6.0ilogger

Custom logger configurations overwritten by last configuration entry


Currently, I have two types of custom loggers that both have their own configuration section, ILoggerProvider, and Ilogger implementation. However, it seems there is an issue with how my configuration scope is being read as the last configuration to be added during DI to our ILoggingBuilder.

Appsetting configuration settings that will get pulled

{    
    "Logging": {
        "LogLevel": {
            "Default": "Warning"
        },
        "Logger1": {
            "LogLevel": {
                "Default": "Information"
            },
            "BenchmarkConfig": {
                "Enable": true,
                "LogThreshold": {
                    "Critical": "00:00:15",
                    "Information": "00:00:00"
                }
            }
        },
        "Logger2": {
            "LogLevel": {
                "Default": "Critical"
            }
        }
    }
}

This is the DI layer that I add them in

protected override void LoggingHook(IServiceCollection services, IConfiguration configuration)
{
    base.LoggingHook(services, configuration);

    _ = services.AddLogging(config => _ = config.AddLogger1(configuration));
    _ = services.AddLogging(config => _ = config.AddLogger2(configuration));
}

Logger1

public static class Logger1Extensions
{
    public static ILoggingBuilder AddLogger1(this ILoggingBuilder builder, IConfiguration configuration)
    {
        var configurationSection = configuration.GetSection(Logger1.Section);
        _ = builder.AddConfiguration(configurationSection);

        builder.Services.AddTransient<InitializedLogMessage>();
        builder.Services.AddSingleton<ILogger1Config>(configurationSection.Get<Logger1Config>());
        builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<ILoggerProvider, Logger1Provider>());
        return builder;
    }
}


public interface ILogger1Config
{
    LogLevel                LogLevel        { get; set; }
    Logger1BenchmarkConfig  BenchmarkConfig { get; set; }
}

public class Logger1 : ILogger
{
    private readonly ILambdaLogger  _lambdaLogger;
    private readonly ILogger1Config _loggerConfig;

    public const string Section                = "Logging:Logger1";
    public const string SectionLogLevel        = Section + ":LogLevel";
    public const string SectionLogLevelDefault = SectionLogLevel + ":Default";

    public Logger1(ILambdaLogger logger, ILogger1Config loggerConfig, InitializedLogMessage logMessage)
    {
        _lambdaLogger = logger;
        _loggerConfig = loggerConfig;
        LogMessage    = logMessage;
    }

    private InitializedLogMessage LogMessage { get; }

    public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
    {
        if (IsEnabled(logLevel))
        {
            _lambdaLogger?.LogLine($"{GetScopePrefix()}[{logLevel}] {formatter(state, exception)}");
        }
    }

    public bool IsEnabled(LogLevel logLevel) => logLevel != LogLevel.None;
}

Logger2

public static class Logger2Extensions
{
  public static ILoggingBuilder AddLogger2Generic<T>(this ILoggingBuilder builder,
    IConfiguration configuration) where T : class, ILoggerProvider
  {
    IConfigurationSection section = configuration.GetSection("Logging:Logger2");
    builder.AddConfiguration((IConfiguration) section);
    builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<ILoggerProvider, T>());
    return builder;
  }

  public static ILoggingBuilder AddLogger2(this ILoggingBuilder builder,
    IConfiguration configuration)
  {
    return builder.AddLogger2Generic<Logger2Provider>(configuration);
  }
}

public class Logger2 : ILogger
{
    private _rest { get; set; }

    public Logger2(IRest rest)
    {
      _rest = rest
    }

    public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
    {
        if (IsEnabled(logLevel))
        {
            _rest.log()
        }
    }

    public bool IsEnabled(LogLevel logLevel) => logLevel != LogLevel.None;

}

If I comment out adding the configuration inside the Logger2Extesions.AddLogger2Generic then everything in Logger1 works fine. But once Logger2 is added into the mix it not only overwrites the configuration for Logger1 but it also reads the log level incorrectly. Since I am using the loglevel enum from microsoft it results in a defaulted loglevel of trace since what it picks up to cast to the enum is invalid.


Solution

  • In your AddLogger1 and AddLogger2/AddLogger2Generic methods, you are calling AddConfiguration and providing the section for that specific logger. Instead, you should call AddConfiguration once, and only once, and provide the "Logging" section. You may also want to call ClearProviders beforehand.

    Try adding this before you add the other loggers (and remember to remove the AddConfiguration calls from those other methods):

    services.AddLogging(config =>
    {
        _ = config.ClearProviders();
        _ = config.AddConfiguration(configuration.GetSection("Logging"));
    });