In Serilog you can do this separation via sub-loggers using filters or via Serilog.Sinks.Map, using context properties to decide which logger will include or exclude certain messages.
The example below defines that all log events will be written to both the Console and to a File, by default, however if the log event has a property called FileOnly
in the log context, it will not be written to the Console, and likewise if the log event has a property called ConsoleOnly
, it will not be written to the File.
using Serilog;
using Serilog.Context;
using Serilog.Filters;
// ...
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Verbose()
.Enrich.FromLogContext()
.WriteTo.Logger(c =>
c.Filter.ByExcluding(Matching.WithProperty("FileOnly"))
.WriteTo.Console())
.WriteTo.Logger(c =>
c.Filter.ByExcluding(Matching.WithProperty("ConsoleOnly"))
.WriteTo.File("C:\\Temp\\SerilogTests.txt"))
.CreateLogger();
// Writes to both Console & File
Log.Information("Hello, world! (Console and File)");
using (LogContext.PushProperty("ConsoleOnly", value: true))
{
// Writes only to the Console
Log.Information("Hello, world! (Console)");
}
using (LogContext.PushProperty("FileOnly", value: true))
{
// Writes only to the File
Log.Information("Hello, world! (File Only)");
}
Log.CloseAndFlush();
N.B.: Ideally you'll come up with better property names that are more generic, so that when you are writing logs within your application it doesn't have to know anything about "Console" or "File". e.g. You could have a property called Secret
or Classified
and decide where to write the logs based on the presence of this property or not.
There are different ways to add properties to a log event, including adding the property in the message template directly when you Log.Information
, etc., via LogContext.PushProperty
as in the example above, and vi Log.ForContext
.
You can see other examples of filtering, sub-loggers, and Serilog.Sinks.Map here: