Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Change / override log event level in Serilog

Tags:

c#

.net

serilog

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. enter image description here

like image 806
Tamás Deme Avatar asked Oct 31 '17 13:10

Tamás Deme


2 Answers

It's possible, but not entirely straightforward, so strap yourself in!

1. Create a sink wrapper

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.

2. Hook the wrapper into the configuration syntax

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);
    }
}

3. Add the wrapper to the configuration

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();
like image 92
Nicholas Blumhardt Avatar answered Oct 25 '22 10:10

Nicholas Blumhardt


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?

like image 27
Klaas van Steeg Avatar answered Oct 25 '22 10:10

Klaas van Steeg