-
Notifications
You must be signed in to change notification settings - Fork 129
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
Populate IConfiguration or IConfigurationSection parameter #97
Comments
@MV10 do you have an example config (maybe from Azure Functions) that illustrates the problem? Also for the purposes of some nice test cases it would be nice to see the variations you need to cater to. |
Fortunately the change is simple. The code is already done in my fork, though I haven't managed to make heads or tails of the unit tests. My need arises from the problem described at the end of the linked thread. I don't know if applies to any other sink, but the MS SQL sink allows you to create arbitrary additional columns by adding a separate configuration section listing the column names and data types. Edit: Just noticed somebody posted an issue about this specific problem: Technically Azure Functions V2 doesn't support this kind of config in the Functions code, the dependency is in the underlying runtime (which gets the .NET runtime spun up long before our own Functions code is ever loaded), but to use things like Serilog many of us build our own little support libraries which will pull in Structurally as {
"Serilog": {
"Using": ["Serilog.Sinks.MSSqlServer"],
"MinimumLevel": "Debug",
"WriteTo": [
{ "Name": "MSSqlServer",
"Args": {
"connectionString": "Server...",
"tableName": "Logs"
}
}
]
},
"MSSqlServerSink": {
"Columns": [
{
"ColumnName": "Hostname",
"DataType": "varchar"
},
{
"ColumnName": "IP",
"DataType": "varchar"
}
]
}
} |
I'm trying to puzzle my way through the tests... I wish there were comments, and I'm not sure how much sense it makes for the tests to be this much more complex than the code being tested! But I noticed the
The README doesn't document this "reference, assembly" syntax, but it has me wondering, if I could divine the correct syntax, could that populate an (If this package would have to change to make that work, I think I still prefer my proposal -- less ceremony for Serilog users -- in my version, It Just Works ™ ). |
Interesting idea! Since the configuration is per-sink-instance, is there some scheme that looks more like: {
"Serilog": {
"Using": ["Serilog.Sinks.MSSqlServer"],
"MinimumLevel": "Debug",
"WriteTo": [
{ "Name": "MSSqlServer",
"Args": {
"connectionString": "Server...",
"tableName": "Logs",
"config": {
"Columns": [
{
"ColumnName": "Hostname",
"DataType": "varchar"
},
{
"ColumnName": "IP",
"DataType": "varchar"
}
]
}
}
}
]
}
} I.e., allow the config to be injected in just like any other sink argument. This is just a sketch, but in the above, Regarding the design/code/tests, this library suffers from having been produced via an evolutionary process, beginning way back at the .NET Core pre-1.0 days; it'd be great to take a fresh look at it at some point - there are some syntactic improvements possible as well. The |
@nblumhardt I had similar thoughts, but I'm leery of breaking existing code by changing the config format, and (if I understood the comments) currently array parameters aren't supported. |
Hi @MV10 - not sure I follow - which array are you referring to? Regarding breakage, since this package and MSSQL config support aren't currently compatible, won't this be additive only? (Apologies if I'm missing the point here - early Saturday morning :-)) |
Edit: Disregard the array comment... Now that I've had lots of coffee (my turn at Saturday!) I see what you're saying about an The more I think about where to put the config, the more I like keeping it in the I'll leave the following post as-is ... the change you're suggesting is more complicated but I think it may be better in the long run. I'll have to experiment more to see what works.
|
The change to support this is super-simple -- the important part is just four lines of code in Pretty good bang-for-the-buck if you ask me. void CallConfigurationMethods(ILookup<string, Dictionary<string, IConfigurationArgumentValue>> methods, IList<MethodInfo> configurationMethods, object receiver, IReadOnlyDictionary<string, LoggingLevelSwitch> declaredLevelSwitches)
{
foreach (var method in methods.SelectMany(g => g.Select(x => new { g.Key, Value = x })))
{
var methodInfo = SelectConfigurationMethod(configurationMethods, method.Key, method.Value);
if (methodInfo != null)
{
var call = (from p in methodInfo.GetParameters().Skip(1)
let directive = method.Value.FirstOrDefault(s => s.Key == p.Name)
select directive.Key == null ? p.DefaultValue : directive.Value.ConvertTo(p.ParameterType, declaredLevelSwitches)).ToList();
// ****************************************************************
// add these
if(_configuration != null) ReplaceValueByType(_configuration, methodInfo, call);
if(_configSection != null) ReplaceValueByType(_configSection, methodInfo, call);
call.Insert(0, receiver);
methodInfo.Invoke(null, call.ToArray());
}
}
// ****************************************************************
// add this
void ReplaceValueByType<T>(T value, MethodInfo methodInfo, List<object> parameterList)
{
var parm = methodInfo.GetParameters().FirstOrDefault(i => i.ParameterType is T);
if(parm != null) parameterList[parm.Position] = value;
}
} |
(Skipping parent post and commenting on grandparent :-)) Sounds like a plan 👍 - yes, one of the benefits of this sink over the current XML/key-value-pair configuration mechanism is that it cleanly handles multiple instances of the same sink, so going the extra distance to preserve that seems worthwhile. Shouldn't be a hugely different change to make; keen to see how it comes out! Cheers, |
I'm also fixing several places where This var filterDirective = _configuration.GetSection("Filter");
if (filterDirective != null) The fix is: var filterDirective = _configuration.GetSection("Filter");
if (filterDirective.GetChildren().Any()) |
I hate to say it, but after pondering this more, I'm back to arguing in favor of my original proposal to automatically and transparently populate references to the entire One thing I 'd change from my original proposal is to not provide a way to "inject" the One very common scenario where this is important is the use of named connection strings. The SQL sink can accept a literal connection string or the name of a connection string. The named version is expected to be found in a separate Furthermore, in Azure, connection strings and other settings are commonly deployed as environment variables alongside XML or JSON files, not to mention more exotic options like Key Vault settings. This means we shouldn't make any assumptions about the source-format of the configuration information. This alludes to another problem with the idea we discussed about putting custom sections into the However, It gets worse. This package allows nested configuration sections. There is no way for this block to decide whether a given section should be recursively processed as a nested configuration or be processed or otherwise stored to be passed to a parameter on the invocation target. (This is also the reason arrays don't work, or parameters like With all that being said, I still agree as much configuration as possible should live within the Looking forward to thoughts on this and hopefully getting it wrapped up! |
I have a proposed solution (tested, working) for the nested configuration issue. I figure it's more likely that sinks or other config targets will need complex objects as parameters versus the frequency that nested configurations are needed. Thinking back to the "WriteTo:Sublogger": {
"Name": "Logger",
"Args": {
"configureLogger>": {
"WriteTo": [
{
"Name": "Console",
"Args": {
"outputTemplate": "[{Timestamp:HH:mm:ss} {SourceContext} [{Level}] {Message}{NewLine}{Exception}",
"theme": "Serilog.Sinks.SystemConsole.Themes.SystemConsoleTheme::Grayscale, Serilog.Sinks.Console"
}
}
]
},
"restrictedToMinimumLevel": "Debug"
}
}, The hint is just an Next, I created a This still wouldn't work to populate an But if everyone can live with hinting for nested configuration, it opens up this package to populating all sorts of complex parameters -- anything the config option binder extension can handle. I already have it working with a |
Changes are completed to populate an https://github.com/MV10/serilog-settings-configuration/tree/iconfig_parameter I also added a couple entries to the end of the README, they start here: Once I can PR this one, my dependent SQL sink changes are ready to PR, too. |
I realized with just one additional line of code, that new The ability to grab a whole section is handy in the case of very complex configuration (again, the SQL sink is a good example, the PR coming later today, last chance to object / comment / discuss... 3... 2... 1... |
Proposal
The package will automatically populate any
IConfiguration
orIConfigurationSection
parameter on the target method.The Issue
Any library being initialized by this package can only receive literals defined in the
WriteTo
entries. If the target supports more complex configuration thanWriteTo
provides (for example, currently arrays are not supported), the target has no means to access any configuration data when the target method is executed.(This was not an issue under the .NET Framework in packages such as
Serilog.Settings.AppSettings
because the oldConfigurationManager
approach is a static class, which means it is always available.Microsoft.Extensions.Configuration
has no equivalent facility, it is the responsibility of the referencing library or application to provide access.)In theory this issue could be avoided by simply not using this package at all, but I argue this limitation won't be obvious to Serilog users. For example, the MS SQL sink can add custom columns via a separate configuration section, so using this package is mutually exclusive with that feature. Each target package could explain any such limitations, but it seems easier in the long run to just provide a means to fix the problem in a way that is transparent to Serilog users.
If we make this package "smart" enough to populate
IConfiguration
orIConfigurationSection
then, as dependent Serilog packages are updated to fullMicrosoft.Extensions.Configuration
support, the problem basically fixes itself. (Probably the README for this package should provide advice to preferIConfiguration
overIConfigurationSection
but that isolates RTFM caveats to the docs in this single repo.)Implementation
Currently, the log config extension accepts either
IConfiguration
orIConfigurationSection
, butIConfiguration
simply retrieves the Serilog section and calls the section-based extension method. The section is passed intoConfigurationReader
where all of the main processing occurs when the main Serilog package calls theILoggerSettings.Configure
method.I propose to create two ctor overloads which also accept
IConfiguration
. They will extract the Serilog section and pass it along to the existing ctors. All ctors will store a local reference toIConfiguration
(if available) andIConfigurationSection
.Finally,
CallConfigurationMethods
would be modified to check eachMethodInfo
for anIConfigurationSection
parameter and anIConfiguration
parameter (when available), then add these to thecall
list before invoking the method.A pair of related tests should also be added, and possibly the sample could be updated.
"All ya gotta do is..."
And yes, I'm proposing to do the work myself, although any Reflection Ninjas in the house are more than welcome to handle it (I'm a little rusty on the specifics).
The text was updated successfully, but these errors were encountered: