-
Notifications
You must be signed in to change notification settings - Fork 131
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add support for IFormatProvider used to convert string to other types #348
Conversation
26921f9
to
24e0a23
Compare
src/Serilog.Settings.Configuration/Settings/Configuration/ConfigurationContext.cs
Outdated
Show resolved
Hide resolved
internal Assembly[] Assemblies { get; } | ||
internal DependencyContext DependencyContext { get; } | ||
internal ConfigurationAssemblySource? ConfigurationAssemblySource { get; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are they mutually exclusive?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes. They influence how the assemblies that contains sinks and other types are discovered. At least one must be specified. There are three matching ConfigurationContext
constructors and then the configuration reader is created depending on how the context was created:
var configurationReader = context switch
{
{ ConfigurationAssemblySource: {} } => GetConfigurationReader(configuration, context, context.ConfigurationAssemblySource.Value),
{ Assemblies: {} } => GetConfigurationReader(configuration, context, context.Assemblies),
_ => GetConfigurationReader(configuration, context ?? new ConfigurationContext(), context?.DependencyContext),
};
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks great; had some thoughts on naming and defaults
README.md
Outdated
@@ -65,8 +65,9 @@ Root section name can be changed: | |||
``` | |||
|
|||
```csharp | |||
var context = new ConfigurationContext { SectionName = "CustomSection" }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
new ConfigurationReaderOptions()
? The "context" suffix gets a bit overloaded, especially when several of the members are just regular old options.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I must admit that I anticipated on my next pull request which will address #206. I plan to add a new property to get access to the log level switches on that new object:
public IReadOnlyDictionary<string, LoggingLevelSwitch> LogLevelSwitches { get; }
I already have a LogLevelSwitches branch on top of this FormatProvider
branch if you want to have a look.
At that point, that ConfigurationReaderOptions
would become «bidirectional», i.e. properties to tweak the reader behavior + property to access things (log level switches for now) created after the configuration is read. So naming it context might be more appropriate I think. Maybe ConfigurationReaderContext
would be a better choice?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the reply! I think even in the "bidirectional" case, it would be more common to see callbacks on the options type, e.g.:
new ConfigurationReaderOptions
{
OnAddedLevelSwitch = levelSwitch => _mySwitches.Add(levelSwitch)
}
This would save us from needing to worry about the lifetime and thread-safety of other things attached to the options type, or having to explain that the type is bidirectional (and flag which members are "in" vs "out").
(Having named a few things *Context
over the years, I've just about always regretted it one way or the other :-) ... for example, I'd love to go back and rename LogContext
to the much more descriptive LoggingScope
, or ForContext()
to WithTag()
or just about any other name ... context is used in so many ways that it's hard to guess its precise intention when you come across one.)
What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very good point! I went with ConfigurationReaderOptions
as you suggested and force-pushed fbc5e72 on top of the dev
branch. I'll adapt my LogLevelSwitches branch with your OnAddedLevelSwitch
callback proposal and I'll open a new pull request to address #206 once this one is merged.
I also adapted the README to mention since version 4.0.0 instead of since version 3.5.0 as I initially did before the rebase.
Side note: how would you name the HttpContext class? 😂
README.md
Outdated
@@ -282,6 +285,8 @@ Some Serilog packages require a reference to a logger configuration object. The | |||
|
|||
When the configuration specifies a discrete value for a parameter (such as a string literal), the package will attempt to convert that value to the target method's declared CLR type of the parameter. Additional explicit handling is provided for parsing strings to `Uri`, `TimeSpan`, `enum`, arrays and custom collections. | |||
|
|||
Since version 3.5.0, conversion will use the invariant culture (`CultureInfo.InvariantCulture`) as long as the `ReadFrom.Configuration(IConfiguration configuration, ConfigurationContext context)` method is used. Obsolete methods use the current culture to preserve backward compatibility. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A lot of people will still want to write ReadFrom.Configuration(config)
and skip the redundant extra argument. A breaking change might be fair, here, given that the source for most settings coming from configuration will be JSON, where anything other than the invariant culture is probably just harboring bugs.
What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, the newly introduced method already supports calling ReadFrom.Configuration(config)
without an extra argument since the context
parameter defaults to null
:
public static LoggerConfiguration Configuration(
this LoggerSettingsConfiguration settingConfiguration,
IConfiguration configuration,
ConfigurationContext context = null)
By the way, I had to remove the default = null
in the overload taking a DependencyContext
. That's what I tried to explain in my original message but was maybe not very clear.
public static LoggerConfiguration Configuration(
this LoggerSettingsConfiguration settingConfiguration,
IConfiguration configuration,
DependencyContext dependencyContext)
So people just using ReadFrom.Configuration(config)
will opt in the new behaviour (invariant culture) by updating to Serilog.Settings.Configuration
3.5.0 and recompiling their app. The only downside is that they will probably not even realise they opt in the new behaviour.
And people using ReadFrom.Configuration(config, … extra argument …)
will get an obsolete warning and will be forced to have a look at the new overload and think about how they want to create the new ConfigurationContext
object.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, thanks - I missed that. I'm trying to get a few minutes spare again to catch up with this, I'll try to loop back later today :-)
src/Serilog.Settings.Configuration/ConfigurationLoggerConfigurationExtensions.cs
Outdated
Show resolved
Hide resolved
Also introduce a new `ConfigurationReaderOptions` class to avoid `ReadFrom.Configuration()` methods exponential growth when adding new options. All _older_ `Configuration()` methods go through the newly introduced `Configuration(LoggerSettingsConfiguration, IConfiguration, ConfigurationReaderOptions)` method that takes an `ConfigurationReaderOptions` instance. Older methods explicitly set the `FormatProvider` option to `null` in order to preserve backward compatibility. By using the new `Configuration()` method, users opt into the new default of having the invariant culture as the format provider. Note: the `= null` default value in the `Configuration()` method taking a `DependencyContext` has been removed in order to make sure the CS0121 compilation does not occur: > [CS0121] The call is ambiguous between the following methods or properties: 'ConfigurationLoggerConfigurationExtensions.Configuration(LoggerSettingsConfiguration, IConfiguration, DependencyContext)' and 'ConfigurationLoggerConfigurationExtensions.Configuration(LoggerSettingsConfiguration, IConfiguration, ConfigurationReaderOptions)' Fixes serilog#325
Awesome PR 👍 |
Also introduce a new
ConfigurationReaderOptions
class to avoidReadFrom.Configuration()
methods exponential growth when adding new options.All older
Configuration()
methods go through the newly introducedConfiguration(LoggerSettingsConfiguration, IConfiguration, ConfigurationReaderOptions)
method that takes anConfigurationReaderOptions
instance.Older methods explicitly set the
FormatProvider
option tonull
in order to preserve backward compatibility.By using the new
Configuration()
method, users opt into the new default of having the invariant culture as the format provider.Note: the
= null
default value in theConfiguration()
method taking aDependencyContext
has been removed in order to make sure the CS0121 compilation does not occur:Fixes #325