Open nCubed opened 4 years ago
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.
Could be closed?
I'd like to expand a bit on this with a similar scenario that also causes some pain points, if I could. It may be appropriate as a new issue but they seem similar enough
If you define a MinimumLevel
section using a simple string
"MinimumLevel": "Information"
Then attempt to override that configuration in another JSON file (e.g. appsettings.Development.json
) in a different format
"MinimumLevel": {
"Default": "Debug"
}
The second value is ignored and the minimum level will be strictly "Information".
Ideally, the library could detect duplicate MinimumLevel
and MinimumLevel:Default
configurations and defer to the most recently defined option. But it would also be worthwhile to simply document cases like this where the override behavior is not intuitive (at least for some).
Worth noting also that the Overrides are still respected, it's just the Default
level that is the issue.
When using multiple appsettings.json files, the "WriteTo" section appears to a full replacement by the overriding appsetting json file.
Sample config / setup...
Program
appsettings.serilog.json
appsettings.serilog.Development.json
Current Behavior (when running under .net core development environment)
Maybe Expected Behavior?
WriteTo
sections should be addedSummary 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.