Is there a way to change the log level of certain events dynamically? (maybe by namespace or a predicate)
I'm looking for something like .MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
but what I really want to do is change the level of Information
events coming from the Microsoft
namespace to Verbose
. Events of higher importance should be left as-is.
EDIT:
Enrichers can't change the log event level.
It's possible, but not entirely straightforward, so strap yourself in!
Instead of an enricher, you'll need to create a wrapper around the target sink. The wrapper will receive events from the logging pipeline, (fairly cheaply) create new events with identical properties, and forward them to the actual sink:
class LevelBoostingWrapper : ILogEventSink, IDisposable
{
readonly ILogEventSink _wrappedSink;
public LevelBoostingWrapper(ILogEventSink wrappedSink)
{
_wrappedSink = wrappedSink;
}
public void Emit(LogEvent logEvent)
{
if (logEvent.Level == LogEventLevel.Warning)
{
var boosted = new LogEvent(
logEvent.Timestamp,
LogEventLevel.Error, // <- the boost
logEvent.Exception,
logEvent.MessageTemplate,
logEvent.Properties
.Select(kvp => new LogEventProperty(kvp.Key, kvp.Value)));
_wrappedSink.Emit(boosted);
}
else
{
_wrappedSink.Emit(logEvent);
}
}
public void Dispose()
{
(_wrappedSink as IDisposable)?.Dispose();
}
}
The actual criterion for deciding which events to modify is up to you, of course.
This little extension makes it more pleasant to set up the wrapper:
static class LoggerSinkConfigurationExtensions
{
public static LoggerConfiguration Boosted(
this LoggerSinkConfiguration lsc,
Action<LoggerSinkConfiguration> writeTo)
{
return LoggerSinkConfiguration.Wrap(
lsc,
wrapped => new LevelBoostingWrapper(wrapped),
writeTo);
}
}
Finally, in the logger configuration, apply the wrapper:
Log.Logger = new LoggerConfiguration()
.WriteTo.Boosted(wt => wt.Console())
.CreateLogger();
Log.Information("This will be unchanged");
Log.Warning("This will be boosted to Error");
Log.CloseAndFlush();
This sink wrapping is great for default sinks. However I would like to configure serilog using a config file, but also wrap the configured sinks to modify specific calls to a lower loglevel.
This is the configuration of my sinks in appsetting.json
{
"Serilog": {
"MinimumLevel": {
"Default": "Verbose",
"Override": {
"Microsoft": "Warning",
"System": "Warning"
}
},
"WriteTo": [
{
"Name": "Console",
"Args": {
"outputTemplate": "===> {Timestamp:HH:mm:ss.fff} [{Level}] {Message}{NewLine}{Exception}"
}
},
{
"Name": "RollingFile",
"Args": {
"pathFormat": "c:\path\file.txt",
"outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] - {Message}{NewLine}{Exception}"
}
},
{
"Name": "DurableHttpUsingTimeRolledBuffers",
"Args": {
"requestUri": "https://[elastic]",
"bufferPathFormat": "c:\path\file.json"
}
}
Now what I do is create a logger from this config:
var configuration = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", false, true)
.Build();
var configLoggerConfig = new LoggerConfiguration().ReadFrom.Configuration(configuration);
var configLogger = configLoggerConfig.CreateLogger();
And then try to wrap it:
var wrappedLoggerconfig = new LoggerConfiguration().WriteTo.LogLevelModifyingSink(wt => wt.Sink(configLogger), modifiers, levelSwitch);
Log.Logger = wrappedLoggerconfig.CreateLogger();
The modifiers is a class that contains the logic for a specific event to be modified. The LogModifyingSink extentionmethod looks lik this:
public static LoggerConfiguration LogLevelModifyingSink(
this LoggerSinkConfiguration loggerConfiguration,
Action<LoggerSinkConfiguration> writeTo,
ILogLevelModifiers logLevelModifiers,
LoggingLevelSwitch levelSwitch)
{
return LoggerSinkConfiguration.Wrap(
loggerConfiguration,
wrapped => new LogLevelModifyingSink(wrapped, logLevelModifiers),
writeTo,
LogEventLevel.Verbose,
levelSwitch);
}
In which the LogLevelModifyingSink is the wrappersink that emits the modified log:
public class LogLevelModifyingSink : ILogEventSink, IDisposable
{
readonly ILogEventSink wrappedSink;
readonly IEnumerable<LogLevelModifier> logLevelModifiers;
public LogLevelModifyingSink(ILogEventSink wrappedSink, ILogLevelModifiers logLevelModifiers)
{
this.wrappedSink = wrappedSink;
this.logLevelModifiers = logLevelModifiers?.GetLevelModifiers();
}
public void Dispose()
{
(wrappedSink as IDisposable)?.Dispose();
}
public void Emit(LogEvent logEvent)
{
var message = logEvent.RenderMessage();
Console.WriteLine(DateTimeOffset.Now.ToString() + " " + message);
if (wrappedSink != null && logLevelModifiers != null && logLevelModifiers.Any())
{
foreach(var modifier in logLevelModifiers)
{
if (modifier.ShouldModify(logEvent))
{
wrappedSink.Emit(modifier.ModifyEvent(logEvent));
return;
}
}
}
wrappedSink.Emit(logEvent);
}
}
Now this works partly. The logmessages are handled by the wrapper for all 3 sinks, however, by creating a logger from a new configuration, the settings for minumumlevel and minimumlevel overrides are not conveyed from the config file and there seams no way to get these settings at runtime. I feel like this solution is not the way to go since I create a logger twice. So my question is, is there a better way to wrap sinks from configuration? Is it possible at all? And how can I preserve my configured settings?
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