Skip to content
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

Merged
merged 1 commit into from
Mar 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
14 changes: 10 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,9 @@ Root section name can be changed:
```

```csharp
var options = new ConfigurationReaderOptions { SectionName = "CustomSection" };
var logger = new LoggerConfiguration()
.ReadFrom.Configuration(configuration, sectionName: "CustomSection")
.ReadFrom.Configuration(configuration, options)
.CreateLogger();
```

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

var options = new ConfigurationReaderOptions(functionDependencyContext) { SectionName = "AzureFunctionsJobHost:Serilog" };
var logger = new LoggerConfiguration()
.ReadFrom.Configuration(hostConfig, sectionName: "AzureFunctionsJobHost:Serilog", dependencyContext: functionDependencyContext)
.ReadFrom.Configuration(hostConfig, options)
.CreateLogger();
```

Expand All @@ -119,8 +121,9 @@ var configurationAssemblies = new[]
typeof(ConsoleLoggerConfigurationExtensions).Assembly,
typeof(FileLoggerConfigurationExtensions).Assembly,
};
var options = new ConfigurationReaderOptions(configurationAssemblies);
var logger = new LoggerConfiguration()
.ReadFrom.Configuration(configuration, configurationAssemblies)
.ReadFrom.Configuration(configuration, options)
.CreateLogger();
```

Expand Down Expand Up @@ -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 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.

### Static member support

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:
Expand Down Expand Up @@ -377,8 +382,9 @@ public class Startup : FunctionsStartup
var functionDependencyContext = DependencyContext.Load(typeof(Startup).Assembly);

var hostConfig = sp.GetRequiredService<IConfiguration>();
var options = new ConfigurationReaderOptions(functionDependencyContext) { SectionName = "AzureFunctionsJobHost:Serilog" };
var logger = new LoggerConfiguration()
.ReadFrom.Configuration(hostConfig, sectionName: "AzureFunctionsJobHost:Serilog", dependencyContext: functionDependencyContext)
.ReadFrom.Configuration(hostConfig, options)
.CreateLogger();

return new SerilogLoggerProvider(logger, dispose: true);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2013-2016 Serilog Contributors
// Copyright 2013-2016 Serilog Contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -42,6 +42,7 @@ public static class ConfigurationLoggerConfigurationExtensions
/// <param name="dependencyContext">The dependency context from which sink/enricher packages can be located. If not supplied, the platform
/// default will be used.</param>
/// <returns>An object allowing configuration to continue.</returns>
[Obsolete("Use ReadFrom.Configuration(IConfiguration configuration, ConfigurationReaderOptions readerOptions) instead.")]
public static LoggerConfiguration Configuration(
this LoggerSettingsConfiguration settingConfiguration,
IConfiguration configuration,
Expand All @@ -52,15 +53,8 @@ public static class ConfigurationLoggerConfigurationExtensions
if (configuration == null) throw new ArgumentNullException(nameof(configuration));
if (sectionName == null) throw new ArgumentNullException(nameof(sectionName));

var assemblyFinder = dependencyContext == null
? AssemblyFinder.Auto()
: AssemblyFinder.ForDependencyContext(dependencyContext);

return settingConfiguration.Settings(
new ConfigurationReader(
configuration.GetSection(sectionName),
assemblyFinder,
configuration));
var readerOptions = new ConfigurationReaderOptions(dependencyContext) { SectionName = sectionName, FormatProvider = null };
return Configuration(settingConfiguration, configuration, readerOptions);
}

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

/// <summary>
Expand Down Expand Up @@ -105,7 +100,8 @@ public static class ConfigurationLoggerConfigurationExtensions
new ConfigurationReader(
configSection,
assemblyFinder,
configuration: null));
configuration: null,
formatProvider: null));
}

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

var assemblyFinder = AssemblyFinder.ForSource(configurationAssemblySource);

return settingConfiguration.Settings(new ConfigurationReader(configuration.GetSection(sectionName), assemblyFinder, configuration));
var readerOptions = new ConfigurationReaderOptions(configurationAssemblySource) { SectionName = sectionName, FormatProvider = null };
return Configuration(settingConfiguration, configuration, readerOptions);
}

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

var assemblyFinder = AssemblyFinder.ForSource(configurationAssemblySource);

return settingConfiguration.Settings(new ConfigurationReader(configSection, assemblyFinder, configuration: null));
return settingConfiguration.Settings(new ConfigurationReader(configSection, assemblyFinder, configuration: null, formatProvider: null));
}

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

return settingConfiguration.Settings(new ConfigurationReader(configuration.GetSection(sectionName), assemblies, new ResolutionContext(configuration)));
var readerOptions = new ConfigurationReaderOptions(assemblies) { SectionName = sectionName, FormatProvider = null };
return Configuration(settingConfiguration, configuration, readerOptions);
}

/// <summary>
Expand All @@ -198,9 +197,51 @@ public static class ConfigurationLoggerConfigurationExtensions
/// <param name="configuration">A configuration object which contains a Serilog section.</param>
/// <param name="assemblies">A collection of assemblies that contains sinks and other types.</param>
/// <returns>An object allowing configuration to continue.</returns>
[Obsolete("Use ReadFrom.Configuration(IConfiguration configuration, ConfigurationReaderOptions readerOptions) instead.")]
public static LoggerConfiguration Configuration(
this LoggerSettingsConfiguration settingConfiguration,
IConfiguration configuration,
params Assembly[] assemblies)
=> Configuration(settingConfiguration, configuration, DefaultSectionName, assemblies);

/// <summary>
/// Reads logger settings from the provided configuration object using the specified context.
/// </summary>
/// <param name="settingConfiguration">Logger setting configuration.</param>
/// <param name="configuration">A configuration object which contains a Serilog section.</param>
/// <param name="readerOptions">Options to adjust how the configuration object is processed.</param>
/// <returns>An object allowing configuration to continue.</returns>
public static LoggerConfiguration Configuration(
this LoggerSettingsConfiguration settingConfiguration,
IConfiguration configuration,
ConfigurationReaderOptions readerOptions = null)
{
var configurationReader = readerOptions switch
{
{ ConfigurationAssemblySource: {} } => GetConfigurationReader(configuration, readerOptions, readerOptions.ConfigurationAssemblySource.Value),
{ Assemblies: {} } => GetConfigurationReader(configuration, readerOptions, readerOptions.Assemblies),
_ => GetConfigurationReader(configuration, readerOptions ?? new ConfigurationReaderOptions(), readerOptions?.DependencyContext),
};
return settingConfiguration.Settings(configurationReader);
}

static ConfigurationReader GetConfigurationReader(IConfiguration configuration, ConfigurationReaderOptions readerOptions, DependencyContext dependencyContext)
{
var assemblyFinder = dependencyContext == null ? AssemblyFinder.Auto() : AssemblyFinder.ForDependencyContext(dependencyContext);
var section = configuration.GetSection(readerOptions.SectionName);
return new ConfigurationReader(section, assemblyFinder, readerOptions.FormatProvider, configuration);
}

static ConfigurationReader GetConfigurationReader(IConfiguration configuration, ConfigurationReaderOptions readerOptions, ConfigurationAssemblySource source)
{
var assemblyFinder = AssemblyFinder.ForSource(source);
var section = configuration.GetSection(readerOptions.SectionName);
return new ConfigurationReader(section, assemblyFinder, readerOptions.FormatProvider, configuration);
}

static ConfigurationReader GetConfigurationReader(IConfiguration configuration, ConfigurationReaderOptions readerOptions, IReadOnlyCollection<Assembly> assemblies)
{
var section = configuration.GetSection(readerOptions.SectionName);
return new ConfigurationReader(section, assemblies, new ResolutionContext(configuration, readerOptions.FormatProvider));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyModel" Version="6.0.0" />
<PackageReference Include="PolySharp" Version="1.12.1" PrivateAssets="All" />
<PackageReference Include="Serilog" Version="2.10.0" />
<None Include="..\..\assets\icon.png" Pack="true" PackagePath="" Visible="false" />
</ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,11 @@ class ConfigurationReader : IConfigurationReader
readonly ResolutionContext _resolutionContext;
readonly IConfigurationRoot _configurationRoot;

public ConfigurationReader(IConfigurationSection configSection, AssemblyFinder assemblyFinder, IConfiguration configuration = null)
public ConfigurationReader(IConfigurationSection configSection, AssemblyFinder assemblyFinder, IFormatProvider formatProvider, IConfiguration configuration = null)
{
_section = configSection ?? throw new ArgumentNullException(nameof(configSection));
_configurationAssemblies = LoadConfigurationAssemblies(_section, assemblyFinder);
_resolutionContext = new ResolutionContext(configuration);
_resolutionContext = new ResolutionContext(configuration, formatProvider);
_configurationRoot = configuration as IConfigurationRoot;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
using System.Globalization;
using System.Reflection;
using Microsoft.Extensions.DependencyModel;

namespace Serilog.Settings.Configuration;

/// <summary>
/// Options to adjust how the configuration object is processed.
/// </summary>
public sealed class ConfigurationReaderOptions
{
/// <summary>
/// Initialize a new instance of the <see cref="ConfigurationReaderOptions"/> class.
/// </summary>
/// <param name="assemblies">A collection of assemblies that contains sinks and other types.</param>
/// <exception cref="ArgumentNullException">The <paramref name="assemblies"/> argument is null.</exception>
/// <exception cref="ArgumentException">The <paramref name="assemblies"/> argument is empty.</exception>
public ConfigurationReaderOptions(params Assembly[] assemblies)
{
Assemblies = assemblies ?? throw new ArgumentNullException(nameof(assemblies));
if (assemblies.Length == 0)
throw new ArgumentException("The assemblies array must not be empty.", nameof(assemblies));
}

/// <summary>
/// Initialize a new instance of the <see cref="ConfigurationReaderOptions"/> class.
/// </summary>
/// <remarks>Prefer the constructor taking explicit assemblies: <see cref="ConfigurationReaderOptions(System.Reflection.Assembly[])"/>. It's the only one supporting single-file publishing.</remarks>
public ConfigurationReaderOptions() : this(dependencyContext: null)
{
}

/// <summary>
/// Initialize a new instance of the <see cref="ConfigurationReaderOptions"/> class.
/// </summary>
/// <param name="dependencyContext">
/// The dependency context from which sink/enricher packages can be located. If <see langword="null"/>, the platform default will be used.
/// </param>
/// <remarks>Prefer the constructor taking explicit assemblies: <see cref="ConfigurationReaderOptions(System.Reflection.Assembly[])"/>. It's the only one supporting single-file publishing.</remarks>
public ConfigurationReaderOptions(DependencyContext dependencyContext) => DependencyContext = dependencyContext;

/// <summary>
/// Initialize a new instance of the <see cref="ConfigurationReaderOptions"/> class.
/// </summary>
/// <param name="configurationAssemblySource">Defines how the package identifies assemblies to scan for sinks and other types.</param>
/// <remarks>Prefer the constructor taking explicit assemblies: <see cref="ConfigurationReaderOptions(System.Reflection.Assembly[])"/>. It's the only one supporting single-file publishing.</remarks>
public ConfigurationReaderOptions(ConfigurationAssemblySource configurationAssemblySource) => ConfigurationAssemblySource = configurationAssemblySource;

/// <summary>
/// The section name for section which contains a Serilog section. Defaults to <c>Serilog</c>.
/// </summary>
public string SectionName { get; init; } = ConfigurationLoggerConfigurationExtensions.DefaultSectionName;

/// <summary>
/// The <see cref="IFormatProvider"/> used when converting strings to other object types. Defaults to the invariant culture.
/// </summary>
public IFormatProvider FormatProvider { get; init; } = CultureInfo.InvariantCulture;

internal Assembly[] Assemblies { get; }
internal DependencyContext DependencyContext { get; }
internal ConfigurationAssemblySource? ConfigurationAssemblySource { get; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,16 @@ sealed class ResolutionContext
readonly IDictionary<string, LoggingFilterSwitchProxy> _declaredFilterSwitches;
readonly IConfiguration _appConfiguration;

public ResolutionContext(IConfiguration appConfiguration = null)
public ResolutionContext(IConfiguration appConfiguration = null, IFormatProvider formatProvider = null)
{
_declaredLevelSwitches = new Dictionary<string, LoggingLevelSwitch>();
_declaredFilterSwitches = new Dictionary<string, LoggingFilterSwitchProxy>();
_appConfiguration = appConfiguration;
FormatProvider = formatProvider;
}

public IFormatProvider FormatProvider { get; }

/// <summary>
/// Looks up a switch in the declared LoggingLevelSwitches
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ public object ConvertTo(Type toType, ResolutionContext resolutionContext)
}
}

return Convert.ChangeType(argumentValue, toType);
return Convert.ChangeType(argumentValue, toType, resolutionContext.FormatProvider);
}

internal static Type FindType(string typeName)
Expand Down