In the previous blog post I talked about how to change the log level at runtime by coupling the
appsettings.json (or rather the
IConfiguration) with the
ILogger. However, the solution has one drawback: you need to change the file
appsettings.json for that. In this post we will be able to change the log level without changing the configuration file.
Want to see some real code? Look at the examples on https://github.com/PawelGerr/Thinktecture.Logging.Configuration
At first we need a custom implementation of
IConfigurationProvider. The actual work does the implementation of
IConfigurationSource is just to inject the provider into your
var config = new ConfigurationBuilder()
----------------------------------------- public class LoggingConfigurationSource : IConfigurationSource
public IConfigurationProvider Build(IConfigurationBuilder builder)
// Our implementation of IConfigurationProvider
return new LoggingConfigurationProvider();
As we can see, the
LoggingConfigurationSource doesn't do pretty much, let us focus on
LoggingConfigurationProvider or rather on the interface
public interface IConfigurationProvider
bool TryGet(string key, out string value);
void Set(string key, string value);
IEnumerable<string> GetChildKeys(IEnumerable<string> earlierKeys, string parentPath);
There are 2 methods that look promising:
Set(key, value) for setting a value for a specific key and
GetReloadToken() to notify other components (like the logger) about changes in the configuration. Now that we know how to change the configuration values, we need to know the keys and values the logger uses to configure itself. Use Microsoft docs for a hint for Microsoft.Extensions.Logging.ILogger or Serilog.Settings.Configuration in case you are using Serilog.
The pattern for MS-logger key is
<<Provider>>:LogLevel:<<Category>>. Here are some examples for the logs coming from Thinktecture components:
LogLevel:Thinktecture. The value is just one of the
public class MyComponent
public MyComponent(ILogger<MyComponent> logger)
logger.LogDebug("Log from Thinktecture.Component");
Let's look at the implementation, luckily there is a base class we can use.
public class LoggingConfigurationProvider : ConfigurationProvider
public void SetLevel(LogLevel level, string category = null, string provider = null)
// returns something like "Console:LogLevel:Thinktecture"
var path = BuildLogLevelPath(category, provider);
var levelName = GetLevelName(level); // returns log level like "Debug"
// Data and OnReload() are provided by the base class
Data[path] = levelName;
OnReload(); // notifies other components
Actually, that's it ... You can change the configuration just by setting and deleting keys in the dictionary
Data and calling
OnReload() afterwards. The only part that's left is to get hold of the instance of
LoggingConfigurationProvider to be able to call the method
SetLevel from outside but I'm pretty sure you don't need any help for that especially having access to my github repo :)
The provided solution does what we intended to, but, do we really want that simple filtering of the logs? Image you are using Entity Framework Core (EF) and there are multiple requests that modify some data. One request is able to commit the transaction the other doesn't and throws, say, an
OptimisticConcurrencyException. Your code catches the exception and handles it by retrying the whole transaction, with success. Entity Framework logs this error (i.e. the SQL statement, arguments etc.) internally. The question is, should this error be logged by EF as an
Error even if it has been handled by our application? If yes then our logs will be full with errors and it would seem as if we have a lot of bugs in our application. Perhaps it would be better to let EF to log its internal errors as
Debug, so that this information is not lost and if our app can't handle the exception then we will log the exception as an error.
But that's for another day ...