2

I have an .net core Web API project that uses Serilog for logging. I want to dynamically update the log levels for different classes at runtime. I am using the default IoC for .net core. Some of these classes (like the controllers) are scoped Transient, while there are some background services scoped Singleton.

I create a LogLevelSwitchWatcherService as an IHostedService that starts up early on in the lifetime of the application. This references a static Loggers class where most of the work happens. The Loggers class is called as part of constructing the IoC container with an AddLogging method.

public static ILoggerFactory AddLogging(this IServiceCollection serviceCollection, IConfiguration configuration)
{
    var loggerConfig = CreateLoggerConfig(configuration);
    Log.Logger = loggerConfig.CreateLogger();
    var loggerFactory = new SerilogLoggerFactory(Log.Logger);
    serviceCollection.AddSingleton(loggerConfig);
    serviceCollection.AddSingleton(loggerFactory);
    _logger = loggerFactory.CreateLogger(typeof(Loggers));
    return loggerFactory;
}

public static LoggerConfiguration CreateLoggerConfig(IConfiguration configuration)
{
    var loggerConfig = new LoggerConfiguration().MinimumLevel.Debug()
                                                .Enrich.WithMachineName()
                                                .Enrich.WithExceptionDetails()
                                                .Enrich.FromLogContext();

    lock (LogLevelSwitches)
    {
        foreach (var @switch in LogLevelSwitches)
        {
            loggerConfig = loggerConfig.MinimumLevel.Override(@switch.Key, @switch.Value);
        }
    }

    if (configuration["SeqUri"] != null)
    {
        var seqUri = configuration["SeqUri"];
        loggerConfig = loggerConfig.WriteTo.Seq(seqUri, LogEventLevel.Debug)
                                   .WriteTo.Console(new JsonFormatter(renderMessage: true), LogEventLevel.Warning);
    }
    else
        loggerConfig = loggerConfig.WriteTo.Console(new JsonFormatter(renderMessage: true), LogEventLevel.Debug);
    return loggerConfig;
}

The CreateLoggerConfig references a Dictionary<string, LoggingLevelSwitch> to keep a local reference of the LoggingLevelSwitch in the class scope. There are some stock ones hard coded in, but also some overrides specified in a database.

The values specified in the LogLevelSwitches appear to take effect at startup. However subsequent changes to these values do not take effect. Furthermore, any additions made to the list of overrides do not take effect.

The LogLevelSwitchWatcherService calls into AddOrUpdateLogLevelSwitch at runtime when it detects a change in the levels specified in the database.

public static bool AddOrUpdateLogLevelSwitch(string @namespace, LogEventLevel logEventLevel, IServiceProvider serviceProvider)
{
    lock (LogLevelSwitches)
    {
        if (LogLevelSwitches.ContainsKey(@namespace) == false)
        {
            _logger.LogInformation("Creating log level switch for {namespace} with {logEventLevel}", @namespace, logEventLevel);
            LogLevelSwitches.Add(@namespace, new LoggingLevelSwitch(logEventLevel));
            var loggerConfiguration = serviceProvider.GetService<LoggerConfiguration>();
            loggerConfiguration.MinimumLevel.Override(@namespace, LogLevelSwitches[@namespace]);
            return true;
        }

        if (logEventLevel != LogLevelSwitches[@namespace].MinimumLevel)
        {
            _logger.LogInformation("Changing log level for {namespace} to {logEventLevel}", @namespace, logEventLevel);
            LogLevelSwitches[@namespace].MinimumLevel = logEventLevel;
        }

        return false;
    }
}

This code will attempt to add any switches specified in the database not present in the dictionary at startup. These switches do not work.

This code will attempt to update any switches that exist in the dictionary when the new value doesn't match the existing value. These switches also do not work.

What I've tried:

  1. Capturing and updating the LoggerConfiguration in the Loggers class
  2. Requesting the LoggerConfiguration from the IoC and updating the MinimumValue
  3. Requesting the LoggerConfiguration from the IoC and using the overload of Override that takes just a LogEventLevel

The scoping of services do not seem to affect the logging.

  1. Am I just taking the whole wrong approach here?
  2. Is it just not possible to modify the MinimumLevel once an ILogger is constructed?
Pete Garafano
  • 4,863
  • 1
  • 23
  • 40

0 Answers0