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?
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.
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
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();
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.
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)
{ ... }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With