diff --git a/.editorconfig b/.editorconfig index 102e19f..3687ef4 100644 --- a/.editorconfig +++ b/.editorconfig @@ -5,6 +5,7 @@ trim_trailing_whitespace = true insert_final_newline = true indent_style = space indent_size = 4 +end_of_line = lf [*.{csproj,json,config,yml,props}] indent_size = 2 diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..6db6fc0 --- /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/netcoreapp2.0/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/CHANGES.md b/CHANGES.md index 8589a7e..1258064 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,7 +1,85 @@ +# Changelog + +3.2.0 (pre-release) + +* #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 + +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 index 0ca3dd9..7a4f2d1 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -3,7 +3,7 @@ latest - - + + diff --git a/README.md b/README.md index 7c59c2e..a13d8a7 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_ sources, including .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 } }, @@ -27,80 +27,194 @@ Configuration is read from the `Serilog` section. } ``` -This example relies on the _Microsoft.Extensions.Configuration.Json_, _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") + .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": { + ... + } +} ``` -Or alternatively, the long-form (`"Name":` ...) syntax from the first example can be used when arguments need to be supplied. +```csharp +var logger = new LoggerConfiguration() + .ReadFrom.Configuration(configuration, sectionName: "CustomSection") + .CreateLogger(); +``` -(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 and auto-discovery of configuration assemblies -### .NET 4.x +`Using` section contains list of **assemblies** in wich configuration methods (`WriteTo.File()`, `Enrich.WithThreadId()`) resides. -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 ommitted: ```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` section. + +### 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 respect 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" + } + } + ] } +} ``` -### Environment variables +Level updates to switches are also respected for a dynamic update. -If your application enables the environment variable configuration source (`AddEnvironmentVariables()`) you can add or override Serilog configuration through the environment. +### WriteTo, Enrich, AuditTo, Destructure sections -For example, to set the minimum log level using the _Windows_ command prompt: +These sections support simplified syntax, for example the following is valid if no arguments are needed by the sinks: +```json +"WriteTo": [ "Console", "DiagnosticTrace" ] ``` -set Serilog:MinimumLevel=Debug -dotnet run + +Or alternatively, the long-form (`"Name":` ...) syntax from the example above can be used when arguments need to be supplied. + +By `Microsoft.Extensions.Configuratiom.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" +} +``` + +And + +```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`) + +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" } +} +``` + +### 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.Filters.Expression](https://github.com/serilog/serilog-filters-expressions)_ package so you can write expression in text form: + +```json +"Filter": [{ + "Name": "ByIncludingOnly", + "Args": { + "expression": "Application = 'Sample'" + } +}] ``` ### 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,7 +224,7 @@ Some Serilog packages require a reference to a logger configuration object. The { "Name": "File", "Args": { - "path": "%TEMP%\\Logs\\serilog-configuration-sample.txt", + "path": "%TEMP%/Logs/serilog-configuration-sample.txt", "outputTemplate": "{Timestamp:o} [{Level:u3}] ({Application}/{MachineName}/{ThreadId}) {Message}{NewLine}{Exception}" } @@ -120,17 +234,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`, `TimeSpan`, `enum` and arrays. +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. -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. +### 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 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/sample/Sample/Sample.csproj b/sample/Sample/Sample.csproj index 6747952..2eb1849 100644 --- a/sample/Sample/Sample.csproj +++ b/sample/Sample/Sample.csproj @@ -1,7 +1,7 @@  - net46;netcoreapp2.0 + netcoreapp2.0;net46 Exe diff --git a/sample/Sample/appsettings.json b/sample/Sample/appsettings.json index 01ffc67..d952590 100644 --- a/sample/Sample/appsettings.json +++ b/sample/Sample/appsettings.json @@ -35,7 +35,7 @@ { "Name": "File", "Args": { - "path": "%TEMP%\\Logs\\serilog-configuration-sample.txt", + "path": "%TEMP%/Logs/serilog-configuration-sample.txt", "outputTemplate": "{Timestamp:o} [{Level:u3}] ({Application}/{MachineName}/{ThreadId}/{ThreadName}) {Message}{NewLine}{Exception}" } } @@ -50,7 +50,7 @@ { "Name": "File", "Args": { - "path": "%TEMP%\\Logs\\serilog-configuration-sample-errors.txt" + "path": "%TEMP%/Logs/serilog-configuration-sample-errors.txt" } } ] 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 77d25b8..334d1db 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,7 @@  - - - net452;netcoreapp2.0;netcoreapp3.1 + netcoreapp3.1;netcoreapp2.0;net452 latest Serilog.Settings.Configuration.Tests ../../assets/Serilog.snk @@ -42,7 +31,7 @@ - + diff --git a/test/TestDummies/TestDummies.csproj b/test/TestDummies/TestDummies.csproj index f2233d1..5806810 100644 --- a/test/TestDummies/TestDummies.csproj +++ b/test/TestDummies/TestDummies.csproj @@ -1,8 +1,7 @@  - latest - net452;netstandard2.0 + netstandard2.0;net452 TestDummies ../../assets/Serilog.snk true