serilog-settings-configuration
serilog-settings-configuration copied to clipboard
Are "WriteTo" sections additive or replacement when using multiple appsettings json files?
When using multiple appsettings.json files, the "WriteTo" section appears to a full replacement by the overriding appsetting json file.
.Net Core 3.1
Serilog.AspNetCore Version="3.4.0"
Serilog.Enrichers.Environment Version="2.1.3"
Serilog.Enrichers.Process Version="2.0.1"
Serilog.Enrichers.Thread Version="3.1.0"
Serilog.Exceptions Version="5.6.0"
Serilog.Extensions.Logging Version="3.0.1"
Serilog.Settings.Configuration Version="3.1.0"
Serilog.Sinks.Async Version="1.4.0"
Serilog.Sinks.ColoredConsole Version="3.0.1"
Serilog.Sinks.Console Version="3.1.1"
Serilog.Sinks.File Version="4.1.0"
Sample config / setup...
Program
public static IHostBuilder CreateHostBuilder(string[] args)
{
return Host.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((hostContext, config) =>
{
string env = hostContext.HostingEnvironment.EnvironmentName;
config.AddJsonFile("appsettings.serilog.json", false, true)
.AddJsonFile($"appsettings.serilog.{env}.json", false, true);
})
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
})
.UseSerilog((hostContext, loggerConfig) =>
{
loggerConfig.ReadFrom.Configuration(hostContext.Configuration);
});
}
appsettings.serilog.json
{
"Serilog": {
"Using": [
"Serilog.Enrichers.Thread",
"Serilog.Exceptions",
"Serilog.Sinks.Async",
"Serilog.Sinks.Console",
"Serilog.Sinks.File"
],
"MinimumLevel": {
"Default": "Information",
"Override": {
"System": "Warning",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Warning",
"System.Net.Http": "Information"
}
},
"Enrich": [
"WithExceptionDetails",
"WithThreadId"
],
"WriteTo": [
{
"Name": "File",
"Args": {
"path": "App_Data/logs/log-.txt",
"rollingInterval": "Day",
"outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss.ffff} [{Level:u3}] [{ThreadId}] {Message:lj} {NewLine}{Exception}"
}
}
]
}
}
appsettings.serilog.Development.json
{
"Serilog": {
"MinimumLevel": {
"Default": "Verbose",
"Override": {
"Microsoft.Hosting.Lifetime": "Information"
}
},
"WriteTo": [
{
"Name": "Async",
"Args": {
"configure": [
{
"Name": "Console",
"Args": {
"restrictedToMinimumLevel": "Information",
"outputTemplate": "{Timestamp:HH:mm:ss} [{Level:u3}] [{ThreadId}] {Message:lj} {NewLine}{Exception}"
}
}
]
}
}
]
}
}
Current Behavior (when running under .net core development environment)
- The Default Minimum log level is updated from info to verbose by the Development json file (this is correct)
- The only sink being written to is the Console sink from the Development json file (not sure if correct)
- The file sink is ignored in the other json file (not sure if correct)
Maybe Expected Behavior?
- The sinks from each
WriteTo
sections should be added
Summary
I think the general question can be resolved with: How can we define a sink (WriteTo
) so that it is only defined in one appsetting file and is picked up by the overriding appsetting config json file? Or do we need to define duplicate sinks in each appsetting for each environment we want the sink to be included?
After writing this, I am about 99% sure this is expected behavior for the overriding appsetting json to overwrite the entire WriteTo
section.
I think I'm having a related issue.
I've built a helper assembly to apply the Serilog configuration the same way in all of our microservices. An extension method for the HostBuilder uses the ConfigureAppConfiguration
on the IHostBuilder
to add an embedded JSON stream to the configuration builder as well as optional file providers for specific Serilog overrides.
Assembly assembly = typeof(ConfigurationBuilderExtensions).Assembly;
configurationBuilder.AddJsonStream(assembly.GetManifestResourceStream($"{typeof(ConfigurationBuilderExtensions).Namespace}.serilog.configuration.web.json"));
Stream stream = assembly.GetManifestResourceStream($"{typeof(ConfigurationBuilderExtensions).Namespace}.serilog.configuration.web.{(Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? "Production").ToLowerInvariant()}.json");
if (stream != null)
{
configurationBuilder.AddJsonStream(stream);
}
return configurationBuilder
.AddJsonFile(Path.Combine(contentRootPath, "serilog.configuration.web.json"), optional: true, reloadOnChange: true)
.AddJsonFile(Path.Combine(contentRootPath, $"serilog.configuration.web.{Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? "Production"}.json"),
optional: true, reloadOnChange: true)
.AddEnvironmentVariables();
This should give me a hierarchical set of configuration where I can define in the embedded JSON a WriteTo
like this:
{
"Serilog": {
"Using": [ "Serilog.Sinks.Console" ],
"MinimumLevel": {
"Default": "Information",
"Override": {
"Microsoft": "Warning",
"System": "Warning"
}
},
"WriteTo": [
{
"Name": "Console"
"Args": {
"theme": "Serilog.Sinks.SystemConsole.Themes.AnsiConsoleTheme::Code, Serilog.Sinks.Console"
}
}
]
}
}
In my testing app I can then include a file serilog.configuration.web.json
that looks like:
{
"Serilog": {
"MinimumLevel": {
"Default": "Verbose"
},
"WriteTo": [
{
"Name": "Console",
"Args": {
"formatter": "Serilog.Formatting.Compact.RenderedCompactJsonFormatter, Serilog.Formatting.Compact"
}
}
]
}
}
Based on my understanding of how the configuration builder works this would give me an IConfiguration
that contains a single WriteTo
element with its Args.formatter
value set to "Serilog.Formatting.Compact.RenderedCompactJsonFormatter, Serilog.Formatting.Compact".
Testing this:
.UseSerilog((context, serviceProvider, loggerConfiguration) =>
{
var formatterVal = context.Configuration.GetValue<string>("Serilog:WriteTo:0:Args:formatter")
loggerConfiguration
.ReadFrom.Configuration(context.Configuration)
.Enrich.FromLogContext();
})
The formatterVal
is resolved to "Serilog.Formatting.Compact.RenderedCompactJsonFormatter, Serilog.Formatting.Compact"
This is expected.
However, the console formatter at runtime is the standard console logger with the "AnsiConsoleTheme" enabled.
I did some digging in the code and found the GetMethods
call. I plucked it into my code and gave it a reference to the WriteTo
configuration section. The result
value returned by the method has a single key "Console". The key's value has two keys, one for the formatter and one for the theme. It seems that the theme value is overriding the formatter.
I'm not sure if this is expected behavior, some oddity with how the configuration works, or something else.
Wild guess, but I think the problem might be that you are defining your sinks as an array in WriteTo
. An array in the JSON configuration will be translated to multiple properties using the the automatic index.
Therefore, if you have this
"WriteTo": [
{
"Name": "File",
"Args": {
"path": "App_Data/logs/log-.txt",
"rollingInterval": "Day",
"outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss.ffff} [{Level:u3}] [{ThreadId}] {Message:lj} {NewLine}{Exception}"
}
}
]
it would be equivalent as having this
"WriteTo:0":
{
"Name": "File",
"Args": {
"path": "App_Data/logs/log-.txt",
"rollingInterval": "Day",
"outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss.ffff} [{Level:u3}] [{ThreadId}] {Message:lj} {NewLine}{Exception}"
}
}
Then, in your *.development.json
you are defining the same property again, WriteTo:0
(since it is also an array with a single element) and this is replacing the one being written in the base appsetings.json.
You could try defining your WriteTo using names instead of the index as explained in the Readme and the sample.