Search code examples
c#.net-coreserilogmqttnet

Config and Serilog sink with parameterised factory method


The package Serilog.Settings.Configuration supports use of Microsoft ILogger configuration settings with Serilog.

    "Console": {
      "LogLevel": {
        "Default": "Debug"
      }
    },

maps to this (if I understand correctly)

.MinimumLevel.Override("Console", LogEventLevel.Debug)
.WriteTo.Console()

I wrote a custom sink for use with MQTT. The extension method that constructs a sink takes parameters. One isn't really expressible as a string.

.WriteTo.MqttSink(managedMqttClientObject, "name of application emitting logs")

I'd like to use Serilog.Settings.Configuration in tandem with my custom sink, but could use advice on how best to go about this.

Ideas so far

  • Code explicitly fishing for config values to determine whether an MqttSink is to be used and if so configure and apply it
  • Make the parameters static properties of the sink class, provide a parameterless factory method that uses the static properties, and marshal their values before the call to .ReadFrom.Configuration(config)

I could also do this

var loggerFactory = new LoggerConfiguration()
.ReadFrom.Configuration(config)
.WriteTo.MqttSink(managedMqttClient, "log source name");

but I'm not really sure what will happen when Serilog.Settings.Configuration fails to find a parameterless factory method MqttSink. If there's a better way than the static property approach, I would really appreciate your guidance.


Solution

  • That's not quite how the config works. You need a Serilog section. There's a sample in the readme file for the repository.

    Obviously your config file can't supply managedMqttClient but if you add a way to set defaults to the class that defines the factory method, you can make the parameters optional for the factory method. Inside the factory method you can fall back to the defaults, something like this.

        public static void SetDefaults(
          IManagedMqttClient managedMqttClient, 
          string eventSource = null)
        {
          __eventSource = eventSource;
          __managedMqttClient = managedMqttClient;
        }
    
    ...
    
        public static LoggerConfiguration MqttSink(
                  this LoggerSinkConfiguration loggerConfiguration,
                  IManagedMqttClient managedMqttClient = null,
                  string eventSource = null,
                  IFormatProvider formatProvider = null)
        {
          var sink = new MqttSink(
            formatProvider, managedMqttClient ?? __managedMqttClient, 
            eventSource ?? __eventSource);
          var config = loggerConfiguration.Sink(sink);
          return config;
        }
    

    This arrangement even lets you override the "default" event source string with the config file, since supplying it in the Serilog config will cause a parameter value to be supplied to the factory method.

      "Serilog": {
        "Using": [
          "Serilog.Sinks.Mqtt"
        ],
        "MinimumLevel": "Information",
        "WriteTo": [
          {
            "Name": "MqttSink",
            "Args": {
              "eventSource": "test-client"
            }
          }
        ]
      }
    

    Assuming the above config, your client code could look like this. I can omit the event source from defaults because it's defined in the config.

    MqttSink.SetDefaults(managedMqttClient);
    var loggerFactory = new LoggerConfiguration().ReadFrom.Configuration(config);
    

    These changes maintain the original call signature, so they are non-breaking for any existing code.