diff --git a/README.md b/README.md
index 9a737a0..26d133b 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-# Serilog.Settings.Configuration [![Build status](https://ci.appveyor.com/api/projects/status/r2bgfimd9ocr61px/branch/master?svg=true)](https://ci.appveyor.com/project/serilog/serilog-settings-configuration/branch/master)
+# Serilog.Settings.Configuration [![Build status](https://ci.appveyor.com/api/projects/status/r2bgfimd9ocr61px/branch/master?svg=true)](https://ci.appveyor.com/project/serilog/serilog-settings-configuration/branch/master) [![NuGet Version](http://img.shields.io/nuget/v/Serilog.Settings.Configuration.svg?style=flat)](https://www.nuget.org/packages/Serilog.Settings.Configuration/)
A Serilog settings provider that reads from _Microsoft.Extensions.Configuration_, .NET Core's `appsettings.json` file.
diff --git a/appveyor.yml b/appveyor.yml
index 0e93876..396e0ef 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -10,7 +10,7 @@ artifacts:
deploy:
- provider: NuGet
api_key:
- secure: bd9z4P73oltOXudAjPehwp9iDKsPtC+HbgshOrSgoyQKr5xVK+bxJQngrDJkHdY8
+ secure: N59tiJECUYpip6tEn0xvdmDAEiP9SIzyLEFLpwiigm/8WhJvBNs13QxzT1/3/JW/
skip_symbols: true
on:
branch: /^(master|dev)$/
diff --git a/sample/Sample/appsettings.json b/sample/Sample/appsettings.json
index 2cbcccc..2771a7f 100644
--- a/sample/Sample/appsettings.json
+++ b/sample/Sample/appsettings.json
@@ -1,6 +1,7 @@
{
"Serilog": {
- "Using": ["Serilog.Sinks.Console"],
+ "Using": [ "Serilog.Sinks.Console" ],
+ "LevelSwitches": { "$controlSwitch": "Verbose" },
"MinimumLevel": {
"Default": "Debug",
"Override": {
@@ -12,6 +13,7 @@
"Name": "Logger",
"Args": {
"configureLogger": {
+ "MinimumLevel": "Verbose",
"WriteTo": [
{
"Name": "Console",
@@ -22,7 +24,8 @@
}
]
},
- "restrictedToMinimumLevel": "Debug"
+ "restrictedToMinimumLevel": "Verbose",
+ "levelSwitch": "$controlSwitch"
}
},
"WriteTo:Async": {
@@ -39,7 +42,7 @@
]
}
},
- "Enrich": ["FromLogContext", "WithMachineName", "WithThreadId"],
+ "Enrich": [ "FromLogContext", "WithMachineName", "WithThreadId" ],
"Properties": {
"Application": "Sample"
},
diff --git a/src/Serilog.Settings.Configuration/ConfigurationLoggerConfigurationExtensions.cs b/src/Serilog.Settings.Configuration/ConfigurationLoggerConfigurationExtensions.cs
index 2de4db4..bc93db1 100644
--- a/src/Serilog.Settings.Configuration/ConfigurationLoggerConfigurationExtensions.cs
+++ b/src/Serilog.Settings.Configuration/ConfigurationLoggerConfigurationExtensions.cs
@@ -17,7 +17,7 @@
using Microsoft.Extensions.DependencyModel;
using Serilog.Configuration;
using Serilog.Settings.Configuration;
-using System.Reflection;
+using Serilog.Settings.Configuration.Assemblies;
namespace Serilog
{
@@ -32,28 +32,53 @@ public static class ConfigurationLoggerConfigurationExtensions
public const string DefaultSectionName = "Serilog";
///
- /// Reads logger settings from the provided configuration object using the default section name. Generally this
+ /// Reads logger settings from the provided configuration object using the provided section name. Generally this
/// is preferable over the other method that takes a configuration section. Only this version will populate
/// IConfiguration parameters on target methods.
///
/// Logger setting configuration.
/// A configuration object which contains a Serilog section.
+ /// A section name for section which contains a Serilog section.
/// The dependency context from which sink/enricher packages can be located. If not supplied, the platform
/// default will be used.
/// An object allowing configuration to continue.
public static LoggerConfiguration Configuration(
this LoggerSettingsConfiguration settingConfiguration,
IConfiguration configuration,
+ string sectionName,
DependencyContext dependencyContext = null)
{
+ if (settingConfiguration == null) throw new ArgumentNullException(nameof(settingConfiguration));
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,
- dependencyContext ?? (Assembly.GetEntryAssembly() != null ? DependencyContext.Default : null)));
+ configuration.GetSection(sectionName),
+ assemblyFinder,
+ configuration));
}
+ ///
+ /// Reads logger settings from the provided configuration object using the default section name. Generally this
+ /// is preferable over the other method that takes a configuration section. Only this version will populate
+ /// IConfiguration parameters on target methods.
+ ///
+ /// Logger setting configuration.
+ /// A configuration object which contains a Serilog section.
+ /// The dependency context from which sink/enricher packages can be located. If not supplied, the platform
+ /// default will be used.
+ /// An object allowing configuration to continue.
+ public static LoggerConfiguration Configuration(
+ this LoggerSettingsConfiguration settingConfiguration,
+ IConfiguration configuration,
+ DependencyContext dependencyContext = null)
+ => Configuration(settingConfiguration, configuration, DefaultSectionName, dependencyContext);
+
///
/// Reads logger settings from the provided configuration section. Generally it is preferable to use the other
/// extension method that takes the full configuration object.
@@ -63,6 +88,7 @@ public static LoggerConfiguration Configuration(
/// The dependency context from which sink/enricher packages can be located. If not supplied, the platform
/// default will be used.
/// An object allowing configuration to continue.
+ [Obsolete("Use ReadFrom.Configuration(IConfiguration configuration, string sectionName, DependencyContext dependencyContext) instead.")]
public static LoggerConfiguration ConfigurationSection(
this LoggerSettingsConfiguration settingConfiguration,
IConfigurationSection configSection,
@@ -71,38 +97,57 @@ public static LoggerConfiguration ConfigurationSection(
if (settingConfiguration == null) throw new ArgumentNullException(nameof(settingConfiguration));
if (configSection == null) throw new ArgumentNullException(nameof(configSection));
+ var assemblyFinder = dependencyContext == null
+ ? AssemblyFinder.Auto()
+ : AssemblyFinder.ForDependencyContext(dependencyContext);
+
return settingConfiguration.Settings(
new ConfigurationReader(
configSection,
- dependencyContext ?? (Assembly.GetEntryAssembly() != null ? DependencyContext.Default : null)));
+ assemblyFinder,
+ configuration: null));
}
///
- /// Reads logger settings from the provided configuration object using the default section name. Generally this
+ /// Reads logger settings from the provided configuration object using the provided section name. Generally this
/// is preferable over the other method that takes a configuration section. Only this version will populate
/// IConfiguration parameters on target methods.
///
/// Logger setting configuration.
/// A configuration object which contains a Serilog section.
+ /// A section name for section which contains a Serilog section.
/// Defines how the package identifies assemblies to scan for sinks and other Types.
/// An object allowing configuration to continue.
public static LoggerConfiguration Configuration(
this LoggerSettingsConfiguration settingConfiguration,
IConfiguration configuration,
+ string sectionName,
ConfigurationAssemblySource configurationAssemblySource)
{
+ if (settingConfiguration == null) throw new ArgumentNullException(nameof(settingConfiguration));
if (configuration == null) throw new ArgumentNullException(nameof(configuration));
+ if (sectionName == null) throw new ArgumentNullException(nameof(sectionName));
+
+ var assemblyFinder = AssemblyFinder.ForSource(configurationAssemblySource);
- if(configurationAssemblySource == ConfigurationAssemblySource.UseLoadedAssemblies)
- {
- return Configuration(settingConfiguration, configuration);
- }
- else
- {
- return settingConfiguration.Settings(new ConfigurationReader(configuration, null));
- }
+ return settingConfiguration.Settings(new ConfigurationReader(configuration.GetSection(sectionName), assemblyFinder, configuration));
}
+ ///
+ /// Reads logger settings from the provided configuration object using the default section name. Generally this
+ /// is preferable over the other method that takes a configuration section. Only this version will populate
+ /// IConfiguration parameters on target methods.
+ ///
+ /// Logger setting configuration.
+ /// A configuration object which contains a Serilog section.
+ /// Defines how the package identifies assemblies to scan for sinks and other Types.
+ /// An object allowing configuration to continue.
+ public static LoggerConfiguration Configuration(
+ this LoggerSettingsConfiguration settingConfiguration,
+ IConfiguration configuration,
+ ConfigurationAssemblySource configurationAssemblySource)
+ => Configuration(settingConfiguration, configuration, DefaultSectionName, configurationAssemblySource);
+
///
/// Reads logger settings from the provided configuration section. Generally it is preferable to use the other
/// extension method that takes the full configuration object.
@@ -111,6 +156,7 @@ public static LoggerConfiguration Configuration(
/// The Serilog configuration section
/// Defines how the package identifies assemblies to scan for sinks and other Types.
/// An object allowing configuration to continue.
+ [Obsolete("Use ReadFrom.Configuration(IConfiguration configuration, string sectionName, ConfigurationAssemblySource configurationAssemblySource) instead.")]
public static LoggerConfiguration ConfigurationSection(
this LoggerSettingsConfiguration settingConfiguration,
IConfigurationSection configSection,
@@ -119,14 +165,9 @@ public static LoggerConfiguration ConfigurationSection(
if (settingConfiguration == null) throw new ArgumentNullException(nameof(settingConfiguration));
if (configSection == null) throw new ArgumentNullException(nameof(configSection));
- if (configurationAssemblySource == ConfigurationAssemblySource.UseLoadedAssemblies)
- {
- return Configuration(settingConfiguration, configSection);
- }
- else
- {
- return settingConfiguration.Settings(new ConfigurationReader(configSection, null));
- }
+ var assemblyFinder = AssemblyFinder.ForSource(configurationAssemblySource);
+
+ return settingConfiguration.Settings(new ConfigurationReader(configSection, assemblyFinder, configuration: null));
}
}
}
diff --git a/src/Serilog.Settings.Configuration/Serilog.Settings.Configuration.csproj b/src/Serilog.Settings.Configuration/Serilog.Settings.Configuration.csproj
index a45deb5..e62947a 100644
--- a/src/Serilog.Settings.Configuration/Serilog.Settings.Configuration.csproj
+++ b/src/Serilog.Settings.Configuration/Serilog.Settings.Configuration.csproj
@@ -2,7 +2,7 @@
Microsoft.Extensions.Configuration (appsettings.json) support for Serilog.
- 3.0.1
+ 3.1.0
Serilog Contributors
netstandard2.0;net451;net461
true
@@ -15,12 +15,16 @@
serilog;json
https://serilog.net/images/serilog-configuration-nuget.png
https://github.com/serilog/serilog-settings-configuration
- https://www.apache.org/licenses/LICENSE-2.0
+ Apache-2.0
https://github.com/serilog/serilog-settings-configuration
git
Serilog
+
+ $(DefineConstants);PRIVATE_BIN
+
+
diff --git a/src/Serilog.Settings.Configuration/Settings/Configuration/Assemblies/AssemblyFinder.cs b/src/Serilog.Settings.Configuration/Settings/Configuration/Assemblies/AssemblyFinder.cs
new file mode 100644
index 0000000..4d356a8
--- /dev/null
+++ b/src/Serilog.Settings.Configuration/Settings/Configuration/Assemblies/AssemblyFinder.cs
@@ -0,0 +1,46 @@
+using System;
+using System.Collections.Generic;
+using System.Reflection;
+using Microsoft.Extensions.DependencyModel;
+
+namespace Serilog.Settings.Configuration.Assemblies
+{
+ abstract class AssemblyFinder
+ {
+ public abstract IReadOnlyList FindAssembliesContainingName(string nameToFind);
+
+ protected static bool IsCaseInsensitiveMatch(string text, string textToFind)
+ {
+ return text != null && text.ToLowerInvariant().Contains(textToFind.ToLowerInvariant());
+ }
+
+ public static AssemblyFinder Auto()
+ {
+ // Need to check `Assembly.GetEntryAssembly()` first because
+ // `DependencyContext.Default` throws an exception when `Assembly.GetEntryAssembly()` returns null
+ if (Assembly.GetEntryAssembly() != null && DependencyContext.Default != null)
+ {
+ return new DependencyContextAssemblyFinder(DependencyContext.Default);
+ }
+ return new DllScanningAssemblyFinder();
+ }
+
+ public static AssemblyFinder ForSource(ConfigurationAssemblySource configurationAssemblySource)
+ {
+ switch (configurationAssemblySource)
+ {
+ case ConfigurationAssemblySource.UseLoadedAssemblies:
+ return Auto();
+ case ConfigurationAssemblySource.AlwaysScanDllFiles:
+ return new DllScanningAssemblyFinder();
+ default:
+ throw new ArgumentOutOfRangeException(nameof(configurationAssemblySource), configurationAssemblySource, null);
+ }
+ }
+
+ public static AssemblyFinder ForDependencyContext(DependencyContext dependencyContext)
+ {
+ return new DependencyContextAssemblyFinder(dependencyContext);
+ }
+ }
+}
diff --git a/src/Serilog.Settings.Configuration/Settings/Configuration/ConfigurationAssemblySource.cs b/src/Serilog.Settings.Configuration/Settings/Configuration/Assemblies/ConfigurationAssemblySource.cs
similarity index 100%
rename from src/Serilog.Settings.Configuration/Settings/Configuration/ConfigurationAssemblySource.cs
rename to src/Serilog.Settings.Configuration/Settings/Configuration/Assemblies/ConfigurationAssemblySource.cs
diff --git a/src/Serilog.Settings.Configuration/Settings/Configuration/Assemblies/DependencyContextAssemblyFinder.cs b/src/Serilog.Settings.Configuration/Settings/Configuration/Assemblies/DependencyContextAssemblyFinder.cs
new file mode 100644
index 0000000..d7faa77
--- /dev/null
+++ b/src/Serilog.Settings.Configuration/Settings/Configuration/Assemblies/DependencyContextAssemblyFinder.cs
@@ -0,0 +1,28 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using Microsoft.Extensions.DependencyModel;
+
+namespace Serilog.Settings.Configuration.Assemblies
+{
+ sealed class DependencyContextAssemblyFinder : AssemblyFinder
+ {
+ readonly DependencyContext _dependencyContext;
+
+ public DependencyContextAssemblyFinder(DependencyContext dependencyContext)
+ {
+ _dependencyContext = dependencyContext ?? throw new ArgumentNullException(nameof(dependencyContext));
+ }
+
+ public override IReadOnlyList FindAssembliesContainingName(string nameToFind)
+ {
+ var query = from library in _dependencyContext.RuntimeLibraries
+ from assemblyName in library.GetDefaultAssemblyNames(_dependencyContext)
+ where IsCaseInsensitiveMatch(assemblyName.Name, nameToFind)
+ select assemblyName;
+
+ return query.ToList().AsReadOnly();
+ }
+ }
+}
diff --git a/src/Serilog.Settings.Configuration/Settings/Configuration/Assemblies/DllScanningAssemblyFinder.cs b/src/Serilog.Settings.Configuration/Settings/Configuration/Assemblies/DllScanningAssemblyFinder.cs
new file mode 100644
index 0000000..95584e2
--- /dev/null
+++ b/src/Serilog.Settings.Configuration/Settings/Configuration/Assemblies/DllScanningAssemblyFinder.cs
@@ -0,0 +1,66 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+
+namespace Serilog.Settings.Configuration.Assemblies
+{
+ sealed class DllScanningAssemblyFinder : AssemblyFinder
+ {
+ public override IReadOnlyList FindAssembliesContainingName(string nameToFind)
+ {
+ var probeDirs = new List();
+
+ if (!string.IsNullOrEmpty(AppDomain.CurrentDomain.BaseDirectory))
+ {
+ probeDirs.Add(AppDomain.CurrentDomain.BaseDirectory);
+
+#if PRIVATE_BIN
+ var privateBinPath = AppDomain.CurrentDomain.SetupInformation.PrivateBinPath;
+ if (!string.IsNullOrEmpty(privateBinPath))
+ {
+ foreach (var path in privateBinPath.Split(';'))
+ {
+ if (Path.IsPathRooted(path))
+ {
+ probeDirs.Add(path);
+ }
+ else
+ {
+ probeDirs.Add(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, path));
+ }
+ }
+ }
+#endif
+ }
+ else
+ {
+ probeDirs.Add(Path.GetDirectoryName(typeof(AssemblyFinder).Assembly.Location));
+ }
+
+ var query = from probeDir in probeDirs
+ where Directory.Exists(probeDir)
+ from outputAssemblyPath in Directory.GetFiles(probeDir, "*.dll")
+ let assemblyFileName = Path.GetFileNameWithoutExtension(outputAssemblyPath)
+ where IsCaseInsensitiveMatch(assemblyFileName, nameToFind)
+ let assemblyName = TryGetAssemblyNameFrom(outputAssemblyPath)
+ where assemblyName != null
+ select assemblyName;
+
+ return query.ToList().AsReadOnly();
+
+ AssemblyName TryGetAssemblyNameFrom(string path)
+ {
+ try
+ {
+ return AssemblyName.GetAssemblyName(path);
+ }
+ catch (BadImageFormatException)
+ {
+ return null;
+ }
+ }
+ }
+ }
+}
diff --git a/src/Serilog.Settings.Configuration/Settings/Configuration/ConfigurationReader.cs b/src/Serilog.Settings.Configuration/Settings/Configuration/ConfigurationReader.cs
index 39be648..3bf16df 100644
--- a/src/Serilog.Settings.Configuration/Settings/Configuration/ConfigurationReader.cs
+++ b/src/Serilog.Settings.Configuration/Settings/Configuration/ConfigurationReader.cs
@@ -1,17 +1,17 @@
-using System;
+using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using Microsoft.Extensions.Configuration;
-using Microsoft.Extensions.DependencyModel;
using Microsoft.Extensions.Primitives;
+using System.Text.RegularExpressions;
using Serilog.Configuration;
using Serilog.Core;
using Serilog.Debugging;
using Serilog.Events;
-using System.Text.RegularExpressions;
+using Serilog.Settings.Configuration.Assemblies;
namespace Serilog.Settings.Configuration
{
@@ -19,52 +19,40 @@ class ConfigurationReader : IConfigurationReader
{
const string LevelSwitchNameRegex = @"^\$[A-Za-z]+[A-Za-z0-9]*$";
- static IConfiguration _configuration;
-
readonly IConfigurationSection _section;
- readonly DependencyContext _dependencyContext;
readonly IReadOnlyCollection _configurationAssemblies;
+ readonly ResolutionContext _resolutionContext;
- public ConfigurationReader(IConfiguration configuration, DependencyContext dependencyContext)
- {
- _configuration = configuration ?? throw new ArgumentNullException(nameof(configuration));
- _section = configuration.GetSection(ConfigurationLoggerConfigurationExtensions.DefaultSectionName);
- _dependencyContext = dependencyContext;
- _configurationAssemblies = LoadConfigurationAssemblies();
- }
-
- // Generally the initial call should use IConfiguration rather than IConfigurationSection, otherwise
- // IConfiguration parameters in the target methods will not be populated.
- public ConfigurationReader(IConfigurationSection configSection, DependencyContext dependencyContext)
+ public ConfigurationReader(IConfigurationSection configSection, AssemblyFinder assemblyFinder, IConfiguration configuration = null)
{
_section = configSection ?? throw new ArgumentNullException(nameof(configSection));
- _dependencyContext = dependencyContext;
- _configurationAssemblies = LoadConfigurationAssemblies();
+ _configurationAssemblies = LoadConfigurationAssemblies(_section, assemblyFinder);
+ _resolutionContext = new ResolutionContext(configuration);
}
// Used internally for processing nested configuration sections -- see GetMethodCalls below.
- internal ConfigurationReader(IConfigurationSection configSection, IReadOnlyCollection configurationAssemblies, DependencyContext dependencyContext)
+ internal ConfigurationReader(IConfigurationSection configSection, IReadOnlyCollection configurationAssemblies, ResolutionContext resolutionContext)
{
_section = configSection ?? throw new ArgumentNullException(nameof(configSection));
- _dependencyContext = dependencyContext;
_configurationAssemblies = configurationAssemblies ?? throw new ArgumentNullException(nameof(configurationAssemblies));
+ _resolutionContext = resolutionContext ?? throw new ArgumentNullException(nameof(resolutionContext));
}
public void Configure(LoggerConfiguration loggerConfiguration)
{
- var declaredLevelSwitches = ProcessLevelSwitchDeclarations();
- ApplyMinimumLevel(loggerConfiguration, declaredLevelSwitches);
- ApplyEnrichment(loggerConfiguration, declaredLevelSwitches);
- ApplyFilters(loggerConfiguration, declaredLevelSwitches);
- ApplyDestructuring(loggerConfiguration, declaredLevelSwitches);
- ApplySinks(loggerConfiguration, declaredLevelSwitches);
- ApplyAuditSinks(loggerConfiguration, declaredLevelSwitches);
+ ProcessLevelSwitchDeclarations();
+
+ ApplyMinimumLevel(loggerConfiguration);
+ ApplyEnrichment(loggerConfiguration);
+ ApplyFilters(loggerConfiguration);
+ ApplyDestructuring(loggerConfiguration);
+ ApplySinks(loggerConfiguration);
+ ApplyAuditSinks(loggerConfiguration);
}
- IReadOnlyDictionary ProcessLevelSwitchDeclarations()
+ void ProcessLevelSwitchDeclarations()
{
var levelSwitchesDirective = _section.GetSection("LevelSwitches");
- var namedSwitches = new Dictionary();
foreach (var levelSwitchDeclaration in levelSwitchesDirective.GetChildren())
{
var switchName = levelSwitchDeclaration.Key;
@@ -74,6 +62,7 @@ IReadOnlyDictionary ProcessLevelSwitchDeclarations()
{
throw new FormatException($"\"{switchName}\" is not a valid name for a Level Switch declaration. Level switch must be declared with a '$' sign, like \"LevelSwitches\" : {{\"$switchName\" : \"InitialLevel\"}}");
}
+
LoggingLevelSwitch newSwitch;
if (string.IsNullOrEmpty(switchInitialLevel))
{
@@ -84,12 +73,15 @@ IReadOnlyDictionary ProcessLevelSwitchDeclarations()
var initialLevel = ParseLogEventLevel(switchInitialLevel);
newSwitch = new LoggingLevelSwitch(initialLevel);
}
- namedSwitches.Add(switchName, newSwitch);
+
+ SubscribeToLoggingLevelChanges(levelSwitchDeclaration, newSwitch);
+
+ // make them available later on when resolving argument values
+ _resolutionContext.AddLevelSwitch(switchName, newSwitch);
}
- return namedSwitches;
}
- void ApplyMinimumLevel(LoggerConfiguration loggerConfiguration, IReadOnlyDictionary declaredLevelSwitches)
+ void ApplyMinimumLevel(LoggerConfiguration loggerConfiguration)
{
var minimumLevelDirective = _section.GetSection("MinimumLevel");
@@ -102,7 +94,7 @@ void ApplyMinimumLevel(LoggerConfiguration loggerConfiguration, IReadOnlyDiction
var minLevelControlledByDirective = minimumLevelDirective.GetSection("ControlledBy");
if (minLevelControlledByDirective.Value != null)
{
- var globalMinimumLevelSwitch = declaredLevelSwitches.LookUpSwitchByName(minLevelControlledByDirective.Value);
+ var globalMinimumLevelSwitch = _resolutionContext.LookUpSwitchByName(minLevelControlledByDirective.Value);
// not calling ApplyMinimumLevel local function because here we have a reference to a LogLevelSwitch already
loggerConfiguration.MinimumLevel.ControlledBy(globalMinimumLevelSwitch);
}
@@ -117,7 +109,7 @@ void ApplyMinimumLevel(LoggerConfiguration loggerConfiguration, IReadOnlyDiction
}
else
{
- var overrideSwitch = declaredLevelSwitches.LookUpSwitchByName(overridenLevelOrSwitch);
+ var overrideSwitch = _resolutionContext.LookUpSwitchByName(overridenLevelOrSwitch);
// not calling ApplyMinimumLevel local function because here we have a reference to a LogLevelSwitch already
loggerConfiguration.MinimumLevel.Override(overridePrefix, overrideSwitch);
}
@@ -130,71 +122,76 @@ void ApplyMinimumLevel(IConfigurationSection directive, Action
- {
- if (Enum.TryParse(directive.Value, out minimumLevel))
- levelSwitch.MinimumLevel = minimumLevel;
- else
- SelfLog.WriteLine($"The value {directive.Value} is not a valid Serilog level.");
- });
+ SubscribeToLoggingLevelChanges(directive, levelSwitch);
}
}
- void ApplyFilters(LoggerConfiguration loggerConfiguration, IReadOnlyDictionary declaredLevelSwitches)
+ void SubscribeToLoggingLevelChanges(IConfigurationSection levelSection, LoggingLevelSwitch levelSwitch)
+ {
+ ChangeToken.OnChange(
+ levelSection.GetReloadToken,
+ () =>
+ {
+ if (Enum.TryParse(levelSection.Value, out LogEventLevel minimumLevel))
+ levelSwitch.MinimumLevel = minimumLevel;
+ else
+ SelfLog.WriteLine($"The value {levelSection.Value} is not a valid Serilog level.");
+ });
+ }
+
+ void ApplyFilters(LoggerConfiguration loggerConfiguration)
{
var filterDirective = _section.GetSection("Filter");
if (filterDirective.GetChildren().Any())
{
var methodCalls = GetMethodCalls(filterDirective);
- CallConfigurationMethods(methodCalls, FindFilterConfigurationMethods(_configurationAssemblies), loggerConfiguration.Filter, declaredLevelSwitches);
+ CallConfigurationMethods(methodCalls, FindFilterConfigurationMethods(_configurationAssemblies), loggerConfiguration.Filter);
}
}
- void ApplyDestructuring(LoggerConfiguration loggerConfiguration, IReadOnlyDictionary declaredLevelSwitches)
+ void ApplyDestructuring(LoggerConfiguration loggerConfiguration)
{
var destructureDirective = _section.GetSection("Destructure");
if (destructureDirective.GetChildren().Any())
{
var methodCalls = GetMethodCalls(destructureDirective);
- CallConfigurationMethods(methodCalls, FindDestructureConfigurationMethods(_configurationAssemblies), loggerConfiguration.Destructure, declaredLevelSwitches);
+ CallConfigurationMethods(methodCalls, FindDestructureConfigurationMethods(_configurationAssemblies), loggerConfiguration.Destructure);
}
}
- void ApplySinks(LoggerConfiguration loggerConfiguration, IReadOnlyDictionary declaredLevelSwitches)
+ void ApplySinks(LoggerConfiguration loggerConfiguration)
{
var writeToDirective = _section.GetSection("WriteTo");
if (writeToDirective.GetChildren().Any())
{
var methodCalls = GetMethodCalls(writeToDirective);
- CallConfigurationMethods(methodCalls, FindSinkConfigurationMethods(_configurationAssemblies), loggerConfiguration.WriteTo, declaredLevelSwitches);
+ CallConfigurationMethods(methodCalls, FindSinkConfigurationMethods(_configurationAssemblies), loggerConfiguration.WriteTo);
}
}
- void ApplyAuditSinks(LoggerConfiguration loggerConfiguration, IReadOnlyDictionary declaredLevelSwitches)
+ void ApplyAuditSinks(LoggerConfiguration loggerConfiguration)
{
var auditToDirective = _section.GetSection("AuditTo");
if (auditToDirective.GetChildren().Any())
{
var methodCalls = GetMethodCalls(auditToDirective);
- CallConfigurationMethods(methodCalls, FindAuditSinkConfigurationMethods(_configurationAssemblies), loggerConfiguration.AuditTo, declaredLevelSwitches);
+ CallConfigurationMethods(methodCalls, FindAuditSinkConfigurationMethods(_configurationAssemblies), loggerConfiguration.AuditTo);
}
}
- void IConfigurationReader.ApplySinks(LoggerSinkConfiguration loggerSinkConfiguration, IReadOnlyDictionary declaredLevelSwitches)
+ void IConfigurationReader.ApplySinks(LoggerSinkConfiguration loggerSinkConfiguration)
{
var methodCalls = GetMethodCalls(_section);
- CallConfigurationMethods(methodCalls, FindSinkConfigurationMethods(_configurationAssemblies), loggerSinkConfiguration, declaredLevelSwitches);
+ CallConfigurationMethods(methodCalls, FindSinkConfigurationMethods(_configurationAssemblies), loggerSinkConfiguration);
}
- void ApplyEnrichment(LoggerConfiguration loggerConfiguration, IReadOnlyDictionary declaredLevelSwitches)
+ void ApplyEnrichment(LoggerConfiguration loggerConfiguration)
{
var enrichDirective = _section.GetSection("Enrich");
if (enrichDirective.GetChildren().Any())
{
var methodCalls = GetMethodCalls(enrichDirective);
- CallConfigurationMethods(methodCalls, FindEventEnricherConfigurationMethods(_configurationAssemblies), loggerConfiguration.Enrich, declaredLevelSwitches);
+ CallConfigurationMethods(methodCalls, FindEventEnricherConfigurationMethods(_configurationAssemblies), loggerConfiguration.Enrich);
}
var propertiesDirective = _section.GetSection("Properties");
@@ -245,11 +242,11 @@ IConfigurationArgumentValue GetArgumentValue(IConfigurationSection argumentSecti
if (argumentSection.Value != null)
{
- argumentValue = new StringArgumentValue(() => argumentSection.Value, argumentSection.GetReloadToken);
+ argumentValue = new StringArgumentValue(argumentSection.Value);
}
else
{
- argumentValue = new ObjectArgumentValue(argumentSection, _configurationAssemblies, _dependencyContext);
+ argumentValue = new ObjectArgumentValue(argumentSection, _configurationAssemblies);
}
return argumentValue;
@@ -265,11 +262,11 @@ string GetSectionName(IConfigurationSection s)
}
}
- IReadOnlyCollection LoadConfigurationAssemblies()
+ static IReadOnlyCollection LoadConfigurationAssemblies(IConfigurationSection section, AssemblyFinder assemblyFinder)
{
var assemblies = new Dictionary();
- var usingSection = _section.GetSection("Using");
+ var usingSection = section.GetSection("Using");
if (usingSection.GetChildren().Any())
{
foreach (var simpleName in usingSection.GetChildren().Select(c => c.Value))
@@ -284,7 +281,7 @@ IReadOnlyCollection LoadConfigurationAssemblies()
}
}
- foreach (var assemblyName in GetSerilogConfigurationAssemblies())
+ foreach (var assemblyName in assemblyFinder.FindAssembliesContainingName("serilog"))
{
var assumed = Assembly.Load(assemblyName);
if (assumed != null && !assemblies.ContainsKey(assumed.FullName))
@@ -294,76 +291,73 @@ IReadOnlyCollection LoadConfigurationAssemblies()
return assemblies.Values.ToList().AsReadOnly();
}
- AssemblyName[] GetSerilogConfigurationAssemblies()
- {
- // ReSharper disable once RedundantAssignment
- var query = Enumerable.Empty();
- var filter = new Func(name => name != null && name.ToLowerInvariant().Contains("serilog"));
-
- if (_dependencyContext != null)
- {
- query = from library in _dependencyContext.RuntimeLibraries
- from assemblyName in library.GetDefaultAssemblyNames(_dependencyContext)
- where filter(assemblyName.Name)
- select assemblyName;
- }
- else
- {
- query = from outputAssemblyPath in System.IO.Directory.GetFiles(AppDomain.CurrentDomain.BaseDirectory, "*.dll")
- let assemblyFileName = System.IO.Path.GetFileNameWithoutExtension(outputAssemblyPath)
- where filter(assemblyFileName)
- select AssemblyName.GetAssemblyName(outputAssemblyPath);
- }
-
- return query.ToArray();
- }
-
- static void CallConfigurationMethods(ILookup> methods, IList configurationMethods, object receiver, IReadOnlyDictionary declaredLevelSwitches)
+ void CallConfigurationMethods(ILookup> methods, IList configurationMethods, object receiver)
{
foreach (var method in methods.SelectMany(g => g.Select(x => new { g.Key, Value = x })))
{
- var methodInfo = SelectConfigurationMethod(configurationMethods, method.Key, method.Value);
+ var methodInfo = SelectConfigurationMethod(configurationMethods, method.Key, method.Value.Keys);
if (methodInfo != null)
{
var call = (from p in methodInfo.GetParameters().Skip(1)
- let directive = method.Value.FirstOrDefault(s => s.Key.Equals(p.Name, StringComparison.OrdinalIgnoreCase))
- select directive.Key == null ? p.DefaultValue : directive.Value.ConvertTo(p.ParameterType, declaredLevelSwitches)).ToList();
-
- var parm = methodInfo.GetParameters().FirstOrDefault(i => i.ParameterType == typeof(IConfiguration));
- if (parm != null && !parm.HasDefaultValue)
- {
- if (_configuration is null)
- {
- throw new InvalidOperationException("Trying to invoke a configuration method accepting a `IConfiguration` argument. " +
- $"This is not supported when only a `IConfigSection` has been provided. (method '{methodInfo}')");
- }
- call[parm.Position - 1] = _configuration;
- }
+ let directive = method.Value.FirstOrDefault(s => ParameterNameMatches(p.Name, s.Key))
+ select directive.Key == null
+ ? GetImplicitValueForNotSpecifiedKey(p, methodInfo)
+ : directive.Value.ConvertTo(p.ParameterType, _resolutionContext)).ToList();
call.Insert(0, receiver);
-
methodInfo.Invoke(null, call.ToArray());
}
}
}
- internal static MethodInfo SelectConfigurationMethod(IEnumerable candidateMethods, string name, Dictionary suppliedArgumentValues)
+ static bool HasImplicitValueWhenNotSpecified(ParameterInfo paramInfo)
+ {
+ return paramInfo.HasDefaultValue
+ // parameters of type IConfiguration are implicitly populated with provided Configuration
+ || paramInfo.ParameterType == typeof(IConfiguration);
+ }
+
+ object GetImplicitValueForNotSpecifiedKey(ParameterInfo parameter, MethodInfo methodToInvoke)
+ {
+ if (!HasImplicitValueWhenNotSpecified(parameter))
+ {
+ throw new InvalidOperationException("GetImplicitValueForNotSpecifiedKey() should only be called for parameters for which HasImplicitValueWhenNotSpecified() is true. " +
+ "This means something is wrong in the Serilog.Settings.Configuration code.");
+ }
+
+ if (parameter.ParameterType == typeof(IConfiguration))
+ {
+ if (_resolutionContext.HasAppConfiguration)
+ {
+ return _resolutionContext.AppConfiguration;
+ }
+ if (parameter.HasDefaultValue)
+ {
+ return parameter.DefaultValue;
+ }
+
+ throw new InvalidOperationException("Trying to invoke a configuration method accepting a `IConfiguration` argument. " +
+ $"This is not supported when only a `IConfigSection` has been provided. (method '{methodToInvoke}')");
+ }
+
+ return parameter.DefaultValue;
+ }
+
+ internal static MethodInfo SelectConfigurationMethod(IEnumerable candidateMethods, string name, IEnumerable suppliedArgumentNames)
{
// Per issue #111, it is safe to use case-insensitive matching on argument names. The CLR doesn't permit this type
// of overloading, and the Microsoft.Extensions.Configuration keys are case-insensitive (case is preserved with some
// config sources, but key-matching is case-insensitive and case-preservation does not appear to be guaranteed).
- return candidateMethods
- .Where(m => m.Name == name &&
- m.GetParameters().Skip(1)
- .All(p => p.HasDefaultValue
- || suppliedArgumentValues.Any(s => s.Key.Equals(p.Name, StringComparison.OrdinalIgnoreCase))
- // parameters of type IConfiguration are implicitly populated with provided Configuration
- || p.ParameterType == typeof(IConfiguration)
- ))
+ var selectedMethod = candidateMethods
+ .Where(m => m.Name == name)
+ .Where(m => m.GetParameters()
+ .Skip(1)
+ .All(p => HasImplicitValueWhenNotSpecified(p) ||
+ ParameterNameMatches(p.Name, suppliedArgumentNames)))
.OrderByDescending(m =>
{
- var matchingArgs = m.GetParameters().Where(p => suppliedArgumentValues.Any(s => s.Key.Equals(p.Name, StringComparison.OrdinalIgnoreCase))).ToList();
+ var matchingArgs = m.GetParameters().Where(p => ParameterNameMatches(p.Name, suppliedArgumentNames)).ToList();
// Prefer the configuration method with most number of matching arguments and of those the ones with
// the most string type parameters to predict best match with least type casting
@@ -372,6 +366,37 @@ internal static MethodInfo SelectConfigurationMethod(IEnumerable can
matchingArgs.Count(p => p.ParameterType == typeof(string)));
})
.FirstOrDefault();
+
+ if (selectedMethod == null)
+ {
+ var methodsByName = candidateMethods
+ .Where(m => m.Name == name)
+ .Select(m => $"{m.Name}({string.Join(", ", m.GetParameters().Skip(1).Select(p => p.Name))})")
+ .ToList();
+
+ if (!methodsByName.Any())
+ SelfLog.WriteLine($"Unable to find a method called {name}. Candidate methods are:{Environment.NewLine}{string.Join(Environment.NewLine, candidateMethods)}");
+ else
+ SelfLog.WriteLine($"Unable to find a method called {name} "
+ + (suppliedArgumentNames.Any()
+ ? "for supplied arguments: " + string.Join(", ", suppliedArgumentNames)
+ : "with no supplied arguments")
+ + ". Candidate methods are:"
+ + Environment.NewLine
+ + string.Join(Environment.NewLine, methodsByName));
+ }
+
+ return selectedMethod;
+ }
+
+ static bool ParameterNameMatches(string actualParameterName, string suppliedName)
+ {
+ return suppliedName.Equals(actualParameterName, StringComparison.OrdinalIgnoreCase);
+ }
+
+ static bool ParameterNameMatches(string actualParameterName, IEnumerable suppliedNames)
+ {
+ return suppliedNames.Any(s => ParameterNameMatches(actualParameterName, s));
}
static IList FindSinkConfigurationMethods(IReadOnlyCollection configurationAssemblies)
diff --git a/src/Serilog.Settings.Configuration/Settings/Configuration/IConfigurationArgumentValue.cs b/src/Serilog.Settings.Configuration/Settings/Configuration/IConfigurationArgumentValue.cs
index 7ef3e04..e24e8e0 100644
--- a/src/Serilog.Settings.Configuration/Settings/Configuration/IConfigurationArgumentValue.cs
+++ b/src/Serilog.Settings.Configuration/Settings/Configuration/IConfigurationArgumentValue.cs
@@ -1,11 +1,9 @@
using System;
-using System.Collections.Generic;
-using Serilog.Core;
namespace Serilog.Settings.Configuration
{
interface IConfigurationArgumentValue
{
- object ConvertTo(Type toType, IReadOnlyDictionary declaredLevelSwitches);
+ object ConvertTo(Type toType, ResolutionContext resolutionContext);
}
}
diff --git a/src/Serilog.Settings.Configuration/Settings/Configuration/IConfigurationReader.cs b/src/Serilog.Settings.Configuration/Settings/Configuration/IConfigurationReader.cs
index 65f3693..f19f142 100644
--- a/src/Serilog.Settings.Configuration/Settings/Configuration/IConfigurationReader.cs
+++ b/src/Serilog.Settings.Configuration/Settings/Configuration/IConfigurationReader.cs
@@ -1,11 +1,9 @@
-using System.Collections.Generic;
-using Serilog.Configuration;
-using Serilog.Core;
+using Serilog.Configuration;
namespace Serilog.Settings.Configuration
{
interface IConfigurationReader : ILoggerSettings
{
- void ApplySinks(LoggerSinkConfiguration loggerSinkConfiguration, IReadOnlyDictionary declaredLevelSwitches);
+ void ApplySinks(LoggerSinkConfiguration loggerSinkConfiguration);
}
}
diff --git a/src/Serilog.Settings.Configuration/Settings/Configuration/LevelSwitchDictionaryExtensions.cs b/src/Serilog.Settings.Configuration/Settings/Configuration/LevelSwitchDictionaryExtensions.cs
deleted file mode 100644
index e7743cf..0000000
--- a/src/Serilog.Settings.Configuration/Settings/Configuration/LevelSwitchDictionaryExtensions.cs
+++ /dev/null
@@ -1,27 +0,0 @@
-using System;
-using System.Collections.Generic;
-using Serilog.Core;
-
-namespace Serilog.Settings.Configuration
-{
- internal static class LevelSwitchDictionaryExtensions
- {
- ///
- /// Looks up a switch in the declared LoggingLevelSwitches
- ///
- /// the dictionary of switches to look up by name
- /// the name of a switch to look up
- /// the LoggingLevelSwitch registered with the name
- /// if no switch has been registered with
- public static LoggingLevelSwitch LookUpSwitchByName(this IReadOnlyDictionary namedLevelSwitches, string switchName)
- {
- if (namedLevelSwitches == null) throw new ArgumentNullException(nameof(namedLevelSwitches));
- if (namedLevelSwitches.TryGetValue(switchName, out var levelSwitch))
- {
- return levelSwitch;
- }
-
- throw new InvalidOperationException($"No LoggingLevelSwitch has been declared with name \"{switchName}\". You might be missing a section \"LevelSwitches\":{{\"{switchName}\":\"InitialLevel\"}}");
- }
- }
-}
diff --git a/src/Serilog.Settings.Configuration/Settings/Configuration/ObjectArgumentValue.cs b/src/Serilog.Settings.Configuration/Settings/Configuration/ObjectArgumentValue.cs
index 8242476..6b54c34 100644
--- a/src/Serilog.Settings.Configuration/Settings/Configuration/ObjectArgumentValue.cs
+++ b/src/Serilog.Settings.Configuration/Settings/Configuration/ObjectArgumentValue.cs
@@ -1,57 +1,54 @@
using Microsoft.Extensions.Configuration;
-using Microsoft.Extensions.DependencyModel;
-using Serilog.Configuration;
-using Serilog.Core;
using System;
using System.Collections.Generic;
using System.Reflection;
+using Serilog.Configuration;
+
namespace Serilog.Settings.Configuration
{
class ObjectArgumentValue : IConfigurationArgumentValue
{
- readonly IConfigurationSection section;
- readonly IReadOnlyCollection configurationAssemblies;
- readonly DependencyContext dependencyContext;
+ readonly IConfigurationSection _section;
+ readonly IReadOnlyCollection _configurationAssemblies;
- public ObjectArgumentValue(IConfigurationSection section, IReadOnlyCollection configurationAssemblies, DependencyContext dependencyContext)
+ public ObjectArgumentValue(IConfigurationSection section, IReadOnlyCollection configurationAssemblies)
{
- this.section = section;
+ _section = section ?? throw new ArgumentNullException(nameof(section));
// used by nested logger configurations to feed a new pass by ConfigurationReader
- this.configurationAssemblies = configurationAssemblies;
- this.dependencyContext = dependencyContext;
+ _configurationAssemblies = configurationAssemblies ?? throw new ArgumentNullException(nameof(configurationAssemblies));
}
- public object ConvertTo(Type toType, IReadOnlyDictionary declaredLevelSwitches)
+ public object ConvertTo(Type toType, ResolutionContext resolutionContext)
{
// return the entire section for internal processing
- if(toType == typeof(IConfigurationSection)) return section;
+ if (toType == typeof(IConfigurationSection)) return _section;
// process a nested configuration to populate an Action<> logger/sink config parameter?
var typeInfo = toType.GetTypeInfo();
- if(typeInfo.IsGenericType &&
+ if (typeInfo.IsGenericType &&
typeInfo.GetGenericTypeDefinition() is Type genericType && genericType == typeof(Action<>))
{
var configType = typeInfo.GenericTypeArguments[0];
- if(configType != typeof(LoggerConfiguration) && configType != typeof(LoggerSinkConfiguration))
+ if (configType != typeof(LoggerConfiguration) && configType != typeof(LoggerSinkConfiguration))
throw new ArgumentException($"Configuration for Action<{configType}> is not implemented.");
- IConfigurationReader configReader = new ConfigurationReader(section, configurationAssemblies, dependencyContext);
+ IConfigurationReader configReader = new ConfigurationReader(_section, _configurationAssemblies, resolutionContext);
- if(configType == typeof(LoggerConfiguration))
+ if (configType == typeof(LoggerConfiguration))
{
return new Action(configReader.Configure);
}
- if(configType == typeof(LoggerSinkConfiguration))
+ if (configType == typeof(LoggerSinkConfiguration))
{
- return new Action(loggerSinkConfig => configReader.ApplySinks(loggerSinkConfig, declaredLevelSwitches));
+ return new Action(loggerSinkConfig => configReader.ApplySinks(loggerSinkConfig));
}
}
// MS Config binding
- return section.Get(toType);
+ return _section.Get(toType);
}
}
}
diff --git a/src/Serilog.Settings.Configuration/Settings/Configuration/ResolutionContext.cs b/src/Serilog.Settings.Configuration/Settings/Configuration/ResolutionContext.cs
new file mode 100644
index 0000000..90ab06d
--- /dev/null
+++ b/src/Serilog.Settings.Configuration/Settings/Configuration/ResolutionContext.cs
@@ -0,0 +1,61 @@
+using System;
+using System.Collections.Generic;
+using Microsoft.Extensions.Configuration;
+
+using Serilog.Core;
+
+namespace Serilog.Settings.Configuration
+{
+ ///
+ /// Keeps track of available elements that are useful when resolving values in the settings system.
+ ///
+ sealed class ResolutionContext
+ {
+ readonly IDictionary _declaredLevelSwitches;
+ readonly IConfiguration _appConfiguration;
+
+ public ResolutionContext(IConfiguration appConfiguration = null)
+ {
+ _declaredLevelSwitches = new Dictionary();
+ _appConfiguration = appConfiguration;
+ }
+
+ ///
+ /// Looks up a switch in the declared LoggingLevelSwitches
+ ///
+ /// the name of a switch to look up
+ /// the LoggingLevelSwitch registered with the name
+ /// if no switch has been registered with
+ public LoggingLevelSwitch LookUpSwitchByName(string switchName)
+ {
+ if (_declaredLevelSwitches.TryGetValue(switchName, out var levelSwitch))
+ {
+ return levelSwitch;
+ }
+
+ throw new InvalidOperationException($"No LoggingLevelSwitch has been declared with name \"{switchName}\". You might be missing a section \"LevelSwitches\":{{\"{switchName}\":\"InitialLevel\"}}");
+ }
+
+ public bool HasAppConfiguration => _appConfiguration != null;
+
+ public IConfiguration AppConfiguration
+ {
+ get
+ {
+ if (!HasAppConfiguration)
+ {
+ throw new InvalidOperationException("AppConfiguration is not available");
+ }
+
+ return _appConfiguration;
+ }
+ }
+
+ public void AddLevelSwitch(string levelSwitchName, LoggingLevelSwitch levelSwitch)
+ {
+ if (levelSwitchName == null) throw new ArgumentNullException(nameof(levelSwitchName));
+ if (levelSwitch == null) throw new ArgumentNullException(nameof(levelSwitch));
+ _declaredLevelSwitches[levelSwitchName] = levelSwitch;
+ }
+ }
+}
diff --git a/src/Serilog.Settings.Configuration/Settings/Configuration/StringArgumentValue.cs b/src/Serilog.Settings.Configuration/Settings/Configuration/StringArgumentValue.cs
index 5291f2a..0a7c09b 100644
--- a/src/Serilog.Settings.Configuration/Settings/Configuration/StringArgumentValue.cs
+++ b/src/Serilog.Settings.Configuration/Settings/Configuration/StringArgumentValue.cs
@@ -3,25 +3,20 @@
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;
-using Microsoft.Extensions.Primitives;
using Serilog.Core;
-using Serilog.Debugging;
-using Serilog.Events;
namespace Serilog.Settings.Configuration
{
class StringArgumentValue : IConfigurationArgumentValue
{
- readonly Func _valueProducer;
- readonly Func _changeTokenProducer;
+ readonly string _providedValue;
static readonly Regex StaticMemberAccessorRegex = new Regex("^(?[^:]+)::(?[A-Za-z][A-Za-z0-9]*)(?[^:]*)$");
- public StringArgumentValue(Func valueProducer, Func changeTokenProducer = null)
+ public StringArgumentValue(string providedValue)
{
- _valueProducer = valueProducer ?? throw new ArgumentNullException(nameof(valueProducer));
- _changeTokenProducer = changeTokenProducer;
+ _providedValue = providedValue ?? throw new ArgumentNullException(nameof(providedValue));
}
static readonly Dictionary> ExtendedTypeConversions = new Dictionary>
@@ -31,13 +26,13 @@ public StringArgumentValue(Func valueProducer, Func change
{ typeof(Type), s => Type.GetType(s, throwOnError:true) },
};
- public object ConvertTo(Type toType, IReadOnlyDictionary declaredLevelSwitches)
+ public object ConvertTo(Type toType, ResolutionContext resolutionContext)
{
- var argumentValue = Environment.ExpandEnvironmentVariables(_valueProducer());
+ var argumentValue = Environment.ExpandEnvironmentVariables(_providedValue);
if (toType == typeof(LoggingLevelSwitch))
{
- return declaredLevelSwitches.LookUpSwitchByName(argumentValue);
+ return resolutionContext.LookUpSwitchByName(argumentValue);
}
var toTypeInfo = toType.GetTypeInfo();
@@ -97,7 +92,7 @@ public object ConvertTo(Type toType, IReadOnlyDictionary
@@ -114,32 +109,21 @@ public object ConvertTo(Type toType, IReadOnlyDictionary
- {
- var newArgumentValue = _valueProducer();
-
- if (Enum.TryParse(newArgumentValue, out minimumLevel))
- levelSwitch.MinimumLevel = minimumLevel;
- else
- SelfLog.WriteLine($"The value `{newArgumentValue}` is not a valid Serilog level.");
- });
+ type = Type.GetType($"{typeName}, Serilog");
}
-
- return levelSwitch;
}
- return Convert.ChangeType(argumentValue, toType);
+ return type;
}
internal static bool TryParseStaticMemberAccessor(string input, out string accessorTypeName, out string memberName)
diff --git a/src/Serilog.Settings.Configuration/Settings/Configuration/SurrogateConfigurationMethods.cs b/src/Serilog.Settings.Configuration/Settings/Configuration/SurrogateConfigurationMethods.cs
index 6350eca..bbde10d 100644
--- a/src/Serilog.Settings.Configuration/Settings/Configuration/SurrogateConfigurationMethods.cs
+++ b/src/Serilog.Settings.Configuration/Settings/Configuration/SurrogateConfigurationMethods.cs
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
+
using Serilog.Configuration;
using Serilog.Core;
using Serilog.Events;
diff --git a/test/Serilog.Settings.Configuration.Tests/ConfigurationReaderTests.cs b/test/Serilog.Settings.Configuration.Tests/ConfigurationReaderTests.cs
index 701cfee..b24007b 100644
--- a/test/Serilog.Settings.Configuration.Tests/ConfigurationReaderTests.cs
+++ b/test/Serilog.Settings.Configuration.Tests/ConfigurationReaderTests.cs
@@ -1,9 +1,8 @@
-using System.Collections.Generic;
-using Serilog.Formatting;
-using Xunit;
+using Xunit;
using System.Reflection;
using System.Linq;
-using Serilog.Core;
+using Serilog.Formatting;
+using Serilog.Settings.Configuration.Assemblies;
using Serilog.Settings.Configuration.Tests.Support;
namespace Serilog.Settings.Configuration.Tests
@@ -14,7 +13,9 @@ public class ConfigurationReaderTests
public ConfigurationReaderTests()
{
- _configurationReader = new ConfigurationReader(JsonStringConfigSource.LoadSection(@"{ 'Serilog': { } }", "Serilog"), null);
+ _configurationReader = new ConfigurationReader(
+ JsonStringConfigSource.LoadSection(@"{ 'Serilog': { } }", "Serilog"),
+ AssemblyFinder.ForSource(ConfigurationAssemblySource.UseLoadedAssemblies));
}
[Fact]
@@ -75,7 +76,7 @@ public void WriteToSupportExpandedSyntaxWithArgs()
Assert.Equal(1, args.Length);
Assert.Equal("outputTemplate", args[0].Key);
- Assert.Equal("{Message}", args[0].Value.ConvertTo(typeof(string), new Dictionary()));
+ Assert.Equal("{Message}", args[0].Value.ConvertTo(typeof(string), new ResolutionContext()));
}
[Fact]
@@ -142,12 +143,9 @@ public void CallableMethodsAreSelected()
{
var options = typeof(DummyLoggerConfigurationExtensions).GetTypeInfo().DeclaredMethods.ToList();
Assert.Equal(2, options.Count(mi => mi.Name == "DummyRollingFile"));
- var suppliedArguments = new Dictionary
- {
- {"pathFormat", new StringArgumentValue(() => "C:\\") }
- };
+ var suppliedArgumentNames = new[] { "pathFormat" };
- var selected = ConfigurationReader.SelectConfigurationMethod(options, "DummyRollingFile", suppliedArguments);
+ var selected = ConfigurationReader.SelectConfigurationMethod(options, "DummyRollingFile", suppliedArgumentNames);
Assert.Equal(typeof(string), selected.GetParameters()[1].ParameterType);
}
@@ -156,13 +154,10 @@ public void MethodsAreSelectedBasedOnCountOfMatchedArguments()
{
var options = typeof(DummyLoggerConfigurationExtensions).GetTypeInfo().DeclaredMethods.ToList();
Assert.Equal(2, options.Count(mi => mi.Name == "DummyRollingFile"));
- var suppliedArguments = new Dictionary()
- {
- { "pathFormat", new StringArgumentValue(() => "C:\\") },
- { "formatter", new StringArgumentValue(() => "SomeFormatter, SomeAssembly") }
- };
- var selected = ConfigurationReader.SelectConfigurationMethod(options, "DummyRollingFile", suppliedArguments);
+ var suppliedArgumentNames = new[] { "pathFormat", "formatter" };
+
+ var selected = ConfigurationReader.SelectConfigurationMethod(options, "DummyRollingFile", suppliedArgumentNames);
Assert.Equal(typeof(ITextFormatter), selected.GetParameters()[1].ParameterType);
}
@@ -171,13 +166,10 @@ public void MethodsAreSelectedBasedOnCountOfMatchedArgumentsAndThenStringType()
{
var options = typeof(DummyLoggerConfigurationWithMultipleMethodsExtensions).GetTypeInfo().DeclaredMethods.ToList();
Assert.Equal(3, options.Count(mi => mi.Name == "DummyRollingFile"));
- var suppliedArguments = new Dictionary()
- {
- { "pathFormat", new StringArgumentValue(() => "C:\\") },
- { "formatter", new StringArgumentValue(() => "SomeFormatter, SomeAssembly") }
- };
- var selected = ConfigurationReader.SelectConfigurationMethod(options, "DummyRollingFile", suppliedArguments);
+ var suppliedArgumentNames = new[] { "pathFormat", "formatter" };
+
+ var selected = ConfigurationReader.SelectConfigurationMethod(options, "DummyRollingFile", suppliedArgumentNames);
Assert.Equal(typeof(string), selected.GetParameters()[2].ParameterType);
}
}
diff --git a/test/Serilog.Settings.Configuration.Tests/ConfigurationSettingsTests.cs b/test/Serilog.Settings.Configuration.Tests/ConfigurationSettingsTests.cs
index d5c5369..eed3803 100644
--- a/test/Serilog.Settings.Configuration.Tests/ConfigurationSettingsTests.cs
+++ b/test/Serilog.Settings.Configuration.Tests/ConfigurationSettingsTests.cs
@@ -13,13 +13,23 @@ namespace Serilog.Settings.Configuration.Tests
public class ConfigurationSettingsTests
{
static LoggerConfiguration ConfigFromJson(string jsonString, string secondJsonSource = null)
+ {
+ return ConfigFromJson(jsonString, secondJsonSource, out _);
+ }
+
+ static LoggerConfiguration ConfigFromJson(string jsonString, out IConfiguration configuration)
+ {
+ return ConfigFromJson(jsonString, null, out configuration);
+ }
+
+ static LoggerConfiguration ConfigFromJson(string jsonString, string secondJsonSource, out IConfiguration configuration)
{
var builder = new ConfigurationBuilder().AddJsonString(jsonString);
if (secondJsonSource != null)
builder.AddJsonString(secondJsonSource);
- var config = builder.Build();
+ configuration = builder.Build();
return new LoggerConfiguration()
- .ReadFrom.Configuration(config);
+ .ReadFrom.Configuration(configuration);
}
[Fact]
@@ -184,6 +194,40 @@ public void TestMinimumLevelOverrides()
Assert.NotNull(evt);
}
+ [Fact]
+ public void TestMinimumLevelOverridesForChildContext()
+ {
+ var json = @"{
+ ""Serilog"": {
+ ""MinimumLevel"" : {
+ ""Default"" : ""Warning"",
+ ""Override"" : {
+ ""System"" : ""Warning"",
+ ""System.Threading"": ""Debug""
+ }
+ }
+ }
+ }";
+
+ LogEvent evt = null;
+
+ var log = ConfigFromJson(json)
+ .WriteTo.Sink(new DelegatingSink(e => evt = e))
+ .CreateLogger();
+
+ log.Write(Some.DebugEvent());
+ Assert.Null(evt);
+
+ var custom = log.ForContext(Constants.SourceContextPropertyName, typeof(System.Threading.Tasks.Task).FullName + "<42>");
+ custom.Write(Some.DebugEvent());
+ Assert.NotNull(evt);
+
+ evt = null;
+ var systemThreadingLogger = log.ForContext();
+ systemThreadingLogger.Write(Some.DebugEvent());
+ Assert.NotNull(evt);
+ }
+
[Fact]
public void SinksWithAbstractParamsAreConfiguredWithTypeName()
{
@@ -419,7 +463,7 @@ public void LoggingLevelSwitchCanBeUsedForMinimumLevelOverrides()
}
[Fact]
-
+
[Trait("BugFix", "https://github.com/serilog/serilog-settings-configuration/issues/142")]
public void SinkWithIConfigurationArguments()
{
@@ -434,14 +478,15 @@ public void SinkWithIConfigurationArguments()
}";
DummyConfigurationSink.Reset();
- var log = ConfigFromJson(json)
+ var log = ConfigFromJson(json, out var expectedConfig)
.CreateLogger();
log.Write(Some.InformationEvent());
Assert.NotNull(DummyConfigurationSink.Configuration);
+ Assert.Same(expectedConfig, DummyConfigurationSink.Configuration);
}
-
+
[Fact]
[Trait("BugFix", "https://github.com/serilog/serilog-settings-configuration/issues/142")]
public void SinkWithOptionalIConfigurationArguments()
@@ -457,15 +502,16 @@ public void SinkWithOptionalIConfigurationArguments()
}";
DummyConfigurationSink.Reset();
- var log = ConfigFromJson(json)
+ var log = ConfigFromJson(json, out var expectedConfig)
.CreateLogger();
log.Write(Some.InformationEvent());
- // null is the default value
- Assert.Null(DummyConfigurationSink.Configuration);
+ // null is the default value, but we have a configuration to provide
+ Assert.NotNull(DummyConfigurationSink.Configuration);
+ Assert.Same(expectedConfig, DummyConfigurationSink.Configuration);
}
-
+
[Fact]
public void SinkWithIConfigSectionArguments()
{
@@ -484,7 +530,7 @@ public void SinkWithIConfigSectionArguments()
.CreateLogger();
log.Write(Some.InformationEvent());
-
+
Assert.NotNull(DummyConfigurationSink.ConfigSection);
Assert.Equal("bar", DummyConfigurationSink.ConfigSection["foo"]);
}
@@ -763,7 +809,7 @@ public void DestructureLimitsCollectionCount()
Assert.DoesNotContain("4", msg);
}
- private string GetDestructuredProperty(object x, string json)
+ private static string GetDestructuredProperty(object x, string json)
{
LogEvent evt = null;
var log = ConfigFromJson(json)
diff --git a/test/Serilog.Settings.Configuration.Tests/DllScanningAssemblyFinderTests.cs b/test/Serilog.Settings.Configuration.Tests/DllScanningAssemblyFinderTests.cs
new file mode 100644
index 0000000..d8ab19a
--- /dev/null
+++ b/test/Serilog.Settings.Configuration.Tests/DllScanningAssemblyFinderTests.cs
@@ -0,0 +1,76 @@
+using System;
+using System.IO;
+
+using Xunit;
+
+using Serilog.Settings.Configuration.Assemblies;
+
+namespace Serilog.Settings.Configuration.Tests
+{
+ public class DllScanningAssemblyFinderTests : IDisposable
+ {
+ const string BinDir1 = "bin1";
+ const string BinDir2 = "bin2";
+ const string BinDir3 = "bin3";
+
+ readonly string _privateBinPath;
+
+ public DllScanningAssemblyFinderTests()
+ {
+ var d1 = GetOrCreateDirectory(BinDir1);
+ var d2 = GetOrCreateDirectory(BinDir2);
+ var d3 = GetOrCreateDirectory(BinDir3);
+
+ _privateBinPath = $"{d1.Name};{d2.FullName};{d3.Name}";
+
+ DirectoryInfo GetOrCreateDirectory(string name)
+ => Directory.Exists(name) ? new DirectoryInfo(name) : Directory.CreateDirectory(name);
+ }
+
+ public void Dispose()
+ {
+ Directory.Delete(BinDir1, true);
+ Directory.Delete(BinDir2, true);
+ Directory.Delete(BinDir3, true);
+ }
+
+ [Fact]
+ public void ShouldProbeCurrentDirectory()
+ {
+ var assemblyNames = new DllScanningAssemblyFinder().FindAssembliesContainingName("testdummies");
+ Assert.Single(assemblyNames);
+ }
+
+#if PRIVATE_BIN
+ [Fact]
+ public void ShouldProbePrivateBinPath()
+ {
+ File.Copy("testdummies.dll", $"{BinDir1}/customSink1.dll", true);
+ File.Copy("testdummies.dll", $"{BinDir2}/customSink2.dll", true);
+ File.Copy("testdummies.dll", $"{BinDir3}/thirdpartydependency.dll", true);
+
+ var ad = AppDomain.CreateDomain("serilog", null,
+ new AppDomainSetup
+ {
+ ApplicationBase = AppDomain.CurrentDomain.BaseDirectory,
+ PrivateBinPath = _privateBinPath
+ });
+
+ try
+ {
+ ad.DoCallBack(DoTestInner);
+ }
+ finally
+ {
+ AppDomain.Unload(ad);
+ }
+
+ void DoTestInner()
+ {
+ var assemblyNames = new DllScanningAssemblyFinder().FindAssembliesContainingName("customSink");
+ Assert.Equal(2, assemblyNames.Count);
+ }
+ }
+#endif
+ }
+}
diff --git a/test/Serilog.Settings.Configuration.Tests/DynamicLevelChangeTests.cs b/test/Serilog.Settings.Configuration.Tests/DynamicLevelChangeTests.cs
new file mode 100644
index 0000000..b1b1f43
--- /dev/null
+++ b/test/Serilog.Settings.Configuration.Tests/DynamicLevelChangeTests.cs
@@ -0,0 +1,90 @@
+using Serilog.Core;
+using Serilog.Events;
+using Serilog.Settings.Configuration.Tests.Support;
+
+using Xunit;
+using Microsoft.Extensions.Configuration;
+
+using TestDummies.Console;
+
+namespace Serilog.Settings.Configuration.Tests
+{
+ public class DynamicLevelChangeTests
+ {
+ const string DefaultConfig = @"{
+ 'Serilog': {
+ 'Using': [ 'TestDummies' ],
+ 'MinimumLevel': {
+ 'Default': 'Information',
+ 'Override': {
+ 'Root.Test': 'Information'
+ }
+ },
+ 'LevelSwitches': { '$mySwitch': 'Information' },
+ 'WriteTo:Dummy': {
+ 'Name': 'DummyConsole',
+ 'Args': {
+ 'levelSwitch': '$mySwitch'
+ }
+ }
+ }
+ }";
+
+ readonly ReloadableConfigurationSource _configSource;
+
+ public DynamicLevelChangeTests()
+ {
+ _configSource = new ReloadableConfigurationSource(JsonStringConfigSource.LoadData(DefaultConfig));
+ }
+
+ [Fact]
+ public void ShouldRespectDynamicLevelChanges()
+ {
+ using (var logger = new LoggerConfiguration()
+ .ReadFrom
+ .Configuration(new ConfigurationBuilder().Add(_configSource).Build())
+ .CreateLogger())
+ {
+ DummyConsoleSink.Emitted.Clear();
+ logger.Write(Some.DebugEvent());
+ Assert.Empty(DummyConsoleSink.Emitted);
+
+ DummyConsoleSink.Emitted.Clear();
+ UpdateConfig(minimumLevel: LogEventLevel.Debug);
+ logger.Write(Some.DebugEvent());
+ Assert.Empty(DummyConsoleSink.Emitted);
+
+ DummyConsoleSink.Emitted.Clear();
+ UpdateConfig(switchLevel: LogEventLevel.Debug);
+ logger.Write(Some.DebugEvent());
+ logger.ForContext(Constants.SourceContextPropertyName, "Root.Test").Write(Some.DebugEvent());
+ Assert.Single(DummyConsoleSink.Emitted);
+
+ DummyConsoleSink.Emitted.Clear();
+ UpdateConfig(overrideLevel: LogEventLevel.Debug);
+ logger.ForContext(Constants.SourceContextPropertyName, "Root.Test").Write(Some.DebugEvent());
+ Assert.Single(DummyConsoleSink.Emitted);
+ }
+ }
+
+ void UpdateConfig(LogEventLevel? minimumLevel = null, LogEventLevel? switchLevel = null, LogEventLevel? overrideLevel = null)
+ {
+ if (minimumLevel.HasValue)
+ {
+ _configSource.Set("Serilog:MinimumLevel:Default", minimumLevel.Value.ToString());
+ }
+
+ if (switchLevel.HasValue)
+ {
+ _configSource.Set("Serilog:LevelSwitches:$mySwitch", switchLevel.Value.ToString());
+ }
+
+ if (overrideLevel.HasValue)
+ {
+ _configSource.Set("Serilog:MinimumLevel:Override:Root.Test", overrideLevel.Value.ToString());
+ }
+
+ _configSource.Reload();
+ }
+ }
+}
diff --git a/test/Serilog.Settings.Configuration.Tests/LoggerConfigurationExtensionsTests.cs b/test/Serilog.Settings.Configuration.Tests/LoggerConfigurationExtensionsTests.cs
index 80d64b0..e2f679e 100644
--- a/test/Serilog.Settings.Configuration.Tests/LoggerConfigurationExtensionsTests.cs
+++ b/test/Serilog.Settings.Configuration.Tests/LoggerConfigurationExtensionsTests.cs
@@ -35,8 +35,10 @@ public void ReadFromConfigurationSectionReadsFromAnArbitrarySection()
.AddJsonString(json)
.Build();
+#pragma warning disable CS0618 // Type or member is obsolete
var log = new LoggerConfiguration()
.ReadFrom.ConfigurationSection(config.GetSection("NotSerilog"))
+#pragma warning restore CS0618 // Type or member is obsolete
.WriteTo.Sink(new DelegatingSink(e => evt = e))
.CreateLogger();
@@ -46,7 +48,7 @@ public void ReadFromConfigurationSectionReadsFromAnArbitrarySection()
Assert.Equal("Test", evt.Properties["App"].LiteralValue());
}
- [Fact(Skip = "Passes when run alone, but fails when the whole suite is run - to fix")]
+ [Fact]
[Trait("BugFix", "https://github.com/serilog/serilog-settings-configuration/issues/143")]
public void ReadFromConfigurationSectionThrowsWhenTryingToCallConfigurationMethodWithIConfigurationParam()
{
@@ -65,17 +67,42 @@ public void ReadFromConfigurationSectionThrowsWhenTryingToCallConfigurationMetho
.Build();
var exception = Assert.Throws(() =>
+#pragma warning disable CS0618 // Type or member is obsolete
new LoggerConfiguration()
.ReadFrom.ConfigurationSection(config.GetSection("NotSerilog"))
+#pragma warning restore CS0618 // Type or member is obsolete
.CreateLogger());
Assert.Equal("Trying to invoke a configuration method accepting a `IConfiguration` argument. " +
"This is not supported when only a `IConfigSection` has been provided. " +
- "(method 'Serilog.LoggerConfiguration DummyWithConfiguration(Serilog.Configuration.LoggerSinkConfiguration, Microsoft.Extensions.Configuration.IConfiguration, Microsoft.Extensions.Configuration.IConfigurationSection, System.String, Serilog.Events.LogEventLevel)')",
+ "(method 'Serilog.LoggerConfiguration DummyWithConfiguration(Serilog.Configuration.LoggerSinkConfiguration, Microsoft.Extensions.Configuration.IConfiguration, Serilog.Events.LogEventLevel)')",
exception.Message);
}
+ [Fact]
+ public void ReadFromConfigurationDoesNotThrowWhenTryingToCallConfigurationMethodWithIConfigurationParam()
+ {
+ var json = @"{
+ ""NotSerilog"": {
+ ""Using"": [""TestDummies""],
+ ""WriteTo"": [{
+ ""Name"": ""DummyWithConfiguration"",
+ ""Args"": {}
+ }]
+ }
+ }";
+
+ var config = new ConfigurationBuilder()
+ .AddJsonString(json)
+ .Build();
+
+ var exception = new LoggerConfiguration()
+ .ReadFrom.Configuration(config, "NotSerilog")
+ .CreateLogger();
+
+ }
+
[Fact]
[Trait("BugFix", "https://github.com/serilog/serilog-settings-configuration/issues/143")]
public void ReadFromConfigurationSectionDoesNotThrowWhenTryingToCallConfigurationMethodWithOptionalIConfigurationParam()
@@ -95,8 +122,10 @@ public void ReadFromConfigurationSectionDoesNotThrowWhenTryingToCallConfiguratio
.Build();
// this should not throw because DummyWithOptionalConfiguration accepts an optional config
+#pragma warning disable CS0618 // Type or member is obsolete
new LoggerConfiguration()
.ReadFrom.ConfigurationSection(config.GetSection("NotSerilog"))
+#pragma warning restore CS0618 // Type or member is obsolete
.CreateLogger();
}
diff --git a/test/Serilog.Settings.Configuration.Tests/Serilog.Settings.Configuration.Tests.csproj b/test/Serilog.Settings.Configuration.Tests/Serilog.Settings.Configuration.Tests.csproj
index 3a88c1b..69ceb38 100644
--- a/test/Serilog.Settings.Configuration.Tests/Serilog.Settings.Configuration.Tests.csproj
+++ b/test/Serilog.Settings.Configuration.Tests/Serilog.Settings.Configuration.Tests.csproj
@@ -19,6 +19,10 @@
true
+
+ $(DefineConstants);PRIVATE_BIN
+
+
diff --git a/test/Serilog.Settings.Configuration.Tests/StringArgumentValueTests.cs b/test/Serilog.Settings.Configuration.Tests/StringArgumentValueTests.cs
index e432eda..a58cee8 100644
--- a/test/Serilog.Settings.Configuration.Tests/StringArgumentValueTests.cs
+++ b/test/Serilog.Settings.Configuration.Tests/StringArgumentValueTests.cs
@@ -1,5 +1,4 @@
using System;
-using System.Collections.Generic;
using Serilog.Core;
using Serilog.Events;
using Serilog.Formatting;
@@ -16,9 +15,9 @@ public class StringArgumentValueTests
[Fact]
public void StringValuesConvertToDefaultInstancesIfTargetIsInterface()
{
- var stringArgumentValue = new StringArgumentValue(() => "Serilog.Formatting.Json.JsonFormatter, Serilog");
+ var stringArgumentValue = new StringArgumentValue("Serilog.Formatting.Json.JsonFormatter, Serilog");
- var result = stringArgumentValue.ConvertTo(typeof(ITextFormatter), new Dictionary());
+ var result = stringArgumentValue.ConvertTo(typeof(ITextFormatter), new ResolutionContext());
Assert.IsType(result);
}
@@ -26,9 +25,9 @@ public void StringValuesConvertToDefaultInstancesIfTargetIsInterface()
[Fact]
public void StringValuesConvertToDefaultInstancesIfTargetIsAbstractClass()
{
- var stringArgumentValue = new StringArgumentValue(() => "Serilog.Settings.Configuration.Tests.Support.ConcreteClass, Serilog.Settings.Configuration.Tests");
+ var stringArgumentValue = new StringArgumentValue("Serilog.Settings.Configuration.Tests.Support.ConcreteClass, Serilog.Settings.Configuration.Tests");
- var result = stringArgumentValue.ConvertTo(typeof(AbstractClass), new Dictionary());
+ var result = stringArgumentValue.ConvertTo(typeof(AbstractClass), new ResolutionContext());
Assert.IsType(result);
}
@@ -75,16 +74,26 @@ public void TryParseStaticMemberAccessorReturnsExpectedResults(string input, str
}
}
+ [Theory]
+ [InlineData("Serilog.Formatting.Json.JsonFormatter", typeof(JsonFormatter))]
+ [InlineData("Serilog.Formatting.Json.JsonFormatter, Serilog", typeof(JsonFormatter))]
+ [InlineData("Serilog.ConfigurationLoggerConfigurationExtensions", typeof(ConfigurationLoggerConfigurationExtensions))]
+ public void FindTypeSupportsSimpleNamesForSerilogTypes(string input, Type targetType)
+ {
+ var type = StringArgumentValue.FindType(input);
+ Assert.Equal(targetType, type);
+ }
+
[Theory]
[InlineData("Serilog.Settings.Configuration.Tests.Support.ClassWithStaticAccessors::InterfaceProperty, Serilog.Settings.Configuration.Tests", typeof(IAmAnInterface))]
[InlineData("Serilog.Settings.Configuration.Tests.Support.ClassWithStaticAccessors::AbstractProperty, Serilog.Settings.Configuration.Tests", typeof(AnAbstractClass))]
[InlineData("Serilog.Settings.Configuration.Tests.Support.ClassWithStaticAccessors::InterfaceField, Serilog.Settings.Configuration.Tests", typeof(IAmAnInterface))]
[InlineData("Serilog.Settings.Configuration.Tests.Support.ClassWithStaticAccessors::AbstractField, Serilog.Settings.Configuration.Tests", typeof(AnAbstractClass))]
- private void StaticMembersAccessorsCanBeUsedForReferenceTypes(string input, Type targetType)
+ public void StaticMembersAccessorsCanBeUsedForReferenceTypes(string input, Type targetType)
{
- var stringArgumentValue = new StringArgumentValue(() => $"{input}");
+ var stringArgumentValue = new StringArgumentValue($"{input}");
- var actual = stringArgumentValue.ConvertTo(targetType, new Dictionary());
+ var actual = stringArgumentValue.ConvertTo(targetType, new ResolutionContext());
Assert.IsAssignableFrom(targetType, actual);
Assert.Equal(ConcreteImpl.Instance, actual);
@@ -99,9 +108,9 @@ private void StaticMembersAccessorsCanBeUsedForReferenceTypes(string input, Type
[InlineData("Serilog.Settings.Configuration.Tests.Support.ClassWithStaticAccessors::InterfaceProperty", typeof(IAmAnInterface))]
public void StaticAccessorOnUnknownTypeThrowsTypeLoadException(string input, Type targetType)
{
- var stringArgumentValue = new StringArgumentValue(() => $"{input}");
+ var stringArgumentValue = new StringArgumentValue($"{input}");
Assert.Throws(() =>
- stringArgumentValue.ConvertTo(targetType, new Dictionary())
+ stringArgumentValue.ConvertTo(targetType, new ResolutionContext())
);
}
@@ -118,9 +127,9 @@ public void StaticAccessorOnUnknownTypeThrowsTypeLoadException(string input, Typ
[InlineData("Serilog.Settings.Configuration.Tests.Support.ClassWithStaticAccessors::InstanceInterfaceField, Serilog.Settings.Configuration.Tests", typeof(IAmAnInterface))]
public void StaticAccessorWithInvalidMemberThrowsInvalidOperationException(string input, Type targetType)
{
- var stringArgumentValue = new StringArgumentValue(() => $"{input}");
+ var stringArgumentValue = new StringArgumentValue($"{input}");
var exception = Assert.Throws(() =>
- stringArgumentValue.ConvertTo(targetType, new Dictionary())
+ stringArgumentValue.ConvertTo(targetType, new ResolutionContext())
);
Assert.Contains("Could not find a public static property or field ", exception.Message);
@@ -132,14 +141,12 @@ public void LevelSwitchesCanBeLookedUpByName()
{
var @switch = new LoggingLevelSwitch(LogEventLevel.Verbose);
var switchName = "$theSwitch";
- var declaredSwitches = new Dictionary()
- {
- {switchName, @switch }
- };
+ var resolutionContext = new ResolutionContext();
+ resolutionContext.AddLevelSwitch(switchName, @switch);
- var stringArgumentValue = new StringArgumentValue(() => switchName);
+ var stringArgumentValue = new StringArgumentValue(switchName);
- var resolvedSwitch = stringArgumentValue.ConvertTo(typeof(LoggingLevelSwitch), declaredSwitches);
+ var resolvedSwitch = stringArgumentValue.ConvertTo(typeof(LoggingLevelSwitch), resolutionContext);
Assert.IsType(resolvedSwitch);
Assert.Same(@switch, resolvedSwitch);
@@ -149,15 +156,13 @@ public void LevelSwitchesCanBeLookedUpByName()
[Fact]
public void ReferencingUndeclaredLevelSwitchThrows()
{
- var declaredSwitches = new Dictionary()
- {
- {"$anotherSwitch", new LoggingLevelSwitch(LogEventLevel.Verbose) }
- };
+ var resolutionContext = new ResolutionContext();
+ resolutionContext.AddLevelSwitch("$anotherSwitch", new LoggingLevelSwitch(LogEventLevel.Verbose));
- var stringArgumentValue = new StringArgumentValue(() => "$mySwitch");
+ var stringArgumentValue = new StringArgumentValue("$mySwitch");
var ex = Assert.Throws(() =>
- stringArgumentValue.ConvertTo(typeof(LoggingLevelSwitch), declaredSwitches)
+ stringArgumentValue.ConvertTo(typeof(LoggingLevelSwitch), resolutionContext)
);
Assert.Contains("$mySwitch", ex.Message);
@@ -168,9 +173,9 @@ public void ReferencingUndeclaredLevelSwitchThrows()
public void StringValuesConvertToTypeFromShortTypeName()
{
var shortTypeName = "System.Version";
- var stringArgumentValue = new StringArgumentValue(() => shortTypeName);
+ var stringArgumentValue = new StringArgumentValue(shortTypeName);
- var actual = (Type)stringArgumentValue.ConvertTo(typeof(Type), new Dictionary());
+ var actual = (Type)stringArgumentValue.ConvertTo(typeof(Type), new ResolutionContext());
Assert.Equal(typeof(Version), actual);
}
@@ -179,9 +184,9 @@ public void StringValuesConvertToTypeFromShortTypeName()
public void StringValuesConvertToTypeFromAssemblyQualifiedName()
{
var assemblyQualifiedName = typeof(Version).AssemblyQualifiedName;
- var stringArgumentValue = new StringArgumentValue(() => assemblyQualifiedName);
+ var stringArgumentValue = new StringArgumentValue(assemblyQualifiedName);
- var actual = (Type)stringArgumentValue.ConvertTo(typeof(Type), new Dictionary());
+ var actual = (Type)stringArgumentValue.ConvertTo(typeof(Type), new ResolutionContext());
Assert.Equal(typeof(Version), actual);
}
diff --git a/test/Serilog.Settings.Configuration.Tests/Support/JsonStringConfigSource.cs b/test/Serilog.Settings.Configuration.Tests/Support/JsonStringConfigSource.cs
index 2269491..31cf8ac 100644
--- a/test/Serilog.Settings.Configuration.Tests/Support/JsonStringConfigSource.cs
+++ b/test/Serilog.Settings.Configuration.Tests/Support/JsonStringConfigSource.cs
@@ -1,8 +1,9 @@
-using Microsoft.Extensions.Configuration;
-using Microsoft.Extensions.Configuration.Json;
-
+using System.Collections.Generic;
using System.IO;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Configuration.Json;
+
namespace Serilog.Settings.Configuration.Tests.Support
{
class JsonStringConfigSource : IConfigurationSource
@@ -24,6 +25,13 @@ public static IConfigurationSection LoadSection(string json, string section)
return new ConfigurationBuilder().Add(new JsonStringConfigSource(json)).Build().GetSection(section);
}
+ public static IDictionary LoadData(string json)
+ {
+ var provider = new JsonStringConfigProvider(json);
+ provider.Load();
+ return provider.Data;
+ }
+
class JsonStringConfigProvider : JsonConfigurationProvider
{
readonly string _json;
@@ -33,6 +41,8 @@ public JsonStringConfigProvider(string json) : base(new JsonConfigurationSource
_json = json;
}
+ public new IDictionary Data => base.Data;
+
public override void Load()
{
Load(StringToStream(_json));
diff --git a/test/Serilog.Settings.Configuration.Tests/Support/ReloadableConfigurationSource.cs b/test/Serilog.Settings.Configuration.Tests/Support/ReloadableConfigurationSource.cs
new file mode 100644
index 0000000..8f90b34
--- /dev/null
+++ b/test/Serilog.Settings.Configuration.Tests/Support/ReloadableConfigurationSource.cs
@@ -0,0 +1,37 @@
+using System.Collections.Generic;
+using Microsoft.Extensions.Configuration;
+
+namespace Serilog.Settings.Configuration.Tests.Support
+{
+ class ReloadableConfigurationSource : IConfigurationSource
+ {
+ readonly ReloadableConfigurationProvider _configProvider;
+ readonly IDictionary _source;
+
+ public ReloadableConfigurationSource(IDictionary source)
+ {
+ _source = source;
+ _configProvider = new ReloadableConfigurationProvider(source);
+ }
+
+ public IConfigurationProvider Build(IConfigurationBuilder builder) => _configProvider;
+
+ public void Reload() => _configProvider.Reload();
+
+ public void Set(string key, string value) => _source[key] = value;
+
+ class ReloadableConfigurationProvider : ConfigurationProvider
+ {
+ readonly IDictionary _source;
+
+ public ReloadableConfigurationProvider(IDictionary source)
+ {
+ _source = source;
+ }
+
+ public override void Load() => Data = _source;
+
+ public void Reload() => OnReload();
+ }
+ }
+}
diff --git a/test/TestDummies/Console/DummyConsoleSink.cs b/test/TestDummies/Console/DummyConsoleSink.cs
index e03de1f..a286bf0 100644
--- a/test/TestDummies/Console/DummyConsoleSink.cs
+++ b/test/TestDummies/Console/DummyConsoleSink.cs
@@ -17,9 +17,9 @@ public DummyConsoleSink(ConsoleTheme theme = null)
public static ConsoleTheme Theme;
[ThreadStatic]
- // ReSharper disable ThreadStaticFieldHasInitializer
- public static List Emitted = new List();
- // ReSharper restore ThreadStaticFieldHasInitializer
+ static List EmittedList = new List();
+
+ public static List Emitted => EmittedList ?? (EmittedList = new List());
public void Emit(LogEvent logEvent)
{
diff --git a/test/TestDummies/DummyLoggerConfigurationExtensions.cs b/test/TestDummies/DummyLoggerConfigurationExtensions.cs
index ec41b34..ecae9cd 100644
--- a/test/TestDummies/DummyLoggerConfigurationExtensions.cs
+++ b/test/TestDummies/DummyLoggerConfigurationExtensions.cs
@@ -109,9 +109,10 @@ public static LoggerConfiguration DummyWithLevelSwitch(
public static LoggerConfiguration DummyConsole(
this LoggerSinkConfiguration loggerSinkConfiguration,
LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum,
+ LoggingLevelSwitch levelSwitch = null,
ConsoleTheme theme = null)
{
- return loggerSinkConfiguration.Sink(new DummyConsoleSink(theme), restrictedToMinimumLevel);
+ return loggerSinkConfiguration.Sink(new DummyConsoleSink(theme), restrictedToMinimumLevel, levelSwitch);
}
public static LoggerConfiguration Dummy(