'Reload Serilog JSON Configuration on changes in .NET Core 2.1

I'm currently working on a ASP.NET Core 2.1 application and I use Serilog for logging. I want to reload the application settings file for my Serilog implementation during runtime.

My goal is to change the log level at runtime e.g. I write into minimumLevel Debug instead of Information and I save the file. I want to trigger a live reload of the settings. My appsettings.json looks like this:

{
  "serilog": {
    "using": [ "Serilog.Sinks.File", "Serilog.Sinks.Console" ],
    "minimumLevel": "Information",
    "writeTo": [
      {
        "name": "File",
        "args": {
          "fileSizeLimitBytes": 256000000,
          "retainedFileCountLimit": 62,
          "rollingInterval": "Day",
          "rollOnFileSizeLimit": true,
      },
      {
        "name": "Console",
      }
    ]
  }
}

In my Program.cs I load the settings with the flag reloadOnChange: true.

public class Program
{
    public static readonly ServiceSettings Settings = new ServiceSettings();
        public static void Main(string[] args)
    {
        //...
    }
    public static IWebHostBuilder CreateWebHostBuilder(string[] args)
    {
        var config = new ConfigurationBuilder()
            .SetBasePath(Directory.GetCurrentDirectory())
            .AddJsonFile(Path.GetFullPath(CoreServiceBase.Instance.ConfigFilePath), optional: false, reloadOnChange: true)
            .AddCommandLine(args)
            .Build();

        config.Bind(Settings);

        return WebHost.CreateDefaultBuilder(args)
            .UseStartup<Startup>()
            .UseSerilog((hostingContext, loggerConfiguration) =>
                    loggerConfiguration.ReadFrom.Configuration(hostingContext.Configuration))
            .UseConfiguration(config);
    }
}

My Startup looks like this:

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {
        // ...
    }

    public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
    {
        // Custom application logging
        ApplicationLogging.LoggerFactory = loggerFactory;

        // ...
    }
}

Do you know how to reload the Serilog configuration during runtime, if I somehow the appsettings.json gets changed. => Live reload for the appsettings.json.

Thank you!!



Solution 1:[1]

You can change the loglevel using LoggingLevelSwitch. You can read about it here

You can use the IOptionsSnapshot<> interface to reload the configuration. You can read more about that here

Solution 2:[2]

You can use Serilog.Settings.Reloader

I don't have ASP example, but on console program you can do this:

        // Service collection
        IServiceCollection serviceCollection = new ServiceCollection()
            .AddLogging(loggingBuilder =>
                loggingBuilder
                    .AddSerilog(SwitchableLogger.Instance, true)
                    .AddSerilogConfigurationLoader(configuration, SwitchableLogger.Instance)
                );

        // Services
        using (var services = serviceCollection.BuildServiceProvider())
        {
            // Create logger
            Microsoft.Extensions.Logging.ILogger logger = services.GetService<Microsoft.Extensions.Logging.ILogger<Program>>();

            // Write
            logger.LogInformation("Hello World");

            // Modify config
            config.Set("Serilog:WriteTo:0:Args:OutputTemplate", "[{SourceContext}] {Message:lj}{NewLine}{Exception}");
            configuration.Reload();

            // Write with the previous logger instance, but with different settings
            logger.LogInformation("Hello world again");
        }

For clarification, the singleton "SwitchableLogger.Instance" is only for demonstration, a new instance "new SwitchableLogger()" can also be created.

Solution 3:[3]

The current Serilog implementation (2.9.0) is such that it is unable to fully reload settings. To work around this issue without introducing additional dependencies, avoid creating static loggers and follow the example provided here: https://github.com/akovac35/Logging/blob/v1.0.4/src/com.github.akovac35.Logging.Serilog/SerilogHelper.cs

public static void CreateLogger()
{
    CreateLogger(configure =>
    {
        configure.AddJsonFile("serilog.json", optional: false, reloadOnChange: true);
    });
}

public static void CreateLogger(Action<IConfigurationBuilder> configure)
{
    if (configure == null) throw new ArgumentNullException(nameof(configure));

    UpdateLogger(configure);
}

public static void UpdateLogger(Action<IConfigurationBuilder> configure)
{
    if (configure == null) throw new ArgumentNullException(nameof(configure));

    // The following pattern fires the reload token only once per settings change           
    var configurationBuilder = new ConfigurationBuilder();
    try
    {
        configure(configurationBuilder);
        IConfiguration configuration = configurationBuilder.Build();

        // Release previous callback - will be released only if this line is reached, allowing for another reload
        _changeCallback?.Dispose();
        _changeCallback = null;
        // .NET will not trigger a reload for invalid config file format, so reaching this line signals Json is OK
        _changeCallback = configuration.GetReloadToken().RegisterChangeCallback(state =>
        {
            UpdateLogger(configure);
        }, null);

        // Reading configuration will fail for invalid properties, that is why reload registration must happen
        // before this line or subsequent file updates may not be detected
        global::Serilog.ILogger newLogger = new LoggerConfiguration()
        .ReadFrom.Configuration(configuration)
        .Enrich.FromLogContext()
        .CreateLogger();

        Log.Logger = newLogger;

        GetLogger().Here(l => l.LogInformation("Updated logger: {@configuration}", configuration));
    }
    catch (Exception ex)
    {
        GetLogger().Here(l => l.LogError(ex, ex.Message));
    }
}

Or you can simply use the Logging.Serilog library which provides utility functions for just that.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2
Solution 3