Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Creating a serilog file sink later at runtime - how?

I use Serilog on the server side for all my .NETCore services using console, file and Graylog sinks. I also like to use it in my Windows fat clients (WPF applications).

With the latter I have a problem since I do not see, how I can add another file sink AFTER a user successfully logged in. I need an application log stored in the global AppData (Environment.SpecialFolder.CommonApplicationData) folder and another User-log in a user subfolder e.g. something like this:

var appName = System.Reflection.Assembly.GetExecutingAssembly().GetName().Name;
AppDataDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), appName);
Directory.CreateDirectory(AppDataDir);
AppLogDir = Path.Combine(AppDataDir, $@"Logs");
Directory.CreateDirectory(AppLogDir);

Log.Logger = new LoggerConfiguration()
    .ReadFrom.AppSettings()
    .Enrich.WithExceptionDetails()
    .Enrich.FromLogContext()
    .Enrich.WithProcessName()
    .Enrich.WithThreadId()
    .Enrich.WithAssemblyName()
    .WriteTo.Console(outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] [{SourceContext}] " +
                                     "ThreadId: [{ThreadId}] {Message:lj}{NewLine}{Exception}")
    .WriteTo.File(Path.Combine(AppLogDir, $"{appName}-.log"), rollOnFileSizeLimit: true,
        fileSizeLimitBytes: 1048576, retainedFileCountLimit: 30,
        outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] [{SourceContext}] " +
                        "[ThreadId: {ThreadId}] {Message:lj}{NewLine}{Exception}")
    .CreateLogger();

Then in my LoginViewModel after a successful login and long after the initial application logger was configured, I know the username and can create a folder for that user and put a logfile there:

var userLogDir = Path.Combine(AppDataDir, userName); // <-- userName comes from login token
Directory.CreateDirectory(userLogDir);

And now comes the tricky bit: How can I add a file sink to my existing logger configuration??? I need something like:

var loggerConfig = Log.GetLoggerConfiguration();  //<--- This is missing, at least I could not find it!
loggerConfig.WriteTo.File(Path.Combine(userLogDir, $"{appName}-{userName}-.log"), rollOnFileSizeLimit: true,
        fileSizeLimitBytes: 10485760, retainedFileCountLimit: 30,
        outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] [{SourceContext}] " +
                        "[ThreadId: {ThreadId}] {Message:lj}{NewLine}{Exception}")

Any idea how this can be accomplished with Serilog?

like image 391
ThommyB Avatar asked Aug 23 '18 06:08

ThommyB


Video Answer


1 Answers

I know this is old but figured I'd answer this since I just had to do something similar (changing the logger at runtime) in one of my projects.

Turns out there's a few ways of doing this, the main thing (at least in the first two) to do is to decouple the logger configuration from the logger itself. This allows you to store the configuration for later use if you want to simply just add on a sink later rather than starting all over.

Replace existing logger

This first method completely replaces the logger with a new one

var logfilepath = GetLogFilePath(Assembly.GetEntryAssembly());
var loggerConfig = new LoggerConfiguration()
  .WriteTo.RollingFile(logfilepath);
  
Log.Logger= loggerConfig.CreateLogger();
  
Log.Debug("It works!");


//Somewhere later in the program
Log.Debug("Setting up database with {ConnectionString}", connectionString);


loggerConfig = loggerConfig.WriteTo.MSSqlServer(
    connectionString: "Server=localhost;Database=LogDb;Integrated Security=SSPI;",
    sinkOptions: new MSSqlServerSinkOptions { TableName = "LogEvents" });

Log.Logger = loggerConfig.CreateLogger(); //Replace the old logger with the new one

Create new logger and send all events to the old logger as well

The next example shows us creating a new logger and then passing all events to the old logger as well, effectively allowing us to just add on. If you go this route it's important that you keep in mind the Logger's lifetime.

Log.Debug("Setting up database with {ConnectionString}", connectionString);

var loggerConfig = new LoggerConfiguration()
  .WriteTo.Sink((ILogEventSink)Log.Logger) //Here we're passing in the old logger
  .WriteTo.MSSqlServer(
    connectionString: "Server=localhost;Database=LogDb;Integrated Security=SSPI;",
    sinkOptions: new MSSqlServerSinkOptions { TableName = "LogEvents" });

Log.Logger = loggerConfig.CreateLogger();

Keeping up with the configuration

When setting the logger up this way we can easily just add the configuration to a DI container as such:

IServiceCollection

services.AddSingleton(loggerConfig);

Unity

container.RegisterInstance(loggerConfig);

This allows us to access our configuration later in the applications life and for the most part keep us from having to reconfigure all the sinks, enrichers, etc, again.

Bonus

As an added bonus, another thing I came across is that we can also load sinks from a dll at runtime as well (Serilog >=3.0). As outlined in this issue we can load configuration from assemblies at run-time by using a ConfigurationAssemblySource.

public static LoggerConfiguration Configuration(
    this LoggerSettingsConfiguration settingConfiguration,
    IConfiguration configuration,
    ConfigurationAssemblySource configurationAssemblySource)
{ ... }
like image 86
DCCoder Avatar answered Sep 19 '22 01:09

DCCoder