diff --git a/.editorconfig b/.editorconfig
index 3687ef4e..55dcd648 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -15,3 +15,5 @@ end_of_line = lf
[*.{cmd, bat}]
end_of_line = crlf
+
+csharp_style_namespace_declarations = file_scoped:suggestion
diff --git a/.gitignore b/.gitignore
index 29e6b229..c90bd95b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -132,7 +132,7 @@ publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
-# TODO: Comment the next line if you want to checkin your web deploy settings
+# TODO: Comment the next line if you want to checkin your web deploy settings
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
@@ -200,7 +200,4 @@ FakesAssemblies/
project.lock.json
-#Test files
-*.txt
-
artifacts/
diff --git a/Build.ps1 b/Build.ps1
index f58ea2ec..15133822 100644
--- a/Build.ps1
+++ b/Build.ps1
@@ -1,60 +1,38 @@
-echo "build: Build started"
+Write-Output "build: Build started"
Push-Location $PSScriptRoot
if(Test-Path .\artifacts) {
- echo "build: Cleaning .\artifacts"
- Remove-Item .\artifacts -Force -Recurse
+ Write-Output "build: Cleaning .\artifacts"
+ Remove-Item .\artifacts -Force -Recurse
}
-& dotnet restore --no-cache
-
$branch = @{ $true = $env:APPVEYOR_REPO_BRANCH; $false = $(git symbolic-ref --short -q HEAD) }[$env:APPVEYOR_REPO_BRANCH -ne $NULL];
$revision = @{ $true = "{0:00000}" -f [convert]::ToInt32("0" + $env:APPVEYOR_BUILD_NUMBER, 10); $false = "local" }[$env:APPVEYOR_BUILD_NUMBER -ne $NULL];
$suffix = @{ $true = ""; $false = "$($branch.Substring(0, [math]::Min(10,$branch.Length)))-$revision"}[$branch -eq "main" -and $revision -ne "local"]
$commitHash = $(git rev-parse --short HEAD)
$buildSuffix = @{ $true = "$($suffix)-$($commitHash)"; $false = "$($branch)-$($commitHash)" }[$suffix -ne ""]
-echo "build: Package version suffix is $suffix"
-echo "build: Build version suffix is $buildSuffix"
-
-foreach ($src in gci src/*) {
- Push-Location $src
-
- echo "build: Packaging project in $src"
-
- & dotnet build -c Release --version-suffix=$buildSuffix
-
- if($suffix) {
- & dotnet pack -c Release --include-source --no-build -o ../../artifacts --version-suffix=$suffix -p:ContinuousIntegrationBuild=true
- } else {
- & dotnet pack -c Release --include-source --no-build -o ../../artifacts -p:ContinuousIntegrationBuild=true
- }
- if($LASTEXITCODE -ne 0) { exit 1 }
+Write-Output "build: Package version suffix is $suffix"
+Write-Output "build: Build version suffix is $buildSuffix"
- Pop-Location
-}
-
-foreach ($test in gci test/*.Tests) {
- Push-Location $test
-
- echo "build: Testing project in $test"
+& dotnet build --configuration Release --version-suffix=$buildSuffix /p:ContinuousIntegrationBuild=true
- & dotnet test -c Release
- if($LASTEXITCODE -ne 0) { exit 3 }
+if($LASTEXITCODE -ne 0) { throw 'build failed' }
- Pop-Location
+if($suffix) {
+ & dotnet pack src\Serilog.Settings.Configuration --configuration Release --no-build --no-restore -o artifacts --version-suffix=$suffix
+} else {
+ & dotnet pack src\Serilog.Settings.Configuration --configuration Release --no-build --no-restore -o artifacts
}
-foreach ($test in gci test/*.PerformanceTests) {
- Push-Location $test
+if($LASTEXITCODE -ne 0) { throw 'pack failed' }
- echo "build: Building performance test project in $test"
+Write-Output "build: Testing"
- & dotnet build -c Release
- if($LASTEXITCODE -ne 0) { exit 2 }
-
- Pop-Location
-}
+# Dotnet test doesn't run separate TargetFrameworks in parallel: https://github.com/dotnet/sdk/issues/19147
+# Workaround: use `dotnet test` on dlls directly in order to pass the `--parallel` option to vstest.
+# The _reported_ runtime is wrong but the _actual_ used runtime is correct, see https://github.com/microsoft/vstest/issues/2037#issuecomment-720549173
+& dotnet test test\Serilog.Settings.Configuration.Tests\bin\Release\*\Serilog.Settings.Configuration.Tests.dll --parallel
-Pop-Location
\ No newline at end of file
+if($LASTEXITCODE -ne 0) { throw 'unit tests failed' }
\ No newline at end of file
diff --git a/Directory.Build.props b/Directory.Build.props
index c402103f..736814b0 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -2,10 +2,10 @@
latest
True
-
+ true
+ $(MSBuildThisFileDirectory)assets/Serilog.snk
+ enable
+ false
+ enable
-
-
-
-
diff --git a/README.md b/README.md
index cc4c7043..9695b7b7 100644
--- a/README.md
+++ b/README.md
@@ -56,7 +56,7 @@ For a more sophisticated example go to the [sample](sample/Sample) folder.
Root section name can be changed:
-```json
+```yaml
{
"CustomSection": {
...
@@ -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();
```
@@ -74,7 +75,7 @@ var logger = new LoggerConfiguration()
`Using` section contains list of **assemblies** in which configuration methods (`WriteTo.File()`, `Enrich.WithThreadId()`) reside.
-```json
+```yaml
"Serilog": {
"Using": [ "Serilog.Sinks.Console", "Serilog.Enrichers.Thread", /* ... */ ],
// ...
@@ -83,7 +84,7 @@ var logger = new LoggerConfiguration()
For .NET Core projects build tools produce `.deps.json` files and this package implements a convention using `Microsoft.Extensions.DependencyModel` to find any package among dependencies with `Serilog` anywhere in the name and pulls configuration methods from it, so the `Using` section in example above can be omitted:
-```json
+```yaml
{
"Serilog": {
"MinimumLevel": "Debug",
@@ -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();
```
@@ -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();
```
@@ -183,6 +186,22 @@ You can also declare `LoggingLevelSwitch`-es in custom section and reference the
Level updates to switches are also respected for a dynamic update.
+Since version 7.0.0, both declared switches (i.e. `Serilog:LevelSwitches` section) and minimum level override switches (i.e. `Serilog:MinimumLevel:Override` section) are exposed through a callback on the reader options so that a reference can be kept:
+
+```csharp
+var allSwitches = new Dictionary();
+var options = new ConfigurationReaderOptions
+{
+ OnLevelSwitchCreated = (switchName, levelSwitch) => allSwitches[switchName] = levelSwitch
+};
+
+var logger = new LoggerConfiguration()
+ .ReadFrom.Configuration(configuration, options)
+ .CreateLogger();
+
+LoggingLevelSwitch controlSwitch = allSwitches["$controlSwitch"];
+```
+
### WriteTo, Enrich, AuditTo, Destructure sections
These sections support simplified syntax, for example the following is valid if no arguments are needed by the sinks:
@@ -195,7 +214,7 @@ Or alternatively, the long-form (`"Name":` ...) syntax from the example above ca
By `Microsoft.Extensions.Configuration.Json` convention, array syntax implicitly defines index for each element in order to make unique paths for configuration keys. So the example above is equivalent to:
-```json
+```yaml
"WriteTo": {
"0": "Console",
"1": "DiagnosticTrace"
@@ -204,7 +223,7 @@ By `Microsoft.Extensions.Configuration.Json` convention, array syntax implicitly
And
-```json
+```yaml
"WriteTo:0": "Console",
"WriteTo:1": "DiagnosticTrace"
```
@@ -213,7 +232,7 @@ And
When overriding settings with [environment variables](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/?view=aspnetcore-3.1#environment-variables) it becomes less convenient and fragile, so you can specify custom names:
-```json
+```yaml
"WriteTo": {
"ConsoleSink": "Console",
"DiagnosticTraceSink": { "Name": "DiagnosticTrace" }
@@ -226,9 +245,9 @@ This section defines a static list of key-value pairs that will enrich log event
### Filter section
-This section defines filters that will be applied to log events. It is especially usefull in combination with _[Serilog.Expressions](https://github.com/serilog/serilog-expressions)_ (or legacy _[Serilog.Filters.Expressions](https://github.com/serilog/serilog-filters-expressions)_) package so you can write expression in text form:
+This section defines filters that will be applied to log events. It is especially useful in combination with _[Serilog.Expressions](https://github.com/serilog/serilog-expressions)_ (or legacy _[Serilog.Filters.Expressions](https://github.com/serilog/serilog-filters-expressions)_) package so you can write expression in text form:
-```json
+```yaml
"Filter": [{
"Name": "ByIncludingOnly",
"Args": {
@@ -239,7 +258,7 @@ This section defines filters that will be applied to log events. It is especiall
Using this package you can also declare `LoggingFilterSwitch`-es in custom section and reference them for filter parameters:
-```json
+```yaml
{
"Serilog": {
"FilterSwitches": { "filterSwitch": "Application = 'Sample'" },
@@ -256,11 +275,27 @@ Using this package you can also declare `LoggingFilterSwitch`-es in custom secti
Level updates to switches are also respected for a dynamic update.
+Since version 7.0.0, filter switches are exposed through a callback on the reader options so that a reference can be kept:
+
+```csharp
+var filterSwitches = new Dictionary();
+var options = new ConfigurationReaderOptions
+{
+ OnFilterSwitchCreated = (switchName, filterSwitch) => filterSwitches[switchName] = filterSwitch
+};
+
+var logger = new LoggerConfiguration()
+ .ReadFrom.Configuration(configuration, options)
+ .CreateLogger();
+
+ILoggingFilterSwitch filterSwitch = filterSwitches["filterSwitch"];
+```
+
### Nested configuration sections
Some Serilog packages require a reference to a logger configuration object. The sample program in this project illustrates this with the following entry configuring the _[Serilog.Sinks.Async](https://github.com/serilog/serilog-sinks-async)_ package to wrap the _[Serilog.Sinks.File](https://github.com/serilog/serilog-sinks-file)_ package. The `configure` parameter references the File sink configuration:
-```json
+```yaml
"WriteTo:Async": {
"Name": "Async",
"Args": {
@@ -282,11 +317,13 @@ 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 7.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:
-```json
+```yaml
{
"Args": {
"encoding": "System.Text.Encoding::UTF8",
@@ -299,7 +336,7 @@ Static member access can be used for passing to the configuration argument via [
If the parameter value is not a discrete value, it will try to find a best matching public constructor for the argument:
-```json
+```yaml
{
"Name": "Console",
"Args": {
@@ -307,6 +344,7 @@ If the parameter value is not a discrete value, it will try to find a best match
// `type` (or $type) is optional, must be specified for abstract declared parameter types
"type": "Serilog.Templates.ExpressionTemplate, Serilog.Expressions",
"template": "[{@t:HH:mm:ss} {@l:u3} {Coalesce(SourceContext, '')}] {@m}\n{@x}"
+ }
}
}
```
@@ -317,7 +355,7 @@ For other cases the package will use the configuration binding system provided b
If parameter type is an interface or an abstract class you need to specify the full type name that implements abstract type. The implementation type should have parameterless constructor.
-```json
+```yaml
"Destructure": [
{ "Name": "With", "Args": { "policy": "Sample.CustomPolicy, Sample" } },
...
@@ -377,8 +415,9 @@ public class Startup : FunctionsStartup
var functionDependencyContext = DependencyContext.Load(typeof(Startup).Assembly);
var hostConfig = sp.GetRequiredService();
+ 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);
@@ -405,3 +444,7 @@ In order to make auto-discovery of configuration assemblies work, modify Functio
```
+
+### Versioning
+
+This package tracks the versioning and target framework support of its [_Microsoft.Extensions.Configuration_](https://nuget.org/packages/Microsoft.Extensions.Configuration) dependency.
diff --git a/appveyor.yml b/appveyor.yml
index 272c7d79..b8049994 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -5,9 +5,27 @@ skip_tags: true
image:
- Visual Studio 2022
- Ubuntu
+ - macOS
build_script:
-- pwsh: ./Build.ps1
+- pwsh: |
+ if ($isWindows) {
+ Invoke-WebRequest "https://dot.net/v1/dotnet-install.ps1" -OutFile "./dotnet-install.ps1"
+ ./dotnet-install.ps1 -JSonFile global.json -Architecture x64 -InstallDir 'C:\Program Files\dotnet'
+ ./Build.ps1
+ }
+ if ($isLinux) {
+ Invoke-WebRequest "https://dot.net/v1/dotnet-install.sh" -OutFile "./dotnet-install.sh"
+ sudo chmod u+x dotnet-install.sh
+ sudo ./dotnet-install.sh --jsonfile global.json --architecture x64 --install-dir '/usr/share/dotnet'
+ ./Build.ps1
+ }
+ if ($isMacOS) {
+ Invoke-WebRequest "https://dot.net/v1/dotnet-install.sh" -OutFile "./dotnet-install.sh"
+ sudo chmod u+x dotnet-install.sh
+ sudo ./dotnet-install.sh --jsonfile global.json --architecture x64 --install-dir '/usr/local/share/dotnet'
+ ./Build.ps1
+ }
test: off
diff --git a/global.json b/global.json
new file mode 100644
index 00000000..1d927afd
--- /dev/null
+++ b/global.json
@@ -0,0 +1,7 @@
+{
+ "sdk": {
+ "version": "7.0.201",
+ "allowPrerelease": false,
+ "rollForward": "latestFeature"
+ }
+}
diff --git a/sample/Sample/CustomFilter.cs b/sample/Sample/CustomFilter.cs
new file mode 100644
index 00000000..99083c61
--- /dev/null
+++ b/sample/Sample/CustomFilter.cs
@@ -0,0 +1,21 @@
+using Serilog.Core;
+using Serilog.Events;
+
+namespace Sample;
+
+// The filter syntax in the sample configuration file is
+// processed by the Serilog.Filters.Expressions package.
+public class CustomFilter : ILogEventFilter
+{
+ readonly LogEventLevel _levelFilter;
+
+ public CustomFilter(LogEventLevel levelFilter = LogEventLevel.Information)
+ {
+ _levelFilter = levelFilter;
+ }
+
+ public bool IsEnabled(LogEvent logEvent)
+ {
+ return logEvent.Level >= _levelFilter;
+ }
+}
diff --git a/sample/Sample/CustomPolicy.cs b/sample/Sample/CustomPolicy.cs
new file mode 100644
index 00000000..56e1b2ae
--- /dev/null
+++ b/sample/Sample/CustomPolicy.cs
@@ -0,0 +1,24 @@
+using System.Diagnostics.CodeAnalysis;
+using Serilog.Core;
+using Serilog.Events;
+
+namespace Sample;
+
+public class CustomPolicy : IDestructuringPolicy
+{
+ public bool TryDestructure(object value, ILogEventPropertyValueFactory propertyValueFactory, [NotNullWhen(true)] out LogEventPropertyValue? result)
+ {
+ result = null;
+
+ if (value is LoginData loginData)
+ {
+ result = new StructureValue(
+ new List
+ {
+ new("Username", new ScalarValue(loginData.Username))
+ });
+ }
+
+ return (result != null);
+ }
+}
diff --git a/sample/Sample/LoginData.cs b/sample/Sample/LoginData.cs
new file mode 100644
index 00000000..2d517a74
--- /dev/null
+++ b/sample/Sample/LoginData.cs
@@ -0,0 +1,8 @@
+namespace Sample;
+
+public class LoginData
+{
+ public string? Username;
+ // ReSharper disable once NotAccessedField.Global
+ public string? Password;
+}
diff --git a/sample/Sample/Program.cs b/sample/Sample/Program.cs
index 4f4d7f9f..8440c57f 100644
--- a/sample/Sample/Program.cs
+++ b/sample/Sample/Program.cs
@@ -1,106 +1,46 @@
-using System;
-
-using System.IO;
-using System.Linq;
-using System.Collections.Generic;
-using System.Threading;
-
-using Microsoft.Extensions.Configuration;
-
+using Microsoft.Extensions.Configuration;
+using Sample;
using Serilog;
using Serilog.Core;
-using Serilog.Events;
using Serilog.Debugging;
// ReSharper disable UnusedType.Global
-namespace Sample
-{
- public class Program
- {
- public static void Main(string[] args)
- {
- SelfLog.Enable(Console.Error);
-
- Thread.CurrentThread.Name = "Main thread";
-
- var configuration = new ConfigurationBuilder()
- .SetBasePath(Directory.GetCurrentDirectory())
- .AddJsonFile(path: "appsettings.json", optional: false, reloadOnChange: true)
- .Build();
+SelfLog.Enable(Console.Error);
- var logger = new LoggerConfiguration()
- .ReadFrom.Configuration(configuration)
- .CreateLogger();
+Thread.CurrentThread.Name = "Main thread";
- logger.Information("Args: {Args}", args);
+var configuration = new ConfigurationBuilder()
+ .SetBasePath(Directory.GetCurrentDirectory())
+ .AddJsonFile(path: "appsettings.json", optional: false, reloadOnChange: true)
+ .Build();
- do
- {
- logger.ForContext().Information("Hello, world!");
- logger.ForContext().Error("Hello, world!");
- logger.ForContext(Constants.SourceContextPropertyName, "Microsoft").Warning("Hello, world!");
- logger.ForContext(Constants.SourceContextPropertyName, "Microsoft").Error("Hello, world!");
- logger.ForContext(Constants.SourceContextPropertyName, "MyApp.Something.Tricky").Verbose("Hello, world!");
+var logger = new LoggerConfiguration()
+ .ReadFrom.Configuration(configuration)
+ .CreateLogger();
- logger.Information("Destructure with max object nesting depth:\n{@NestedObject}",
- new { FiveDeep = new { Two = new { Three = new { Four = new { Five = "the end" } } } } });
+logger.Information("Args: {Args}", args);
- logger.Information("Destructure with max string length:\n{@LongString}",
- new { TwentyChars = "0123456789abcdefghij" });
-
- logger.Information("Destructure with max collection count:\n{@BigData}",
- new { TenItems = new[] { "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten" } });
-
- logger.Information("Destructure with policy to strip password:\n{@LoginData}",
- new LoginData { Username = "BGates", Password = "isityearoflinuxyet" });
-
- Console.WriteLine("\nPress \"q\" to quit, or any other key to run again.\n");
- }
- while (!args.Contains("--run-once") && (Console.ReadKey().KeyChar != 'q'));
- }
- }
-
- // The filter syntax in the sample configuration file is
- // processed by the Serilog.Filters.Expressions package.
- public class CustomFilter : ILogEventFilter
- {
- readonly LogEventLevel _levelFilter;
-
- public CustomFilter(LogEventLevel levelFilter = LogEventLevel.Information)
- {
- _levelFilter = levelFilter;
- }
+do
+{
+ logger.ForContext().Information("Hello, world!");
+ logger.ForContext().Error("Hello, world!");
+ logger.ForContext(Constants.SourceContextPropertyName, "Microsoft").Warning("Hello, world!");
+ logger.ForContext(Constants.SourceContextPropertyName, "Microsoft").Error("Hello, world!");
+ logger.ForContext(Constants.SourceContextPropertyName, "MyApp.Something.Tricky").Verbose("Hello, world!");
- public bool IsEnabled(LogEvent logEvent)
- {
- return logEvent.Level >= _levelFilter;
- }
- }
+ logger.Information("Destructure with max object nesting depth:\n{@NestedObject}",
+ new { FiveDeep = new { Two = new { Three = new { Four = new { Five = "the end" } } } } });
- public class LoginData
- {
- public string Username;
- // ReSharper disable once NotAccessedField.Global
- public string Password;
- }
+ logger.Information("Destructure with max string length:\n{@LongString}",
+ new { TwentyChars = "0123456789abcdefghij" });
- public class CustomPolicy : IDestructuringPolicy
- {
- public bool TryDestructure(object value, ILogEventPropertyValueFactory propertyValueFactory, out LogEventPropertyValue result)
- {
- result = null;
+ logger.Information("Destructure with max collection count:\n{@BigData}",
+ new { TenItems = new[] { "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten" } });
- if (value is LoginData loginData)
- {
- result = new StructureValue(
- new List
- {
- new("Username", new ScalarValue(loginData.Username))
- });
- }
+ logger.Information("Destructure with policy to strip password:\n{@LoginData}",
+ new LoginData { Username = "BGates", Password = "isityearoflinuxyet" });
- return (result != null);
- }
- }
+ Console.WriteLine("\nPress \"q\" to quit, or any other key to run again.\n");
}
+while (!args.Contains("--run-once") && (Console.ReadKey().KeyChar != 'q'));
diff --git a/sample/Sample/Sample.csproj b/sample/Sample/Sample.csproj
index 25aedf5b..6357b2be 100644
--- a/sample/Sample/Sample.csproj
+++ b/sample/Sample/Sample.csproj
@@ -1,7 +1,7 @@
- net6.0;netcoreapp3.1;net462
+ net6.0;net7.0;net462
Exe
@@ -14,7 +14,7 @@
-
+
@@ -22,6 +22,7 @@
+
diff --git a/serilog-settings-configuration.sln b/serilog-settings-configuration.sln
index ec9c9b22..f0209fd7 100644
--- a/serilog-settings-configuration.sln
+++ b/serilog-settings-configuration.sln
@@ -17,6 +17,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "assets", "assets", "{62D0B9
LICENSE = LICENSE
README.md = README.md
serilog-settings-configuration.sln.DotSettings = serilog-settings-configuration.sln.DotSettings
+ global.json = global.json
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{D551DCB0-7771-4D01-BEBD-F7B57D1CF0E3}"
@@ -31,6 +32,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sample", "sample\Sample\Sam
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestDummies", "test\TestDummies\TestDummies.csproj", "{B7CF5068-DD19-4868-A268-5280BDE90361}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestApp", "test\TestApp\TestApp.csproj", "{1B6E08F3-16C9-4912-BEEE-57DB78C92A12}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -53,6 +56,8 @@ Global
{B7CF5068-DD19-4868-A268-5280BDE90361}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B7CF5068-DD19-4868-A268-5280BDE90361}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B7CF5068-DD19-4868-A268-5280BDE90361}.Release|Any CPU.Build.0 = Release|Any CPU
+ {1B6E08F3-16C9-4912-BEEE-57DB78C92A12}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {1B6E08F3-16C9-4912-BEEE-57DB78C92A12}.Release|Any CPU.ActiveCfg = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -62,6 +67,7 @@ Global
{F793C6E8-C40A-4018-8884-C97E2BE38A54} = {D551DCB0-7771-4D01-BEBD-F7B57D1CF0E3}
{A00E5E32-54F9-401A-BBA1-2F6FCB6366CD} = {D24872B9-57F3-42A7-BC8D-F9DA222FCE1B}
{B7CF5068-DD19-4868-A268-5280BDE90361} = {D551DCB0-7771-4D01-BEBD-F7B57D1CF0E3}
+ {1B6E08F3-16C9-4912-BEEE-57DB78C92A12} = {D551DCB0-7771-4D01-BEBD-F7B57D1CF0E3}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {485F8843-42D7-4267-B5FB-20FE9181DEE9}
diff --git a/serilog-settings-configuration.sln.DotSettings b/serilog-settings-configuration.sln.DotSettings
index 2fdb7b2f..c9460776 100644
--- a/serilog-settings-configuration.sln.DotSettings
+++ b/serilog-settings-configuration.sln.DotSettings
@@ -1,6 +1,6 @@
-
-
+
+
True
True
True
@@ -477,7 +477,7 @@ II.2.12 <HandlesEvent />
</Patterns>
CustomLayout
-
+
True
False
True
@@ -538,11 +538,13 @@ II.2.12 <HandlesEvent />
<Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" />
<Policy Inspect="True" Prefix="T" Suffix="" Style="AaBb" />
<Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" />
+ True
True
True
True
True
True
+ True
True
True
True
@@ -551,11 +553,11 @@ II.2.12 <HandlesEvent />
True
True
True
-
-
-
-
+
+
+
+
<data />
<data><IncludeFilters /><ExcludeFilters /></data>
True
- True
\ No newline at end of file
+ True
diff --git a/src/Serilog.Settings.Configuration/ConfigurationLoggerConfigurationExtensions.cs b/src/Serilog.Settings.Configuration/ConfigurationLoggerConfigurationExtensions.cs
index aa709456..0a2bb7d3 100644
--- a/src/Serilog.Settings.Configuration/ConfigurationLoggerConfigurationExtensions.cs
+++ b/src/Serilog.Settings.Configuration/ConfigurationLoggerConfigurationExtensions.cs
@@ -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.
@@ -12,7 +12,6 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-using System;
using System.Reflection;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyModel;
@@ -20,189 +19,229 @@
using Serilog.Settings.Configuration;
using Serilog.Settings.Configuration.Assemblies;
-namespace Serilog
+namespace Serilog;
+
+///
+/// Extends with support for System.Configuration appSettings elements.
+///
+public static class ConfigurationLoggerConfigurationExtensions
{
///
- /// Extends with support for System.Configuration appSettings elements.
+ /// Configuration section name required by this package.
+ ///
+ public const string DefaultSectionName = "Serilog";
+
+ ///
+ /// 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.
///
- public static class ConfigurationLoggerConfigurationExtensions
+ /// 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.
+ [Obsolete("Use ReadFrom.Configuration(IConfiguration configuration, ConfigurationReaderOptions readerOptions) instead.")]
+ public static LoggerConfiguration Configuration(
+ this LoggerSettingsConfiguration settingConfiguration,
+ IConfiguration configuration,
+ string sectionName,
+ DependencyContext? dependencyContext = null)
{
- ///
- /// Configuration section name required by this package.
- ///
- public const string DefaultSectionName = "Serilog";
-
- ///
- /// 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.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.
- ///
- /// Logger setting configuration.
- /// The Serilog configuration 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.
- [Obsolete("Use ReadFrom.Configuration(IConfiguration configuration, string sectionName, DependencyContext dependencyContext) instead.")]
- public static LoggerConfiguration ConfigurationSection(
- this LoggerSettingsConfiguration settingConfiguration,
- IConfigurationSection configSection,
- DependencyContext dependencyContext = null)
- {
- 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,
- assemblyFinder,
- configuration: null));
- }
-
- ///
- /// 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);
-
- 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.
- ///
- /// Logger setting 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,
- ConfigurationAssemblySource configurationAssemblySource)
- {
- if (settingConfiguration == null) throw new ArgumentNullException(nameof(settingConfiguration));
- if (configSection == null) throw new ArgumentNullException(nameof(configSection));
-
- var assemblyFinder = AssemblyFinder.ForSource(configurationAssemblySource);
-
- return settingConfiguration.Settings(new ConfigurationReader(configSection, assemblyFinder, configuration: null));
- }
-
- ///
- /// Reads logger settings from the provided configuration object using the provided section name.
- ///
- /// Logger setting configuration.
- /// A configuration object which contains a Serilog section.
- /// A section name for section which contains a Serilog section.
- /// A collection of assemblies that contains sinks and other types.
- /// An object allowing configuration to continue.
- public static LoggerConfiguration Configuration(
- this LoggerSettingsConfiguration settingConfiguration,
- IConfiguration configuration,
- string sectionName,
- params Assembly[] assemblies)
+ 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 readerOptions = new ConfigurationReaderOptions(dependencyContext) { SectionName = sectionName, FormatProvider = null };
+ return Configuration(settingConfiguration, configuration, readerOptions);
+ }
+
+ ///
+ /// 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.
+ [Obsolete("Use ReadFrom.Configuration(IConfiguration configuration, ConfigurationReaderOptions readerOptions) instead.")]
+ public static LoggerConfiguration Configuration(
+ this LoggerSettingsConfiguration settingConfiguration,
+ IConfiguration configuration,
+ DependencyContext dependencyContext)
+ => 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.
+ ///
+ /// Logger setting configuration.
+ /// The Serilog configuration 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.
+ [Obsolete("Use ReadFrom.Configuration(IConfiguration configuration, string sectionName, DependencyContext dependencyContext) instead.")]
+ public static LoggerConfiguration ConfigurationSection(
+ this LoggerSettingsConfiguration settingConfiguration,
+ IConfigurationSection configSection,
+ DependencyContext? dependencyContext = null)
+ {
+ 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,
+ assemblyFinder,
+ new ConfigurationReaderOptions { FormatProvider = null },
+ configuration: null));
+ }
+
+ ///
+ /// 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.
+ [Obsolete("Use ReadFrom.Configuration(IConfiguration configuration, ConfigurationReaderOptions readerOptions) instead.")]
+ 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 readerOptions = new ConfigurationReaderOptions(configurationAssemblySource) { SectionName = sectionName, FormatProvider = null };
+ return Configuration(settingConfiguration, configuration, readerOptions);
+ }
+
+ ///
+ /// 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.
+ [Obsolete("Use ReadFrom.Configuration(IConfiguration configuration, ConfigurationReaderOptions readerOptions) instead.")]
+ 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.
+ ///
+ /// Logger setting 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,
+ ConfigurationAssemblySource configurationAssemblySource)
+ {
+ if (settingConfiguration == null) throw new ArgumentNullException(nameof(settingConfiguration));
+ if (configSection == null) throw new ArgumentNullException(nameof(configSection));
+
+ var assemblyFinder = AssemblyFinder.ForSource(configurationAssemblySource);
+
+ return settingConfiguration.Settings(new ConfigurationReader(configSection, assemblyFinder, new ConfigurationReaderOptions { FormatProvider = null }, configuration: null));
+ }
+
+ ///
+ /// Reads logger settings from the provided configuration object using the provided section name.
+ ///
+ /// Logger setting configuration.
+ /// A configuration object which contains a Serilog section.
+ /// A section name for section which contains a Serilog section.
+ /// A collection of assemblies that contains sinks and other types.
+ /// An object allowing configuration to continue.
+ [Obsolete("Use ReadFrom.Configuration(IConfiguration configuration, ConfigurationReaderOptions readerOptions) instead.")]
+ public static LoggerConfiguration Configuration(
+ this LoggerSettingsConfiguration settingConfiguration,
+ IConfiguration configuration,
+ string sectionName,
+ params Assembly[] assemblies)
+ {
+ 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 readerOptions = new ConfigurationReaderOptions(assemblies) { SectionName = sectionName, FormatProvider = null };
+ return Configuration(settingConfiguration, configuration, readerOptions);
+ }
+
+ ///
+ /// Reads logger settings from the provided configuration object using the default section name.
+ ///
+ /// Logger setting configuration.
+ /// A configuration object which contains a Serilog section.
+ /// A collection of assemblies that contains sinks and other types.
+ /// An object allowing configuration to continue.
+ [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);
+
+ ///
+ /// Reads logger settings from the provided configuration object using the specified context.
+ ///
+ /// Logger setting configuration.
+ /// A configuration object which contains a Serilog section.
+ /// Options to adjust how the configuration object is processed.
+ /// An object allowing configuration to continue.
+ public static LoggerConfiguration Configuration(
+ this LoggerSettingsConfiguration settingConfiguration,
+ IConfiguration configuration,
+ ConfigurationReaderOptions? readerOptions = null)
+ {
+ var configurationReader = readerOptions switch
{
- if (settingConfiguration == null) throw new ArgumentNullException(nameof(settingConfiguration));
- 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)));
- }
-
- ///
- /// Reads logger settings from the provided configuration object using the default section name.
- ///
- /// Logger setting configuration.
- /// A configuration object which contains a Serilog section.
- /// A collection of assemblies that contains sinks and other types.
- /// An object allowing configuration to continue.
- public static LoggerConfiguration Configuration(
- this LoggerSettingsConfiguration settingConfiguration,
- IConfiguration configuration,
- params Assembly[] assemblies)
- => Configuration(settingConfiguration, configuration, DefaultSectionName, assemblies);
+ { 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 = string.IsNullOrWhiteSpace(readerOptions.SectionName) ? configuration : configuration.GetSection(readerOptions.SectionName!);
+ return new ConfigurationReader(section, assemblyFinder, readerOptions, configuration);
+ }
+
+ static ConfigurationReader GetConfigurationReader(IConfiguration configuration, ConfigurationReaderOptions readerOptions, ConfigurationAssemblySource source)
+ {
+ var assemblyFinder = AssemblyFinder.ForSource(source);
+ var section = string.IsNullOrWhiteSpace(readerOptions.SectionName) ? configuration : configuration.GetSection(readerOptions.SectionName!);
+ return new ConfigurationReader(section, assemblyFinder, readerOptions, configuration);
+ }
+
+ static ConfigurationReader GetConfigurationReader(IConfiguration configuration, ConfigurationReaderOptions readerOptions, IReadOnlyCollection assemblies)
+ {
+ var section = string.IsNullOrWhiteSpace(readerOptions.SectionName) ? configuration : configuration.GetSection(readerOptions.SectionName!);
+ return new ConfigurationReader(section, assemblies, new ResolutionContext(configuration, readerOptions));
}
}
diff --git a/src/Serilog.Settings.Configuration/Serilog.Settings.Configuration.csproj b/src/Serilog.Settings.Configuration/Serilog.Settings.Configuration.csproj
index 76b2299c..ed870186 100644
--- a/src/Serilog.Settings.Configuration/Serilog.Settings.Configuration.csproj
+++ b/src/Serilog.Settings.Configuration/Serilog.Settings.Configuration.csproj
@@ -2,20 +2,16 @@
Microsoft.Extensions.Configuration (appsettings.json) support for Serilog.
- 3.4.0
- latest
+
+ 7.0.0
Serilog Contributors
- netstandard2.0;net451;net461
- true
+
+ net462;netstandard2.0;net6.0;net7.0
true
Serilog.Settings.Configuration
- ../../assets/Serilog.snk
- true
- true
- Serilog.Settings.Configuration
serilog;json
icon.png
- https://github.com/serilog/serilog-settings-configuration/
Apache-2.0
Serilog
true
@@ -25,17 +21,19 @@
-
-
-
-
+
-
-
+
+
+
+
+
-
-
+
+
+
+
diff --git a/src/Serilog.Settings.Configuration/Settings/Configuration/Assemblies/AssemblyFinder.cs b/src/Serilog.Settings.Configuration/Settings/Configuration/Assemblies/AssemblyFinder.cs
index cc16a557..9582e6a4 100644
--- a/src/Serilog.Settings.Configuration/Settings/Configuration/Assemblies/AssemblyFinder.cs
+++ b/src/Serilog.Settings.Configuration/Settings/Configuration/Assemblies/AssemblyFinder.cs
@@ -1,50 +1,34 @@
-using System;
-using System.Collections.Generic;
-using System.Reflection;
+using System.Reflection;
using Microsoft.Extensions.DependencyModel;
-namespace Serilog.Settings.Configuration.Assemblies
+namespace Serilog.Settings.Configuration.Assemblies;
+
+abstract class AssemblyFinder
{
- abstract class AssemblyFinder
- {
- public abstract IReadOnlyList FindAssembliesContainingName(string nameToFind);
+ public abstract IReadOnlyList FindAssembliesContainingName(string nameToFind);
- protected static bool IsCaseInsensitiveMatch(string text, string textToFind)
- {
- return text != null && text.ToLowerInvariant().Contains(textToFind.ToLowerInvariant());
- }
+ protected static bool IsCaseInsensitiveMatch(string? text, string textToFind)
+ {
+ return text != null && text.ToLowerInvariant().Contains(textToFind.ToLowerInvariant());
+ }
- public static AssemblyFinder Auto()
- {
- try
- {
- // 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);
- }
- }
- catch (NotSupportedException) when (typeof(object).Assembly.Location is "") // bundled mode detection
- {
- }
-
- return new DllScanningAssemblyFinder();
- }
+ public static AssemblyFinder Auto()
+ {
+ return new CompositeAssemblyFinder(new DependencyContextAssemblyFinder(DependencyContext.Default), new DllScanningAssemblyFinder());
+ }
- public static AssemblyFinder ForSource(ConfigurationAssemblySource configurationAssemblySource)
+ public static AssemblyFinder ForSource(ConfigurationAssemblySource configurationAssemblySource)
+ {
+ return configurationAssemblySource switch
{
- return configurationAssemblySource switch
- {
- ConfigurationAssemblySource.UseLoadedAssemblies => Auto(),
- ConfigurationAssemblySource.AlwaysScanDllFiles => new DllScanningAssemblyFinder(),
- _ => throw new ArgumentOutOfRangeException(nameof(configurationAssemblySource), configurationAssemblySource, null),
- };
- }
+ ConfigurationAssemblySource.UseLoadedAssemblies => Auto(),
+ ConfigurationAssemblySource.AlwaysScanDllFiles => new DllScanningAssemblyFinder(),
+ _ => throw new ArgumentOutOfRangeException(nameof(configurationAssemblySource), configurationAssemblySource, null),
+ };
+ }
- public static AssemblyFinder ForDependencyContext(DependencyContext dependencyContext)
- {
- return new DependencyContextAssemblyFinder(dependencyContext);
- }
+ public static AssemblyFinder ForDependencyContext(DependencyContext dependencyContext)
+ {
+ return new DependencyContextAssemblyFinder(dependencyContext);
}
}
diff --git a/src/Serilog.Settings.Configuration/Settings/Configuration/Assemblies/CompositeAssemblyFinder.cs b/src/Serilog.Settings.Configuration/Settings/Configuration/Assemblies/CompositeAssemblyFinder.cs
new file mode 100644
index 00000000..9f3c9ef2
--- /dev/null
+++ b/src/Serilog.Settings.Configuration/Settings/Configuration/Assemblies/CompositeAssemblyFinder.cs
@@ -0,0 +1,23 @@
+using System.Reflection;
+
+namespace Serilog.Settings.Configuration.Assemblies;
+
+class CompositeAssemblyFinder : AssemblyFinder
+{
+ readonly AssemblyFinder[] _assemblyFinders;
+
+ public CompositeAssemblyFinder(params AssemblyFinder[] assemblyFinders)
+ {
+ _assemblyFinders = assemblyFinders;
+ }
+
+ public override IReadOnlyList FindAssembliesContainingName(string nameToFind)
+ {
+ var assemblyNames = new List();
+ foreach (var assemblyFinder in _assemblyFinders)
+ {
+ assemblyNames.AddRange(assemblyFinder.FindAssembliesContainingName(nameToFind));
+ }
+ return assemblyNames;
+ }
+}
diff --git a/src/Serilog.Settings.Configuration/Settings/Configuration/Assemblies/ConfigurationAssemblySource.cs b/src/Serilog.Settings.Configuration/Settings/Configuration/Assemblies/ConfigurationAssemblySource.cs
index de5800cf..b6a60657 100644
--- a/src/Serilog.Settings.Configuration/Settings/Configuration/Assemblies/ConfigurationAssemblySource.cs
+++ b/src/Serilog.Settings.Configuration/Settings/Configuration/Assemblies/ConfigurationAssemblySource.cs
@@ -12,21 +12,20 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-namespace Serilog.Settings.Configuration
+namespace Serilog.Settings.Configuration;
+
+///
+/// Defines how the package will identify the assemblies which are scanned for sinks and other Type information.
+///
+public enum ConfigurationAssemblySource
{
///
- /// Defines how the package will identify the assemblies which are scanned for sinks and other Type information.
+ /// Try to scan the assemblies already in memory. This is the default. If GetEntryAssembly is null, fallback to DLL scanning.
///
- public enum ConfigurationAssemblySource
- {
- ///
- /// Try to scan the assemblies already in memory. This is the default. If GetEntryAssembly is null, fallback to DLL scanning.
- ///
- UseLoadedAssemblies,
+ UseLoadedAssemblies,
- ///
- /// Scan for assemblies in DLLs from the working directory. This is the fallback when GetEntryAssembly is null.
- ///
- AlwaysScanDllFiles
- }
+ ///
+ /// Scan for assemblies in DLLs from the working directory. This is the fallback when GetEntryAssembly is null.
+ ///
+ AlwaysScanDllFiles
}
diff --git a/src/Serilog.Settings.Configuration/Settings/Configuration/Assemblies/DependencyContextAssemblyFinder.cs b/src/Serilog.Settings.Configuration/Settings/Configuration/Assemblies/DependencyContextAssemblyFinder.cs
index 7a6feaa6..2ed07ce9 100644
--- a/src/Serilog.Settings.Configuration/Settings/Configuration/Assemblies/DependencyContextAssemblyFinder.cs
+++ b/src/Serilog.Settings.Configuration/Settings/Configuration/Assemblies/DependencyContextAssemblyFinder.cs
@@ -1,37 +1,36 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Reflection;
+using System.Reflection;
using Microsoft.Extensions.DependencyModel;
-namespace Serilog.Settings.Configuration.Assemblies
+namespace Serilog.Settings.Configuration.Assemblies;
+
+sealed class DependencyContextAssemblyFinder : AssemblyFinder
{
- sealed class DependencyContextAssemblyFinder : AssemblyFinder
+ readonly DependencyContext? _dependencyContext;
+
+ public DependencyContextAssemblyFinder(DependencyContext? dependencyContext)
{
- readonly DependencyContext _dependencyContext;
+ _dependencyContext = dependencyContext;
+ }
- public DependencyContextAssemblyFinder(DependencyContext dependencyContext)
- {
- _dependencyContext = dependencyContext ?? throw new ArgumentNullException(nameof(dependencyContext));
- }
+ public override IReadOnlyList FindAssembliesContainingName(string nameToFind)
+ {
+ if (_dependencyContext == null)
+ return Array.Empty();
- public override IReadOnlyList FindAssembliesContainingName(string nameToFind)
- {
- var query = from library in _dependencyContext.RuntimeLibraries
- where IsReferencingSerilog(library)
- from assemblyName in library.GetDefaultAssemblyNames(_dependencyContext)
- where IsCaseInsensitiveMatch(assemblyName.Name, nameToFind)
- select assemblyName;
+ var query = from library in _dependencyContext.RuntimeLibraries
+ where IsReferencingSerilog(library)
+ from assemblyName in library.GetDefaultAssemblyNames(_dependencyContext)
+ where IsCaseInsensitiveMatch(assemblyName.Name, nameToFind)
+ select assemblyName;
- return query.ToList().AsReadOnly();
-
- static bool IsReferencingSerilog(Library library)
- {
- const string Serilog = "serilog";
- return library.Dependencies.Any(dependency =>
- dependency.Name.StartsWith(Serilog, StringComparison.OrdinalIgnoreCase) &&
- (dependency.Name.Length == Serilog.Length || dependency.Name[Serilog.Length] == '.'));
- }
+ return query.ToList();
+
+ static bool IsReferencingSerilog(Library library)
+ {
+ const string Serilog = "serilog";
+ return library.Dependencies.Any(dependency =>
+ dependency.Name.StartsWith(Serilog, StringComparison.OrdinalIgnoreCase) &&
+ (dependency.Name.Length == Serilog.Length || dependency.Name[Serilog.Length] == '.'));
}
}
}
diff --git a/src/Serilog.Settings.Configuration/Settings/Configuration/Assemblies/DllScanningAssemblyFinder.cs b/src/Serilog.Settings.Configuration/Settings/Configuration/Assemblies/DllScanningAssemblyFinder.cs
index 9cc9139f..c38c88bf 100644
--- a/src/Serilog.Settings.Configuration/Settings/Configuration/Assemblies/DllScanningAssemblyFinder.cs
+++ b/src/Serilog.Settings.Configuration/Settings/Configuration/Assemblies/DllScanningAssemblyFinder.cs
@@ -1,65 +1,64 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
using System.Reflection;
-namespace Serilog.Settings.Configuration.Assemblies
+namespace Serilog.Settings.Configuration.Assemblies;
+
+sealed class DllScanningAssemblyFinder : AssemblyFinder
{
- sealed class DllScanningAssemblyFinder : AssemblyFinder
+ public override IReadOnlyList FindAssembliesContainingName(string nameToFind)
{
- public override IReadOnlyList FindAssembliesContainingName(string nameToFind)
+ var probeDirs = new List();
+
+ if (!string.IsNullOrEmpty(AppDomain.CurrentDomain.BaseDirectory))
{
- var probeDirs = new List();
-
- if (!string.IsNullOrEmpty(AppDomain.CurrentDomain.BaseDirectory))
- {
- probeDirs.Add(AppDomain.CurrentDomain.BaseDirectory);
+ probeDirs.Add(AppDomain.CurrentDomain.BaseDirectory);
#if NETFRAMEWORK
- var privateBinPath = AppDomain.CurrentDomain.SetupInformation.PrivateBinPath;
- if (!string.IsNullOrEmpty(privateBinPath))
+ var privateBinPath = AppDomain.CurrentDomain.SetupInformation.PrivateBinPath;
+ if (!string.IsNullOrEmpty(privateBinPath))
+ {
+ foreach (var path in privateBinPath.Split(';'))
{
- foreach (var path in privateBinPath.Split(';'))
+ if (Path.IsPathRooted(path))
{
- if (Path.IsPathRooted(path))
- {
- probeDirs.Add(path);
- }
- else
- {
- probeDirs.Add(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, path));
- }
+ probeDirs.Add(path);
+ }
+ else
+ {
+ probeDirs.Add(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, path));
}
}
-#endif
}
- else
+#endif
+ }
+ else
+ {
+ var assemblyLocation = Path.GetDirectoryName(typeof(AssemblyFinder).Assembly.Location);
+ if (assemblyLocation != null)
{
- probeDirs.Add(Path.GetDirectoryName(typeof(AssemblyFinder).Assembly.Location));
+ probeDirs.Add(assemblyLocation);
}
+ }
- 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;
+ 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();
+ return query.ToList();
- static AssemblyName TryGetAssemblyNameFrom(string path)
+ static AssemblyName? TryGetAssemblyNameFrom(string path)
+ {
+ try
{
- try
- {
- return AssemblyName.GetAssemblyName(path);
- }
- catch (BadImageFormatException)
- {
- return null;
- }
+ 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 642d0bfd..2a6c3170 100644
--- a/src/Serilog.Settings.Configuration/Settings/Configuration/ConfigurationReader.cs
+++ b/src/Serilog.Settings.Configuration/Settings/Configuration/ConfigurationReader.cs
@@ -1,7 +1,5 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
using System.Reflection;
+using System.Runtime.CompilerServices;
using System.Text.RegularExpressions;
using Microsoft.Extensions.Configuration;
@@ -13,528 +11,589 @@
using Serilog.Events;
using Serilog.Settings.Configuration.Assemblies;
-namespace Serilog.Settings.Configuration
+namespace Serilog.Settings.Configuration;
+
+class ConfigurationReader : IConfigurationReader
{
- class ConfigurationReader : IConfigurationReader
- {
- const string LevelSwitchNameRegex = @"^\${0,1}[A-Za-z]+[A-Za-z0-9]*$";
+ const string LevelSwitchNameRegex = @"^\${0,1}[A-Za-z]+[A-Za-z0-9]*$";
- readonly IConfigurationSection _section;
- readonly IReadOnlyCollection _configurationAssemblies;
- readonly ResolutionContext _resolutionContext;
+ readonly IConfiguration _section;
+ readonly IReadOnlyCollection _configurationAssemblies;
+ readonly ResolutionContext _resolutionContext;
+ readonly IConfigurationRoot? _configurationRoot;
- public ConfigurationReader(IConfigurationSection configSection, AssemblyFinder assemblyFinder, IConfiguration configuration = null)
- {
- _section = configSection ?? throw new ArgumentNullException(nameof(configSection));
- _configurationAssemblies = LoadConfigurationAssemblies(_section, assemblyFinder);
- _resolutionContext = new ResolutionContext(configuration);
- }
+ public ConfigurationReader(IConfiguration configSection, AssemblyFinder assemblyFinder, ConfigurationReaderOptions readerOptions, IConfiguration? configuration = null)
+ {
+ _section = configSection ?? throw new ArgumentNullException(nameof(configSection));
+ _configurationAssemblies = LoadConfigurationAssemblies(_section, assemblyFinder);
+ _resolutionContext = new ResolutionContext(configuration, readerOptions);
+ _configurationRoot = configuration as IConfigurationRoot;
+ }
- // Used internally for processing nested configuration sections -- see GetMethodCalls below.
- internal ConfigurationReader(IConfigurationSection configSection, IReadOnlyCollection configurationAssemblies, ResolutionContext resolutionContext)
- {
- _section = configSection ?? throw new ArgumentNullException(nameof(configSection));
- _configurationAssemblies = configurationAssemblies ?? throw new ArgumentNullException(nameof(configurationAssemblies));
- _resolutionContext = resolutionContext ?? throw new ArgumentNullException(nameof(resolutionContext));
- }
+ // Used internally for processing nested configuration sections -- see GetMethodCalls below.
+ internal ConfigurationReader(IConfiguration configSection, IReadOnlyCollection configurationAssemblies, ResolutionContext resolutionContext)
+ {
+ _section = configSection ?? throw new ArgumentNullException(nameof(configSection));
+ _configurationAssemblies = configurationAssemblies ?? throw new ArgumentNullException(nameof(configurationAssemblies));
+ _resolutionContext = resolutionContext ?? throw new ArgumentNullException(nameof(resolutionContext));
+ _configurationRoot = resolutionContext.HasAppConfiguration ? resolutionContext.AppConfiguration as IConfigurationRoot : null;
+ }
- public void Configure(LoggerConfiguration loggerConfiguration)
- {
- ProcessLevelSwitchDeclarations();
- ProcessFilterSwitchDeclarations();
-
- ApplyMinimumLevel(loggerConfiguration);
- ApplyEnrichment(loggerConfiguration);
- ApplyFilters(loggerConfiguration);
- ApplyDestructuring(loggerConfiguration);
- ApplySinks(loggerConfiguration);
- ApplyAuditSinks(loggerConfiguration);
- }
+ public void Configure(LoggerConfiguration loggerConfiguration)
+ {
+ ProcessLevelSwitchDeclarations();
+ ProcessFilterSwitchDeclarations();
+
+ ApplyMinimumLevel(loggerConfiguration);
+ ApplyEnrichment(loggerConfiguration);
+ ApplyFilters(loggerConfiguration);
+ ApplyDestructuring(loggerConfiguration);
+ ApplySinks(loggerConfiguration);
+ ApplyAuditSinks(loggerConfiguration);
+ }
- void ProcessFilterSwitchDeclarations()
+ void ProcessFilterSwitchDeclarations()
+ {
+ var filterSwitchesDirective = _section.GetSection("FilterSwitches");
+
+ foreach (var filterSwitchDeclaration in filterSwitchesDirective.GetChildren())
{
- var filterSwitchesDirective = _section.GetSection("FilterSwitches");
+ var filterSwitch = LoggingFilterSwitchProxy.Create();
+ if (filterSwitch == null)
+ {
+ SelfLog.WriteLine($"FilterSwitches section found, but neither Serilog.Expressions nor Serilog.Filters.Expressions is referenced.");
+ break;
+ }
- foreach (var filterSwitchDeclaration in filterSwitchesDirective.GetChildren())
+ var switchName = filterSwitchDeclaration.Key;
+ // switchName must be something like $switch to avoid ambiguities
+ if (!IsValidSwitchName(switchName))
{
- var filterSwitch = LoggingFilterSwitchProxy.Create();
- if (filterSwitch == null)
- {
- SelfLog.WriteLine($"FilterSwitches section found, but neither Serilog.Expressions nor Serilog.Filters.Expressions is referenced.");
- break;
- }
+ throw new FormatException($"\"{switchName}\" is not a valid name for a Filter Switch declaration. The first character of the name must be a letter or '$' sign, like \"FilterSwitches\" : {{\"$switchName\" : \"{{FilterExpression}}\"}}");
+ }
- var switchName = filterSwitchDeclaration.Key;
- // switchName must be something like $switch to avoid ambiguities
- if (!IsValidSwitchName(switchName))
- {
- throw new FormatException($"\"{switchName}\" is not a valid name for a Filter Switch declaration. The first character of the name must be a letter or '$' sign, like \"FilterSwitches\" : {{\"$switchName\" : \"{{FilterExpression}}\"}}");
- }
+ SetFilterSwitch(throwOnError: true);
+ SubscribeToFilterExpressionChanges();
- SetFilterSwitch(throwOnError: true);
- SubscribeToFilterExpressionChanges();
+ _resolutionContext.AddFilterSwitch(switchName, filterSwitch);
+ _resolutionContext.ReaderOptions.OnFilterSwitchCreated?.Invoke(switchName, filterSwitch);
- _resolutionContext.AddFilterSwitch(switchName, filterSwitch);
+ void SubscribeToFilterExpressionChanges()
+ {
+ ChangeToken.OnChange(filterSwitchDeclaration.GetReloadToken, () => SetFilterSwitch(throwOnError: false));
+ }
- void SubscribeToFilterExpressionChanges()
+ void SetFilterSwitch(bool throwOnError)
+ {
+ var filterExpr = filterSwitchDeclaration.Value;
+ if (string.IsNullOrWhiteSpace(filterExpr))
{
- ChangeToken.OnChange(filterSwitchDeclaration.GetReloadToken, () => SetFilterSwitch(throwOnError: false));
+ filterSwitch.Expression = null;
+ return;
}
- void SetFilterSwitch(bool throwOnError)
+ try
{
- var filterExpr = filterSwitchDeclaration.Value;
- if (string.IsNullOrWhiteSpace(filterExpr))
- {
- filterSwitch.Expression = null;
- return;
- }
-
- try
+ filterSwitch.Expression = filterExpr;
+ }
+ catch (Exception e)
+ {
+ var errMsg = $"The expression '{filterExpr}' is invalid filter expression: {e.Message}.";
+ if (throwOnError)
{
- filterSwitch.Expression = filterExpr;
+ throw new InvalidOperationException(errMsg, e);
}
- catch (Exception e)
- {
- var errMsg = $"The expression '{filterExpr}' is invalid filter expression: {e.Message}.";
- if (throwOnError)
- {
- throw new InvalidOperationException(errMsg, e);
- }
- SelfLog.WriteLine(errMsg);
- }
+ SelfLog.WriteLine(errMsg);
}
}
}
+ }
- void ProcessLevelSwitchDeclarations()
+ void ProcessLevelSwitchDeclarations()
+ {
+ var levelSwitchesDirective = _section.GetSection("LevelSwitches");
+ foreach (var levelSwitchDeclaration in levelSwitchesDirective.GetChildren())
{
- var levelSwitchesDirective = _section.GetSection("LevelSwitches");
- foreach (var levelSwitchDeclaration in levelSwitchesDirective.GetChildren())
+ var switchName = levelSwitchDeclaration.Key;
+ var switchInitialLevel = levelSwitchDeclaration.Value;
+ // switchName must be something like $switch to avoid ambiguities
+ if (!IsValidSwitchName(switchName))
{
- var switchName = levelSwitchDeclaration.Key;
- var switchInitialLevel = levelSwitchDeclaration.Value;
- // switchName must be something like $switch to avoid ambiguities
- if (!IsValidSwitchName(switchName))
- {
- throw new FormatException($"\"{switchName}\" is not a valid name for a Level Switch declaration. The first character of the name must be a letter or '$' sign, like \"LevelSwitches\" : {{\"$switchName\" : \"InitialLevel\"}}");
- }
+ throw new FormatException($"\"{switchName}\" is not a valid name for a Level Switch declaration. The first character of the name must be a letter or '$' sign, like \"LevelSwitches\" : {{\"$switchName\" : \"InitialLevel\"}}");
+ }
- LoggingLevelSwitch newSwitch;
- if (string.IsNullOrEmpty(switchInitialLevel))
- {
- newSwitch = new LoggingLevelSwitch();
- }
- else
- {
- var initialLevel = ParseLogEventLevel(switchInitialLevel);
- newSwitch = new LoggingLevelSwitch(initialLevel);
- }
+ LoggingLevelSwitch newSwitch;
+ if (string.IsNullOrEmpty(switchInitialLevel))
+ {
+ newSwitch = new LoggingLevelSwitch();
+ }
+ else
+ {
+ var initialLevel = ParseLogEventLevel(switchInitialLevel!);
+ newSwitch = new LoggingLevelSwitch(initialLevel);
+ }
- SubscribeToLoggingLevelChanges(levelSwitchDeclaration, newSwitch);
+ SubscribeToLoggingLevelChanges(levelSwitchDeclaration, newSwitch);
- // make them available later on when resolving argument values
- _resolutionContext.AddLevelSwitch(switchName, newSwitch);
- }
+ // make them available later on when resolving argument values
+ var referenceName = _resolutionContext.AddLevelSwitch(switchName, newSwitch);
+ _resolutionContext.ReaderOptions.OnLevelSwitchCreated?.Invoke(referenceName, newSwitch);
}
+ }
+
+ void ApplyMinimumLevel(LoggerConfiguration loggerConfiguration)
+ {
+ var minimumLevelDirective = _section.GetSection("MinimumLevel");
- void ApplyMinimumLevel(LoggerConfiguration loggerConfiguration)
+ IConfigurationSection? defaultMinLevelDirective = GetDefaultMinLevelDirective();
+ if (defaultMinLevelDirective?.Value != null)
{
- var minimumLevelDirective = _section.GetSection("MinimumLevel");
+ ApplyMinimumLevelConfiguration(defaultMinLevelDirective, (configuration, levelSwitch) => configuration.ControlledBy(levelSwitch));
+ }
- var defaultMinLevelDirective = minimumLevelDirective.Value != null ? minimumLevelDirective : minimumLevelDirective.GetSection("Default");
- if (defaultMinLevelDirective.Value != null)
+ var minLevelControlledByDirective = minimumLevelDirective.GetSection("ControlledBy");
+ if (minLevelControlledByDirective.Value != null)
+ {
+ var globalMinimumLevelSwitch = _resolutionContext.LookUpLevelSwitchByName(minLevelControlledByDirective.Value);
+ // not calling ApplyMinimumLevel local function because here we have a reference to a LogLevelSwitch already
+ loggerConfiguration.MinimumLevel.ControlledBy(globalMinimumLevelSwitch);
+ }
+
+ foreach (var overrideDirective in minimumLevelDirective.GetSection("Override").GetChildren())
+ {
+ var overridePrefix = overrideDirective.Key;
+ var overridenLevelOrSwitch = overrideDirective.Value;
+ if (Enum.TryParse(overridenLevelOrSwitch, out LogEventLevel _))
{
- ApplyMinimumLevelConfiguration(defaultMinLevelDirective, (configuration, levelSwitch) => configuration.ControlledBy(levelSwitch));
+ ApplyMinimumLevelConfiguration(overrideDirective, (configuration, levelSwitch) =>
+ {
+ configuration.Override(overridePrefix, levelSwitch);
+ _resolutionContext.ReaderOptions.OnLevelSwitchCreated?.Invoke(overridePrefix, levelSwitch);
+ });
}
-
- var minLevelControlledByDirective = minimumLevelDirective.GetSection("ControlledBy");
- if (minLevelControlledByDirective.Value != null)
+ else if (!string.IsNullOrEmpty(overridenLevelOrSwitch))
{
- var globalMinimumLevelSwitch = _resolutionContext.LookUpLevelSwitchByName(minLevelControlledByDirective.Value);
+ var overrideSwitch = _resolutionContext.LookUpLevelSwitchByName(overridenLevelOrSwitch!);
// not calling ApplyMinimumLevel local function because here we have a reference to a LogLevelSwitch already
- loggerConfiguration.MinimumLevel.ControlledBy(globalMinimumLevelSwitch);
+ loggerConfiguration.MinimumLevel.Override(overridePrefix, overrideSwitch);
}
+ }
+
+ void ApplyMinimumLevelConfiguration(IConfigurationSection directive, Action applyConfigAction)
+ {
+ var minimumLevel = ParseLogEventLevel(directive.Value!);
+
+ var levelSwitch = new LoggingLevelSwitch(minimumLevel);
+ applyConfigAction(loggerConfiguration.MinimumLevel, levelSwitch);
+
+ SubscribeToLoggingLevelChanges(directive, levelSwitch);
+ }
- foreach (var overrideDirective in minimumLevelDirective.GetSection("Override").GetChildren())
+ IConfigurationSection? GetDefaultMinLevelDirective()
+ {
+ var defaultLevelDirective = minimumLevelDirective.GetSection("Default");
+ if (_configurationRoot != null && minimumLevelDirective.Value != null && defaultLevelDirective.Value != null)
{
- var overridePrefix = overrideDirective.Key;
- var overridenLevelOrSwitch = overrideDirective.Value;
- if (Enum.TryParse(overridenLevelOrSwitch, out LogEventLevel _))
+ foreach (var provider in _configurationRoot.Providers.Reverse())
{
- ApplyMinimumLevelConfiguration(overrideDirective, (configuration, levelSwitch) => configuration.Override(overridePrefix, levelSwitch));
- }
- else
- {
- var overrideSwitch = _resolutionContext.LookUpLevelSwitchByName(overridenLevelOrSwitch);
- // not calling ApplyMinimumLevel local function because here we have a reference to a LogLevelSwitch already
- loggerConfiguration.MinimumLevel.Override(overridePrefix, overrideSwitch);
+ if (provider.TryGet(minimumLevelDirective.Path, out _))
+ {
+ return _configurationRoot.GetSection(minimumLevelDirective.Path);
+ }
+
+ if (provider.TryGet(defaultLevelDirective.Path, out _))
+ {
+ return _configurationRoot.GetSection(defaultLevelDirective.Path);
+ }
}
+
+ return null;
}
- void ApplyMinimumLevelConfiguration(IConfigurationSection directive, Action applyConfigAction)
- {
- var minimumLevel = ParseLogEventLevel(directive.Value);
+ return minimumLevelDirective.Value != null ? minimumLevelDirective : minimumLevelDirective.GetSection("Default");
+ }
+ }
- var levelSwitch = new LoggingLevelSwitch(minimumLevel);
- applyConfigAction(loggerConfiguration.MinimumLevel, levelSwitch);
+ 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.");
+ });
+ }
- SubscribeToLoggingLevelChanges(directive, levelSwitch);
- }
+ void ApplyFilters(LoggerConfiguration loggerConfiguration)
+ {
+ var filterDirective = _section.GetSection("Filter");
+ if (filterDirective.GetChildren().Any())
+ {
+ var methodCalls = GetMethodCalls(filterDirective);
+ CallConfigurationMethods(methodCalls, FindFilterConfigurationMethods(_configurationAssemblies, _resolutionContext.ReaderOptions.AllowInternalTypes, _resolutionContext.ReaderOptions.AllowInternalMethods), loggerConfiguration.Filter);
}
+ }
- void SubscribeToLoggingLevelChanges(IConfigurationSection levelSection, LoggingLevelSwitch levelSwitch)
+ void ApplyDestructuring(LoggerConfiguration loggerConfiguration)
+ {
+ var destructureDirective = _section.GetSection("Destructure");
+ if (destructureDirective.GetChildren().Any())
{
- 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.");
- });
+ var methodCalls = GetMethodCalls(destructureDirective);
+ CallConfigurationMethods(methodCalls, FindDestructureConfigurationMethods(_configurationAssemblies, _resolutionContext.ReaderOptions.AllowInternalTypes, _resolutionContext.ReaderOptions.AllowInternalMethods), loggerConfiguration.Destructure);
}
+ }
- void ApplyFilters(LoggerConfiguration loggerConfiguration)
+ void ApplySinks(LoggerConfiguration loggerConfiguration)
+ {
+ var writeToDirective = _section.GetSection("WriteTo");
+ if (writeToDirective.GetChildren().Any())
{
- var filterDirective = _section.GetSection("Filter");
- if (filterDirective.GetChildren().Any())
- {
- var methodCalls = GetMethodCalls(filterDirective);
- CallConfigurationMethods(methodCalls, FindFilterConfigurationMethods(_configurationAssemblies), loggerConfiguration.Filter);
- }
+ var methodCalls = GetMethodCalls(writeToDirective);
+ CallConfigurationMethods(methodCalls, FindSinkConfigurationMethods(_configurationAssemblies, _resolutionContext.ReaderOptions.AllowInternalTypes, _resolutionContext.ReaderOptions.AllowInternalMethods), loggerConfiguration.WriteTo);
}
+ }
- void ApplyDestructuring(LoggerConfiguration loggerConfiguration)
+ void ApplyAuditSinks(LoggerConfiguration loggerConfiguration)
+ {
+ var auditToDirective = _section.GetSection("AuditTo");
+ if (auditToDirective.GetChildren().Any())
{
- var destructureDirective = _section.GetSection("Destructure");
- if (destructureDirective.GetChildren().Any())
- {
- var methodCalls = GetMethodCalls(destructureDirective);
- CallConfigurationMethods(methodCalls, FindDestructureConfigurationMethods(_configurationAssemblies), loggerConfiguration.Destructure);
- }
+ var methodCalls = GetMethodCalls(auditToDirective);
+ CallConfigurationMethods(methodCalls, FindAuditSinkConfigurationMethods(_configurationAssemblies, _resolutionContext.ReaderOptions.AllowInternalTypes, _resolutionContext.ReaderOptions.AllowInternalMethods), loggerConfiguration.AuditTo);
}
+ }
- void ApplySinks(LoggerConfiguration loggerConfiguration)
+ void IConfigurationReader.ApplySinks(LoggerSinkConfiguration loggerSinkConfiguration)
+ {
+ var methodCalls = GetMethodCalls(_section);
+ CallConfigurationMethods(methodCalls, FindSinkConfigurationMethods(_configurationAssemblies, _resolutionContext.ReaderOptions.AllowInternalTypes, _resolutionContext.ReaderOptions.AllowInternalMethods), loggerSinkConfiguration);
+ }
+
+ void IConfigurationReader.ApplyEnrichment(LoggerEnrichmentConfiguration loggerEnrichmentConfiguration)
+ {
+ var methodCalls = GetMethodCalls(_section);
+ CallConfigurationMethods(methodCalls, FindEventEnricherConfigurationMethods(_configurationAssemblies, _resolutionContext.ReaderOptions.AllowInternalTypes, _resolutionContext.ReaderOptions.AllowInternalMethods), loggerEnrichmentConfiguration);
+ }
+
+ void ApplyEnrichment(LoggerConfiguration loggerConfiguration)
+ {
+ var enrichDirective = _section.GetSection("Enrich");
+ if (enrichDirective.GetChildren().Any())
{
- var writeToDirective = _section.GetSection("WriteTo");
- if (writeToDirective.GetChildren().Any())
- {
- var methodCalls = GetMethodCalls(writeToDirective);
- CallConfigurationMethods(methodCalls, FindSinkConfigurationMethods(_configurationAssemblies), loggerConfiguration.WriteTo);
- }
+ var methodCalls = GetMethodCalls(enrichDirective);
+ CallConfigurationMethods(methodCalls, FindEventEnricherConfigurationMethods(_configurationAssemblies, _resolutionContext.ReaderOptions.AllowInternalTypes, _resolutionContext.ReaderOptions.AllowInternalMethods), loggerConfiguration.Enrich);
}
- void ApplyAuditSinks(LoggerConfiguration loggerConfiguration)
+ var propertiesDirective = _section.GetSection("Properties");
+ if (propertiesDirective.GetChildren().Any())
{
- var auditToDirective = _section.GetSection("AuditTo");
- if (auditToDirective.GetChildren().Any())
+ foreach (var enrichPropertyDirective in propertiesDirective.GetChildren())
{
- var methodCalls = GetMethodCalls(auditToDirective);
- CallConfigurationMethods(methodCalls, FindAuditSinkConfigurationMethods(_configurationAssemblies), loggerConfiguration.AuditTo);
+ // Null is an acceptable value here; annotations on Serilog need updating.
+ loggerConfiguration.Enrich.WithProperty(enrichPropertyDirective.Key, enrichPropertyDirective.Value!);
}
}
+ }
- void IConfigurationReader.ApplySinks(LoggerSinkConfiguration loggerSinkConfiguration)
+ internal ILookup> GetMethodCalls(IConfiguration directive)
+ {
+ var children = directive.GetChildren().ToList();
+
+ var result =
+ (from child in children
+ where child.Value != null // Plain string
+ select new { Name = child.Value, Args = new Dictionary() })
+ .Concat(
+ (from child in children
+ where child.Value == null
+ let name = GetSectionName(child)
+ let callArgs = (from argument in child.GetSection("Args").GetChildren()
+ select new
+ {
+ Name = argument.Key,
+ Value = GetArgumentValue(argument, _configurationAssemblies)
+ }).ToDictionary(p => p.Name, p => p.Value)
+ select new { Name = name, Args = callArgs }))
+ .ToLookup(p => p.Name, p => p.Args);
+
+ return result;
+
+ static string GetSectionName(IConfigurationSection s)
{
- var methodCalls = GetMethodCalls(_section);
- CallConfigurationMethods(methodCalls, FindSinkConfigurationMethods(_configurationAssemblies), loggerSinkConfiguration);
+ var name = s.GetSection("Name");
+ if (name.Value == null)
+ throw new InvalidOperationException($"The configuration value in {name.Path} has no 'Name' element.");
+
+ return name.Value;
}
+ }
- void IConfigurationReader.ApplyEnrichment(LoggerEnrichmentConfiguration loggerEnrichmentConfiguration)
+ internal static IConfigurationArgumentValue GetArgumentValue(IConfigurationSection argumentSection, IReadOnlyCollection configurationAssemblies)
+ {
+ IConfigurationArgumentValue argumentValue;
+
+ // Reject configurations where an element has both scalar and complex
+ // values as a result of reading multiple configuration sources.
+ if (argumentSection.Value != null && argumentSection.GetChildren().Any())
+ throw new InvalidOperationException(
+ $"The value for the argument '{argumentSection.Path}' is assigned different value " +
+ "types in more than one configuration source. Ensure all configurations consistently " +
+ "use either a scalar (int, string, boolean) or a complex (array, section, list, " +
+ "POCO, etc.) type for this argument value.");
+
+ if (argumentSection.Value != null)
{
- var methodCalls = GetMethodCalls(_section);
- CallConfigurationMethods(methodCalls, FindEventEnricherConfigurationMethods(_configurationAssemblies), loggerEnrichmentConfiguration);
+ argumentValue = new StringArgumentValue(argumentSection.Value);
}
-
- void ApplyEnrichment(LoggerConfiguration loggerConfiguration)
+ else
{
- var enrichDirective = _section.GetSection("Enrich");
- if (enrichDirective.GetChildren().Any())
- {
- var methodCalls = GetMethodCalls(enrichDirective);
- CallConfigurationMethods(methodCalls, FindEventEnricherConfigurationMethods(_configurationAssemblies), loggerConfiguration.Enrich);
- }
-
- var propertiesDirective = _section.GetSection("Properties");
- if (propertiesDirective.GetChildren().Any())
- {
- foreach (var enrichPropertyDirective in propertiesDirective.GetChildren())
- {
- loggerConfiguration.Enrich.WithProperty(enrichPropertyDirective.Key, enrichPropertyDirective.Value);
- }
- }
+ argumentValue = new ObjectArgumentValue(argumentSection, configurationAssemblies);
}
- internal ILookup> GetMethodCalls(IConfigurationSection directive)
+ return argumentValue;
+ }
+
+ static IReadOnlyCollection LoadConfigurationAssemblies(IConfiguration section, AssemblyFinder assemblyFinder)
+ {
+ var serilogAssembly = typeof(ILogger).Assembly;
+ var assemblies = new HashSet { serilogAssembly };
+
+ var usingSection = section.GetSection("Using");
+ if (usingSection.GetChildren().Any())
{
- var children = directive.GetChildren().ToList();
-
- var result =
- (from child in children
- where child.Value != null // Plain string
- select new { Name = child.Value, Args = new Dictionary() })
- .Concat(
- (from child in children
- where child.Value == null
- let name = GetSectionName(child)
- let callArgs = (from argument in child.GetSection("Args").GetChildren()
- select new
- {
- Name = argument.Key,
- Value = GetArgumentValue(argument, _configurationAssemblies)
- }).ToDictionary(p => p.Name, p => p.Value)
- select new { Name = name, Args = callArgs }))
- .ToLookup(p => p.Name, p => p.Args);
-
- return result;
-
- static string GetSectionName(IConfigurationSection s)
+ foreach (var simpleName in usingSection.GetChildren().Select(c => c.Value))
{
- var name = s.GetSection("Name");
- if (name.Value == null)
- throw new InvalidOperationException($"The configuration value in {name.Path} has no 'Name' element.");
+ if (string.IsNullOrWhiteSpace(simpleName))
+ throw new InvalidOperationException(
+ $"A zero-length or whitespace assembly name was supplied to a {usingSection.Path} configuration statement.");
- return name.Value;
+ var assembly = Assembly.Load(new AssemblyName(simpleName));
+ assemblies.Add(assembly);
}
}
- internal static IConfigurationArgumentValue GetArgumentValue(IConfigurationSection argumentSection, IReadOnlyCollection configurationAssemblies)
+ foreach (var assemblyName in assemblyFinder.FindAssembliesContainingName("serilog"))
{
- IConfigurationArgumentValue argumentValue;
-
- // Reject configurations where an element has both scalar and complex
- // values as a result of reading multiple configuration sources.
- if (argumentSection.Value != null && argumentSection.GetChildren().Any())
- throw new InvalidOperationException(
- $"The value for the argument '{argumentSection.Path}' is assigned different value " +
- "types in more than one configuration source. Ensure all configurations consistently " +
- "use either a scalar (int, string, boolean) or a complex (array, section, list, " +
- "POCO, etc.) type for this argument value.");
-
- if (argumentSection.Value != null)
- {
- argumentValue = new StringArgumentValue(argumentSection.Value);
- }
- else
- {
- argumentValue = new ObjectArgumentValue(argumentSection, configurationAssemblies);
- }
-
- return argumentValue;
+ var assumed = Assembly.Load(assemblyName);
+ assemblies.Add(assumed);
}
- static IReadOnlyCollection LoadConfigurationAssemblies(IConfigurationSection section, AssemblyFinder assemblyFinder)
+ if (assemblies.Count == 1)
{
- var serilogAssembly = typeof(ILogger).Assembly;
- var assemblies = new Dictionary { [serilogAssembly.FullName] = serilogAssembly };
+ var message = $"""
+ No {usingSection.Path} configuration section is defined and no Serilog assemblies were found.
+ This is most likely because the application is published as single-file.
+ Either add a {usingSection.Path} section or explicitly specify assemblies that contains sinks and other types through the reader options. For example:
+ var options = new ConfigurationReaderOptions(typeof(ConsoleLoggerConfigurationExtensions).Assembly, typeof(SerilogExpression).Assembly);
+ new LoggerConfiguration().ReadFrom.Configuration(configuration, options);
+ """;
+ throw new InvalidOperationException(message);
+ }
- var usingSection = section.GetSection("Using");
- if (usingSection.GetChildren().Any())
- {
- foreach (var simpleName in usingSection.GetChildren().Select(c => c.Value))
- {
- if (string.IsNullOrWhiteSpace(simpleName))
- throw new InvalidOperationException(
- "A zero-length or whitespace assembly name was supplied to a Serilog.Using configuration statement.");
+ return assemblies;
+ }
- var assembly = Assembly.Load(new AssemblyName(simpleName));
- if (!assemblies.ContainsKey(assembly.FullName))
- assemblies.Add(assembly.FullName, assembly);
- }
- }
+ void CallConfigurationMethods(ILookup> methods, IReadOnlyCollection 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.Keys.ToList());
- foreach (var assemblyName in assemblyFinder.FindAssembliesContainingName("serilog"))
+ if (methodInfo != null)
{
- var assumed = Assembly.Load(assemblyName);
- if (assumed != null && !assemblies.ContainsKey(assumed.FullName))
- assemblies.Add(assumed.FullName, assumed);
+ var call = (from p in methodInfo.GetParameters().Skip(1)
+ 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());
}
-
- return assemblies.Values.ToList().AsReadOnly();
}
+ }
- void CallConfigurationMethods(ILookup> methods, IReadOnlyCollection 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.Keys.ToList());
-
- if (methodInfo != null)
- {
- var call = (from p in methodInfo.GetParameters().Skip(1)
- 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());
- }
- }
- }
+ static bool HasImplicitValueWhenNotSpecified(ParameterInfo paramInfo)
+ {
+ return paramInfo.HasDefaultValue
+ // parameters of type IConfiguration are implicitly populated with provided Configuration
+ || paramInfo.ParameterType == typeof(IConfiguration);
+ }
- static bool HasImplicitValueWhenNotSpecified(ParameterInfo paramInfo)
+ object? GetImplicitValueForNotSpecifiedKey(ParameterInfo parameter, MethodInfo methodToInvoke)
+ {
+ if (!HasImplicitValueWhenNotSpecified(parameter))
{
- return paramInfo.HasDefaultValue
- // parameters of type IConfiguration are implicitly populated with provided Configuration
- || paramInfo.ParameterType == typeof(IConfiguration);
+ 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.");
}
- object GetImplicitValueForNotSpecifiedKey(ParameterInfo parameter, MethodInfo methodToInvoke)
+ if (parameter.ParameterType == typeof(IConfiguration))
{
- if (!HasImplicitValueWhenNotSpecified(parameter))
+ if (_resolutionContext.HasAppConfiguration)
{
- 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.");
+ return _resolutionContext.AppConfiguration;
}
-
- if (parameter.ParameterType == typeof(IConfiguration))
+ if (parameter.HasDefaultValue)
{
- 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;
}
- 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}')");
}
- internal static MethodInfo SelectConfigurationMethod(IReadOnlyCollection candidateMethods, string name, IReadOnlyCollection suppliedArgumentNames)
+ return parameter.DefaultValue;
+ }
+
+ internal static MethodInfo? SelectConfigurationMethod(IReadOnlyCollection candidateMethods, string name, IReadOnlyCollection 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).
+ 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 => 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
+ return new Tuple(
+ matchingArgs.Count,
+ matchingArgs.Count(p => p.ParameterType == typeof(string)));
+ })
+ .FirstOrDefault();
+
+ if (selectedMethod == null)
{
- // 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).
- var selectedMethod = candidateMethods
+ var methodsByName = 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 => 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
- return new Tuple(
- matchingArgs.Count,
- matchingArgs.Count(p => p.ParameterType == typeof(string)));
- })
- .FirstOrDefault();
+ .Select(m => $"{m.Name}({string.Join(", ", m.GetParameters().Skip(1).Select(p => p.Name))})")
+ .ToList();
- if (selectedMethod == null)
+ if (!methodsByName.Any())
{
- 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())
+ if (candidateMethods.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));
+ {
+ SelfLog.WriteLine($"Unable to find a method called {name}. No candidates found.");
+ }
+ }
+ 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;
}
+ return selectedMethod;
+ }
- static bool ParameterNameMatches(string actualParameterName, string suppliedName)
- {
- return suppliedName.Equals(actualParameterName, StringComparison.OrdinalIgnoreCase);
- }
+ 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 bool ParameterNameMatches(string? actualParameterName, IEnumerable suppliedNames)
+ {
+ return suppliedNames.Any(s => ParameterNameMatches(actualParameterName, s));
+ }
- static IReadOnlyCollection FindSinkConfigurationMethods(IReadOnlyCollection configurationAssemblies)
- {
- var found = FindConfigurationExtensionMethods(configurationAssemblies, typeof(LoggerSinkConfiguration));
- if (configurationAssemblies.Contains(typeof(LoggerSinkConfiguration).GetTypeInfo().Assembly))
- found.AddRange(SurrogateConfigurationMethods.WriteTo);
+ static IReadOnlyCollection FindSinkConfigurationMethods(IReadOnlyCollection configurationAssemblies, bool allowInternalTypes, bool allowInternalMethods)
+ {
+ var found = FindConfigurationExtensionMethods(configurationAssemblies, typeof(LoggerSinkConfiguration), allowInternalTypes, allowInternalMethods);
+ if (configurationAssemblies.Contains(typeof(LoggerSinkConfiguration).GetTypeInfo().Assembly))
+ found.AddRange(SurrogateConfigurationMethods.WriteTo);
- return found;
- }
+ return found;
+ }
- static IReadOnlyCollection FindAuditSinkConfigurationMethods(IReadOnlyCollection configurationAssemblies)
- {
- var found = FindConfigurationExtensionMethods(configurationAssemblies, typeof(LoggerAuditSinkConfiguration));
- if (configurationAssemblies.Contains(typeof(LoggerAuditSinkConfiguration).GetTypeInfo().Assembly))
- found.AddRange(SurrogateConfigurationMethods.AuditTo);
- return found;
- }
+ static IReadOnlyCollection FindAuditSinkConfigurationMethods(IReadOnlyCollection configurationAssemblies, bool allowInternalTypes, bool allowInternalMethods)
+ {
+ var found = FindConfigurationExtensionMethods(configurationAssemblies, typeof(LoggerAuditSinkConfiguration), allowInternalTypes, allowInternalMethods);
+ if (configurationAssemblies.Contains(typeof(LoggerAuditSinkConfiguration).GetTypeInfo().Assembly))
+ found.AddRange(SurrogateConfigurationMethods.AuditTo);
+ return found;
+ }
- static IReadOnlyCollection FindFilterConfigurationMethods(IReadOnlyCollection configurationAssemblies)
- {
- var found = FindConfigurationExtensionMethods(configurationAssemblies, typeof(LoggerFilterConfiguration));
- if (configurationAssemblies.Contains(typeof(LoggerFilterConfiguration).GetTypeInfo().Assembly))
- found.AddRange(SurrogateConfigurationMethods.Filter);
+ static IReadOnlyCollection FindFilterConfigurationMethods(IReadOnlyCollection configurationAssemblies, bool allowInternalTypes, bool allowInternalMethods)
+ {
+ var found = FindConfigurationExtensionMethods(configurationAssemblies, typeof(LoggerFilterConfiguration), allowInternalTypes, allowInternalMethods);
+ if (configurationAssemblies.Contains(typeof(LoggerFilterConfiguration).GetTypeInfo().Assembly))
+ found.AddRange(SurrogateConfigurationMethods.Filter);
- return found;
- }
+ return found;
+ }
- static IReadOnlyCollection FindDestructureConfigurationMethods(IReadOnlyCollection configurationAssemblies)
- {
- var found = FindConfigurationExtensionMethods(configurationAssemblies, typeof(LoggerDestructuringConfiguration));
- if (configurationAssemblies.Contains(typeof(LoggerDestructuringConfiguration).GetTypeInfo().Assembly))
- found.AddRange(SurrogateConfigurationMethods.Destructure);
+ static IReadOnlyCollection FindDestructureConfigurationMethods(IReadOnlyCollection configurationAssemblies, bool allowInternalTypes, bool allowInternalMethods)
+ {
+ var found = FindConfigurationExtensionMethods(configurationAssemblies, typeof(LoggerDestructuringConfiguration), allowInternalTypes, allowInternalMethods);
+ if (configurationAssemblies.Contains(typeof(LoggerDestructuringConfiguration).GetTypeInfo().Assembly))
+ found.AddRange(SurrogateConfigurationMethods.Destructure);
- return found;
- }
+ return found;
+ }
- static IReadOnlyCollection FindEventEnricherConfigurationMethods(IReadOnlyCollection configurationAssemblies)
- {
- var found = FindConfigurationExtensionMethods(configurationAssemblies, typeof(LoggerEnrichmentConfiguration));
- if (configurationAssemblies.Contains(typeof(LoggerEnrichmentConfiguration).GetTypeInfo().Assembly))
- found.AddRange(SurrogateConfigurationMethods.Enrich);
+ static IReadOnlyCollection FindEventEnricherConfigurationMethods(IReadOnlyCollection configurationAssemblies, bool allowInternalTypes, bool allowInternalMethods)
+ {
+ var found = FindConfigurationExtensionMethods(configurationAssemblies, typeof(LoggerEnrichmentConfiguration), allowInternalTypes, allowInternalMethods);
+ if (configurationAssemblies.Contains(typeof(LoggerEnrichmentConfiguration).GetTypeInfo().Assembly))
+ found.AddRange(SurrogateConfigurationMethods.Enrich);
- return found;
- }
+ return found;
+ }
- static List FindConfigurationExtensionMethods(IReadOnlyCollection configurationAssemblies, Type configType)
+ static List FindConfigurationExtensionMethods(IReadOnlyCollection configurationAssemblies, Type configType, bool allowInternalTypes, bool allowInternalMethods)
+ {
+ // ExtensionAttribute can be polyfilled to support extension methods
+ static bool HasCustomExtensionAttribute(MethodInfo m)
{
- // ExtensionAttribute can be polyfilled to support extension methods
- bool HasExtensionAttribute(MethodInfo m) =>
- m.CustomAttributes.Any(a => a.AttributeType.FullName == "System.Runtime.CompilerServices.ExtensionAttribute");
-
- return configurationAssemblies
- .SelectMany(a => a.ExportedTypes
- .Select(t => t.GetTypeInfo())
- .Where(t => t.IsSealed && t.IsAbstract && !t.IsNested))
- .SelectMany(t => t.DeclaredMethods)
- .Where(m => m.IsStatic && m.IsPublic && HasExtensionAttribute(m))
- .Where(m => m.GetParameters()[0].ParameterType == configType)
- .ToList();
+ try
+ {
+ return m.CustomAttributes.Any(a => a.AttributeType.FullName == "System.Runtime.CompilerServices.ExtensionAttribute");
+ }
+ catch (CustomAttributeFormatException)
+ {
+ return false;
+ }
}
- internal static bool IsValidSwitchName(string input)
- {
- return Regex.IsMatch(input, LevelSwitchNameRegex);
- }
+ return configurationAssemblies
+ .SelectMany(a => (allowInternalTypes ? a.GetTypes() : a.ExportedTypes)
+ .Select(t => t.GetTypeInfo())
+ .Where(t => t.IsSealed && t.IsAbstract && !t.IsNested && (t.IsPublic || allowInternalTypes && !t.IsVisible)))
+ .SelectMany(t => t.DeclaredMethods)
+ .Where(m => m.IsStatic && (m.IsPublic || allowInternalMethods && m.IsAssembly) && (m.IsDefined(typeof(ExtensionAttribute), false) || HasCustomExtensionAttribute(m)))
+ .Where(m => m.GetParameters()[0].ParameterType == configType)
+ .ToList();
+ }
- static LogEventLevel ParseLogEventLevel(string value)
- {
- if (!Enum.TryParse(value, out LogEventLevel parsedLevel))
- throw new InvalidOperationException($"The value {value} is not a valid Serilog level.");
- return parsedLevel;
- }
+ internal static bool IsValidSwitchName(string input)
+ {
+ return Regex.IsMatch(input, LevelSwitchNameRegex);
+ }
+ static LogEventLevel ParseLogEventLevel(string value)
+ {
+ if (!Enum.TryParse(value, ignoreCase: true, out LogEventLevel parsedLevel))
+ throw new InvalidOperationException($"The value {value} is not a valid Serilog level.");
+ return parsedLevel;
}
}
diff --git a/src/Serilog.Settings.Configuration/Settings/Configuration/ConfigurationReaderOptions.cs b/src/Serilog.Settings.Configuration/Settings/Configuration/ConfigurationReaderOptions.cs
new file mode 100644
index 00000000..4510aa9f
--- /dev/null
+++ b/src/Serilog.Settings.Configuration/Settings/Configuration/ConfigurationReaderOptions.cs
@@ -0,0 +1,88 @@
+using System.Globalization;
+using System.Reflection;
+using Microsoft.Extensions.DependencyModel;
+using Serilog.Core;
+
+namespace Serilog.Settings.Configuration;
+
+///
+/// Options to adjust how the configuration object is processed.
+///
+public sealed class ConfigurationReaderOptions
+{
+ ///
+ /// Initialize a new instance of the class.
+ ///
+ /// A collection of assemblies that contains sinks and other types.
+ /// The argument is null.
+ /// The argument is empty.
+ 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));
+ }
+
+ ///
+ /// Initialize a new instance of the class.
+ ///
+ /// Prefer the constructor taking explicit assemblies: . It's the only one supporting single-file publishing.
+ public ConfigurationReaderOptions() : this(dependencyContext: null)
+ {
+ }
+
+ ///
+ /// Initialize a new instance of the class.
+ ///
+ ///
+ /// The dependency context from which sink/enricher packages can be located. If , the platform default will be used.
+ ///
+ /// Prefer the constructor taking explicit assemblies: . It's the only one supporting single-file publishing.
+ public ConfigurationReaderOptions(DependencyContext? dependencyContext) => DependencyContext = dependencyContext;
+
+ ///
+ /// Initialize a new instance of the class.
+ ///
+ /// Defines how the package identifies assemblies to scan for sinks and other types.
+ /// Prefer the constructor taking explicit assemblies: . It's the only one supporting single-file publishing.
+ public ConfigurationReaderOptions(ConfigurationAssemblySource configurationAssemblySource) => ConfigurationAssemblySource = configurationAssemblySource;
+
+ ///
+ /// The section name for section which contains a Serilog section. Defaults to Serilog.
+ ///
+ public string? SectionName { get; init; } = ConfigurationLoggerConfigurationExtensions.DefaultSectionName;
+
+ ///
+ /// The used when converting strings to other object types. Defaults to the invariant culture.
+ ///
+ public IFormatProvider? FormatProvider { get; init; } = CultureInfo.InvariantCulture;
+
+ ///
+ /// Allows to use internal types for extension methods for sink configuration. Defaults to .
+ ///
+ public bool AllowInternalTypes { get; init; }
+
+ ///
+ /// Allows to use internal extension methods for sink configuration. Defaults to .
+ ///
+ public bool AllowInternalMethods { get; init; }
+
+ ///
+ /// Called when a log level switch is created while reading the configuration.
+ /// Log level switches are created either from the Serilog:LevelSwitches section (declared switches) or the Serilog:MinimumLevel:Override section (minimum level override switches).
+ ///
+ /// - For declared switches, the switch name includes the leading $ character.
+ /// - For minimum level override switches, the switch name is the (partial) namespace or type name of the override.
+ ///
+ ///
+ public Action? OnLevelSwitchCreated { get; init; }
+
+ ///
+ /// Called when a log filter switch is created while reading the Serilog:FilterSwitches section of the configuration.
+ ///
+ public Action? OnFilterSwitchCreated { get; init; }
+
+ internal Assembly[]? Assemblies { get; }
+ internal DependencyContext? DependencyContext { get; }
+ internal ConfigurationAssemblySource? ConfigurationAssemblySource { get; }
+}
diff --git a/src/Serilog.Settings.Configuration/Settings/Configuration/IConfigurationArgumentValue.cs b/src/Serilog.Settings.Configuration/Settings/Configuration/IConfigurationArgumentValue.cs
index e24e8e02..8a1e86fd 100644
--- a/src/Serilog.Settings.Configuration/Settings/Configuration/IConfigurationArgumentValue.cs
+++ b/src/Serilog.Settings.Configuration/Settings/Configuration/IConfigurationArgumentValue.cs
@@ -1,9 +1,6 @@
-using System;
+namespace Serilog.Settings.Configuration;
-namespace Serilog.Settings.Configuration
+interface IConfigurationArgumentValue
{
- interface IConfigurationArgumentValue
- {
- object ConvertTo(Type toType, ResolutionContext resolutionContext);
- }
+ 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 af815af2..f3dd36ff 100644
--- a/src/Serilog.Settings.Configuration/Settings/Configuration/IConfigurationReader.cs
+++ b/src/Serilog.Settings.Configuration/Settings/Configuration/IConfigurationReader.cs
@@ -1,10 +1,9 @@
using Serilog.Configuration;
-namespace Serilog.Settings.Configuration
+namespace Serilog.Settings.Configuration;
+
+interface IConfigurationReader : ILoggerSettings
{
- interface IConfigurationReader : ILoggerSettings
- {
- void ApplySinks(LoggerSinkConfiguration loggerSinkConfiguration);
- void ApplyEnrichment(LoggerEnrichmentConfiguration loggerEnrichmentConfiguration);
- }
+ void ApplySinks(LoggerSinkConfiguration loggerSinkConfiguration);
+ void ApplyEnrichment(LoggerEnrichmentConfiguration loggerEnrichmentConfiguration);
}
diff --git a/src/Serilog.Settings.Configuration/Settings/Configuration/ILoggingFilterSwitch.cs b/src/Serilog.Settings.Configuration/Settings/Configuration/ILoggingFilterSwitch.cs
new file mode 100644
index 00000000..321295bb
--- /dev/null
+++ b/src/Serilog.Settings.Configuration/Settings/Configuration/ILoggingFilterSwitch.cs
@@ -0,0 +1,16 @@
+namespace Serilog.Settings.Configuration;
+
+///
+/// A log event filter that can be modified at runtime.
+///
+///
+/// Under the hood, the logging filter switch is either a Serilog.Expressions.LoggingFilterSwitch or a Serilog.Filters.Expressions.LoggingFilterSwitch instance.
+///
+public interface ILoggingFilterSwitch
+{
+ ///
+ /// A filter expression against which log events will be tested.
+ /// Only expressions that evaluate to true are included by the filter. A null expression will accept all events.
+ ///
+ public string? Expression { get; set; }
+}
diff --git a/src/Serilog.Settings.Configuration/Settings/Configuration/LoggingFilterSwitchProxy.cs b/src/Serilog.Settings.Configuration/Settings/Configuration/LoggingFilterSwitchProxy.cs
index 676a2dd8..8da70d07 100644
--- a/src/Serilog.Settings.Configuration/Settings/Configuration/LoggingFilterSwitchProxy.cs
+++ b/src/Serilog.Settings.Configuration/Settings/Configuration/LoggingFilterSwitchProxy.cs
@@ -1,50 +1,48 @@
-using System;
+namespace Serilog.Settings.Configuration;
-namespace Serilog.Settings.Configuration
+class LoggingFilterSwitchProxy : ILoggingFilterSwitch
{
- class LoggingFilterSwitchProxy
+ readonly Action _setProxy;
+ readonly Func _getProxy;
+
+ LoggingFilterSwitchProxy(object realSwitch)
{
- readonly Action _setProxy;
- readonly Func _getProxy;
+ RealSwitch = realSwitch ?? throw new ArgumentNullException(nameof(realSwitch));
- LoggingFilterSwitchProxy(object realSwitch)
- {
- RealSwitch = realSwitch ?? throw new ArgumentNullException(nameof(realSwitch));
+ var type = realSwitch.GetType();
+ var expressionProperty = type.GetProperty("Expression") ?? throw new MissingMemberException(type.FullName, "Expression");
+
+ _setProxy = (Action)Delegate.CreateDelegate(
+ typeof(Action),
+ realSwitch,
+ expressionProperty.GetSetMethod() ?? throw new MissingMethodException(type.FullName, "set_Expression"));
- var type = realSwitch.GetType();
- var expressionProperty = type.GetProperty("Expression") ?? throw new MissingMemberException(type.FullName, "Expression");
+ _getProxy = (Func)Delegate.CreateDelegate(
+ typeof(Func),
+ realSwitch,
+ expressionProperty.GetGetMethod() ?? throw new MissingMethodException(type.FullName, "get_Expression"));
+ }
- _setProxy = (Action)Delegate.CreateDelegate(
- typeof(Action),
- realSwitch,
- expressionProperty.GetSetMethod());
+ public object RealSwitch { get; }
- _getProxy = (Func)Delegate.CreateDelegate(
- typeof(Func),
- realSwitch,
- expressionProperty.GetGetMethod());
- }
+ public string? Expression
+ {
+ get => _getProxy();
+ set => _setProxy(value);
+ }
- public object RealSwitch { get; }
+ public static LoggingFilterSwitchProxy? Create(string? expression = null)
+ {
+ var filterSwitchType =
+ Type.GetType("Serilog.Expressions.LoggingFilterSwitch, Serilog.Expressions") ??
+ Type.GetType("Serilog.Filters.Expressions.LoggingFilterSwitch, Serilog.Filters.Expressions");
- public string Expression
+ if (filterSwitchType is null)
{
- get => _getProxy();
- set => _setProxy(value);
+ return null;
}
- public static LoggingFilterSwitchProxy Create(string expression = null)
- {
- var filterSwitchType =
- Type.GetType("Serilog.Expressions.LoggingFilterSwitch, Serilog.Expressions") ??
- Type.GetType("Serilog.Filters.Expressions.LoggingFilterSwitch, Serilog.Filters.Expressions");
-
- if (filterSwitchType is null)
- {
- return null;
- }
-
- return new LoggingFilterSwitchProxy(Activator.CreateInstance(filterSwitchType, expression));
- }
+ var realSwitch = Activator.CreateInstance(filterSwitchType, expression) ?? throw new InvalidOperationException($"Activator.CreateInstance returned null for {filterSwitchType}");
+ return new LoggingFilterSwitchProxy(realSwitch);
}
}
diff --git a/src/Serilog.Settings.Configuration/Settings/Configuration/ObjectArgumentValue.cs b/src/Serilog.Settings.Configuration/Settings/Configuration/ObjectArgumentValue.cs
index d22f78b1..151ce5b5 100644
--- a/src/Serilog.Settings.Configuration/Settings/Configuration/ObjectArgumentValue.cs
+++ b/src/Serilog.Settings.Configuration/Settings/Configuration/ObjectArgumentValue.cs
@@ -1,6 +1,4 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
+using System.Diagnostics.CodeAnalysis;
using System.Linq.Expressions;
using System.Reflection;
@@ -8,234 +6,233 @@
using Serilog.Configuration;
-namespace Serilog.Settings.Configuration
+namespace Serilog.Settings.Configuration;
+
+class ObjectArgumentValue : IConfigurationArgumentValue
{
- class ObjectArgumentValue : IConfigurationArgumentValue
+ readonly IConfigurationSection _section;
+ readonly IReadOnlyCollection _configurationAssemblies;
+
+ public ObjectArgumentValue(IConfigurationSection section, IReadOnlyCollection configurationAssemblies)
{
- readonly IConfigurationSection _section;
- readonly IReadOnlyCollection _configurationAssemblies;
+ _section = section ?? throw new ArgumentNullException(nameof(section));
- public ObjectArgumentValue(IConfigurationSection section, IReadOnlyCollection configurationAssemblies)
- {
- _section = section ?? throw new ArgumentNullException(nameof(section));
+ // used by nested logger configurations to feed a new pass by ConfigurationReader
+ _configurationAssemblies = configurationAssemblies ?? throw new ArgumentNullException(nameof(configurationAssemblies));
+ }
- // used by nested logger configurations to feed a new pass by ConfigurationReader
- _configurationAssemblies = configurationAssemblies ?? throw new ArgumentNullException(nameof(configurationAssemblies));
- }
+ public object? ConvertTo(Type toType, ResolutionContext resolutionContext)
+ {
+ // return the entire section for internal processing
+ if (toType == typeof(IConfigurationSection)) return _section;
- public object ConvertTo(Type toType, ResolutionContext resolutionContext)
+ // process a nested configuration to populate an Action<> logger/sink config parameter?
+ var typeInfo = toType.GetTypeInfo();
+ if (typeInfo.IsGenericType &&
+ typeInfo.GetGenericTypeDefinition() is Type genericType && genericType == typeof(Action<>))
{
- // return the entire section for internal processing
- if (toType == typeof(IConfigurationSection)) return _section;
+ var configType = typeInfo.GenericTypeArguments[0];
+ IConfigurationReader configReader = new ConfigurationReader(_section, _configurationAssemblies, resolutionContext);
- // process a nested configuration to populate an Action<> logger/sink config parameter?
- var typeInfo = toType.GetTypeInfo();
- if (typeInfo.IsGenericType &&
- typeInfo.GetGenericTypeDefinition() is Type genericType && genericType == typeof(Action<>))
+ return configType switch
{
- var configType = typeInfo.GenericTypeArguments[0];
- IConfigurationReader configReader = new ConfigurationReader(_section, _configurationAssemblies, resolutionContext);
-
- return configType switch
- {
- _ when configType == typeof(LoggerConfiguration) => new Action(configReader.Configure),
- _ when configType == typeof(LoggerSinkConfiguration) => new Action(configReader.ApplySinks),
- _ when configType == typeof(LoggerEnrichmentConfiguration) => new Action(configReader.ApplyEnrichment),
- _ => throw new ArgumentException($"Configuration resolution for Action<{configType.Name}> parameter type at the path {_section.Path} is not implemented.")
- };
- }
+ _ when configType == typeof(LoggerConfiguration) => new Action(configReader.Configure),
+ _ when configType == typeof(LoggerSinkConfiguration) => new Action(configReader.ApplySinks),
+ _ when configType == typeof(LoggerEnrichmentConfiguration) => new Action(configReader.ApplyEnrichment),
+ _ => throw new ArgumentException($"Configuration resolution for Action<{configType.Name}> parameter type at the path {_section.Path} is not implemented.")
+ };
+ }
- if (toType.IsArray)
- return CreateArray();
+ if (toType.IsArray)
+ return CreateArray();
- if (IsContainer(toType, out var elementType) && TryCreateContainer(out var container))
- return container;
+ if (IsContainer(toType, out var elementType) && TryCreateContainer(out var container))
+ return container;
- if (TryBuildCtorExpression(_section, toType, resolutionContext, out var ctorExpression))
- {
- return Expression.Lambda>(ctorExpression).Compile().Invoke();
- }
+ if (TryBuildCtorExpression(_section, toType, resolutionContext, out var ctorExpression))
+ {
+ return Expression.Lambda>(ctorExpression).Compile().Invoke();
+ }
- // MS Config binding can work with a limited set of primitive types and collections
- return _section.Get(toType);
+ // MS Config binding can work with a limited set of primitive types and collections
+ return _section.Get(toType);
- object CreateArray()
+ object CreateArray()
+ {
+ var arrayElementType = toType.GetElementType()!;
+ var configurationElements = _section.GetChildren().ToArray();
+ var array = Array.CreateInstance(arrayElementType, configurationElements.Length);
+ for (int i = 0; i < configurationElements.Length; ++i)
{
- var arrayElementType = toType.GetElementType()!;
- var configurationElements = _section.GetChildren().ToArray();
- var array = Array.CreateInstance(arrayElementType, configurationElements.Length);
- for (int i = 0; i < configurationElements.Length; ++i)
- {
- var argumentValue = ConfigurationReader.GetArgumentValue(configurationElements[i], _configurationAssemblies);
- var value = argumentValue.ConvertTo(arrayElementType, resolutionContext);
- array.SetValue(value, i);
- }
-
- return array;
+ var argumentValue = ConfigurationReader.GetArgumentValue(configurationElements[i], _configurationAssemblies);
+ var value = argumentValue.ConvertTo(arrayElementType, resolutionContext);
+ array.SetValue(value, i);
}
- bool TryCreateContainer(out object result)
- {
- result = null;
+ return array;
+ }
- if (toType.GetConstructor(Type.EmptyTypes) == null)
- return false;
+ bool TryCreateContainer([NotNullWhen(true)] out object? result)
+ {
+ result = null;
- // https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/object-and-collection-initializers#collection-initializers
- var addMethod = toType.GetMethods().FirstOrDefault(m => !m.IsStatic && m.Name == "Add" && m.GetParameters().Length == 1 && m.GetParameters()[0].ParameterType == elementType);
- if (addMethod == null)
- return false;
+ if (toType.GetConstructor(Type.EmptyTypes) == null)
+ return false;
- var configurationElements = _section.GetChildren().ToArray();
- result = Activator.CreateInstance(toType);
+ // https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/object-and-collection-initializers#collection-initializers
+ var addMethod = toType.GetMethods().FirstOrDefault(m => !m.IsStatic && m.Name == "Add" && m.GetParameters().Length == 1 && m.GetParameters()[0].ParameterType == elementType);
+ if (addMethod == null)
+ return false;
- for (int i = 0; i < configurationElements.Length; ++i)
- {
- var argumentValue = ConfigurationReader.GetArgumentValue(configurationElements[i], _configurationAssemblies);
- var value = argumentValue.ConvertTo(elementType, resolutionContext);
- addMethod.Invoke(result, new[] { value });
- }
+ var configurationElements = _section.GetChildren().ToArray();
+ result = Activator.CreateInstance(toType) ?? throw new InvalidOperationException($"Activator.CreateInstance returned null for {toType}");
- return true;
+ for (int i = 0; i < configurationElements.Length; ++i)
+ {
+ var argumentValue = ConfigurationReader.GetArgumentValue(configurationElements[i], _configurationAssemblies);
+ var value = argumentValue.ConvertTo(elementType, resolutionContext);
+ addMethod.Invoke(result, new[] { value });
}
+
+ return true;
}
+ }
- internal static bool TryBuildCtorExpression(
- IConfigurationSection section, Type parameterType, ResolutionContext resolutionContext, out NewExpression ctorExpression)
- {
- ctorExpression = null;
+ internal static bool TryBuildCtorExpression(
+ IConfigurationSection section, Type parameterType, ResolutionContext resolutionContext, [NotNullWhen(true)] out NewExpression? ctorExpression)
+ {
+ ctorExpression = null;
- var typeDirective = section.GetValue("$type") switch
+ var typeDirective = section.GetValue("$type") switch
+ {
+ not null => "$type",
+ null => section.GetValue("type") switch
{
- not null => "$type",
- null => section.GetValue("type") switch
- {
- not null => "type",
- null => null,
- },
- };
+ not null => "type",
+ null => null,
+ },
+ };
- var type = typeDirective switch
- {
- not null => Type.GetType(section.GetValue(typeDirective), throwOnError: false),
- null => parameterType,
- };
+ var type = typeDirective switch
+ {
+ not null => Type.GetType(section.GetValue(typeDirective)!, throwOnError: false),
+ null => parameterType,
+ };
- if (type is null or { IsAbstract: true })
- {
- return false;
- }
+ if (type is null or { IsAbstract: true })
+ {
+ return false;
+ }
- var suppliedArguments = section.GetChildren().Where(s => s.Key != typeDirective)
- .ToDictionary(s => s.Key, StringComparer.OrdinalIgnoreCase);
+ var suppliedArguments = section.GetChildren().Where(s => s.Key != typeDirective)
+ .ToDictionary(s => s.Key, StringComparer.OrdinalIgnoreCase);
- if (suppliedArguments.Count == 0 &&
- type.GetConstructor(Type.EmptyTypes) is ConstructorInfo parameterlessCtor)
- {
- ctorExpression = Expression.New(parameterlessCtor);
- return true;
- }
+ if (suppliedArguments.Count == 0 &&
+ type.GetConstructor(Type.EmptyTypes) is ConstructorInfo parameterlessCtor)
+ {
+ ctorExpression = Expression.New(parameterlessCtor);
+ return true;
+ }
- var ctor =
- (from c in type.GetConstructors()
- from p in c.GetParameters()
- let argumentBindResult = suppliedArguments.TryGetValue(p.Name, out var argValue) switch
- {
- true => new { success = true, hasMatch = true, value = (object)argValue },
- false => p.HasDefaultValue switch
- {
- true => new { success = true, hasMatch = false, value = p.DefaultValue },
- false => new { success = false, hasMatch = false, value = (object)null },
- },
- }
- group new { argumentBindResult, p.ParameterType } by c into gr
- where gr.All(z => z.argumentBindResult.success)
- let matchedArgs = gr.Where(z => z.argumentBindResult.hasMatch).ToList()
- orderby matchedArgs.Count descending,
- matchedArgs.Count(p => p.ParameterType == typeof(string)) descending
- select new
+ var ctor =
+ (from c in type.GetConstructors()
+ from p in c.GetParameters()
+ let argumentBindResult = suppliedArguments.TryGetValue(p.Name ?? "", out var argValue) switch
+ {
+ true => new { success = true, hasMatch = true, value = (object?)argValue },
+ false => p.HasDefaultValue switch
{
- ConstructorInfo = gr.Key,
- ArgumentValues = gr.Select(z => new { Value = z.argumentBindResult.value, Type = z.ParameterType })
- .ToList()
- }).FirstOrDefault();
+ true => new { success = true, hasMatch = false, value = (object?)p.DefaultValue },
+ false => new { success = false, hasMatch = false, value = (object?)null },
+ },
+ }
+ group new { argumentBindResult, p.ParameterType } by c into gr
+ where gr.All(z => z.argumentBindResult.success)
+ let matchedArgs = gr.Where(z => z.argumentBindResult.hasMatch).ToList()
+ orderby matchedArgs.Count descending,
+ matchedArgs.Count(p => p.ParameterType == typeof(string)) descending
+ select new
+ {
+ ConstructorInfo = gr.Key,
+ ArgumentValues = gr.Select(z => new { Value = z.argumentBindResult.value, Type = z.ParameterType })
+ .ToList()
+ }).FirstOrDefault();
+
+ if (ctor is null)
+ {
+ return false;
+ }
- if (ctor is null)
+ var ctorArguments = new List();
+ foreach (var argumentValue in ctor.ArgumentValues)
+ {
+ if (TryBindToCtorArgument(argumentValue.Value, argumentValue.Type, resolutionContext, out var argumentExpression))
{
- return false;
+ ctorArguments.Add(argumentExpression);
}
-
- var ctorArguments = new List();
- foreach (var argumentValue in ctor.ArgumentValues)
+ else
{
- if (TryBindToCtorArgument(argumentValue.Value, argumentValue.Type, resolutionContext, out var argumentExpression))
- {
- ctorArguments.Add(argumentExpression);
- }
- else
- {
- return false;
- }
+ return false;
}
+ }
- ctorExpression = Expression.New(ctor.ConstructorInfo, ctorArguments);
- return true;
+ ctorExpression = Expression.New(ctor.ConstructorInfo, ctorArguments);
+ return true;
- static bool TryBindToCtorArgument(object value, Type type, ResolutionContext resolutionContext, out Expression argumentExpression)
- {
- argumentExpression = null;
+ static bool TryBindToCtorArgument(object value, Type type, ResolutionContext resolutionContext, [NotNullWhen(true)] out Expression? argumentExpression)
+ {
+ argumentExpression = null;
- if (value is IConfigurationSection s)
+ if (value is IConfigurationSection s)
+ {
+ if (s.Value is string argValue)
{
- if (s.Value is string argValue)
+ var stringArgumentValue = new StringArgumentValue(argValue);
+ try
{
- var stringArgumentValue = new StringArgumentValue(argValue);
- try
- {
- argumentExpression = Expression.Constant(
- stringArgumentValue.ConvertTo(type, resolutionContext),
- type);
-
- return true;
- }
- catch (Exception)
- {
- return false;
- }
+ argumentExpression = Expression.Constant(
+ stringArgumentValue.ConvertTo(type, resolutionContext),
+ type);
+
+ return true;
}
- else if (s.GetChildren().Any())
+ catch (Exception)
{
- if (TryBuildCtorExpression(s, type, resolutionContext, out var ctorExpression))
- {
- argumentExpression = ctorExpression;
- return true;
- }
-
return false;
}
}
+ else if (s.GetChildren().Any())
+ {
+ if (TryBuildCtorExpression(s, type, resolutionContext, out var ctorExpression))
+ {
+ argumentExpression = ctorExpression;
+ return true;
+ }
- argumentExpression = Expression.Constant(value, type);
- return true;
+ return false;
+ }
}
+
+ argumentExpression = Expression.Constant(value, type);
+ return true;
}
+ }
- static bool IsContainer(Type type, out Type elementType)
+ static bool IsContainer(Type type, [NotNullWhen(true)] out Type? elementType)
+ {
+ elementType = null;
+ foreach (var iface in type.GetInterfaces())
{
- elementType = null;
- foreach (var iface in type.GetInterfaces())
+ if (iface.IsGenericType)
{
- if (iface.IsGenericType)
+ if (iface.GetGenericTypeDefinition() == typeof(IEnumerable<>))
{
- if (iface.GetGenericTypeDefinition() == typeof(IEnumerable<>))
- {
- elementType = iface.GetGenericArguments()[0];
- return true;
- }
+ elementType = iface.GetGenericArguments()[0];
+ return true;
}
}
-
- return false;
}
+
+ return false;
}
}
diff --git a/src/Serilog.Settings.Configuration/Settings/Configuration/ResolutionContext.cs b/src/Serilog.Settings.Configuration/Settings/Configuration/ResolutionContext.cs
index 2963a64e..61d40d86 100644
--- a/src/Serilog.Settings.Configuration/Settings/Configuration/ResolutionContext.cs
+++ b/src/Serilog.Settings.Configuration/Settings/Configuration/ResolutionContext.cs
@@ -1,85 +1,77 @@
-using System;
-using System.Collections.Generic;
-using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Configuration;
using Serilog.Core;
-namespace Serilog.Settings.Configuration
+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 IDictionary _declaredFilterSwitches;
+ readonly IConfiguration? _appConfiguration;
+
+ public ResolutionContext(IConfiguration? appConfiguration = null, ConfigurationReaderOptions? readerOptions = null)
+ {
+ _declaredLevelSwitches = new Dictionary();
+ _declaredFilterSwitches = new Dictionary();
+ _appConfiguration = appConfiguration;
+ ReaderOptions = readerOptions ?? new ConfigurationReaderOptions();
+ }
+
+ public ConfigurationReaderOptions ReaderOptions { get; }
+
///
- /// Keeps track of available elements that are useful when resolving values in the settings system.
+ /// Looks up a switch in the declared LoggingLevelSwitches
///
- sealed class ResolutionContext
+ /// the name of a switch to look up
+ /// the LoggingLevelSwitch registered with the name
+ /// if no switch has been registered with
+ public LoggingLevelSwitch LookUpLevelSwitchByName(string switchName)
{
- readonly IDictionary _declaredLevelSwitches;
- readonly IDictionary _declaredFilterSwitches;
- readonly IConfiguration _appConfiguration;
-
- public ResolutionContext(IConfiguration appConfiguration = null)
+ if (_declaredLevelSwitches.TryGetValue(switchName, out var levelSwitch))
{
- _declaredLevelSwitches = new Dictionary();
- _declaredFilterSwitches = new Dictionary();
- _appConfiguration = appConfiguration;
+ return levelSwitch;
}
- ///
- /// 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 LookUpLevelSwitchByName(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\"}}");
- }
+ throw new InvalidOperationException($"No LoggingLevelSwitch has been declared with name \"{switchName}\". You might be missing a section \"LevelSwitches\":{{\"{switchName}\":\"InitialLevel\"}}");
+ }
- public LoggingFilterSwitchProxy LookUpFilterSwitchByName(string switchName)
+ public LoggingFilterSwitchProxy LookUpFilterSwitchByName(string switchName)
+ {
+ if (_declaredFilterSwitches.TryGetValue(switchName, out var filterSwitch))
{
- if (_declaredFilterSwitches.TryGetValue(switchName, out var filterSwitch))
- {
- return filterSwitch;
- }
-
- throw new InvalidOperationException($"No LoggingFilterSwitch has been declared with name \"{switchName}\". You might be missing a section \"FilterSwitches\":{{\"{switchName}\":\"{{FilterExpression}}\"}}");
+ return filterSwitch;
}
- public bool HasAppConfiguration => _appConfiguration != null;
+ throw new InvalidOperationException($"No LoggingFilterSwitch has been declared with name \"{switchName}\". You might be missing a section \"FilterSwitches\":{{\"{switchName}\":\"{{FilterExpression}}\"}}");
+ }
- public IConfiguration AppConfiguration
- {
- get
- {
- if (!HasAppConfiguration)
- {
- throw new InvalidOperationException("AppConfiguration is not available");
- }
+ public bool HasAppConfiguration => _appConfiguration != null;
- return _appConfiguration;
- }
- }
+ public IConfiguration AppConfiguration => _appConfiguration ?? throw new InvalidOperationException("AppConfiguration is not available");
- public void AddLevelSwitch(string levelSwitchName, LoggingLevelSwitch levelSwitch)
- {
- if (levelSwitchName == null) throw new ArgumentNullException(nameof(levelSwitchName));
- if (levelSwitch == null) throw new ArgumentNullException(nameof(levelSwitch));
- _declaredLevelSwitches[ToSwitchReference(levelSwitchName)] = levelSwitch;
- }
+ public string AddLevelSwitch(string levelSwitchName, LoggingLevelSwitch levelSwitch)
+ {
+ if (levelSwitchName == null) throw new ArgumentNullException(nameof(levelSwitchName));
+ if (levelSwitch == null) throw new ArgumentNullException(nameof(levelSwitch));
+ var referenceName = ToSwitchReference(levelSwitchName);
+ _declaredLevelSwitches[referenceName] = levelSwitch;
+ return referenceName;
+ }
- public void AddFilterSwitch(string filterSwitchName, LoggingFilterSwitchProxy filterSwitch)
- {
- if (filterSwitchName == null) throw new ArgumentNullException(nameof(filterSwitchName));
- if (filterSwitch == null) throw new ArgumentNullException(nameof(filterSwitch));
- _declaredFilterSwitches[ToSwitchReference(filterSwitchName)] = filterSwitch;
- }
+ public void AddFilterSwitch(string filterSwitchName, LoggingFilterSwitchProxy filterSwitch)
+ {
+ if (filterSwitchName == null) throw new ArgumentNullException(nameof(filterSwitchName));
+ if (filterSwitch == null) throw new ArgumentNullException(nameof(filterSwitch));
+ var referenceName = ToSwitchReference(filterSwitchName);
+ _declaredFilterSwitches[referenceName] = filterSwitch;
+ }
- string ToSwitchReference(string switchName)
- {
- return switchName.StartsWith("$") ? switchName : $"${switchName}";
- }
+ string ToSwitchReference(string switchName)
+ {
+ return switchName.StartsWith("$") ? switchName : $"${switchName}";
}
}
diff --git a/src/Serilog.Settings.Configuration/Settings/Configuration/StringArgumentValue.cs b/src/Serilog.Settings.Configuration/Settings/Configuration/StringArgumentValue.cs
index 69dceb3f..ebf3d2ea 100644
--- a/src/Serilog.Settings.Configuration/Settings/Configuration/StringArgumentValue.cs
+++ b/src/Serilog.Settings.Configuration/Settings/Configuration/StringArgumentValue.cs
@@ -1,165 +1,197 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
+using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Text.RegularExpressions;
using Serilog.Core;
-namespace Serilog.Settings.Configuration
+namespace Serilog.Settings.Configuration;
+
+class StringArgumentValue : IConfigurationArgumentValue
{
- class StringArgumentValue : IConfigurationArgumentValue
- {
- readonly string _providedValue;
+ readonly string _providedValue;
- static readonly Regex StaticMemberAccessorRegex = new Regex("^(?[^:]+)::(?[A-Za-z][A-Za-z0-9]*)(?[^:]*)$");
+ static readonly Regex StaticMemberAccessorRegex = new Regex("^(?[^:]+)::(?[A-Za-z][A-Za-z0-9]*)(?[^:]*)$");
- public StringArgumentValue(string providedValue)
+ public StringArgumentValue(string providedValue)
+ {
+ _providedValue = providedValue ?? throw new ArgumentNullException(nameof(providedValue));
+ }
+
+ static readonly Dictionary> ExtendedTypeConversions = new Dictionary>
{
- _providedValue = providedValue ?? throw new ArgumentNullException(nameof(providedValue));
- }
+ { typeof(Uri), s => new Uri(s) },
+ { typeof(TimeSpan), s => TimeSpan.Parse(s) },
+ { typeof(Type), s => Type.GetType(s, throwOnError:true)! },
+ };
- static readonly Dictionary> ExtendedTypeConversions = new Dictionary>
- {
- { typeof(Uri), s => new Uri(s) },
- { typeof(TimeSpan), s => TimeSpan.Parse(s) },
- { typeof(Type), s => Type.GetType(s, throwOnError:true) },
- };
+ public object? ConvertTo(Type toType, ResolutionContext resolutionContext)
+ {
+ var argumentValue = Environment.ExpandEnvironmentVariables(_providedValue);
- public object ConvertTo(Type toType, ResolutionContext resolutionContext)
+ if (toType == typeof(LoggingLevelSwitch))
{
- var argumentValue = Environment.ExpandEnvironmentVariables(_providedValue);
-
- if (toType == typeof(LoggingLevelSwitch))
- {
- return resolutionContext.LookUpLevelSwitchByName(argumentValue);
- }
+ return resolutionContext.LookUpLevelSwitchByName(argumentValue);
+ }
- if (toType.FullName == "Serilog.Expressions.LoggingFilterSwitch" ||
- toType.FullName == "Serilog.Filters.Expressions.LoggingFilterSwitch")
- {
- return resolutionContext.LookUpFilterSwitchByName(argumentValue).RealSwitch;
- }
+ if (toType.FullName == "Serilog.Expressions.LoggingFilterSwitch" ||
+ toType.FullName == "Serilog.Filters.Expressions.LoggingFilterSwitch")
+ {
+ return resolutionContext.LookUpFilterSwitchByName(argumentValue).RealSwitch;
+ }
- var toTypeInfo = toType.GetTypeInfo();
- if (toTypeInfo.IsGenericType && toType.GetGenericTypeDefinition() == typeof(Nullable<>))
- {
- if (string.IsNullOrEmpty(argumentValue))
- return null;
+ var toTypeInfo = toType.GetTypeInfo();
+ if (toTypeInfo.IsGenericType && toType.GetGenericTypeDefinition() == typeof(Nullable<>))
+ {
+ if (string.IsNullOrEmpty(argumentValue))
+ return null;
- // unwrap Nullable<> type since we're not handling null situations
- toType = toTypeInfo.GenericTypeArguments[0];
- toTypeInfo = toType.GetTypeInfo();
- }
+ // unwrap Nullable<> type since we're not handling null situations
+ toType = toTypeInfo.GenericTypeArguments[0];
+ toTypeInfo = toType.GetTypeInfo();
+ }
- if (toTypeInfo.IsEnum)
- return Enum.Parse(toType, argumentValue);
+ if (toTypeInfo.IsEnum)
+ return Enum.Parse(toType, argumentValue);
- var convertor = ExtendedTypeConversions
- .Where(t => t.Key.GetTypeInfo().IsAssignableFrom(toTypeInfo))
- .Select(t => t.Value)
- .FirstOrDefault();
+ var convertor = ExtendedTypeConversions
+ .Where(t => t.Key.GetTypeInfo().IsAssignableFrom(toTypeInfo))
+ .Select(t => t.Value)
+ .FirstOrDefault();
- if (convertor != null)
- return convertor(argumentValue);
+ if (convertor != null)
+ return convertor(argumentValue);
- if (!string.IsNullOrWhiteSpace(argumentValue))
+ if (!string.IsNullOrWhiteSpace(argumentValue))
+ {
+ // check if value looks like a static property or field directive
+ // like "Namespace.TypeName::StaticProperty, AssemblyName"
+ if (toType != typeof(string) &&
+ TryParseStaticMemberAccessor(argumentValue, out var accessorTypeName, out var memberName))
{
- // check if value looks like a static property or field directive
- // like "Namespace.TypeName::StaticProperty, AssemblyName"
- if (toType != typeof(string) &&
- TryParseStaticMemberAccessor(argumentValue, out var accessorTypeName, out var memberName))
+ var accessorType = Type.GetType(accessorTypeName, throwOnError: true)!;
+
+ // if delegate, look for a method and then construct a delegate
+ if (typeof(Delegate).IsAssignableFrom(toType) || typeof(MethodInfo) == toType)
{
- var accessorType = Type.GetType(accessorTypeName, throwOnError: true);
- // is there a public static property with that name ?
- var publicStaticPropertyInfo = accessorType.GetTypeInfo().DeclaredProperties
+ var methodCandidates = accessorType.GetTypeInfo().DeclaredMethods
.Where(x => x.Name == memberName)
- .Where(x => x.GetMethod != null)
- .Where(x => x.GetMethod.IsPublic)
- .FirstOrDefault(x => x.GetMethod.IsStatic);
+ .Where(x => x.IsPublic)
+ .Where(x => !x.IsGenericMethod)
+ .Where(x => x.IsStatic)
+ .ToList();
- if (publicStaticPropertyInfo != null)
+ if (methodCandidates.Count > 1)
{
- return publicStaticPropertyInfo.GetValue(null); // static property, no instance to pass
+ // filter possible method overloads
+
+ var delegateSig = toType.GetMethod("Invoke");
+ var delegateParameters = delegateSig!.GetParameters().Select(x => x.ParameterType);
+ methodCandidates = methodCandidates
+ .Where(x => x.ReturnType == delegateSig.ReturnType && x.GetParameters().Select(y => y.ParameterType).SequenceEqual(delegateParameters))
+ .ToList();
}
- // no property ? look for a public static field
- var publicStaticFieldInfo = accessorType.GetTypeInfo().DeclaredFields
- .Where(x => x.Name == memberName)
- .Where(x => x.IsPublic)
- .FirstOrDefault(x => x.IsStatic);
+ var methodCandidate = methodCandidates.SingleOrDefault();
- if (publicStaticFieldInfo != null)
+ if (methodCandidate != null)
{
- return publicStaticFieldInfo.GetValue(null); // static field, no instance to pass
+ if (typeof(MethodInfo) == toType)
+ {
+ return methodCandidate;
+ }
+ else
+ {
+ return methodCandidate.CreateDelegate(toType);
+ }
}
-
- throw new InvalidOperationException($"Could not find a public static property or field with name `{memberName}` on type `{accessorTypeName}`");
}
- if (toTypeInfo.IsInterface || toTypeInfo.IsAbstract)
- {
- // maybe it's the assembly-qualified type name of a concrete implementation
- // with a default constructor
- var type = FindType(argumentValue.Trim());
- if (type == null)
- {
- throw new InvalidOperationException($"Type {argumentValue} was not found.");
- }
+ // is there a public static property with that name ?
+ var publicStaticPropertyInfo = accessorType.GetTypeInfo().DeclaredProperties
+ .Where(x => x.Name == memberName)
+ .FirstOrDefault(x => x.GetMethod != null && x.GetMethod.IsPublic && x.GetMethod.IsStatic);
- var ctor = type.GetTypeInfo().DeclaredConstructors.Where(ci => !ci.IsStatic).FirstOrDefault(ci =>
- {
- var parameters = ci.GetParameters();
- return parameters.Length == 0 || parameters.All(pi => pi.HasDefaultValue);
- });
+ if (publicStaticPropertyInfo != null)
+ {
+ return publicStaticPropertyInfo.GetValue(null); // static property, no instance to pass
+ }
- if (ctor == null)
- throw new InvalidOperationException($"A default constructor was not found on {type.FullName}.");
+ // no property ? look for a public static field
+ var publicStaticFieldInfo = accessorType.GetTypeInfo().DeclaredFields
+ .Where(x => x.Name == memberName)
+ .Where(x => x.IsPublic)
+ .FirstOrDefault(x => x.IsStatic);
- var call = ctor.GetParameters().Select(pi => pi.DefaultValue).ToArray();
- return ctor.Invoke(call);
+ if (publicStaticFieldInfo != null)
+ {
+ return publicStaticFieldInfo.GetValue(null); // static field, no instance to pass
}
- }
- return Convert.ChangeType(argumentValue, toType);
- }
+ throw new InvalidOperationException($"Could not find a public static property or field with name `{memberName}` on type `{accessorTypeName}`");
+ }
- internal static Type FindType(string typeName)
- {
- var type = Type.GetType(typeName);
- if (type == null)
+ if (toTypeInfo.IsInterface || toTypeInfo.IsAbstract)
{
- if (!typeName.Contains(','))
+ // maybe it's the assembly-qualified type name of a concrete implementation
+ // with a default constructor
+ var type = FindType(argumentValue.Trim());
+ if (type == null)
{
- type = Type.GetType($"{typeName}, Serilog");
+ throw new InvalidOperationException($"Type {argumentValue} was not found.");
}
- }
- return type;
+ var ctor = type.GetTypeInfo().DeclaredConstructors.Where(ci => !ci.IsStatic).FirstOrDefault(ci =>
+ {
+ var parameters = ci.GetParameters();
+ return parameters.Length == 0 || parameters.All(pi => pi.HasDefaultValue);
+ });
+
+ if (ctor == null)
+ throw new InvalidOperationException($"A default constructor was not found on {type.FullName}.");
+
+ var call = ctor.GetParameters().Select(pi => pi.DefaultValue).ToArray();
+ return ctor.Invoke(call);
+ }
}
- internal static bool TryParseStaticMemberAccessor(string input, out string accessorTypeName, out string memberName)
+ return Convert.ChangeType(argumentValue, toType, resolutionContext.ReaderOptions.FormatProvider);
+ }
+
+ internal static Type? FindType(string typeName)
+ {
+ var type = Type.GetType(typeName);
+ if (type == null)
{
- if (input == null)
+ if (!typeName.Contains(','))
{
- accessorTypeName = null;
- memberName = null;
- return false;
- }
- if (StaticMemberAccessorRegex.IsMatch(input))
- {
- var match = StaticMemberAccessorRegex.Match(input);
- var shortAccessorTypeName = match.Groups["shortTypeName"].Value;
- var rawMemberName = match.Groups["memberName"].Value;
- var extraQualifiers = match.Groups["typeNameExtraQualifiers"].Value;
-
- memberName = rawMemberName.Trim();
- accessorTypeName = shortAccessorTypeName.Trim() + extraQualifiers.TrimEnd();
- return true;
+ type = Type.GetType($"{typeName}, Serilog");
}
+ }
+
+ return type;
+ }
+
+ internal static bool TryParseStaticMemberAccessor(string? input, [NotNullWhen(true)] out string? accessorTypeName, [NotNullWhen(true)] out string? memberName)
+ {
+ if (input == null)
+ {
accessorTypeName = null;
memberName = null;
return false;
}
+ if (StaticMemberAccessorRegex.IsMatch(input))
+ {
+ var match = StaticMemberAccessorRegex.Match(input);
+ var shortAccessorTypeName = match.Groups["shortTypeName"].Value;
+ var rawMemberName = match.Groups["memberName"].Value;
+ var extraQualifiers = match.Groups["typeNameExtraQualifiers"].Value;
+
+ memberName = rawMemberName.Trim();
+ accessorTypeName = shortAccessorTypeName.Trim() + extraQualifiers.TrimEnd();
+ return true;
+ }
+ accessorTypeName = null;
+ memberName = null;
+ return false;
}
}
diff --git a/src/Serilog.Settings.Configuration/Settings/Configuration/SurrogateConfigurationMethods.cs b/src/Serilog.Settings.Configuration/Settings/Configuration/SurrogateConfigurationMethods.cs
index 4b460b32..b758d7c3 100644
--- a/src/Serilog.Settings.Configuration/Settings/Configuration/SurrogateConfigurationMethods.cs
+++ b/src/Serilog.Settings.Configuration/Settings/Configuration/SurrogateConfigurationMethods.cs
@@ -1,124 +1,120 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Reflection;
+using System.Reflection;
using Serilog.Configuration;
using Serilog.Core;
using Serilog.Events;
-namespace Serilog.Settings.Configuration
+namespace Serilog.Settings.Configuration;
+
+///
+/// Contains "fake extension" methods for the Serilog configuration API.
+/// By default the settings know how to find extension methods, but some configuration
+/// are actually "regular" method calls and would not be found otherwise.
+///
+/// This static class contains internal methods that can be used instead.
+///
+///
+static class SurrogateConfigurationMethods
{
- ///
- /// Contains "fake extension" methods for the Serilog configuration API.
- /// By default the settings know how to find extension methods, but some configuration
- /// are actually "regular" method calls and would not be found otherwise.
- ///
- /// This static class contains internal methods that can be used instead.
- ///
- ///
- static class SurrogateConfigurationMethods
- {
- static readonly Dictionary SurrogateMethodCandidates = typeof(SurrogateConfigurationMethods)
- .GetTypeInfo().DeclaredMethods
- .GroupBy(m => m.GetParameters().First().ParameterType)
- .ToDictionary(g => g.Key, g => g.ToArray());
-
-
- internal static readonly MethodInfo[] WriteTo = SurrogateMethodCandidates[typeof(LoggerSinkConfiguration)];
- internal static readonly MethodInfo[] AuditTo = SurrogateMethodCandidates[typeof(LoggerAuditSinkConfiguration)];
- internal static readonly MethodInfo[] Enrich = SurrogateMethodCandidates[typeof(LoggerEnrichmentConfiguration)];
- internal static readonly MethodInfo[] Destructure = SurrogateMethodCandidates[typeof(LoggerDestructuringConfiguration)];
- internal static readonly MethodInfo[] Filter = SurrogateMethodCandidates[typeof(LoggerFilterConfiguration)];
-
- /*
- Pass-through calls to various Serilog config methods which are
- implemented as instance methods rather than extension methods.
- ConfigurationReader adds those to the already discovered extension methods
- so they can be invoked as well.
- */
-
- // ReSharper disable UnusedMember.Local
- // those methods are discovered through reflection by `SurrogateMethodCandidates`
- // ReSharper has no way to see that they are actually used ...
-
- // .WriteTo...
- // ========
- static LoggerConfiguration Sink(
- LoggerSinkConfiguration loggerSinkConfiguration,
- ILogEventSink sink,
- LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum,
- LoggingLevelSwitch levelSwitch = null)
- => loggerSinkConfiguration.Sink(sink, restrictedToMinimumLevel, levelSwitch);
-
- static LoggerConfiguration Logger(
- LoggerSinkConfiguration loggerSinkConfiguration,
- Action configureLogger,
- LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum,
- LoggingLevelSwitch levelSwitch = null)
- => loggerSinkConfiguration.Logger(configureLogger, restrictedToMinimumLevel, levelSwitch);
-
- // .AuditTo...
- // ========
- static LoggerConfiguration Sink(
- LoggerAuditSinkConfiguration auditSinkConfiguration,
- ILogEventSink sink,
- LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum,
- LoggingLevelSwitch levelSwitch = null)
- => auditSinkConfiguration.Sink(sink, restrictedToMinimumLevel, levelSwitch);
-
- static LoggerConfiguration Logger(
- LoggerAuditSinkConfiguration auditSinkConfiguration,
- Action configureLogger,
- LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum,
- LoggingLevelSwitch levelSwitch = null)
- => auditSinkConfiguration.Logger(configureLogger, restrictedToMinimumLevel, levelSwitch);
-
- // .Filter...
- // =======
- // TODO: add overload for array argument (ILogEventEnricher[])
- // expose `With(params ILogEventFilter[] filters)` as if it was `With(ILogEventFilter filter)`
- static LoggerConfiguration With(LoggerFilterConfiguration loggerFilterConfiguration, ILogEventFilter filter)
- => loggerFilterConfiguration.With(filter);
-
- // .Destructure...
- // ============
- // TODO: add overload for array argument (IDestructuringPolicy[])
- // expose `With(params IDestructuringPolicy[] destructuringPolicies)` as if it was `With(IDestructuringPolicy policy)`
- static LoggerConfiguration With(LoggerDestructuringConfiguration loggerDestructuringConfiguration, IDestructuringPolicy policy)
- => loggerDestructuringConfiguration.With(policy);
-
- static LoggerConfiguration ToMaximumDepth(LoggerDestructuringConfiguration loggerDestructuringConfiguration, int maximumDestructuringDepth)
- => loggerDestructuringConfiguration.ToMaximumDepth(maximumDestructuringDepth);
-
- static LoggerConfiguration ToMaximumStringLength(LoggerDestructuringConfiguration loggerDestructuringConfiguration, int maximumStringLength)
- => loggerDestructuringConfiguration.ToMaximumStringLength(maximumStringLength);
-
- static LoggerConfiguration ToMaximumCollectionCount(LoggerDestructuringConfiguration loggerDestructuringConfiguration, int maximumCollectionCount)
- => loggerDestructuringConfiguration.ToMaximumCollectionCount(maximumCollectionCount);
-
- static LoggerConfiguration AsScalar(LoggerDestructuringConfiguration loggerDestructuringConfiguration, Type scalarType)
- => loggerDestructuringConfiguration.AsScalar(scalarType);
-
- // .Enrich...
- // =======
- // expose `With(params ILogEventEnricher[] enrichers)` as if it was `With(ILogEventEnricher enricher)`
- static LoggerConfiguration With(
- LoggerEnrichmentConfiguration loggerEnrichmentConfiguration,
- ILogEventEnricher enricher)
- => loggerEnrichmentConfiguration.With(enricher);
-
- static LoggerConfiguration AtLevel(
- LoggerEnrichmentConfiguration loggerEnrichmentConfiguration,
- Action configureEnricher,
- LogEventLevel enrichFromLevel = LevelAlias.Minimum,
- LoggingLevelSwitch levelSwitch = null)
- => levelSwitch != null ? loggerEnrichmentConfiguration.AtLevel(levelSwitch, configureEnricher)
- : loggerEnrichmentConfiguration.AtLevel(enrichFromLevel, configureEnricher);
-
- static LoggerConfiguration FromLogContext(LoggerEnrichmentConfiguration loggerEnrichmentConfiguration)
- => loggerEnrichmentConfiguration.FromLogContext();
-
- // ReSharper restore UnusedMember.Local
- }
+ static readonly Dictionary SurrogateMethodCandidates = typeof(SurrogateConfigurationMethods)
+ .GetTypeInfo().DeclaredMethods
+ .GroupBy(m => m.GetParameters().First().ParameterType)
+ .ToDictionary(g => g.Key, g => g.ToArray());
+
+
+ internal static readonly MethodInfo[] WriteTo = SurrogateMethodCandidates[typeof(LoggerSinkConfiguration)];
+ internal static readonly MethodInfo[] AuditTo = SurrogateMethodCandidates[typeof(LoggerAuditSinkConfiguration)];
+ internal static readonly MethodInfo[] Enrich = SurrogateMethodCandidates[typeof(LoggerEnrichmentConfiguration)];
+ internal static readonly MethodInfo[] Destructure = SurrogateMethodCandidates[typeof(LoggerDestructuringConfiguration)];
+ internal static readonly MethodInfo[] Filter = SurrogateMethodCandidates[typeof(LoggerFilterConfiguration)];
+
+ /*
+ Pass-through calls to various Serilog config methods which are
+ implemented as instance methods rather than extension methods.
+ ConfigurationReader adds those to the already discovered extension methods
+ so they can be invoked as well.
+ */
+
+ // ReSharper disable UnusedMember.Local
+ // those methods are discovered through reflection by `SurrogateMethodCandidates`
+ // ReSharper has no way to see that they are actually used ...
+
+ // .WriteTo...
+ // ========
+ static LoggerConfiguration Sink(
+ LoggerSinkConfiguration loggerSinkConfiguration,
+ ILogEventSink sink,
+ LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum,
+ LoggingLevelSwitch? levelSwitch = null)
+ => loggerSinkConfiguration.Sink(sink, restrictedToMinimumLevel, levelSwitch);
+
+ static LoggerConfiguration Logger(
+ LoggerSinkConfiguration loggerSinkConfiguration,
+ Action configureLogger,
+ LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum,
+ LoggingLevelSwitch? levelSwitch = null)
+ => loggerSinkConfiguration.Logger(configureLogger, restrictedToMinimumLevel, levelSwitch);
+
+ // .AuditTo...
+ // ========
+ static LoggerConfiguration Sink(
+ LoggerAuditSinkConfiguration auditSinkConfiguration,
+ ILogEventSink sink,
+ LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum,
+ LoggingLevelSwitch? levelSwitch = null)
+ => auditSinkConfiguration.Sink(sink, restrictedToMinimumLevel, levelSwitch);
+
+ static LoggerConfiguration Logger(
+ LoggerAuditSinkConfiguration auditSinkConfiguration,
+ Action configureLogger,
+ LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum,
+ LoggingLevelSwitch? levelSwitch = null)
+ => auditSinkConfiguration.Logger(configureLogger, restrictedToMinimumLevel, levelSwitch);
+
+ // .Filter...
+ // =======
+ // TODO: add overload for array argument (ILogEventEnricher[])
+ // expose `With(params ILogEventFilter[] filters)` as if it was `With(ILogEventFilter filter)`
+ static LoggerConfiguration With(LoggerFilterConfiguration loggerFilterConfiguration, ILogEventFilter filter)
+ => loggerFilterConfiguration.With(filter);
+
+ // .Destructure...
+ // ============
+ // TODO: add overload for array argument (IDestructuringPolicy[])
+ // expose `With(params IDestructuringPolicy[] destructuringPolicies)` as if it was `With(IDestructuringPolicy policy)`
+ static LoggerConfiguration With(LoggerDestructuringConfiguration loggerDestructuringConfiguration, IDestructuringPolicy policy)
+ => loggerDestructuringConfiguration.With(policy);
+
+ static LoggerConfiguration ToMaximumDepth(LoggerDestructuringConfiguration loggerDestructuringConfiguration, int maximumDestructuringDepth)
+ => loggerDestructuringConfiguration.ToMaximumDepth(maximumDestructuringDepth);
+
+ static LoggerConfiguration ToMaximumStringLength(LoggerDestructuringConfiguration loggerDestructuringConfiguration, int maximumStringLength)
+ => loggerDestructuringConfiguration.ToMaximumStringLength(maximumStringLength);
+
+ static LoggerConfiguration ToMaximumCollectionCount(LoggerDestructuringConfiguration loggerDestructuringConfiguration, int maximumCollectionCount)
+ => loggerDestructuringConfiguration.ToMaximumCollectionCount(maximumCollectionCount);
+
+ static LoggerConfiguration AsScalar(LoggerDestructuringConfiguration loggerDestructuringConfiguration, Type scalarType)
+ => loggerDestructuringConfiguration.AsScalar(scalarType);
+
+ // .Enrich...
+ // =======
+ // expose `With(params ILogEventEnricher[] enrichers)` as if it was `With(ILogEventEnricher enricher)`
+ static LoggerConfiguration With(
+ LoggerEnrichmentConfiguration loggerEnrichmentConfiguration,
+ ILogEventEnricher enricher)
+ => loggerEnrichmentConfiguration.With(enricher);
+
+ static LoggerConfiguration AtLevel(
+ LoggerEnrichmentConfiguration loggerEnrichmentConfiguration,
+ Action configureEnricher,
+ LogEventLevel enrichFromLevel = LevelAlias.Minimum,
+ LoggingLevelSwitch? levelSwitch = null)
+ => levelSwitch != null ? loggerEnrichmentConfiguration.AtLevel(levelSwitch, configureEnricher)
+ : loggerEnrichmentConfiguration.AtLevel(enrichFromLevel, configureEnricher);
+
+ static LoggerConfiguration FromLogContext(LoggerEnrichmentConfiguration loggerEnrichmentConfiguration)
+ => loggerEnrichmentConfiguration.FromLogContext();
+
+ // ReSharper restore UnusedMember.Local
}
diff --git a/test/Serilog.Settings.Configuration.Tests/ApiApprovalTests.cs b/test/Serilog.Settings.Configuration.Tests/ApiApprovalTests.cs
new file mode 100644
index 00000000..08bf7f46
--- /dev/null
+++ b/test/Serilog.Settings.Configuration.Tests/ApiApprovalTests.cs
@@ -0,0 +1,25 @@
+#if NET7_0
+
+using PublicApiGenerator;
+using Shouldly;
+
+namespace Serilog.Settings.Configuration.Tests;
+
+public class ApiApprovalTests
+{
+ [Fact]
+ public void PublicApi_Should_Not_Change_Unintentionally()
+ {
+ var assembly = typeof(ConfigurationReaderOptions).Assembly;
+ var publicApi = assembly.GeneratePublicApi(
+ new()
+ {
+ IncludeAssemblyAttributes = false,
+ ExcludeAttributes = new[] { "System.Diagnostics.DebuggerDisplayAttribute" },
+ });
+
+ publicApi.ShouldMatchApproved(options => options.WithFilenameGenerator((_, _, fileType, fileExtension) => $"{assembly.GetName().Name!}.{fileType}.{fileExtension}"));
+ }
+}
+
+#endif
diff --git a/test/Serilog.Settings.Configuration.Tests/ConfigurationReaderTests.cs b/test/Serilog.Settings.Configuration.Tests/ConfigurationReaderTests.cs
index f5c96930..d235e8f2 100644
--- a/test/Serilog.Settings.Configuration.Tests/ConfigurationReaderTests.cs
+++ b/test/Serilog.Settings.Configuration.Tests/ConfigurationReaderTests.cs
@@ -1,176 +1,300 @@
-using Xunit;
using System.Reflection;
-using System.Linq;
+using Microsoft.Extensions.Configuration;
+using Serilog.Events;
using Serilog.Formatting;
using Serilog.Settings.Configuration.Assemblies;
using Serilog.Settings.Configuration.Tests.Support;
+using static Serilog.Settings.Configuration.Tests.Support.ConfigurationReaderTestHelpers;
-namespace Serilog.Settings.Configuration.Tests
+namespace Serilog.Settings.Configuration.Tests;
+
+public class ConfigurationReaderTests
{
- public class ConfigurationReaderTests
+ readonly ConfigurationReader _configurationReader;
+
+ public ConfigurationReaderTests()
{
- readonly ConfigurationReader _configurationReader;
+ _configurationReader = new ConfigurationReader(
+ JsonStringConfigSource.LoadSection("{ \"Serilog\": { } }", "Serilog"),
+ AssemblyFinder.ForSource(ConfigurationAssemblySource.UseLoadedAssemblies),
+ new ConfigurationReaderOptions());
+ }
- public ConfigurationReaderTests()
+ [Fact]
+ public void WriteToSupportSimplifiedSyntax()
+ {
+ // language=json
+ var json = """
{
- _configurationReader = new ConfigurationReader(
- JsonStringConfigSource.LoadSection(@"{ 'Serilog': { } }", "Serilog"),
- AssemblyFinder.ForSource(ConfigurationAssemblySource.UseLoadedAssemblies));
+ "WriteTo": [ "LiterateConsole", "DiagnosticTrace" ]
}
+ """;
- [Fact]
- public void WriteToSupportSimplifiedSyntax()
- {
- var json = @"
-{
- 'WriteTo': [ 'LiterateConsole', 'DiagnosticTrace' ]
-}";
+ var result = _configurationReader.GetMethodCalls(JsonStringConfigSource.LoadSection(json, "WriteTo"));
+ Assert.Equal(2, result.Count);
+ Assert.True(result.Contains("LiterateConsole"));
+ Assert.True(result.Contains("DiagnosticTrace"));
- var result = _configurationReader.GetMethodCalls(JsonStringConfigSource.LoadSection(json, "WriteTo"));
- Assert.Equal(2, result.Count);
- Assert.True(result.Contains("LiterateConsole"));
- Assert.True(result.Contains("DiagnosticTrace"));
+ Assert.Single(result["LiterateConsole"]);
+ Assert.Single(result["DiagnosticTrace"]);
+ }
- Assert.Single(result["LiterateConsole"]);
- Assert.Single(result["DiagnosticTrace"]);
+ [Fact]
+ public void WriteToSupportExpandedSyntaxWithoutArgs()
+ {
+ // language=json
+ var json = """
+ {
+ "WriteTo": [ {
+ "Name": "LiterateConsole"
+ }]
}
+ """;
- [Fact]
- public void WriteToSupportExpandedSyntaxWithoutArgs()
- {
- var json = @"
-{
- 'WriteTo': [ {
- 'Name': 'LiterateConsole'
- }]
-}";
+ var result = _configurationReader.GetMethodCalls(JsonStringConfigSource.LoadSection(json, "WriteTo"));
+ Assert.Equal(1, result.Count);
+ Assert.True(result.Contains("LiterateConsole"));
- var result = _configurationReader.GetMethodCalls(JsonStringConfigSource.LoadSection(json, "WriteTo"));
- Assert.Equal(1, result.Count);
- Assert.True(result.Contains("LiterateConsole"));
+ Assert.Single(result["LiterateConsole"]);
+ }
- Assert.Single(result["LiterateConsole"]);
+ [Fact]
+ public void WriteToSupportExpandedSyntaxWithArgs()
+ {
+ // language=json
+ var json = """
+ {
+ "WriteTo": [ {
+ "Name": "LiterateConsole",
+ "Args": {
+ "outputTemplate": "{Message}"
+ }
+ }]
}
+ """;
- [Fact]
- public void WriteToSupportExpandedSyntaxWithArgs()
- {
- var json = @"
-{
- 'WriteTo': [ {
- 'Name': 'LiterateConsole',
- 'Args': {
- 'outputTemplate': '{Message}'
- },
- }]
-}";
+ var result = _configurationReader.GetMethodCalls(JsonStringConfigSource.LoadSection(json, "WriteTo"));
- var result = _configurationReader.GetMethodCalls(JsonStringConfigSource.LoadSection(json, "WriteTo"));
+ Assert.Single(result);
+ Assert.True(result.Contains("LiterateConsole"));
- Assert.Single(result);
- Assert.True(result.Contains("LiterateConsole"));
+ Assert.Single(result["LiterateConsole"]);
- Assert.Single(result["LiterateConsole"]);
+ var args = result["LiterateConsole"].Single().ToArray();
- var args = result["LiterateConsole"].Single().ToArray();
+ Assert.Single(args);
+ Assert.Equal("outputTemplate", args[0].Key);
+ Assert.Equal("{Message}", args[0].Value.ConvertTo(typeof(string), new ResolutionContext()));
+ }
- Assert.Single(args);
- Assert.Equal("outputTemplate", args[0].Key);
- Assert.Equal("{Message}", args[0].Value.ConvertTo(typeof(string), new ResolutionContext()));
+ [Fact]
+ public void WriteToSupportMultipleSinksOfTheSameKind()
+ {
+ // language=json
+ var json = """
+ {
+ "WriteTo": [
+ {
+ "Name": "LiterateConsole",
+ "Args": {
+ "outputTemplate": "{Message}"
+ }
+ },
+ "DiagnosticTrace"
+ ],
+ "WriteTo:File1": {
+ "Name": "File",
+ "Args": {
+ "outputTemplate": "{Message}"
+ }
+ },
+ "WriteTo:File2": {
+ "Name": "File",
+ "Args": {
+ "outputTemplate": "{Message}"
+ }
+ }
}
+ """;
+
+ var result = _configurationReader.GetMethodCalls(JsonStringConfigSource.LoadSection(json, "WriteTo"));
- [Fact]
- public void WriteToSupportMultipleSinksOfTheSameKind()
+ Assert.Equal(3, result.Count);
+ Assert.True(result.Contains("LiterateConsole"));
+ Assert.True(result.Contains("DiagnosticTrace"));
+ Assert.True(result.Contains("File"));
+
+ Assert.Single(result["LiterateConsole"]);
+ Assert.Single(result["DiagnosticTrace"]);
+ Assert.Equal(2, result["File"].Count());
+ }
+
+ [Fact]
+ public void Enrich_SupportSimplifiedSyntax()
+ {
+ // language=json
+ var json = """
{
- var json = @"
-{
- 'WriteTo': [
- {
- 'Name': 'LiterateConsole',
- 'Args': {
- 'outputTemplate': '{Message}'
- },
- },
- 'DiagnosticTrace'
- ],
- 'WriteTo:File1': {
- 'Name': 'File',
- 'Args': {
- 'outputTemplate': '{Message}'
- },
- },
- 'WriteTo:File2': {
- 'Name': 'File',
- 'Args': {
- 'outputTemplate': '{Message}'
- },
+ "Enrich": [ "FromLogContext", "WithMachineName", "WithThreadId" ]
+ }
+ """;
+
+ var result = _configurationReader.GetMethodCalls(JsonStringConfigSource.LoadSection(json, "Enrich"));
+ Assert.Equal(3, result.Count);
+ Assert.True(result.Contains("FromLogContext"));
+ Assert.True(result.Contains("WithMachineName"));
+ Assert.True(result.Contains("WithThreadId"));
+
+ Assert.Single(result["FromLogContext"]);
+ Assert.Single(result["WithMachineName"]);
+ Assert.Single(result["WithThreadId"]);
}
-}";
- var result = _configurationReader.GetMethodCalls(JsonStringConfigSource.LoadSection(json, "WriteTo"));
+ [Fact]
+ public void CallableMethodsAreSelected()
+ {
+ var options = typeof(DummyLoggerConfigurationExtensions).GetTypeInfo().DeclaredMethods.ToList();
+ Assert.Equal(2, options.Count(mi => mi.Name == "DummyRollingFile"));
+ var suppliedArgumentNames = new[] { "pathFormat" };
- Assert.Equal(3, result.Count);
- Assert.True(result.Contains("LiterateConsole"));
- Assert.True(result.Contains("DiagnosticTrace"));
- Assert.True(result.Contains("File"));
+ var selected = ConfigurationReader.SelectConfigurationMethod(options, "DummyRollingFile", suppliedArgumentNames);
+ Assert.Equal(typeof(string), selected?.GetParameters()[1].ParameterType);
+ }
- Assert.Single(result["LiterateConsole"]);
- Assert.Single(result["DiagnosticTrace"]);
- Assert.Equal(2, result["File"].Count());
- }
+ [Fact]
+ public void MethodsAreSelectedBasedOnCountOfMatchedArguments()
+ {
+ var options = typeof(DummyLoggerConfigurationExtensions).GetTypeInfo().DeclaredMethods.ToList();
+ Assert.Equal(2, options.Count(mi => mi.Name == "DummyRollingFile"));
- [Fact]
- public void Enrich_SupportSimplifiedSyntax()
- {
- var json = @"
-{
- 'Enrich': [ 'FromLogContext', 'WithMachineName', 'WithThreadId' ]
-}";
-
- var result = _configurationReader.GetMethodCalls(JsonStringConfigSource.LoadSection(json, "Enrich"));
- Assert.Equal(3, result.Count);
- Assert.True(result.Contains("FromLogContext"));
- Assert.True(result.Contains("WithMachineName"));
- Assert.True(result.Contains("WithThreadId"));
-
- Assert.Single(result["FromLogContext"]);
- Assert.Single(result["WithMachineName"]);
- Assert.Single(result["WithThreadId"]);
- }
+ var suppliedArgumentNames = new[] { "pathFormat", "formatter" };
- [Fact]
- public void CallableMethodsAreSelected()
- {
- var options = typeof(DummyLoggerConfigurationExtensions).GetTypeInfo().DeclaredMethods.ToList();
- Assert.Equal(2, options.Count(mi => mi.Name == "DummyRollingFile"));
- var suppliedArgumentNames = new[] { "pathFormat" };
+ var selected = ConfigurationReader.SelectConfigurationMethod(options, "DummyRollingFile", suppliedArgumentNames);
+ Assert.Equal(typeof(ITextFormatter), selected?.GetParameters()[1].ParameterType);
+ }
- var selected = ConfigurationReader.SelectConfigurationMethod(options, "DummyRollingFile", suppliedArgumentNames);
- Assert.Equal(typeof(string), selected.GetParameters()[1].ParameterType);
- }
+ [Fact]
+ public void MethodsAreSelectedBasedOnCountOfMatchedArgumentsAndThenStringType()
+ {
+ var options = typeof(DummyLoggerConfigurationWithMultipleMethodsExtensions).GetTypeInfo().DeclaredMethods.ToList();
+ Assert.Equal(3, options.Count(mi => mi.Name == "DummyRollingFile"));
- [Fact]
- public void MethodsAreSelectedBasedOnCountOfMatchedArguments()
- {
- var options = typeof(DummyLoggerConfigurationExtensions).GetTypeInfo().DeclaredMethods.ToList();
- Assert.Equal(2, options.Count(mi => mi.Name == "DummyRollingFile"));
+ var suppliedArgumentNames = new[] { "pathFormat", "formatter" };
- var suppliedArgumentNames = new[] { "pathFormat", "formatter" };
+ var selected = ConfigurationReader.SelectConfigurationMethod(options, "DummyRollingFile", suppliedArgumentNames);
+ Assert.Equal(typeof(string), selected?.GetParameters()[2].ParameterType);
+ }
- var selected = ConfigurationReader.SelectConfigurationMethod(options, "DummyRollingFile", suppliedArgumentNames);
- Assert.Equal(typeof(ITextFormatter), selected.GetParameters()[1].ParameterType);
+ public static IEnumerable