Skip to content

Commit 0fc8b57

Browse files
authored
Merge pull request serilog#348 from 0xced/FormatProvider
Add support for IFormatProvider used to convert string to other types
2 parents ab5d7a4 + fbc5e72 commit 0fc8b57

12 files changed

+213
-34
lines changed

README.md

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,9 @@ Root section name can be changed:
6565
```
6666

6767
```csharp
68+
var options = new ConfigurationReaderOptions { SectionName = "CustomSection" };
6869
var logger = new LoggerConfiguration()
69-
.ReadFrom.Configuration(configuration, sectionName: "CustomSection")
70+
.ReadFrom.Configuration(configuration, options)
7071
.CreateLogger();
7172
```
7273

@@ -106,8 +107,9 @@ In case of [non-standard](#azure-functions-v2-v3) dependency management you can
106107
```csharp
107108
var functionDependencyContext = DependencyContext.Load(typeof(Startup).Assembly);
108109

110+
var options = new ConfigurationReaderOptions(functionDependencyContext) { SectionName = "AzureFunctionsJobHost:Serilog" };
109111
var logger = new LoggerConfiguration()
110-
.ReadFrom.Configuration(hostConfig, sectionName: "AzureFunctionsJobHost:Serilog", dependencyContext: functionDependencyContext)
112+
.ReadFrom.Configuration(hostConfig, options)
111113
.CreateLogger();
112114
```
113115

@@ -119,8 +121,9 @@ var configurationAssemblies = new[]
119121
typeof(ConsoleLoggerConfigurationExtensions).Assembly,
120122
typeof(FileLoggerConfigurationExtensions).Assembly,
121123
};
124+
var options = new ConfigurationReaderOptions(configurationAssemblies);
122125
var logger = new LoggerConfiguration()
123-
.ReadFrom.Configuration(configuration, configurationAssemblies)
126+
.ReadFrom.Configuration(configuration, options)
124127
.CreateLogger();
125128
```
126129

@@ -282,6 +285,8 @@ Some Serilog packages require a reference to a logger configuration object. The
282285

283286
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.
284287

288+
Since version 4.0.0, conversion will use the invariant culture (`CultureInfo.InvariantCulture`) as long as the `ReadFrom.Configuration(IConfiguration configuration, ConfigurationReaderOptions options)` method is used. Obsolete methods use the current culture to preserve backward compatibility.
289+
285290
### Static member support
286291

