Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What does "Using" in Serilog JSON configuration do?

What does actually Using do in Serilog JSON configuration (e.g. in AppSettings.json file in .Net Core environment)?

Let us take this configuration for example:

"Serilog": {
  "Using": [ "Serilog.Sinks.Console" ], <=======***HERE***=========
  "MinimumLevel": "Debug",
  "WriteTo": [
    { "Name": "Console" },
    {
      "Name": "RollingFile",
      "Args": {
        "pathFormat": "logs\\log-{Date}.txt",
        "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level}] {Message}{NewLine}{Exception}"
      }
    }
  ],
  "Enrich": [ "FromLogContext", "WithMachineName", "WithThreadId" ],
  "Properties": {
    "Application": "My Application"
  }
}

In the example above we used File sink WITHOUT adding it to Using attribute. However, everything seems to be working ok.

So I cannot understand exactly why we basically need this Using. Could someone explain it to me please?

like image 330
Mohammed Noureldin Avatar asked Dec 18 '22 19:12

Mohammed Noureldin


1 Answers

This is covered in the documentation for Serilog.Settings.Configuration:

(This package implements a convention using DependencyContext to find any package with Serilog anywhere in the name and pulls configuration methods from it, so the Using example above is redundant.)

This means that it's used to define which packages are used for locating Serilog sinks, but it's redundant when using the Serilog.Settings.Configuration package.


More Information

I've had a look at the Serilog source code in order to be able to provide more information on exactly what Using does and why it might be needed in the first place. I hope the following explanation is helpful.

Consider the following code-based setup:

Log.Logger = new LoggerConfiguration()
    .WriteTo.Console()
    .CreateLogger();

In this example, Console is an extension method for LoggerSinkConfiguration (and so it takes, as its first parameter, an instance of LoggerSinkConfiguration). When using this code-based approach, the code will only compile if this extension method can be found within a referenced assembly.

Next, consider the following approach, which uses the IConfiguration-based approach:

Log.Logger = new LoggerConfiguration()
    .ReadFrom.Configuration(someConfiguration)
    .CreateLogger();
{
    "Serilog": {
        "Using": ["Serilog.Sinks.Console"], // Redundant.
        "WriteTo": ["Console"]
    }
}

In this example, the compilation process has no knowledge of what the JSON string value "Console" refers to and so there's a need for a process that can go from the string "Console" to the Console() extension method mentioned above. In order to do that, Serilog needs to first find the extension method at runtime (which, in this example, lives in the Serilog.Sinks.Console assembly).

This finding process is done using reflection, which does a bit of assembly scanning to find public static methods that take as their first parameter a LoggerSinkConfiguration. The Using directive you've asked about in your question is a mechanism for helping determine exactly which assemblies should be scanned when looking for these extension methods.

As the documentation states, the IConfiguration-based approach uses DependencyContext in order to automatically scan assemblies that have Serilog in their name. Because Serilog.Sinks.Console does have Serilog in its name, there's no need to add this to the Using directive. You also have the option to provide your own DependencyContext instance when using this approach and so you might then need to be explicit about which assemblies to scan when looking for sinks.

like image 73
Kirk Larkin Avatar answered Dec 30 '22 10:12

Kirk Larkin