diff --git a/.editorconfig b/.editorconfig
index f16002a..3687ef4 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -5,8 +5,9 @@ trim_trailing_whitespace = true
insert_final_newline = true
indent_style = space
indent_size = 4
+end_of_line = lf
-[*.{csproj,json,config,yml}]
+[*.{csproj,json,config,yml,props}]
indent_size = 2
[*.sh]
diff --git a/.gitignore b/.gitignore
index ff4324d..29e6b22 100644
--- a/.gitignore
+++ b/.gitignore
@@ -201,4 +201,6 @@ FakesAssemblies/
project.lock.json
#Test files
-*.txt
\ No newline at end of file
+*.txt
+
+artifacts/
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index d70d01d..0000000
--- a/.travis.yml
+++ /dev/null
@@ -1,11 +0,0 @@
-language: csharp
-
-matrix:
- include:
- - os: linux
- dist: trusty
- sudo: required
- dotnet: 2.1.4
- group: edge
-script:
- - ./build.sh
\ No newline at end of file
diff --git a/.vscode/launch.json b/.vscode/launch.json
new file mode 100644
index 0000000..921ec11
--- /dev/null
+++ b/.vscode/launch.json
@@ -0,0 +1,33 @@
+{
+ // Use IntelliSense to find out which attributes exist for C# debugging
+ // Use hover for the description of the existing attributes
+ // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md
+ "version": "0.2.0",
+ "configurations": [
+ {
+ "name": ".NET Core Launch (console)",
+ "type": "coreclr",
+ "request": "launch",
+ "preLaunchTask": "build",
+ // If you have changed target frameworks, make sure to update the program path.
+ "program": "${workspaceFolder}/sample/Sample/bin/Debug/netcoreapp3.1/Sample.dll",
+ "args": [],
+ "cwd": "${workspaceFolder}/sample/Sample",
+ // For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console
+ "console": "integratedTerminal",
+ "internalConsoleOptions": "neverOpen",
+ "stopAtEntry": false,
+ "linux": {
+ "env": {
+ "TEMP": "/tmp"
+ }
+ }
+ },
+ {
+ "name": ".NET Core Attach",
+ "type": "coreclr",
+ "request": "attach",
+ "processId": "${command:pickProcess}"
+ }
+ ]
+}
diff --git a/.vscode/tasks.json b/.vscode/tasks.json
new file mode 100644
index 0000000..56efa0c
--- /dev/null
+++ b/.vscode/tasks.json
@@ -0,0 +1,42 @@
+{
+ "version": "2.0.0",
+ "tasks": [
+ {
+ "label": "build",
+ "command": "dotnet",
+ "type": "process",
+ "args": [
+ "build",
+ "${workspaceFolder}/sample/Sample/Sample.csproj",
+ "/property:GenerateFullPaths=true",
+ "/consoleloggerparameters:NoSummary"
+ ],
+ "problemMatcher": "$msCompile"
+ },
+ {
+ "label": "publish",
+ "command": "dotnet",
+ "type": "process",
+ "args": [
+ "publish",
+ "${workspaceFolder}/sample/Sample/Sample.csproj",
+ "/property:GenerateFullPaths=true",
+ "/consoleloggerparameters:NoSummary"
+ ],
+ "problemMatcher": "$msCompile"
+ },
+ {
+ "label": "watch",
+ "command": "dotnet",
+ "type": "process",
+ "args": [
+ "watch",
+ "run",
+ "${workspaceFolder}/sample/Sample/Sample.csproj",
+ "/property:GenerateFullPaths=true",
+ "/consoleloggerparameters:NoSummary"
+ ],
+ "problemMatcher": "$msCompile"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/Build.ps1 b/Build.ps1
index 4830b07..5dcd00a 100644
--- a/Build.ps1
+++ b/Build.ps1
@@ -2,48 +2,70 @@ echo "build: Build started"
Push-Location $PSScriptRoot
-if(Test-Path .\artifacts) {
- echo "build: Cleaning .\artifacts"
- Remove-Item .\artifacts -Force -Recurse
+if (Test-Path .\artifacts) {
+ echo "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 "master" -and $revision -ne "local"]
+$suffix = @{ $true = ""; $false = "$($branch.Substring(0, [math]::Min(10,$branch.Length)))-$revision"}[$branch -eq "main" -and $revision -ne "local"]
echo "build: Version suffix is $suffix"
-foreach ($src in ls src/*) {
+foreach ($src in dir src/*) {
Push-Location $src
- echo "build: Packaging project in $src"
+ echo "build: Packaging project in $src"
- & dotnet pack -c Release -o ..\..\artifacts --version-suffix=$suffix --include-source
- if($LASTEXITCODE -ne 0) { exit 1 }
+ & dotnet pack -c Release -o ..\..\artifacts --version-suffix=$suffix -p:ContinuousIntegrationBuild=true
+ if ($LASTEXITCODE -ne 0) { exit 1 }
Pop-Location
}
-foreach ($test in ls test/*.PerformanceTests) {
+foreach ($test in dir test/*.PerformanceTests) {
Push-Location $test
- echo "build: Building performance test project in $test"
+ echo "build: Building performance test project in $test"
& dotnet build -c Release
- if($LASTEXITCODE -ne 0) { exit 2 }
+ if ($LASTEXITCODE -ne 0) { exit 2 }
Pop-Location
}
-foreach ($test in ls test/*.Tests) {
+foreach ($test in dir test/*.Tests) {
Push-Location $test
- echo "build: Testing project in $test"
+ echo "build: Testing project in $test"
- & dotnet test -c Release
- if($LASTEXITCODE -ne 0) { exit 3 }
+ if ($PSVersionTable.Platform -eq "Unix") {
+ & dotnet test -c Release -f netcoreapp2.1
+ & dotnet test -c Release -f netcoreapp3.1
+ & dotnet test -c Release -f net50
+ } else {
+ & dotnet test -c Release
+ }
+
+ if ($LASTEXITCODE -ne 0) { exit 3 }
+
+ Pop-Location
+}
+
+if ($PSVersionTable.Platform -eq "Unix") {
+ Push-Location sample/Sample
+
+ & dotnet run -f netcoreapp2.1 -c Release --run-once
+ if ($LASTEXITCODE -ne 0) { exit 4 }
+
+ & dotnet run -f netcoreapp3.1 -c Release --run-once
+ if ($LASTEXITCODE -ne 0) { exit 4 }
+
+ & dotnet run -f net50 -c Release --run-once
+ if ($LASTEXITCODE -ne 0) { exit 4 }
Pop-Location
}
diff --git a/CHANGES.md b/CHANGES.md
index 8589a7e..8753e6e 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,7 +1,90 @@
+# Changelog
+
+3.2.0 (pre-release)
+
+* #162 - LoggingFilterSwitch support
+* #202 - added support to AuditTo.Logger
+* #203 - added support for custom types in arrays and custom collections
+* #218 - fixed an issue with `dotnet restore` with `rid` specified if referenced from `netstandard` project
+* #219 - reduced search graph for configuration dlls to avoid native assets
+* #221 - added support for conditional/leveled enrichers from Serilog 2.9+
+* #222 - updated Microsoft.Extensions.DependencyModel
+* #231 - make '$' sign optional for minimum level / filter switch declarations
+* #237 - DependencyContextAssemblyFinder fix: check `serilog` at the start of the name for any dependent package
+* #239 - handle NotSupportedException for .net 5.0 single file applications
+* #260 - skip static constructor on binding for complex parameters types
+
+3.1.0
+
+* #155 - improve SelfLog output when misconfigured
+* #160 - respect dynamic logging level changes for LevelSwitch section
+* #158 - update NuGet package license format to new format
+* #159 - DllScanningAssemblyFinder fixes #157, #150, #122, #156
+* #161 - support simple type names for Serilog types
+* #151 - no longer rely on static state in ConfigurationReader
+* #179 - added missing null checks for settingConfiguration
+* #163 - added new ReadFrom.Configuration(...) overloads; marked old as obsolete
+* #176 - added test to show how to filter child contexts
+
+3.0.1
+
+* #142 - Fix IConfiguration parameters not being populated
+* #143 - Fix ReadFrom.ConfigurationSection() looking for sections below a root Serilog section
+
+3.0.0
+
+* #91 & #92 - Fix cherrypick from master
+* #97 - Support of IConfiguration parameters & IConfigurationSection parameters
+* #83 - Updated dependencies of Microsoft.Extensions.DependencyModel,
+ Microsoft.Extensions.Configuration.Abstraction & Microsoft.Extensions.Options.ConfigurationExtensions per TFM
+* #98 - specify string array params
+* Target Framework change to netcoreapp2.0
+* Build updates including addition of Travis Build
+* #105 - detect and fail on ambiguous configurations
+* #110 - destructure support
+* #111 - case-insensitive argument matching
+* #132 - choose string overloads to resolve binding ambiguities
+* #134 - specify repository URL in package
+* #124 - build a .NET 4.6.1 target
+* #136 - control assembly source
+* #138 - remove unnecessary package ref
+* #139 - remove unused class
+* #140 - expand support for destructure/enrich/filter configuration
+
+2.6.1
+
+* #92 - fix WriteTo.Logger handling
+
+2.6.0
+
+* #67 - improve error reporting when trying to convert from a missing class
+* #74 - support abstract classes (in addition to interfaces) as values
+* #84 - (documentation update)
+* #88 - LoggingLevelSwitch support
+
+2.4.0
+
+* #46 - configure sub-loggers through JSON settings
+* #48 - permit multiple sinks of the same kind
+
+2.3.1
+
+* #44 - fix ReadFrom.Configuration() on AWS Lambda; VS 2017 tooling
+
+2.3.0
+
+* #40 - fix loading of configuration assemblies with names differing from their packages
+* #36 - "Filter" support
+
+2.2.0
+
+* #20 - support MSBuild (non-project.json) projects
+
2.1.0
- * #14 - MinimumLevel.Override()
- * #15 - Overload selection fix
+
+* #14 - MinimumLevel.Override()
+* #15 - Overload selection fix
2.0.0
- * Initial version
-
+
+* Initial version
diff --git a/Directory.Build.props b/Directory.Build.props
new file mode 100644
index 0000000..03b68ca
--- /dev/null
+++ b/Directory.Build.props
@@ -0,0 +1,11 @@
+
+
+ latest
+ True
+
+
+
+
+
+
+
diff --git a/README.md b/README.md
index 26d133b..0e8a10e 100644
--- a/README.md
+++ b/README.md
@@ -1,19 +1,19 @@
# Serilog.Settings.Configuration [![Build status](https://ci.appveyor.com/api/projects/status/r2bgfimd9ocr61px/branch/master?svg=true)](https://ci.appveyor.com/project/serilog/serilog-settings-configuration/branch/master) [![NuGet Version](http://img.shields.io/nuget/v/Serilog.Settings.Configuration.svg?style=flat)](https://www.nuget.org/packages/Serilog.Settings.Configuration/)
-A Serilog settings provider that reads from _Microsoft.Extensions.Configuration_, .NET Core's `appsettings.json` file.
+A Serilog settings provider that reads from [Microsoft.Extensions.Configuration](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/?view=aspnetcore-3.1) sources, including .NET Core's `appsettings.json` file.
-Configuration is read from the `Serilog` section.
+By default, configuration is read from the `Serilog` section.
```json
{
"Serilog": {
- "Using": ["Serilog.Sinks.Console"],
+ "Using": [ "Serilog.Sinks.Console", "Serilog.Sinks.File" ],
"MinimumLevel": "Debug",
"WriteTo": [
{ "Name": "Console" },
- { "Name": "File", "Args": { "path": "%TEMP%\\Logs\\serilog-configuration-sample.txt" } }
+ { "Name": "File", "Args": { "path": "Logs/log.txt" } }
],
- "Enrich": ["FromLogContext", "WithMachineName", "WithThreadId"],
+ "Enrich": [ "FromLogContext", "WithMachineName", "WithThreadId" ],
"Destructure": [
{ "Name": "With", "Args": { "policy": "Sample.CustomPolicy, Sample" } },
{ "Name": "ToMaximumDepth", "Args": { "maximumDestructuringDepth": 4 } },
@@ -21,86 +21,231 @@ Configuration is read from the `Serilog` section.
{ "Name": "ToMaximumCollectionCount", "Args": { "maximumCollectionCount": 10 } }
],
"Properties": {
- "Application": "Sample"
+ "Application": "Sample"
}
}
}
```
-This example relies on the _Serilog.Sinks.Console_, _Serilog.Sinks.File_, _Serilog.Enrichers.Environment_, _Serilog.Settings.Configuration_ and _Serilog.Enrichers.Thread_ packages also being installed.
-
After installing this package, use `ReadFrom.Configuration()` and pass an `IConfiguration` object.
```csharp
-public class Program
+static void Main(string[] args)
{
- public static void Main(string[] args)
- {
- var configuration = new ConfigurationBuilder()
- .AddJsonFile("appsettings.json")
- .Build();
+ var configuration = new ConfigurationBuilder()
+ .SetBasePath(Directory.GetCurrentDirectory())
+ .AddJsonFile("appsettings.json")
+ .AddJsonFile($"appsettings.{Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? "Production"}.json", true)
+ .Build();
- var logger = new LoggerConfiguration()
- .ReadFrom.Configuration(configuration)
- .CreateLogger();
+ var logger = new LoggerConfiguration()
+ .ReadFrom.Configuration(configuration)
+ .CreateLogger();
- logger.Information("Hello, world!");
- }
+ logger.Information("Hello, world!");
}
```
-The `WriteTo` and `Enrich` sections support the same syntax, for example the following is valid if no arguments are needed by the sinks:
+This example relies on the _[Microsoft.Extensions.Configuration.Json](https://www.nuget.org/packages/Microsoft.Extensions.Configuration.Json/)_, _[Serilog.Sinks.Console](https://github.com/serilog/serilog-sinks-console)_, _[Serilog.Sinks.File](https://github.com/serilog/serilog-sinks-file)_, _[Serilog.Enrichers.Environment](https://github.com/serilog/serilog-enrichers-environment)_ and _[Serilog.Enrichers.Thread](https://github.com/serilog/serilog-enrichers-thread)_ packages also being installed.
+
+For a more sophisticated example go to the [sample](sample/Sample) folder.
+
+## Syntax description
+
+### Root section name
+
+Root section name can be changed:
```json
-"WriteTo": ["Console", "DiagnosticTrace"]
+{
+ "CustomSection": {
+ ...
+ }
+}
+```
+
+```csharp
+var logger = new LoggerConfiguration()
+ .ReadFrom.Configuration(configuration, sectionName: "CustomSection")
+ .CreateLogger();
```
-Or alternatively, the long-form (`"Name":` ...) syntax from the first example can be used when arguments need to be supplied.
+### Using section and auto-discovery of configuration assemblies
-(This package implements a convention using `DependencyContext` to find any package with `Serilog` anywhere in the name and pulls configuration methods from it, so the `Using` example above is redundant.)
+`Using` section contains list of **assemblies** in which configuration methods (`WriteTo.File()`, `Enrich.WithThreadId()`) reside.
-### .NET 4.x
+```json
+"Serilog": {
+ "Using": [ "Serilog.Sinks.Console", "Serilog.Enrichers.Thread", /* ... */ ],
+ // ...
+}
+```
-To use this package in .NET 4.x applications, add `preserveCompilationContext` to `buildOptions` in _project.json_.
+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
-"net4.6": {
- "buildOptions": {
- "preserveCompilationContext": true
- }
-},
+{
+ "Serilog": {
+ "MinimumLevel": "Debug",
+ "WriteTo": [ "Console" ],
+ ...
+ }
+}
+```
+
+In order to utilize this convention for .NET Framework projects which are built with .NET Core CLI tools specify `PreserveCompilationContext` to `true` in the csproj properties:
+
+```xml
+
+ true
+
```
-### Level overrides
+In case of [non-standard](#azure-functions-v2-v3) dependency management you can pass a custom `DependencyContext` object:
+
+```csharp
+var functionDependencyContext = DependencyContext.Load(typeof(Startup).Assembly);
+
+var logger = new LoggerConfiguration()
+ .ReadFrom.Configuration(hostConfig, sectionName: "AzureFunctionsJobHost:Serilog", dependencyContext: functionDependencyContext)
+ .CreateLogger();
+```
+
+For legacy .NET Framework projects it also scans default probing path(s).
+
+For all other cases, as well as in the case of non-conventional configuration assembly names **DO** use [Using](#using-section-and-auto-discovery-of-configuration-assemblies) section.
+
+#### .NET 5.0 Single File Applications
+
+Currently, auto-discovery of configuration assemblies is not supported in bundled mode. **DO** use [Using](#using-section-and-auto-discovery-of-configuration-assemblies) section for workaround.
+
+### MinimumLevel, LevelSwitches, overrides and dynamic reload
The `MinimumLevel` configuration property can be set to a single value as in the sample above, or, levels can be overridden per logging source.
This is useful in ASP.NET Core applications, which will often specify minimum level as:
```json
- "MinimumLevel": {
- "Default": "Information",
- "Override": {
- "Microsoft": "Warning",
- "System": "Warning"
- }
+"MinimumLevel": {
+ "Default": "Information",
+ "Override": {
+ "Microsoft": "Warning",
+ "System": "Warning"
}
+}
+```
+
+`MinimumLevel` section also respects dynamic reload if the underlying provider supports it.
+
+```csharp
+var configuration = new ConfigurationBuilder()
+ .SetBasePath(Directory.GetCurrentDirectory())
+ .AddJsonFile(path: "appsettings.json", reloadOnChange: true)
+ .Build();
+```
+
+Any changes for `Default`, `Microsoft`, `System` sources will be applied at runtime.
+
+(Note: only existing sources are respected for a dynamic update. Inserting new records in `Override` section is **not** supported.)
+
+You can also declare `LoggingLevelSwitch`-es in custom section and reference them for sink parameters:
+
+```json
+{
+ "Serilog": {
+ "LevelSwitches": { "controlSwitch": "Verbose" },
+ "WriteTo": [
+ {
+ "Name": "Seq",
+ "Args": {
+ "serverUrl": "http://localhost:5341",
+ "apiKey": "yeEZyL3SMcxEKUijBjN",
+ "controlLevelSwitch": "$controlSwitch"
+ }
+ }
+ ]
+ }
+}
+```
+
+Level updates to switches are also respected for a dynamic update.
+
+### WriteTo, Enrich, AuditTo, Destructure sections
+
+These sections support simplified syntax, for example the following is valid if no arguments are needed by the sinks:
+
+```json
+"WriteTo": [ "Console", "DiagnosticTrace" ]
+```
+
+Or alternatively, the long-form (`"Name":` ...) syntax from the example above can be used when arguments need to be supplied.
+
+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
+"WriteTo": {
+ "0": "Console",
+ "1": "DiagnosticTrace"
+}
```
-### Environment variables
+And
-If your application enables the environment variable configuration source (`AddEnvironmentVariables()`) you can add or override Serilog configuration through the environment.
+```json
+"WriteTo:0": "Console",
+"WriteTo:1": "DiagnosticTrace"
+```
+
+(The result paths for the keys will be the same, i.e. `Serilog:WriteTo:0` and `Serilog:WriteTo:1`)
-For example, to set the minimum log level using the _Windows_ command prompt:
+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
+"WriteTo": {
+ "ConsoleSink": "Console",
+ "DiagnosticTraceSink": { "Name": "DiagnosticTrace" }
+}
```
-set Serilog:MinimumLevel=Debug
-dotnet run
+
+### Properties section
+
+This section defines a static list of key-value pairs that will enrich log events.
+
+### 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:
+
+```json
+"Filter": [{
+ "Name": "ByIncludingOnly",
+ "Args": {
+ "expression": "Application = 'Sample'"
+ }
+}]
```
+Using this package you can also declare `LoggingFilterSwitch`-es in custom section and reference them for filter parameters:
+
+```json
+{
+ "Serilog": {
+ "FilterSwitches": { "filterSwitch": "Application = 'Sample'" },
+ "Filter": [
+ {
+ "Name": "ControlledBy",
+ "Args": {
+ "switch": "$filterSwitch"
+ }
+ }
+ ]
+}
+```
+
+Level updates to switches are also respected for a dynamic update.
+
### 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_ package to wrap the _Serilog.Sinks.File_ package. The `configure` parameter references the File sink configuration:
+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
"WriteTo:Async": {
@@ -110,8 +255,9 @@ Some Serilog packages require a reference to a logger configuration object. The
{
"Name": "File",
"Args": {
- "path": "%TEMP%\\Logs\\serilog-configuration-sample.txt",
- "outputTemplate": "{Timestamp:o} [{Level:u3}] ({Application}/{MachineName}/{ThreadId}) {Message}{NewLine}{Exception}"
+ "path": "%TEMP%/Logs/serilog-configuration-sample.txt",
+ "outputTemplate":
+ "{Timestamp:o} [{Level:u3}] ({Application}/{MachineName}/{ThreadId}) {Message}{NewLine}{Exception}"
}
}
]
@@ -119,17 +265,103 @@ Some Serilog packages require a reference to a logger configuration object. The
},
```
-### IConfiguration parameter
+## Arguments binding
-If a Serilog package requires additional external configuration information (for example, access to a `ConnectionStrings` section, which would be outside of the `Serilog` section), the sink should include an `IConfiguration` parameter in the configuration extension method. This package will automatically populate that parameter. It should not be declared in the argument list in the configuration source.
+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.
### Complex parameter value binding
-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` and `TimeSpan` objects and `enum` elements.
+If the parameter value is not a discrete value, the package will use the configuration binding system provided by _[Microsoft.Extensions.Options.ConfigurationExtensions](https://www.nuget.org/packages/Microsoft.Extensions.Options.ConfigurationExtensions/)_ to attempt to populate the parameter. Almost anything that can be bound by `IConfiguration.Get` should work with this package. An example of this is the optional `List` parameter used to configure the .NET Standard version of the _[Serilog.Sinks.MSSqlServer](https://github.com/serilog/serilog-sinks-mssqlserver)_ package.
+
+### Abstract parameter types
+
+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
+"Destructure": [
+ { "Name": "With", "Args": { "policy": "Sample.CustomPolicy, Sample" } },
+ ...
+],
+```
+
+### IConfiguration parameter
-If the parameter value is not a discrete value, the package will use the configuration binding system provided by _Microsoft.Extensions.Options.ConfigurationExtensions_ to attempt to populate the parameter. Almost anything that can be bound by `IConfiguration.Get` should work with this package. An example of this is the optional `List` parameter used to configure the .NET Standard version of the _Serilog.Sinks.MSSqlServer_ package.
+If a Serilog package requires additional external configuration information (for example, access to a `ConnectionStrings` section, which would be outside of the `Serilog` section), the sink should include an `IConfiguration` parameter in the configuration extension method. This package will automatically populate that parameter. It should not be declared in the argument list in the configuration source.
### IConfigurationSection parameters
Certain Serilog packages may require configuration information that can't be easily represented by discrete values or direct binding-friendly representations. An example might be lists of values to remove from a collection of default values. In this case the method can accept an entire `IConfigurationSection` as a call parameter and this package will recognize that and populate the parameter. In this way, Serilog packages can support arbitrarily complex configuration scenarios.
+## Samples
+
+### Azure Functions (v2, v3)
+
+hosts.json
+
+```json
+{
+ "version": "2.0",
+ "logging": {
+ "applicationInsights": {
+ "samplingExcludedTypes": "Request",
+ "samplingSettings": {
+ "isEnabled": true
+ }
+ }
+ },
+ "Serilog": {
+ "MinimumLevel": {
+ "Default": "Information",
+ "Override": {
+ "Microsoft": "Warning",
+ "System": "Warning"
+ }
+ },
+ "Enrich": [ "FromLogContext" ],
+ "WriteTo": [
+ { "Name": "Seq", "Args": { "serverUrl": "http://localhost:5341" } }
+ ]
+ }
+}
+```
+
+In `Startup.cs` section name should be prefixed with [AzureFunctionsJobHost](https://docs.microsoft.com/en-us/azure/azure-functions/functions-app-settings#azurefunctionsjobhost__)
+
+```csharp
+public class Startup : FunctionsStartup
+{
+ public override void Configure(IFunctionsHostBuilder builder)
+ {
+ builder.Services.AddSingleton(sp =>
+ {
+ var functionDependencyContext = DependencyContext.Load(typeof(Startup).Assembly);
+
+ var hostConfig = sp.GetRequiredService();
+ var logger = new LoggerConfiguration()
+ .ReadFrom.Configuration(hostConfig, sectionName: "AzureFunctionsJobHost:Serilog", dependencyContext: functionDependencyContext)
+ .CreateLogger();
+
+ return new SerilogLoggerProvider(logger, dispose: true);
+ });
+ }
+}
+```
+
+In order to make auto-discovery of configuration assemblies work, modify Function's csproj file
+
+```xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
diff --git a/appveyor.yml b/appveyor.yml
index 396e0ef..5aae7af 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -1,23 +1,34 @@
version: '{build}'
skip_tags: true
-image: Visual Studio 2017
+image:
+ - Visual Studio 2019
+ - Ubuntu
configuration: Release
build_script:
- ps: ./Build.ps1
+for:
+-
+ matrix:
+ only:
+ - image: Ubuntu
+ build_script:
+ - pwsh ./Build.ps1
test: off
artifacts:
- path: artifacts/Serilog.*.nupkg
+- path: artifacts/Serilog.*.snupkg
deploy:
- provider: NuGet
api_key:
- secure: N59tiJECUYpip6tEn0xvdmDAEiP9SIzyLEFLpwiigm/8WhJvBNs13QxzT1/3/JW/
- skip_symbols: true
+ secure: 6WetFj2k7TEactDaHhg0m0q/WpCldFAUtgAjN8VK9Qn2fsY1vdufRB8XIKnPX9zn
on:
- branch: /^(master|dev)$/
+ branch: /^(main|dev)$/
- provider: GitHub
auth_token:
secure: p4LpVhBKxGS5WqucHxFQ5c7C8cP74kbNB0Z8k9Oxx/PMaDQ1+ibmoexNqVU5ZlmX
- artifact: /Serilog.*\.nupkg/
+ artifacts:
+ /Serilog.*\.nupkg/
+ /Serilog.*\.snupkg/
tag: v$(appveyor_build_version)
on:
- branch: master
+ branch: main
diff --git a/assets/icon.png b/assets/icon.png
new file mode 100644
index 0000000..0ffa019
Binary files /dev/null and b/assets/icon.png differ
diff --git a/build.sh b/build.sh
deleted file mode 100755
index 19ab21c..0000000
--- a/build.sh
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-
-set -e
-dotnet --info
-dotnet restore
-
-for path in src/**/*.csproj; do
- dotnet build -f netstandard2.0 -c Release ${path}
-done
-
-for path in test/*.Tests/*.csproj; do
- dotnet test -f netcoreapp2.0 -c Release ${path}
-done
-
-cd sample/Sample/
-dotnet build -f netcoreapp2.0 -c Release
-dotnet bin/Release/netcoreapp2.0/Sample.dll --run-once
diff --git a/sample/Sample/Program.cs b/sample/Sample/Program.cs
index 562e427..726df35 100644
--- a/sample/Sample/Program.cs
+++ b/sample/Sample/Program.cs
@@ -1,12 +1,16 @@
using System;
-using Microsoft.Extensions.Configuration;
-using Serilog;
using System.IO;
using System.Linq;
+using System.Collections.Generic;
+using System.Threading;
+
+using Microsoft.Extensions.Configuration;
+
+using Serilog;
using Serilog.Core;
using Serilog.Events;
-using System.Collections.Generic;
+using Serilog.Debugging;
namespace Sample
{
@@ -14,6 +18,10 @@ 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)
@@ -47,7 +55,7 @@ public static void Main(string[] args)
Console.WriteLine("\nPress \"q\" to quit, or any other key to run again.\n");
}
- while(!args.Contains("--run-once") && (Console.ReadKey().KeyChar != 'q'));
+ while (!args.Contains("--run-once") && (Console.ReadKey().KeyChar != 'q'));
}
}
@@ -73,7 +81,7 @@ public bool TryDestructure(object value, ILogEventPropertyValueFactory propertyV
{
result = null;
- if(value is LoginData)
+ if (value is LoginData)
{
result = new StructureValue(
new List
diff --git a/sample/Sample/Sample.csproj b/sample/Sample/Sample.csproj
index 1b69583..d64b702 100644
--- a/sample/Sample/Sample.csproj
+++ b/sample/Sample/Sample.csproj
@@ -1,7 +1,7 @@
- net46;netcoreapp2.0
+ net50;netcoreapp3.1;netcoreapp2.1;net46
Exe
@@ -15,19 +15,30 @@
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
-
-
-
-
+
+
+
diff --git a/sample/Sample/appsettings.json b/sample/Sample/appsettings.json
index 2771a7f..0d89e71 100644
--- a/sample/Sample/appsettings.json
+++ b/sample/Sample/appsettings.json
@@ -1,7 +1,8 @@
{
"Serilog": {
"Using": [ "Serilog.Sinks.Console" ],
- "LevelSwitches": { "$controlSwitch": "Verbose" },
+ "LevelSwitches": { "controlSwitch": "Verbose" },
+ "FilterSwitches": { "$filterSwitch": "Application = 'Sample'" },
"MinimumLevel": {
"Default": "Debug",
"Override": {
@@ -35,14 +36,45 @@
{
"Name": "File",
"Args": {
- "path": "%TEMP%\\Logs\\serilog-configuration-sample.txt",
- "outputTemplate": "{Timestamp:o} [{Level:u3}] ({Application}/{MachineName}/{ThreadId}) {Message}{NewLine}{Exception}"
+ "path": "%TEMP%/Logs/serilog-configuration-sample.txt",
+ "outputTemplate": "{Timestamp:o} [{Level:u3}] ({Application}/{MachineName}/{ThreadId}/{ThreadName}) {Message}{NewLine}{Exception}"
}
}
]
}
},
- "Enrich": [ "FromLogContext", "WithMachineName", "WithThreadId" ],
+ "WriteTo:ConditionalSink": {
+ "Name": "Conditional",
+ "Args": {
+ "expression": "@Level in ['Error', 'Fatal']",
+ "configureSink": [
+ {
+ "Name": "File",
+ "Args": {
+ "path": "%TEMP%/Logs/serilog-configuration-sample-errors.txt"
+ }
+ }
+ ]
+ }
+ },
+ "Enrich": [
+ "FromLogContext",
+ "WithThreadId",
+ {
+ "Name": "AtLevel",
+ "Args": {
+ "enrichFromLevel": "Error",
+ "configureEnricher": [ "WithThreadName" ]
+ }
+ },
+ {
+ "Name": "When",
+ "Args": {
+ "expression": "Application = 'Sample'",
+ "configureEnricher": [ "WithMachineName" ]
+ }
+ }
+ ],
"Properties": {
"Application": "Sample"
},
@@ -66,9 +98,9 @@
],
"Filter": [
{
- "Name": "ByIncludingOnly",
+ "Name": "ControlledBy",
"Args": {
- "expression": "Application = 'Sample'"
+ "switch": "$filterSwitch"
}
},
{
diff --git a/serilog-settings-configuration.sln b/serilog-settings-configuration.sln
index 82c4512..ec9c9b2 100644
--- a/serilog-settings-configuration.sln
+++ b/serilog-settings-configuration.sln
@@ -1,16 +1,19 @@
Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio 15
-VisualStudioVersion = 15.0.27130.0
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.29709.97
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{4E41FD57-5FAB-4E3C-B16E-463DE98338BC}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "assets", "assets", "{62D0B904-1D11-4962-A4A8-DE28672AA28B}"
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
+ .gitignore = .gitignore
appveyor.yml = appveyor.yml
Build.ps1 = Build.ps1
CHANGES.md = CHANGES.md
+ Directory.Build.props = Directory.Build.props
+ assets\icon.png = assets\icon.png
LICENSE = LICENSE
README.md = README.md
serilog-settings-configuration.sln.DotSettings = serilog-settings-configuration.sln.DotSettings
diff --git a/src/Serilog.Settings.Configuration/Serilog.Settings.Configuration.csproj b/src/Serilog.Settings.Configuration/Serilog.Settings.Configuration.csproj
index e62947a..e54b5a5 100644
--- a/src/Serilog.Settings.Configuration/Serilog.Settings.Configuration.csproj
+++ b/src/Serilog.Settings.Configuration/Serilog.Settings.Configuration.csproj
@@ -1,8 +1,9 @@
-
+
Microsoft.Extensions.Configuration (appsettings.json) support for Serilog.
- 3.1.0
+ 3.2.0
+ latest
Serilog Contributors
netstandard2.0;net451;net461
true
@@ -13,12 +14,14 @@
true
Serilog.Settings.Configuration
serilog;json
- https://serilog.net/images/serilog-configuration-nuget.png
- https://github.com/serilog/serilog-settings-configuration
+ icon.png
+ https://github.com/serilog/serilog-settings-configuration/
Apache-2.0
- https://github.com/serilog/serilog-settings-configuration
- git
Serilog
+ true
+ true
+ true
+ snupkg
@@ -26,10 +29,12 @@
-
-
+
+
+
+
-
+
diff --git a/src/Serilog.Settings.Configuration/Settings/Configuration/Assemblies/AssemblyFinder.cs b/src/Serilog.Settings.Configuration/Settings/Configuration/Assemblies/AssemblyFinder.cs
index 4d356a8..cc16a55 100644
--- a/src/Serilog.Settings.Configuration/Settings/Configuration/Assemblies/AssemblyFinder.cs
+++ b/src/Serilog.Settings.Configuration/Settings/Configuration/Assemblies/AssemblyFinder.cs
@@ -16,26 +16,30 @@ protected static bool IsCaseInsensitiveMatch(string text, string textToFind)
public static AssemblyFinder Auto()
{
- // Need to check `Assembly.GetEntryAssembly()` first because
- // `DependencyContext.Default` throws an exception when `Assembly.GetEntryAssembly()` returns null
- if (Assembly.GetEntryAssembly() != null && DependencyContext.Default != null)
+ try
{
- return new DependencyContextAssemblyFinder(DependencyContext.Default);
+ // 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 ForSource(ConfigurationAssemblySource configurationAssemblySource)
{
- switch (configurationAssemblySource)
+ return configurationAssemblySource switch
{
- case ConfigurationAssemblySource.UseLoadedAssemblies:
- return Auto();
- case ConfigurationAssemblySource.AlwaysScanDllFiles:
- return new DllScanningAssemblyFinder();
- default:
- 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)
diff --git a/src/Serilog.Settings.Configuration/Settings/Configuration/Assemblies/DependencyContextAssemblyFinder.cs b/src/Serilog.Settings.Configuration/Settings/Configuration/Assemblies/DependencyContextAssemblyFinder.cs
index d7faa77..7a6feaa 100644
--- a/src/Serilog.Settings.Configuration/Settings/Configuration/Assemblies/DependencyContextAssemblyFinder.cs
+++ b/src/Serilog.Settings.Configuration/Settings/Configuration/Assemblies/DependencyContextAssemblyFinder.cs
@@ -18,11 +18,20 @@ public DependencyContextAssemblyFinder(DependencyContext dependencyContext)
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;
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] == '.'));
+ }
}
}
}
diff --git a/src/Serilog.Settings.Configuration/Settings/Configuration/Assemblies/DllScanningAssemblyFinder.cs b/src/Serilog.Settings.Configuration/Settings/Configuration/Assemblies/DllScanningAssemblyFinder.cs
index 95584e2..102b492 100644
--- a/src/Serilog.Settings.Configuration/Settings/Configuration/Assemblies/DllScanningAssemblyFinder.cs
+++ b/src/Serilog.Settings.Configuration/Settings/Configuration/Assemblies/DllScanningAssemblyFinder.cs
@@ -50,7 +50,7 @@ where IsCaseInsensitiveMatch(assemblyFileName, nameToFind)
return query.ToList().AsReadOnly();
- AssemblyName TryGetAssemblyNameFrom(string path)
+ static AssemblyName TryGetAssemblyNameFrom(string path)
{
try
{
diff --git a/src/Serilog.Settings.Configuration/Settings/Configuration/ConfigurationReader.cs b/src/Serilog.Settings.Configuration/Settings/Configuration/ConfigurationReader.cs
index 3bf16df..720d56d 100644
--- a/src/Serilog.Settings.Configuration/Settings/Configuration/ConfigurationReader.cs
+++ b/src/Serilog.Settings.Configuration/Settings/Configuration/ConfigurationReader.cs
@@ -3,9 +3,10 @@
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
+using System.Text.RegularExpressions;
+
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Primitives;
-using System.Text.RegularExpressions;
using Serilog.Configuration;
using Serilog.Core;
@@ -17,7 +18,7 @@ namespace Serilog.Settings.Configuration
{
class ConfigurationReader : IConfigurationReader
{
- const string LevelSwitchNameRegex = @"^\$[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;
@@ -41,6 +42,7 @@ internal ConfigurationReader(IConfigurationSection configSection, IReadOnlyColle
public void Configure(LoggerConfiguration loggerConfiguration)
{
ProcessLevelSwitchDeclarations();
+ ProcessFilterSwitchDeclarations();
ApplyMinimumLevel(loggerConfiguration);
ApplyEnrichment(loggerConfiguration);
@@ -50,6 +52,63 @@ public void Configure(LoggerConfiguration loggerConfiguration)
ApplyAuditSinks(loggerConfiguration);
}
+ void ProcessFilterSwitchDeclarations()
+ {
+ var filterSwitchesDirective = _section.GetSection("FilterSwitches");
+
+ foreach (var filterSwitchDeclaration in filterSwitchesDirective.GetChildren())
+ {
+ var filterSwitch = LoggingFilterSwitchProxy.Create();
+ if (filterSwitch == null)
+ {
+ SelfLog.WriteLine($"FilterSwitches section found, but neither Serilog.Expressions nor Serilog.Filters.Expressions is referenced.");
+ break;
+ }
+
+ 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();
+
+ _resolutionContext.AddFilterSwitch(switchName, filterSwitch);
+
+ void SubscribeToFilterExpressionChanges()
+ {
+ ChangeToken.OnChange(filterSwitchDeclaration.GetReloadToken, () => SetFilterSwitch(throwOnError: false));
+ }
+
+ void SetFilterSwitch(bool throwOnError)
+ {
+ 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)
+ {
+ throw new InvalidOperationException(errMsg, e);
+ }
+
+ SelfLog.WriteLine(errMsg);
+ }
+ }
+ }
+ }
+
void ProcessLevelSwitchDeclarations()
{
var levelSwitchesDirective = _section.GetSection("LevelSwitches");
@@ -60,7 +119,7 @@ void ProcessLevelSwitchDeclarations()
// 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. Level switch must be declared with a '$' 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;
@@ -94,7 +153,7 @@ void ApplyMinimumLevel(LoggerConfiguration loggerConfiguration)
var minLevelControlledByDirective = minimumLevelDirective.GetSection("ControlledBy");
if (minLevelControlledByDirective.Value != null)
{
- var globalMinimumLevelSwitch = _resolutionContext.LookUpSwitchByName(minLevelControlledByDirective.Value);
+ 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);
}
@@ -109,7 +168,7 @@ void ApplyMinimumLevel(LoggerConfiguration loggerConfiguration)
}
else
{
- var overrideSwitch = _resolutionContext.LookUpSwitchByName(overridenLevelOrSwitch);
+ 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);
}
@@ -185,6 +244,12 @@ void IConfigurationReader.ApplySinks(LoggerSinkConfiguration loggerSinkConfigura
CallConfigurationMethods(methodCalls, FindSinkConfigurationMethods(_configurationAssemblies), loggerSinkConfiguration);
}
+ void IConfigurationReader.ApplyEnrichment(LoggerEnrichmentConfiguration loggerEnrichmentConfiguration)
+ {
+ var methodCalls = GetMethodCalls(_section);
+ CallConfigurationMethods(methodCalls, FindEventEnricherConfigurationMethods(_configurationAssemblies), loggerEnrichmentConfiguration);
+ }
+
void ApplyEnrichment(LoggerConfiguration loggerConfiguration)
{
var enrichDirective = _section.GetSection("Enrich");
@@ -220,39 +285,14 @@ internal ILookup> GetMet
select new
{
Name = argument.Key,
- Value = GetArgumentValue(argument)
+ 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;
- IConfigurationArgumentValue GetArgumentValue(IConfigurationSection argumentSection)
- {
- 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;
- }
-
- string GetSectionName(IConfigurationSection s)
+ static string GetSectionName(IConfigurationSection s)
{
var name = s.GetSection("Name");
if (name.Value == null)
@@ -262,9 +302,35 @@ string GetSectionName(IConfigurationSection s)
}
}
+ 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)
+ {
+ argumentValue = new StringArgumentValue(argumentSection.Value);
+ }
+ else
+ {
+ argumentValue = new ObjectArgumentValue(argumentSection, configurationAssemblies);
+ }
+
+ return argumentValue;
+ }
+
static IReadOnlyCollection LoadConfigurationAssemblies(IConfigurationSection section, AssemblyFinder assemblyFinder)
{
- var assemblies = new Dictionary();
+ var serilogAssembly = typeof(ILogger).Assembly;
+ var assemblies = new Dictionary { [serilogAssembly.FullName] = serilogAssembly };
var usingSection = section.GetSection("Using");
if (usingSection.GetChildren().Any())
diff --git a/src/Serilog.Settings.Configuration/Settings/Configuration/IConfigurationReader.cs b/src/Serilog.Settings.Configuration/Settings/Configuration/IConfigurationReader.cs
index f19f142..af815af 100644
--- a/src/Serilog.Settings.Configuration/Settings/Configuration/IConfigurationReader.cs
+++ b/src/Serilog.Settings.Configuration/Settings/Configuration/IConfigurationReader.cs
@@ -1,9 +1,10 @@
-using Serilog.Configuration;
+using Serilog.Configuration;
namespace Serilog.Settings.Configuration
{
interface IConfigurationReader : ILoggerSettings
{
void ApplySinks(LoggerSinkConfiguration loggerSinkConfiguration);
+ void ApplyEnrichment(LoggerEnrichmentConfiguration loggerEnrichmentConfiguration);
}
}
diff --git a/src/Serilog.Settings.Configuration/Settings/Configuration/LoggingFilterSwitchProxy.cs b/src/Serilog.Settings.Configuration/Settings/Configuration/LoggingFilterSwitchProxy.cs
new file mode 100644
index 0000000..4736ee2
--- /dev/null
+++ b/src/Serilog.Settings.Configuration/Settings/Configuration/LoggingFilterSwitchProxy.cs
@@ -0,0 +1,49 @@
+using System;
+
+namespace Serilog.Settings.Configuration
+{
+ class LoggingFilterSwitchProxy
+ {
+ readonly Action _setProxy;
+ readonly Func _getProxy;
+
+ LoggingFilterSwitchProxy(object realSwitch)
+ {
+ RealSwitch = realSwitch ?? throw new ArgumentNullException(nameof(realSwitch));
+
+ var expressionProperty = realSwitch.GetType().GetProperty("Expression");
+
+ _setProxy = (Action)Delegate.CreateDelegate(
+ typeof(Action),
+ realSwitch,
+ expressionProperty.GetSetMethod());
+
+ _getProxy = (Func)Delegate.CreateDelegate(
+ typeof(Func),
+ realSwitch,
+ expressionProperty.GetGetMethod());
+ }
+
+ public object RealSwitch { get; }
+
+ public string Expression
+ {
+ get => _getProxy();
+ set => _setProxy(value);
+ }
+
+ 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));
+ }
+ }
+}
diff --git a/src/Serilog.Settings.Configuration/Settings/Configuration/ObjectArgumentValue.cs b/src/Serilog.Settings.Configuration/Settings/Configuration/ObjectArgumentValue.cs
index 6b54c34..83f7443 100644
--- a/src/Serilog.Settings.Configuration/Settings/Configuration/ObjectArgumentValue.cs
+++ b/src/Serilog.Settings.Configuration/Settings/Configuration/ObjectArgumentValue.cs
@@ -1,8 +1,10 @@
-using Microsoft.Extensions.Configuration;
using System;
using System.Collections.Generic;
+using System.Linq;
using System.Reflection;
+using Microsoft.Extensions.Configuration;
+
using Serilog.Configuration;
namespace Serilog.Settings.Configuration
@@ -31,24 +33,83 @@ public object ConvertTo(Type toType, ResolutionContext resolutionContext)
typeInfo.GetGenericTypeDefinition() is Type genericType && genericType == typeof(Action<>))
{
var configType = typeInfo.GenericTypeArguments[0];
- if (configType != typeof(LoggerConfiguration) && configType != typeof(LoggerSinkConfiguration))
- throw new ArgumentException($"Configuration for Action<{configType}> is not implemented.");
-
IConfigurationReader configReader = new ConfigurationReader(_section, _configurationAssemblies, resolutionContext);
- if (configType == typeof(LoggerConfiguration))
+ return configType switch
{
- return new Action(configReader.Configure);
+ _ 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 (IsContainer(toType, out var elementType) && TryCreateContainer(out var result))
+ return result;
+
+ // MS Config binding can work with a limited set of primitive types and collections
+ return _section.Get(toType);
+
+ object CreateArray()
+ {
+ var elementType = toType.GetElementType();
+ var configurationElements = _section.GetChildren().ToArray();
+ var result = Array.CreateInstance(elementType, configurationElements.Length);
+ for (int i = 0; i < configurationElements.Length; ++i)
+ {
+ var argumentValue = ConfigurationReader.GetArgumentValue(configurationElements[i], _configurationAssemblies);
+ var value = argumentValue.ConvertTo(elementType, resolutionContext);
+ result.SetValue(value, i);
}
- if (configType == typeof(LoggerSinkConfiguration))
+ return result;
+ }
+
+ bool TryCreateContainer(out object result)
+ {
+ result = null;
+
+ if (toType.GetConstructor(Type.EmptyTypes) == null)
+ return false;
+
+ // 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;
+
+ var configurationElements = _section.GetChildren().ToArray();
+ result = Activator.CreateInstance(toType);
+
+ for (int i = 0; i < configurationElements.Length; ++i)
{
- return new Action(loggerSinkConfig => configReader.ApplySinks(loggerSinkConfig));
+ var argumentValue = ConfigurationReader.GetArgumentValue(configurationElements[i], _configurationAssemblies);
+ var value = argumentValue.ConvertTo(elementType, resolutionContext);
+ addMethod.Invoke(result, new object[] { value });
}
+
+ return true;
}
+ }
- // MS Config binding
- return _section.Get(toType);
+ static bool IsContainer(Type type, out Type elementType)
+ {
+ elementType = null;
+ foreach (var iface in type.GetInterfaces())
+ {
+ if (iface.IsGenericType)
+ {
+ if (iface.GetGenericTypeDefinition() == typeof(IEnumerable<>))
+ {
+ elementType = iface.GetGenericArguments()[0];
+ return true;
+ }
+ }
+ }
+
+ return false;
}
}
}
diff --git a/src/Serilog.Settings.Configuration/Settings/Configuration/ResolutionContext.cs b/src/Serilog.Settings.Configuration/Settings/Configuration/ResolutionContext.cs
index 90ab06d..2963a64 100644
--- a/src/Serilog.Settings.Configuration/Settings/Configuration/ResolutionContext.cs
+++ b/src/Serilog.Settings.Configuration/Settings/Configuration/ResolutionContext.cs
@@ -12,11 +12,13 @@ namespace Serilog.Settings.Configuration
sealed class ResolutionContext
{
readonly IDictionary _declaredLevelSwitches;
+ readonly IDictionary _declaredFilterSwitches;
readonly IConfiguration _appConfiguration;
public ResolutionContext(IConfiguration appConfiguration = null)
{
_declaredLevelSwitches = new Dictionary();
+ _declaredFilterSwitches = new Dictionary();
_appConfiguration = appConfiguration;
}
@@ -26,7 +28,7 @@ public ResolutionContext(IConfiguration appConfiguration = null)
/// the name of a switch to look up
/// the LoggingLevelSwitch registered with the name
/// if no switch has been registered with
- public LoggingLevelSwitch LookUpSwitchByName(string switchName)
+ public LoggingLevelSwitch LookUpLevelSwitchByName(string switchName)
{
if (_declaredLevelSwitches.TryGetValue(switchName, out var levelSwitch))
{
@@ -36,6 +38,16 @@ public LoggingLevelSwitch LookUpSwitchByName(string switchName)
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)
+ {
+ 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}}\"}}");
+ }
+
public bool HasAppConfiguration => _appConfiguration != null;
public IConfiguration AppConfiguration
@@ -55,7 +67,19 @@ public void AddLevelSwitch(string levelSwitchName, LoggingLevelSwitch levelSwitc
{
if (levelSwitchName == null) throw new ArgumentNullException(nameof(levelSwitchName));
if (levelSwitch == null) throw new ArgumentNullException(nameof(levelSwitch));
- _declaredLevelSwitches[levelSwitchName] = levelSwitch;
+ _declaredLevelSwitches[ToSwitchReference(levelSwitchName)] = levelSwitch;
+ }
+
+ 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;
+ }
+
+ 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 0a7c09b..b73ae2c 100644
--- a/src/Serilog.Settings.Configuration/Settings/Configuration/StringArgumentValue.cs
+++ b/src/Serilog.Settings.Configuration/Settings/Configuration/StringArgumentValue.cs
@@ -32,7 +32,13 @@ public object ConvertTo(Type toType, ResolutionContext resolutionContext)
if (toType == typeof(LoggingLevelSwitch))
{
- return resolutionContext.LookUpSwitchByName(argumentValue);
+ return resolutionContext.LookUpLevelSwitchByName(argumentValue);
+ }
+
+ if (toType.FullName == "Serilog.Expressions.LoggingFilterSwitch" ||
+ toType.FullName == "Serilog.Filters.Expressions.LoggingFilterSwitch")
+ {
+ return resolutionContext.LookUpFilterSwitchByName(argumentValue).RealSwitch;
}
var toTypeInfo = toType.GetTypeInfo();
@@ -93,20 +99,22 @@ public object ConvertTo(Type toType, ResolutionContext resolutionContext)
// maybe it's the assembly-qualified type name of a concrete implementation
// with a default constructor
var type = FindType(argumentValue.Trim());
- if (type != null)
+ if (type == null)
{
- var ctor = type.GetTypeInfo().DeclaredConstructors.FirstOrDefault(ci =>
- {
- var parameters = ci.GetParameters();
- return parameters.Length == 0 || parameters.All(pi => pi.HasDefaultValue);
- });
+ throw new InvalidOperationException($"Type {argumentValue} was not found.");
+ }
+
+ 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}.");
+ 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);
- }
+ var call = ctor.GetParameters().Select(pi => pi.DefaultValue).ToArray();
+ return ctor.Invoke(call);
}
return Convert.ChangeType(argumentValue, toType);
diff --git a/src/Serilog.Settings.Configuration/Settings/Configuration/SurrogateConfigurationMethods.cs b/src/Serilog.Settings.Configuration/Settings/Configuration/SurrogateConfigurationMethods.cs
index bbde10d..4b460b3 100644
--- a/src/Serilog.Settings.Configuration/Settings/Configuration/SurrogateConfigurationMethods.cs
+++ b/src/Serilog.Settings.Configuration/Settings/Configuration/SurrogateConfigurationMethods.cs
@@ -67,6 +67,13 @@ static LoggerConfiguration Sink(
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[])
@@ -101,6 +108,14 @@ static LoggerConfiguration With(
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();
diff --git a/test/Serilog.Settings.Configuration.Tests/ConfigurationSettingsTests.cs b/test/Serilog.Settings.Configuration.Tests/ConfigurationSettingsTests.cs
index eed3803..472b0f9 100644
--- a/test/Serilog.Settings.Configuration.Tests/ConfigurationSettingsTests.cs
+++ b/test/Serilog.Settings.Configuration.Tests/ConfigurationSettingsTests.cs
@@ -162,6 +162,37 @@ public void AuditSinksAreConfigured()
Assert.Equal(1, DummyRollingFileAuditSink.Emitted.Count);
}
+ [Fact]
+ public void AuditToSubLoggersAreConfigured()
+ {
+ var json = @"{
+ ""Serilog"": {
+ ""Using"": [""TestDummies""],
+ ""AuditTo"": [{
+ ""Name"": ""Logger"",
+ ""Args"": {
+ ""configureLogger"" : {
+ ""AuditTo"": [{
+ ""Name"": ""DummyRollingFile"",
+ ""Args"": {""pathFormat"" : ""C:\\""}
+ }]}
+ }
+ }]
+ }
+ }";
+
+ var log = ConfigFromJson(json)
+ .CreateLogger();
+
+ DummyRollingFileSink.Reset();
+ DummyRollingFileAuditSink.Reset();
+
+ log.Write(Some.InformationEvent());
+
+ Assert.Equal(0, DummyRollingFileSink.Emitted.Count);
+ Assert.Equal(1, DummyRollingFileAuditSink.Emitted.Count);
+ }
+
[Fact]
public void TestMinimumLevelOverrides()
{
@@ -274,8 +305,10 @@ public void SinksAreConfiguredWithStaticMember()
[Theory]
[InlineData("$switchName", true)]
[InlineData("$SwitchName", true)]
+ [InlineData("SwitchName", true)]
[InlineData("$switch1", true)]
[InlineData("$sw1tch0", true)]
+ [InlineData("sw1tch0", true)]
[InlineData("$SWITCHNAME", true)]
[InlineData("$$switchname", false)]
[InlineData("$switchname$", false)]
@@ -295,29 +328,60 @@ public void LoggingLevelSwitchNameValidityScenarios(string switchName, bool expe
public void LoggingLevelSwitchWithInvalidNameThrowsFormatException()
{
var json = @"{
- ""Serilog"": {
- ""LevelSwitches"": {""switchNameNotStartingWithDollar"" : ""Warning"" }
+ ""Serilog"": {
+ ""LevelSwitches"": {""1InvalidSwitchName"" : ""Warning"" }
}
}";
var ex = Assert.Throws(() => ConfigFromJson(json));
- Assert.Contains("\"switchNameNotStartingWithDollar\"", ex.Message);
+ Assert.Contains("\"1InvalidSwitchName\"", ex.Message);
Assert.Contains("'$' sign", ex.Message);
Assert.Contains("\"LevelSwitches\" : {\"$switchName\" :", ex.Message);
}
- [Fact]
- public void LoggingLevelSwitchIsConfigured()
+ [Theory]
+ [InlineData("$mySwitch")]
+ [InlineData("mySwitch")]
+ public void LoggingFilterSwitchIsConfigured(string switchName)
{
- var json = @"{
- ""Serilog"": {
- ""LevelSwitches"": {""$switch1"" : ""Warning"" },
- ""MinimumLevel"" : {
- ""ControlledBy"" : ""$switch1""
- }
- }
- }";
+ var json = $@"{{
+ 'Serilog': {{
+ 'FilterSwitches': {{ '{switchName}': 'Prop = 42' }},
+ 'Filter:BySwitch': {{
+ 'Name': 'ControlledBy',
+ 'Args': {{
+ 'switch': '$mySwitch'
+ }}
+ }}
+ }}
+ }}";
+ LogEvent evt = null;
+
+ var log = ConfigFromJson(json)
+ .WriteTo.Sink(new DelegatingSink(e => evt = e))
+ .CreateLogger();
+
+ log.Write(Some.InformationEvent());
+ Assert.Null(evt);
+
+ log.ForContext("Prop", 42).Write(Some.InformationEvent());
+ Assert.NotNull(evt);
+ }
+
+ [Theory]
+ [InlineData("$switch1")]
+ [InlineData("switch1")]
+ public void LoggingLevelSwitchIsConfigured(string switchName)
+ {
+ var json = $@"{{
+ 'Serilog': {{
+ 'LevelSwitches': {{ '{switchName}' : 'Warning' }},
+ 'MinimumLevel' : {{
+ 'ControlledBy' : '$switch1'
+ }}
+ }}
+ }}";
LogEvent evt = null;
var log = ConfigFromJson(json)
@@ -584,6 +648,46 @@ public void SinkWithStringArrayArgument()
Assert.Equal(1, DummyRollingFileSink.Emitted.Count);
}
+ [Fact]
+ public void DestructureWithCollectionsOfTypeArgument()
+ {
+ var json = @"{
+ ""Serilog"": {
+ ""Using"": [ ""TestDummies"" ],
+ ""Destructure"": [{
+ ""Name"": ""DummyArrayOfType"",
+ ""Args"": {
+ ""list"": [
+ ""System.Byte"",
+ ""System.Int16""
+ ],
+ ""array"" : [
+ ""System.Int32"",
+ ""System.String""
+ ],
+ ""type"" : ""System.TimeSpan"",
+ ""custom"" : [
+ ""System.Int64""
+ ],
+ ""customString"" : [
+ ""System.UInt32""
+ ]
+ }
+ }]
+ }
+ }";
+
+ DummyPolicy.Current = null;
+
+ ConfigFromJson(json);
+
+ Assert.Equal(typeof(TimeSpan), DummyPolicy.Current.Type);
+ Assert.Equal(new[] { typeof(int), typeof(string) }, DummyPolicy.Current.Array);
+ Assert.Equal(new[] { typeof(byte), typeof(short) }, DummyPolicy.Current.List);
+ Assert.Equal(typeof(long), DummyPolicy.Current.Custom.First);
+ Assert.Equal("System.UInt32", DummyPolicy.Current.CustomStrings.First);
+ }
+
[Fact]
public void SinkWithIntArrayArgument()
{
diff --git a/test/Serilog.Settings.Configuration.Tests/DllScanningAssemblyFinderTests.cs b/test/Serilog.Settings.Configuration.Tests/DllScanningAssemblyFinderTests.cs
index d8ab19a..be937a2 100644
--- a/test/Serilog.Settings.Configuration.Tests/DllScanningAssemblyFinderTests.cs
+++ b/test/Serilog.Settings.Configuration.Tests/DllScanningAssemblyFinderTests.cs
@@ -65,7 +65,7 @@ public void ShouldProbePrivateBinPath()
AppDomain.Unload(ad);
}
- void DoTestInner()
+ static void DoTestInner()
{
var assemblyNames = new DllScanningAssemblyFinder().FindAssembliesContainingName("customSink");
Assert.Equal(2, assemblyNames.Count);
diff --git a/test/Serilog.Settings.Configuration.Tests/DynamicLevelChangeTests.cs b/test/Serilog.Settings.Configuration.Tests/DynamicLevelChangeTests.cs
index b1b1f43..6b439f6 100644
--- a/test/Serilog.Settings.Configuration.Tests/DynamicLevelChangeTests.cs
+++ b/test/Serilog.Settings.Configuration.Tests/DynamicLevelChangeTests.cs
@@ -21,6 +21,13 @@ public class DynamicLevelChangeTests
}
},
'LevelSwitches': { '$mySwitch': 'Information' },
+ 'FilterSwitches': { '$myFilter': null },
+ 'Filter:Dummy': {
+ 'Name': 'ControlledBy',
+ 'Args': {
+ 'switch': '$myFilter'
+ }
+ },
'WriteTo:Dummy': {
'Name': 'DummyConsole',
'Args': {
@@ -64,10 +71,22 @@ public void ShouldRespectDynamicLevelChanges()
UpdateConfig(overrideLevel: LogEventLevel.Debug);
logger.ForContext(Constants.SourceContextPropertyName, "Root.Test").Write(Some.DebugEvent());
Assert.Single(DummyConsoleSink.Emitted);
+
+ DummyConsoleSink.Emitted.Clear();
+ UpdateConfig(filterExpression: "Prop = 'Val_1'");
+ logger.Write(Some.DebugEvent());
+ logger.ForContext("Prop", "Val_1").Write(Some.DebugEvent());
+ Assert.Single(DummyConsoleSink.Emitted);
+
+ DummyConsoleSink.Emitted.Clear();
+ UpdateConfig(filterExpression: "Prop = 'Val_2'");
+ logger.Write(Some.DebugEvent());
+ logger.ForContext("Prop", "Val_1").Write(Some.DebugEvent());
+ Assert.Empty(DummyConsoleSink.Emitted);
}
}
- void UpdateConfig(LogEventLevel? minimumLevel = null, LogEventLevel? switchLevel = null, LogEventLevel? overrideLevel = null)
+ void UpdateConfig(LogEventLevel? minimumLevel = null, LogEventLevel? switchLevel = null, LogEventLevel? overrideLevel = null, string filterExpression = null)
{
if (minimumLevel.HasValue)
{
@@ -84,6 +103,11 @@ void UpdateConfig(LogEventLevel? minimumLevel = null, LogEventLevel? switchLevel
_configSource.Set("Serilog:MinimumLevel:Override:Root.Test", overrideLevel.Value.ToString());
}
+ if (filterExpression != null)
+ {
+ _configSource.Set("Serilog:FilterSwitches:$myFilter", filterExpression);
+ }
+
_configSource.Reload();
}
}
diff --git a/test/Serilog.Settings.Configuration.Tests/Serilog.Settings.Configuration.Tests.csproj b/test/Serilog.Settings.Configuration.Tests/Serilog.Settings.Configuration.Tests.csproj
index 69ceb38..9a02189 100644
--- a/test/Serilog.Settings.Configuration.Tests/Serilog.Settings.Configuration.Tests.csproj
+++ b/test/Serilog.Settings.Configuration.Tests/Serilog.Settings.Configuration.Tests.csproj
@@ -1,18 +1,8 @@
-
-
- net452;netcoreapp2.0
+ net50;netcoreapp3.1;netcoreapp2.1;net452
+ latest
Serilog.Settings.Configuration.Tests
../../assets/Serilog.snk
true
@@ -29,23 +19,25 @@
-
-
+
-
-
-
+
+
-
-
-
-
+
+
-
-
+
+
+
+
+
+
+
+
diff --git a/test/Serilog.Settings.Configuration.Tests/Support/DelegatingSink.cs b/test/Serilog.Settings.Configuration.Tests/Support/DelegatingSink.cs
index 710d0c8..07a848b 100644
--- a/test/Serilog.Settings.Configuration.Tests/Support/DelegatingSink.cs
+++ b/test/Serilog.Settings.Configuration.Tests/Support/DelegatingSink.cs
@@ -10,8 +10,7 @@ public class DelegatingSink : ILogEventSink
public DelegatingSink(Action write)
{
- if (write == null) throw new ArgumentNullException(nameof(write));
- _write = write;
+ _write = write ?? throw new ArgumentNullException(nameof(write));
}
public void Emit(LogEvent logEvent)
diff --git a/test/Serilog.Settings.Configuration.Tests/Support/Extensions.cs b/test/Serilog.Settings.Configuration.Tests/Support/Extensions.cs
index 02e3487..6ef5d96 100644
--- a/test/Serilog.Settings.Configuration.Tests/Support/Extensions.cs
+++ b/test/Serilog.Settings.Configuration.Tests/Support/Extensions.cs
@@ -8,5 +8,11 @@ public static object LiteralValue(this LogEventPropertyValue @this)
{
return ((ScalarValue)@this).Value;
}
+
+ public static string ToValidJson(this string str)
+ {
+ str = str.Replace('\'', '"');
+ return str;
+ }
}
}
diff --git a/test/Serilog.Settings.Configuration.Tests/Support/JsonStringConfigSource.cs b/test/Serilog.Settings.Configuration.Tests/Support/JsonStringConfigSource.cs
index 31cf8ac..1175ee4 100644
--- a/test/Serilog.Settings.Configuration.Tests/Support/JsonStringConfigSource.cs
+++ b/test/Serilog.Settings.Configuration.Tests/Support/JsonStringConfigSource.cs
@@ -45,7 +45,7 @@ public JsonStringConfigProvider(string json) : base(new JsonConfigurationSource
public override void Load()
{
- Load(StringToStream(_json));
+ Load(StringToStream(_json.ToValidJson()));
}
static Stream StringToStream(string str)
diff --git a/test/TestDummies/DummyLoggerConfigurationExtensions.cs b/test/TestDummies/DummyLoggerConfigurationExtensions.cs
index ecae9cd..7c1d109 100644
--- a/test/TestDummies/DummyLoggerConfigurationExtensions.cs
+++ b/test/TestDummies/DummyLoggerConfigurationExtensions.cs
@@ -1,13 +1,13 @@
-using System;
+using Microsoft.Extensions.Configuration;
using Serilog;
-using Serilog.Events;
-using Serilog.Formatting;
using Serilog.Configuration;
using Serilog.Core;
+using Serilog.Events;
+using Serilog.Formatting;
+using System;
+using System.Collections.Generic;
using TestDummies.Console;
using TestDummies.Console.Themes;
-using Microsoft.Extensions.Configuration;
-using System.Collections.Generic;
namespace TestDummies
{
@@ -63,13 +63,20 @@ public static LoggerConfiguration DummyWithConfigSection(
public static LoggerConfiguration DummyRollingFile(
this LoggerSinkConfiguration loggerSinkConfiguration,
- List objectBinding,
+ List objectBinding,
string pathFormat,
LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum)
{
return loggerSinkConfiguration.Sink(new DummyRollingFileSink(), restrictedToMinimumLevel);
}
+ public class Binding
+ {
+ public string Foo { get; set; }
+
+ public string Abc { get; set; }
+ }
+
public static LoggerConfiguration DummyRollingFile(
this LoggerSinkConfiguration loggerSinkConfiguration,
string[] stringArrayBinding,
@@ -133,5 +140,21 @@ string hardCodedString
return loggerDestructuringConfiguration.With(new DummyHardCodedStringDestructuringPolicy(hardCodedString));
}
+ public static LoggerConfiguration DummyArrayOfType(this LoggerDestructuringConfiguration loggerSinkConfiguration,
+ List list,
+ Type[] array = null,
+ Type type = null,
+ CustomCollection custom = null,
+ CustomCollection customString = null)
+ {
+ return loggerSinkConfiguration.With(DummyPolicy.Current = new DummyPolicy
+ {
+ List = list,
+ Array = array,
+ Type = type,
+ Custom = custom,
+ CustomStrings = customString,
+ });
+ }
}
}
diff --git a/test/TestDummies/DummyPolicy.cs b/test/TestDummies/DummyPolicy.cs
new file mode 100644
index 0000000..ada4c2f
--- /dev/null
+++ b/test/TestDummies/DummyPolicy.cs
@@ -0,0 +1,48 @@
+using Serilog.Core;
+using Serilog.Events;
+using System;
+using System.Collections;
+using System.Collections.Generic;
+
+namespace TestDummies
+{
+ public class DummyPolicy : IDestructuringPolicy
+ {
+ public static DummyPolicy Current { get; set; }
+
+ public Type[] Array { get; set; }
+
+ public List List { get; set; }
+
+ public CustomCollection Custom { get; set; }
+
+ public CustomCollection CustomStrings { get; set; }
+
+ public Type Type { get; set; }
+
+ public bool TryDestructure(object value, ILogEventPropertyValueFactory propertyValueFactory, out LogEventPropertyValue result)
+ {
+ result = null;
+ return false;
+ }
+ }
+
+ public class CustomCollection : IEnumerable
+ {
+ private readonly List inner = new List();
+
+ public void Add(T item) => inner.Add(item);
+
+ // wrong signature for collection initializer
+ public int Add() => 0;
+
+ // wrong signature for collection initializer
+ public void Add(string a, byte b) { }
+
+ public T First => inner.Count > 0 ? inner[0] : default;
+
+ public IEnumerator GetEnumerator() => inner.GetEnumerator();
+
+ IEnumerator IEnumerable.GetEnumerator() => inner.GetEnumerator();
+ }
+}
diff --git a/test/TestDummies/DummyThreadIdEnricher.cs b/test/TestDummies/DummyThreadIdEnricher.cs
index a640d55..3460d14 100644
--- a/test/TestDummies/DummyThreadIdEnricher.cs
+++ b/test/TestDummies/DummyThreadIdEnricher.cs
@@ -5,6 +5,10 @@ namespace TestDummies
{
public class DummyThreadIdEnricher : ILogEventEnricher
{
+ static DummyThreadIdEnricher()
+ {
+ }
+
public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
{
logEvent.AddPropertyIfAbsent(propertyFactory
diff --git a/test/TestDummies/Properties/AssemblyInfo.cs b/test/TestDummies/Properties/AssemblyInfo.cs
deleted file mode 100644
index 8903297..0000000
--- a/test/TestDummies/Properties/AssemblyInfo.cs
+++ /dev/null
@@ -1,18 +0,0 @@
-using System.Reflection;
-using System.Runtime.InteropServices;
-
-// General Information about an assembly is controlled through the following
-// set of attributes. Change these attribute values to modify the information
-// associated with an assembly.
-[assembly: AssemblyConfiguration("")]
-[assembly: AssemblyCompany("")]
-[assembly: AssemblyProduct("TestDummies")]
-[assembly: AssemblyTrademark("")]
-
-// Setting ComVisible to false makes the types in this assembly not visible
-// to COM components. If you need to access a type in this assembly from
-// COM, set the ComVisible attribute to true on that type.
-[assembly: ComVisible(false)]
-
-// The following GUID is for the ID of the typelib if this project is exposed to COM
-[assembly: Guid("2bb12ce5-c867-43bd-ae5d-253fe3248c7f")]
diff --git a/test/TestDummies/TestDummies.csproj b/test/TestDummies/TestDummies.csproj
index 5c212f5..5806810 100644
--- a/test/TestDummies/TestDummies.csproj
+++ b/test/TestDummies/TestDummies.csproj
@@ -1,15 +1,11 @@
- net452;netstandard2.0
+ netstandard2.0;net452
TestDummies
../../assets/Serilog.snk
true
true
- TestDummies
- false
- false
- false