287292
Static member access can be used for passing to the configuration argument via [special](https://github.com/serilog/serilog-settings-configuration/blob/dev/test/Serilog.Settings.Configuration.Tests/StringArgumentValueTests.cs#L35) syntax:
@@ -377,8 +382,9 @@ public class Startup : FunctionsStartup
377382
var functionDependencyContext = DependencyContext.Load(typeof(Startup).Assembly);
378383

379384
var hostConfig = sp.GetRequiredService<IConfiguration>();
385+
var options = new ConfigurationReaderOptions(functionDependencyContext) { SectionName = "AzureFunctionsJobHost:Serilog" };
380386
var logger = new LoggerConfiguration()
381-
.ReadFrom.Configuration(hostConfig, sectionName: "AzureFunctionsJobHost:Serilog", dependencyContext: functionDependencyContext)
387+
.ReadFrom.Configuration(hostConfig, options)
382388
.CreateLogger();
383389

384390
return new SerilogLoggerProvider(logger, dispose: true);

src/Serilog.Settings.Configuration/ConfigurationLoggerConfigurationExtensions.cs

Lines changed: 58 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright 2013-2016 Serilog Contributors
1+
// Copyright 2013-2016 Serilog Contributors
22
//
33
// Licensed under the Apache License, Version 2.0 (the "License");
44
// you may not use this file except in compliance with the License.
@@ -42,6 +42,7 @@ public static class ConfigurationLoggerConfigurationExtensions
4242
/// <param name="dependencyContext">The dependency context from which sink/enricher packages can be located. If not supplied, the platform
4343
/// default will be used.</param>
4444
/// <returns>An object allowing configuration to continue.</returns>
45+
[Obsolete("Use ReadFrom.Configuration(IConfiguration configuration, ConfigurationReaderOptions readerOptions) instead.")]
4546
public static LoggerConfiguration Configuration(
4647
this LoggerSettingsConfiguration settingConfiguration,
4748
IConfiguration configuration,
@@ -52,15 +53,8 @@ public static LoggerConfiguration Configuration(
5253
if (configuration == null) throw new ArgumentNullException(nameof(configuration));
5354
if (sectionName == null) throw new ArgumentNullException(nameof(sectionName));
5455

55-
var assemblyFinder = dependencyContext == null
56-
? AssemblyFinder.Auto()
57-
: AssemblyFinder.ForDependencyContext(dependencyContext);
58-
59-
return settingConfiguration.Settings(
60-
new ConfigurationReader(
61-
configuration.GetSection(sectionName),
62-
assemblyFinder,
63-
configuration));
56+
var readerOptions = new ConfigurationReaderOptions(dependencyContext) { SectionName = sectionName, FormatProvider = null };
57+
return Configuration(settingConfiguration, configuration, readerOptions);
6458
}
6559

6660
/// <summary>
@@ -73,10 +67,11 @@ public static LoggerConfiguration Configuration(
7367
/// <param name="dependencyContext">The dependency context from which sink/enricher packages can be located. If not supplied, the platform
7468
/// default will be used.</param>
7569
/// <returns>An object allowing configuration to continue.</returns>
70+
[Obsolete("Use ReadFrom.Configuration(IConfiguration configuration, ConfigurationReaderOptions readerOptions) instead.")]
7671
public static LoggerConfiguration Configuration(
7772
this LoggerSettingsConfiguration settingConfiguration,
7873
IConfiguration configuration,
79-
DependencyContext dependencyContext = null)
74+
DependencyContext dependencyContext)
8075
=> Configuration(settingConfiguration, configuration, DefaultSectionName, dependencyContext);
8176

8277
/// <summary>
@@ -105,7 +100,8 @@ public static LoggerConfiguration ConfigurationSection(
105100
new ConfigurationReader(
106101
configSection,
107102
assemblyFinder,
108-
configuration: null));
103+
configuration: null,
104+
formatProvider: null));
109105
}
110106

