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:
- Capturing and updating the
LoggerConfiguration
in theLoggers
class - Requesting the
LoggerConfiguration
from the IoC and updating theMinimumValue
- Requesting the
LoggerConfiguration
from the IoC and using the overload ofOverride
that takes just aLogEventLevel
The scoping of services do not seem to affect the logging.
- Am I just taking the whole wrong approach here?
- Is it just not possible to modify the
MinimumLevel
once anILogger
is constructed?