111107
/// <summary>
@@ -118,6 +114,7 @@ public static LoggerConfiguration ConfigurationSection(
118114
/// <param name="sectionName">A section name for section which contains a Serilog section.</param>
119115
/// <param name="configurationAssemblySource">Defines how the package identifies assemblies to scan for sinks and other types.</param>
120116
/// <returns>An object allowing configuration to continue.</returns>
117+
[Obsolete("Use ReadFrom.Configuration(IConfiguration configuration, ConfigurationReaderOptions readerOptions) instead.")]
121118
public static LoggerConfiguration Configuration(
122119
this LoggerSettingsConfiguration settingConfiguration,
123120
IConfiguration configuration,
@@ -128,9 +125,8 @@ public static LoggerConfiguration Configuration(
128125
if (configuration == null) throw new ArgumentNullException(nameof(configuration));
129126
if (sectionName == null) throw new ArgumentNullException(nameof(sectionName));
130127

131-
var assemblyFinder = AssemblyFinder.ForSource(configurationAssemblySource);
132-
133-
return settingConfiguration.Settings(new ConfigurationReader(configuration.GetSection(sectionName), assemblyFinder, configuration));
128+
var readerOptions = new ConfigurationReaderOptions(configurationAssemblySource) { SectionName = sectionName, FormatProvider = null };
129+
return Configuration(settingConfiguration, configuration, readerOptions);
134130
}
135131

136132
/// <summary>
@@ -142,6 +138,7 @@ public static LoggerConfiguration Configuration(
142138
/// <param name="configuration">A configuration object which contains a Serilog section.</param>
143139
/// <param name="configurationAssemblySource">Defines how the package identifies assemblies to scan for sinks and other types.</param>
144140
/// <returns>An object allowing configuration to continue.</returns>
141+
[Obsolete("Use ReadFrom.Configuration(IConfiguration configuration, ConfigurationReaderOptions readerOptions) instead.")]
145142
public static LoggerConfiguration Configuration(
146143
this LoggerSettingsConfiguration settingConfiguration,
147144
IConfiguration configuration,
@@ -167,7 +164,7 @@ public static LoggerConfiguration ConfigurationSection(
167164

168165
var assemblyFinder = AssemblyFinder.ForSource(configurationAssemblySource);
169166

170-
return settingConfiguration.Settings(new ConfigurationReader(configSection, assemblyFinder, configuration: null));
167+
return settingConfiguration.Settings(new ConfigurationReader(configSection, assemblyFinder, configuration: null, formatProvider: null));
171168
}
172169

173170
/// <summary>
@@ -178,6 +175,7 @@ public static LoggerConfiguration ConfigurationSection(
178175
/// <param name="sectionName">A section name for section which contains a Serilog section.</param>
179176
/// <param name="assemblies">A collection of assemblies that contains sinks and other types.</param>
180177
/// <returns>An object allowing configuration to continue.</returns>
178+
[Obsolete("Use ReadFrom.Configuration(IConfiguration configuration, ConfigurationReaderOptions readerOptions) instead.")]
181179
public static LoggerConfiguration Configuration(
182180
this LoggerSettingsConfiguration settingConfiguration,
183181
IConfiguration configuration,
@@ -188,7 +186,8 @@ public static LoggerConfiguration Configuration(
188186
if (configuration == null) throw new ArgumentNullException(nameof(configuration));
189187
if (sectionName == null) throw new ArgumentNullException(nameof(sectionName));
190188

191-
return settingConfiguration.Settings(new ConfigurationReader(configuration.GetSection(sectionName), assemblies, new ResolutionContext(configuration)));
189+
var readerOptions = new ConfigurationReaderOptions(assemblies) { SectionName = sectionName, FormatProvider = null };
190+
return Configuration(settingConfiguration, configuration, readerOptions);
192191
}
193192

194193
/// <summary>
@@ -198,9 +197,51 @@ public static LoggerConfiguration Configuration(
198197
/// <param name="configuration">A configuration object which contains a Serilog section.</param>
199198
/// <param name="assemblies">A collection of assemblies that contains sinks and other types.</param>
200199
/// <returns>An object allowing configuration to continue.</returns>
200+
[Obsolete("Use ReadFrom.Configuration(IConfiguration configuration, ConfigurationReaderOptions readerOptions) instead.")]
201201
public static LoggerConfiguration Configuration(
202202
this LoggerSettingsConfiguration settingConfiguration,
203203
IConfiguration configuration,
204204
params Assembly[] assemblies)
205205
=> Configuration(settingConfiguration, configuration, DefaultSectionName, assemblies);
206+
207+
/// <summary>
208+
/// Reads logger settings from the provided configuration object using the specified context.
209+
/// </summary>
210+
/// <param name="settingConfiguration">Logger setting configuration.</param>
211+
/// <param name="configuration">A configuration object which contains a Serilog section.</param>
212+
/// <param name="readerOptions">Options to adjust how the configuration object is processed.</param>
213+
/// <returns>An object allowing configuration to continue.</returns>
214+
public static LoggerConfiguration Configuration(
215+
this LoggerSettingsConfiguration settingConfiguration,
216+
IConfiguration configuration,
217+
ConfigurationReaderOptions readerOptions = null)
218+
{
219+
var configurationReader = readerOptions switch
220+
{
221+
{ ConfigurationAssemblySource: {} } => GetConfigurationReader(configuration, readerOptions, readerOptions.ConfigurationAssemblySource.Value),
222+
{ Assemblies: {} } => GetConfigurationReader(configuration, readerOptions, readerOptions.Assemblies),
223+
_ => GetConfigurationReader(configuration, readerOptions ?? new ConfigurationReaderOptions(), readerOptions?.DependencyContext),
224+
};
225+
return settingConfiguration.Settings(configurationReader);
226+
}
227+
228+
static ConfigurationReader GetConfigurationReader(IConfiguration configuration, ConfigurationReaderOptions readerOptions, DependencyContext dependencyContext)
229+
{
230+
var assemblyFinder = dependencyContext == null ? AssemblyFinder.Auto() : AssemblyFinder.ForDependencyContext(dependencyContext);
231+
var section = configuration.GetSection(readerOptions.SectionName);
232+
return new ConfigurationReader(section, assemblyFinder, readerOptions.FormatProvider, configuration);
233+
}
234+
235+
static ConfigurationReader GetConfigurationReader(IConfiguration configuration, ConfigurationReaderOptions readerOptions, ConfigurationAssemblySource source)
236+
{
237+
var assemblyFinder = AssemblyFinder.ForSource(source);
238+
var section = configuration.GetSection(readerOptions.SectionName);
239+
return new ConfigurationReader(section, assemblyFinder, readerOptions.FormatProvider, configuration);
240+
}
241+
242+
static ConfigurationReader GetConfigurationReader(IConfiguration configuration, ConfigurationReaderOptions readerOptions, IReadOnlyCollection<Assembly> assemblies)
243+
{
244+
var section = configuration.GetSection(readerOptions.SectionName);
245+
return new ConfigurationReader(section, assemblies, new ResolutionContext(configuration, readerOptions.FormatProvider));
246+
}
206247
}

src/Serilog.Settings.Configuration/Serilog.Settings.Configuration.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />
2929
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="6.0.0" />
3030
<PackageReference Include="Microsoft.Extensions.DependencyModel" Version="6.0.0" />
31+
<PackageReference Include="PolySharp" Version="1.12.1" PrivateAssets="All" />
3132
<PackageReference Include="Serilog" Version="2.10.0" />
3233
<None Include="..\..\assets\icon.png" Pack="true" PackagePath="" Visible="false" />
3334
</ItemGroup>

src/Serilog.Settings.Configuration/Settings/Configuration/ConfigurationReader.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,11 @@ class ConfigurationReader : IConfigurationReader
2222
readonly ResolutionContext _resolutionContext;
2323
readonly IConfigurationRoot _configurationRoot;
2424

25-
public ConfigurationReader(IConfigurationSection configSection, AssemblyFinder assemblyFinder, IConfiguration configuration = null)
25+
public ConfigurationReader(IConfigurationSection configSection, AssemblyFinder assemblyFinder, IFormatProvider formatProvider, IConfiguration configuration = null)
2626
{
2727
_section = configSection ?? throw new ArgumentNullException(nameof(configSection));
2828
_configurationAssemblies = LoadConfigurationAssemblies(_section, assemblyFinder);
29-
_resolutionContext = new ResolutionContext(configuration);
29+
_resolutionContext = new ResolutionContext(configuration, formatProvider);
3030
_configurationRoot = configuration as IConfigurationRoot;
3131
}
3232

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
using System.Globalization;
2+
using System.Reflection;
3+
using Microsoft.Extensions.DependencyModel;
4+
5+
namespace Serilog.Settings.Configuration;
6+
7+
/// <summary>
8+
/// Options to adjust how the configuration object is processed.
9+
/// </summary>
10+
public sealed class ConfigurationReaderOptions
11+
{
12+
/// <summary>
13+
/// Initialize a new instance of the <see cref="ConfigurationReaderOptions"/> class.
14+
/// </summary>
15+
/// <param name="assemblies">A collection of assemblies that contains sinks and other types.</param>
16+
/// <exception cref="ArgumentNullException">The <paramref name="assemblies"/> argument is null.</exception>
17+
/// <exception cref="ArgumentException">The <paramref name="assemblies"/> argument is empty.</exception>
18+
public ConfigurationReaderOptions(params Assembly[] assemblies)
19+
{
20+
Assemblies = assemblies ?? throw new ArgumentNullException(nameof(assemblies));
21+
if (assemblies.Length == 0)
22+
throw new ArgumentException("The assemblies array must not be empty.", nameof(assemblies));
23+
}
24+
25+
/// <summary>
26+
/// Initialize a new instance of the <see cref="ConfigurationReaderOptions"/> class.
27+
/// </summary>
28+
/// <remarks>Prefer the constructor taking explicit assemblies: <see cref="ConfigurationReaderOptions(System.Reflection.Assembly[])"/>. It's the only one supporting single-file publishing.</remarks>
29+
public ConfigurationReaderOptions() : this(dependencyContext: null)
30+
{
31+
}
32+
33+
/// <summary>
34+
/// Initialize a new instance of the <see cref="ConfigurationReaderOptions"/> class.
35+
/// </summary>
36+
/// <param name="dependencyContext">
37+
/// The dependency context from which sink/enricher packages can be located. If <see langword="null"/>, the platform default will be used.
38+
/// </param>
39+
/// <remarks>Prefer the constructor taking explicit assemblies: <see cref="ConfigurationReaderOptions(System.Reflection.Assembly[])"/>. It's the only one supporting single-file publishing.</remarks>
40+
public ConfigurationReaderOptions(DependencyContext dependencyContext) => DependencyContext = dependencyContext;
41+
42+
/// <summary>
43+
/// Initialize a new instance of the <see cref="ConfigurationReaderOptions"/> class.
44+
/// </summary>
45+
/// <param name="configurationAssemblySource">Defines how the package identifies assemblies to scan for sinks and other types.</param>
46+
/// <remarks>Prefer the constructor taking explicit assemblies: <see cref="ConfigurationReaderOptions(System.Reflection.Assembly[])"/>. It's the only one supporting single-file publishing.</remarks>
47+
public ConfigurationReaderOptions(ConfigurationAssemblySource configurationAssemblySource) => ConfigurationAssemblySource = configurationAssemblySource;
48+
49+
/// <summary>
50+
/// The section name for section which contains a Serilog section. Defaults to <c>Serilog</c>.
51+
/// </summary>
52+
public string SectionName { get; init; } = ConfigurationLoggerConfigurationExtensions.DefaultSectionName;
53+
54+
/// <summary>
55+
/// The <see cref="IFormatProvider"/> used when converting strings to other object types. Defaults to the invariant culture.
56+
/// </summary>
57+
public IFormatProvider FormatProvider { get; init; } = CultureInfo.InvariantCulture;
58+
59+
internal Assembly[] Assemblies { get; }
60+
internal DependencyContext DependencyContext { get; }
61+
internal ConfigurationAssemblySource? ConfigurationAssemblySource { get; }
62+
}

src/Serilog.Settings.Configuration/Settings/Configuration/ResolutionContext.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,16 @@ sealed class ResolutionContext
1313
readonly IDictionary<string, LoggingFilterSwitchProxy> _declaredFilterSwitches;
1414
readonly IConfiguration _appConfiguration;
1515

16-
public ResolutionContext(IConfiguration appConfiguration = null)
16+
public ResolutionContext(IConfiguration appConfiguration = null, IFormatProvider formatProvider = null)
1717
{
1818
_declaredLevelSwitches = new Dictionary<string, LoggingLevelSwitch>();
1919
_declaredFilterSwitches = new Dictionary<string, LoggingFilterSwitchProxy>();
2020
_appConfiguration = appConfiguration;
21+
FormatProvider = formatProvider;
2122
}
2223

24+
public IFormatProvider FormatProvider { get; }
25+
2326
/// <summary>
2427
/// Looks up a switch in the declared LoggingLevelSwitches
2528
/// </summary>

src/Serilog.Settings.Configuration/Settings/Configuration/StringArgumentValue.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ public object ConvertTo(Type toType, ResolutionContext resolutionContext)
155155
}
156156
}
157157

158-
return Convert.ChangeType(argumentValue, toType);
158+
return Convert.ChangeType(argumentValue, toType, resolutionContext.FormatProvider);
159159
}
160160

161161
internal static Type FindType(string typeName)

0 commit comments

Comments
 (0)