diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..112f424 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,23 @@ +root = true + +[*] +trim_trailing_whitespace = true +insert_final_newline = true +indent_style = space +indent_size = 4 +charset = utf-8 +end_of_line = lf + +[*.{csproj,json,config,yml,props}] +indent_size = 2 + +[*.sh] +end_of_line = lf + +[*.{cmd, bat}] +end_of_line = crlf + +# C# formatting settings - Namespace options +csharp_style_namespace_declarations = file_scoped:suggestion + +csharp_style_prefer_switch_expression = true:suggestion \ No newline at end of file diff --git a/Build.ps1 b/Build.ps1 index 41fbf5d..a674b52 100644 --- a/Build.ps1 +++ b/Build.ps1 @@ -11,7 +11,7 @@ if(Test-Path .\artifacts) { $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" @@ -25,8 +25,8 @@ foreach ($src in ls src/*) { } else { & dotnet pack -c Release --include-source -o ..\..\artifacts } - - if($LASTEXITCODE -ne 0) { exit 1 } + + if($LASTEXITCODE -ne 0) { exit 1 } Pop-Location } diff --git a/Directory.Build.props b/Directory.Build.props new file mode 100644 index 0000000..2b620bc --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,12 @@ + + + + latest + True + true + $(MSBuildThisFileDirectory)assets/Serilog.snk + true + enable + + + diff --git a/Directory.Build.targets b/Directory.Build.targets new file mode 100644 index 0000000..faf2349 --- /dev/null +++ b/Directory.Build.targets @@ -0,0 +1,3 @@ + + + diff --git a/README.md b/README.md index 905fc55..bbed04e 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Serilog.Extensions.Logging [![Build status](https://ci.appveyor.com/api/projects/status/865nohxfiq1rnby0/branch/master?svg=true)](https://ci.appveyor.com/project/serilog/serilog-framework-logging/history) [![NuGet Version](http://img.shields.io/nuget/v/Serilog.Extensions.Logging.svg?style=flat)](https://www.nuget.org/packages/Serilog.Extensions.Logging/) +# Serilog.Extensions.Logging [![Build status](https://ci.appveyor.com/api/projects/status/865nohxfiq1rnby0/branch/master?svg=true)](https://ci.appveyor.com/project/serilog/serilog-framework-logging/history) [![NuGet Version](http://img.shields.io/nuget/v/Serilog.Extensions.Logging.svg?style=flat)](https://www.nuget.org/packages/Serilog.Extensions.Logging/) A Serilog provider for [Microsoft.Extensions.Logging](https://www.nuget.org/packages/Microsoft.Extensions.Logging), the logging subsystem used by ASP.NET Core. @@ -16,9 +16,9 @@ The package implements `AddSerilog()` on `ILoggingBuilder` and `ILoggerFactory` **First**, install the _Serilog.Extensions.Logging_ [NuGet package](https://www.nuget.org/packages/Serilog.Extensions.Logging) into your web or console app. You will need a way to view the log messages - _Serilog.Sinks.Console_ writes these to the console. -```powershell -Install-Package Serilog.Extensions.Logging -DependencyVersion Highest -Install-Package Serilog.Sinks.Console +```sh +dotnet add package Serilog.Extensions.Logging +dotnet add package Serilog.Sinks.Console ``` **Next**, in your application's `Startup` method, configure Serilog first: @@ -34,7 +34,7 @@ public class Startup .Enrich.FromLogContext() .WriteTo.Console() .CreateLogger(); - + // Other startup code ``` @@ -46,7 +46,7 @@ call `AddSerilog()` on the provided `loggingBuilder`. { services.AddLogging(loggingBuilder => loggingBuilder.AddSerilog(dispose: true)); - + // Other services ... } ``` @@ -60,7 +60,7 @@ call `AddSerilog()` on the provided `loggingBuilder`. IApplicationLifetime appLifetime) { loggerfactory.AddSerilog(); - + // Ensure any buffered events are sent at shutdown appLifetime.ApplicationStopped.Register(Log.CloseAndFlush); ``` @@ -69,7 +69,7 @@ That's it! With the level bumped up a little you should see log output like: ``` [22:14:44.646 DBG] RouteCollection.RouteAsync - Routes: + Routes: Microsoft.AspNet.Mvc.Routing.AttributeRoute {controller=Home}/{action=Index}/{id?} Handled? True @@ -89,7 +89,7 @@ _Microsoft.Extensions.Logging_ provides the `BeginScope` API, which can be used Using the extension method will add a `Scope` property to your log events. This is most useful for adding simple "scope strings" to your events, as in the following code: -```cs +```csharp using (_logger.BeginScope("Transaction")) { _logger.LogInformation("Beginning..."); _logger.LogInformation("Completed in {DurationMs}ms...", 30); @@ -101,7 +101,7 @@ using (_logger.BeginScope("Transaction")) { If you simply want to add a "bag" of additional properties to your log events, however, this extension method approach can be overly verbose. For example, to add `TransactionId` and `ResponseJson` properties to your log events, you would have to do something like the following: -```cs +```csharp // WRONG! Prefer the dictionary approach below instead using (_logger.BeginScope("TransactionId: {TransactionId}, ResponseJson: {ResponseJson}", 12345, jsonString)) { _logger.LogInformation("Completed in {DurationMs}ms...", 30); @@ -119,11 +119,12 @@ using (_logger.BeginScope("TransactionId: {TransactionId}, ResponseJson: {Respon // } ``` -Not only does this add the unnecessary `Scope` property to your event, but it also duplicates serialized values between `Scope` and the intended properties, as you can see here with `ResponseJson`. If this were "real" JSON like an API response, then a potentially very large block of text would be duplicated within your log event! Moreover, the template string within `BeginScope` is rather arbitrary when all you want to do is add a bag of properties, and you start mixing enriching concerns with formatting concerns. +Not only does this add the unnecessary `Scope` property to your event, but it also duplicates serialized values between `Scope` and the intended properties, as you can see here with `ResponseJson`. If this were "real" JSON like an API response, then a potentially very large block of text would be duplicated within your log event! +Moreover, the template string within `BeginScope` is rather arbitrary when all you want to do is add a bag of properties, and you start mixing enriching concerns with formatting concerns. A far better alternative is to use the `BeginScope(TState state)` method. If you provide any `IEnumerable>` to this method, then Serilog will output the key/value pairs as structured properties _without_ the `Scope` property, as in this example: -```cs +```csharp var scopeProps = new Dictionary { { "TransactionId", 12345 }, { "ResponseJson", jsonString }, @@ -143,6 +144,10 @@ using (_logger.BeginScope(scopeProps) { // } ``` +### Versioning + +This package tracks the versioning and target framework support of its [_Microsoft.Extensions.Logging_](https://nuget.org/packages/Microsoft.Extensions.Logging) dependency. + ### Credits This package evolved from an earlier package _Microsoft.Framework.Logging.Serilog_ [provided by the ASP.NET team](https://github.com/aspnet/Logging/pull/182). diff --git a/appveyor.yml b/appveyor.yml index 628e491..3e4a484 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,6 +1,6 @@ version: '{build}' skip_tags: true -image: Visual Studio 2019 +image: Visual Studio 2022 install: - ps: mkdir -Force ".\build\" | Out-Null build_script: @@ -11,15 +11,15 @@ artifacts: deploy: - provider: NuGet api_key: - secure: kYR3BYzJm3wSFbFjPhgTzuDHHcE8ApoNUmHvJvunWZ39nyrqVk6J6srjzYVQ7/gX + secure: EN9f+XXE3fW+ebL4wxrIbafdtbNvRfddBN8UUixvctYh4qMBHzr1JdnM83QsM1zo skip_symbols: true on: - branch: /^(master|dev)$/ + branch: /^(main|dev)$/ - provider: GitHub auth_token: secure: p4LpVhBKxGS5WqucHxFQ5c7C8cP74kbNB0Z8k9Oxx/PMaDQ1+ibmoexNqVU5ZlmX artifact: /Serilog.*\.nupkg/ tag: v$(appveyor_build_version) on: - branch: master + branch: main diff --git a/global.json b/global.json deleted file mode 100644 index e3bbb00..0000000 --- a/global.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "sdk": { - "allowPrerelease": false, - "version": "3.1.100", - "rollForward": "latestFeature" - } -} diff --git a/samples/Sample/Program.cs b/samples/Sample/Program.cs index 03014b7..d70b9aa 100644 --- a/samples/Sample/Program.cs +++ b/samples/Sample/Program.cs @@ -1,79 +1,68 @@ -using System; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Serilog; using Serilog.Extensions.Logging; -namespace Sample -{ - public class Program - { - public static void Main(string[] args) - { - // Creating a `LoggerProviderCollection` lets Serilog optionally write - // events through other dynamically-added MEL ILoggerProviders. - var providers = new LoggerProviderCollection(); +// Creating a `LoggerProviderCollection` lets Serilog optionally write +// events through other dynamically-added MEL ILoggerProviders. +var providers = new LoggerProviderCollection(); - Log.Logger = new LoggerConfiguration() - .MinimumLevel.Debug() - .WriteTo.Console() - .WriteTo.Providers(providers) - .CreateLogger(); +Log.Logger = new LoggerConfiguration() + .MinimumLevel.Debug() + .WriteTo.Console() + .WriteTo.Providers(providers) + .CreateLogger(); - var services = new ServiceCollection(); +var services = new ServiceCollection(); - services.AddSingleton(providers); - services.AddSingleton(sc => - { - var providerCollection = sc.GetService(); - var factory = new SerilogLoggerFactory(null, true, providerCollection); +services.AddSingleton(providers); +services.AddSingleton(sc => +{ + var providerCollection = sc.GetService(); + var factory = new SerilogLoggerFactory(null, true, providerCollection); - foreach (var provider in sc.GetServices()) - factory.AddProvider(provider); + foreach (var provider in sc.GetServices()) + factory.AddProvider(provider); - return factory; - }); + return factory; +}); - services.AddLogging(l => l.AddConsole()); +services.AddLogging(l => l.AddConsole()); - var serviceProvider = services.BuildServiceProvider(); - var logger = serviceProvider.GetRequiredService>(); +var serviceProvider = services.BuildServiceProvider(); +var logger = serviceProvider.GetRequiredService>(); - var startTime = DateTimeOffset.UtcNow; - logger.LogInformation(1, "Started at {StartTime} and 0x{Hello:X} is hex of 42", startTime, 42); +var startTime = DateTimeOffset.UtcNow; +logger.LogInformation(1, "Started at {StartTime} and 0x{Hello:X} is hex of 42", startTime, 42); - try - { - throw new Exception("Boom!"); - } - catch (Exception ex) - { - logger.LogCritical("Unexpected critical error starting application", ex); - logger.Log(LogLevel.Critical, 0, "Unexpected critical error", ex, null); - // This write should not log anything - logger.Log(LogLevel.Critical, 0, null, null, null); - logger.LogError("Unexpected error", ex); - logger.LogWarning("Unexpected warning", ex); - } +try +{ + throw new Exception("Boom!"); +} +catch (Exception ex) +{ + logger.LogCritical(ex, "Unexpected critical error starting application"); + logger.Log(LogLevel.Critical, 0, "Unexpected critical error", ex, null!); + // This write should not log anything + logger.Log(LogLevel.Critical, 0, null!, null, null!); + logger.LogError(ex, "Unexpected error"); + logger.LogWarning(ex, "Unexpected warning"); +} - using (logger.BeginScope("Main")) - { - logger.LogInformation("Waiting for user input"); - var key = Console.Read(); - logger.LogInformation("User pressed {@KeyInfo}", new { Key = key, KeyChar = (char)key }); - } +using (logger.BeginScope("Main")) +{ + logger.LogInformation("Waiting for user input"); + var key = Console.Read(); + logger.LogInformation("User pressed {@KeyInfo}", new { Key = key, KeyChar = (char)key }); +} - var endTime = DateTimeOffset.UtcNow; - logger.LogInformation(2, "Stopping at {StopTime}", endTime); +var endTime = DateTimeOffset.UtcNow; +logger.LogInformation(2, "Stopping at {StopTime}", endTime); - logger.LogInformation("Stopping"); +logger.LogInformation("Stopping"); - logger.LogInformation(Environment.NewLine); - logger.LogInformation("{Result,-10:l}{StartTime,15:l}{EndTime,15:l}{Duration,15:l}", "RESULT", "START TIME", "END TIME", "DURATION(ms)"); - logger.LogInformation("{Result,-10:l}{StartTime,15:l}{EndTime,15:l}{Duration,15:l}", "------", "----- ----", "--- ----", "------------"); - logger.LogInformation("{Result,-10:l}{StartTime,15:mm:s tt}{EndTime,15:mm:s tt}{Duration,15}", "SUCCESS", startTime, endTime, (endTime - startTime).TotalMilliseconds); +logger.LogInformation("{Result,-10:l}{StartTime,15:l}{EndTime,15:l}{Duration,15:l}", "RESULT", "START TIME", "END TIME", "DURATION(ms)"); +logger.LogInformation("{Result,-10:l}{StartTime,15:l}{EndTime,15:l}{Duration,15:l}", "------", "----- ----", "--- ----", "------------"); +logger.LogInformation("{Result,-10:l}{StartTime,15:mm:s tt}{EndTime,15:mm:s tt}{Duration,15}", "SUCCESS", startTime, endTime, (endTime - startTime).TotalMilliseconds); - serviceProvider.Dispose(); - } - } -} +serviceProvider.Dispose(); diff --git a/samples/Sample/Sample.csproj b/samples/Sample/Sample.csproj index c557b8a..ef89f43 100644 --- a/samples/Sample/Sample.csproj +++ b/samples/Sample/Sample.csproj @@ -1,10 +1,10 @@  - netcoreapp2.0 + net7.0 Sample Exe - Sample + enable @@ -12,10 +12,9 @@ - - - - + + + - \ No newline at end of file + diff --git a/serilog-extensions-logging.sln b/serilog-extensions-logging.sln index c53414c..9e91f73 100644 --- a/serilog-extensions-logging.sln +++ b/serilog-extensions-logging.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.29209.62 +# Visual Studio Version 17 +VisualStudioVersion = 17.5.33424.131 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{A1893BD1-333D-4DFE-A0F0-DDBB2FE526E0}" EndProject @@ -17,9 +17,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sample", "samples\Sample\Sa EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "assets", "assets", "{9C21B9DF-AEDD-4AA6-BEA4-912DEF3E5B8E}" ProjectSection(SolutionItems) = preProject + .editorconfig = .editorconfig appveyor.yml = appveyor.yml Build.ps1 = Build.ps1 - global.json = global.json + Directory.Build.props = Directory.Build.props + Directory.Build.targets = Directory.Build.targets README.md = README.md assets\Serilog.snk = assets\Serilog.snk EndProjectSection diff --git a/serilog-extensions-logging.sln.DotSettings b/serilog-extensions-logging.sln.DotSettings index c2fd9da..23a0f99 100644 --- a/serilog-extensions-logging.sln.DotSettings +++ b/serilog-extensions-logging.sln.DotSettings @@ -1,12 +1,2 @@  - True - True - True - True - True - True - True - True - True - True - True \ No newline at end of file + True \ No newline at end of file diff --git a/src/Serilog.Extensions.Logging/Extensions/Logging/CachingMessageTemplateParser.cs b/src/Serilog.Extensions.Logging/Extensions/Logging/CachingMessageTemplateParser.cs index d47e84f..8966ef7 100644 --- a/src/Serilog.Extensions.Logging/Extensions/Logging/CachingMessageTemplateParser.cs +++ b/src/Serilog.Extensions.Logging/Extensions/Logging/CachingMessageTemplateParser.cs @@ -1,4 +1,4 @@ -// Copyright (c) Serilog Contributors +// Copyright (c) Serilog Contributors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -13,55 +13,53 @@ // limitations under the License. -using System; using Serilog.Events; using Serilog.Parsing; using System.Collections; -namespace Serilog.Extensions.Logging +namespace Serilog.Extensions.Logging; + +class CachingMessageTemplateParser { - class CachingMessageTemplateParser - { - readonly MessageTemplateParser _innerParser = new MessageTemplateParser(); - - readonly object _templatesLock = new object(); - readonly Hashtable _templates = new Hashtable(); + readonly MessageTemplateParser _innerParser = new(); - const int MaxCacheItems = 1000; - const int MaxCachedTemplateLength = 1024; + readonly object _templatesLock = new(); + readonly Hashtable _templates = new(); - public MessageTemplate Parse(string messageTemplate) - { - if (messageTemplate == null) throw new ArgumentNullException(nameof(messageTemplate)); + const int MaxCacheItems = 1000; + const int MaxCachedTemplateLength = 1024; - if (messageTemplate.Length > MaxCachedTemplateLength) - return _innerParser.Parse(messageTemplate); + public MessageTemplate Parse(string messageTemplate) + { + if (messageTemplate == null) throw new ArgumentNullException(nameof(messageTemplate)); - // ReSharper disable once InconsistentlySynchronizedField - // ignored warning because this is by design - var result = (MessageTemplate)_templates[messageTemplate]; - if (result != null) - return result; + if (messageTemplate.Length > MaxCachedTemplateLength) + return _innerParser.Parse(messageTemplate); - result = _innerParser.Parse(messageTemplate); + // ReSharper disable once InconsistentlySynchronizedField + // ignored warning because this is by design + var result = (MessageTemplate?)_templates[messageTemplate]; + if (result != null) + return result; - lock (_templatesLock) - { - // Exceeding MaxCacheItems is *not* the sunny day scenario; all we're doing here is preventing out-of-memory - // conditions when the library is used incorrectly. Correct use (templates, rather than - // direct message strings) should barely, if ever, overflow this cache. + result = _innerParser.Parse(messageTemplate); - // Changing workloads through the lifecycle of an app instance mean we can gain some ground by - // potentially dropping templates generated only in startup, or only during specific infrequent - // activities. + lock (_templatesLock) + { + // Exceeding MaxCacheItems is *not* the sunny day scenario; all we're doing here is preventing out-of-memory + // conditions when the library is used incorrectly. Correct use (templates, rather than + // direct message strings) should barely, if ever, overflow this cache. - if (_templates.Count == MaxCacheItems) - _templates.Clear(); + // Changing workloads through the lifecycle of an app instance mean we can gain some ground by + // potentially dropping templates generated only in startup, or only during specific infrequent + // activities. - _templates[messageTemplate] = result; - } + if (_templates.Count == MaxCacheItems) + _templates.Clear(); - return result; + _templates[messageTemplate] = result; } + + return result; } } diff --git a/src/Serilog.Extensions.Logging/Extensions/Logging/LevelConvert.cs b/src/Serilog.Extensions.Logging/Extensions/Logging/LevelConvert.cs index 58354e0..a77494e 100644 --- a/src/Serilog.Extensions.Logging/Extensions/Logging/LevelConvert.cs +++ b/src/Serilog.Extensions.Logging/Extensions/Logging/LevelConvert.cs @@ -1,4 +1,4 @@ -// Copyright 2019 Serilog Contributors +// Copyright 2019 Serilog Contributors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -17,65 +17,49 @@ // ReSharper disable RedundantCaseLabel -namespace Serilog.Extensions.Logging +namespace Serilog.Extensions.Logging; + +/// +/// Converts between Serilog and Microsoft.Extensions.Logging level enum values. +/// +public static class LevelConvert { /// - /// Converts between Serilog and Microsoft.Extensions.Logging level enum values. + /// Convert to the equivalent Serilog . /// - public static class LevelConvert + /// A Microsoft.Extensions.Logging . + /// The Serilog equivalent of . + /// The value has no Serilog equivalent. It is mapped to + /// as the closest approximation, but this has entirely + /// different semantics. + public static LogEventLevel ToSerilogLevel(LogLevel logLevel) { - /// - /// Convert to the equivalent Serilog . - /// - /// A Microsoft.Extensions.Logging . - /// The Serilog equivalent of . - /// The value has no Serilog equivalent. It is mapped to - /// as the closest approximation, but this has entirely - /// different semantics. - public static LogEventLevel ToSerilogLevel(LogLevel logLevel) + return logLevel switch { - switch (logLevel) - { - case LogLevel.None: - case LogLevel.Critical: - return LogEventLevel.Fatal; - case LogLevel.Error: - return LogEventLevel.Error; - case LogLevel.Warning: - return LogEventLevel.Warning; - case LogLevel.Information: - return LogEventLevel.Information; - case LogLevel.Debug: - return LogEventLevel.Debug; - case LogLevel.Trace: - default: - return LogEventLevel.Verbose; - } - } + LogLevel.None or LogLevel.Critical => LogEventLevel.Fatal, + LogLevel.Error => LogEventLevel.Error, + LogLevel.Warning => LogEventLevel.Warning, + LogLevel.Information => LogEventLevel.Information, + LogLevel.Debug => LogEventLevel.Debug, + _ => LogEventLevel.Verbose, + }; + } - /// - /// Convert to the equivalent Microsoft.Extensions.Logging . - /// - /// A Serilog . - /// The Microsoft.Extensions.Logging equivalent of . - public static LogLevel ToExtensionsLevel(LogEventLevel logEventLevel) + /// + /// Convert to the equivalent Microsoft.Extensions.Logging . + /// + /// A Serilog . + /// The Microsoft.Extensions.Logging equivalent of . + public static LogLevel ToExtensionsLevel(LogEventLevel logEventLevel) + { + return logEventLevel switch { - switch (logEventLevel) - { - case LogEventLevel.Fatal: - return LogLevel.Critical; - case LogEventLevel.Error: - return LogLevel.Error; - case LogEventLevel.Warning: - return LogLevel.Warning; - case LogEventLevel.Information: - return LogLevel.Information; - case LogEventLevel.Debug: - return LogLevel.Debug; - case LogEventLevel.Verbose: - default: - return LogLevel.Trace; - } - } + LogEventLevel.Fatal => LogLevel.Critical, + LogEventLevel.Error => LogLevel.Error, + LogEventLevel.Warning => LogLevel.Warning, + LogEventLevel.Information => LogLevel.Information, + LogEventLevel.Debug => LogLevel.Debug, + _ => LogLevel.Trace, + }; } } diff --git a/src/Serilog.Extensions.Logging/Extensions/Logging/LoggerProviderCollection.cs b/src/Serilog.Extensions.Logging/Extensions/Logging/LoggerProviderCollection.cs index 7a0e08b..b944040 100644 --- a/src/Serilog.Extensions.Logging/Extensions/Logging/LoggerProviderCollection.cs +++ b/src/Serilog.Extensions.Logging/Extensions/Logging/LoggerProviderCollection.cs @@ -13,55 +13,50 @@ // limitations under the License. -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; using Microsoft.Extensions.Logging; -namespace Serilog.Extensions.Logging +namespace Serilog.Extensions.Logging; + +/// +/// A dynamically-modifiable collection of s. +/// +public class LoggerProviderCollection : IDisposable { + volatile ILoggerProvider[] _providers = Array.Empty(); + /// - /// A dynamically-modifiable collection of s. + /// Add to the collection. /// - public class LoggerProviderCollection : IDisposable + /// A logger provider. + public void AddProvider(ILoggerProvider provider) { - volatile ILoggerProvider[] _providers = Array.Empty(); - - /// - /// Add to the collection. - /// - /// A logger provider. - public void AddProvider(ILoggerProvider provider) - { - if (provider == null) throw new ArgumentNullException(nameof(provider)); + if (provider == null) throw new ArgumentNullException(nameof(provider)); - ILoggerProvider[] existing, added; + ILoggerProvider[] existing, added; - do - { - existing = _providers; - added = existing.Concat(new[] { provider }).ToArray(); - } + do + { + existing = _providers; + added = existing.Concat(new[] { provider }).ToArray(); + } #pragma warning disable 420 // ref to a volatile field - while (Interlocked.CompareExchange(ref _providers, added, existing) != existing); + while (Interlocked.CompareExchange(ref _providers, added, existing) != existing); #pragma warning restore 420 - } - - /// - /// Get the currently-active providers. - /// - /// - /// If the collection has been disposed, we'll leave the individual - /// providers with the job of throwing . - /// - public IEnumerable Providers => _providers; + } - /// - public void Dispose() - { - foreach (var provider in _providers) - provider.Dispose(); - } + /// + /// Get the currently-active providers. + /// + /// + /// If the collection has been disposed, we'll leave the individual + /// providers with the job of throwing . + /// + public IEnumerable Providers => _providers; + + /// + public void Dispose() + { + foreach (var provider in _providers) + provider.Dispose(); } } diff --git a/src/Serilog.Extensions.Logging/Extensions/Logging/LoggerProviderCollectionSink.cs b/src/Serilog.Extensions.Logging/Extensions/Logging/LoggerProviderCollectionSink.cs index afe4a29..0febb52 100644 --- a/src/Serilog.Extensions.Logging/Extensions/Logging/LoggerProviderCollectionSink.cs +++ b/src/Serilog.Extensions.Logging/Extensions/Logging/LoggerProviderCollectionSink.cs @@ -1,4 +1,4 @@ -// Copyright 2019 Serilog Contributors +// Copyright 2019 Serilog Contributors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,65 +12,63 @@ // See the License for the specific language governing permissions and // limitations under the License. -using System; using Microsoft.Extensions.Logging; using Serilog.Core; using Serilog.Events; -namespace Serilog.Extensions.Logging +namespace Serilog.Extensions.Logging; + +class LoggerProviderCollectionSink : ILogEventSink, IDisposable { - class LoggerProviderCollectionSink : ILogEventSink, IDisposable + readonly LoggerProviderCollection _providers; + + public LoggerProviderCollectionSink(LoggerProviderCollection providers) + { + _providers = providers ?? throw new ArgumentNullException(nameof(providers)); + } + + public void Emit(LogEvent logEvent) { - readonly LoggerProviderCollection _providers; + string categoryName = "None"; + EventId eventId = default; - public LoggerProviderCollectionSink(LoggerProviderCollection providers) + if (logEvent.Properties.TryGetValue("SourceContext", out var sourceContextProperty) && + sourceContextProperty is ScalarValue sourceContextValue && + sourceContextValue.Value is string sourceContext) { - _providers = providers ?? throw new ArgumentNullException(nameof(providers)); + categoryName = sourceContext; } - - public void Emit(LogEvent logEvent) + if (logEvent.Properties.TryGetValue("EventId", out var eventIdPropertyValue) && eventIdPropertyValue is StructureValue structuredEventId) { - string categoryName = null; - EventId eventId = default; - - if (logEvent.Properties.TryGetValue("SourceContext", out var sourceContextProperty) && - sourceContextProperty is ScalarValue sourceContextValue && - sourceContextValue.Value is string sourceContext) + string? name = null; + var id = 0; + foreach (var item in structuredEventId.Properties) { - categoryName = sourceContext; + if (item.Name == "Id" && item.Value is ScalarValue sv && sv.Value is int i) id = i; + if (item.Name == "Name" && item.Value is ScalarValue sv2 && sv2.Value is string s) name = s; } - if (logEvent.Properties.TryGetValue("EventId", out var eventIdPropertyValue) && eventIdPropertyValue is StructureValue structuredEventId) - { - string name = null; - var id = 0; - foreach (var item in structuredEventId.Properties) - { - if (item.Name == "Id" && item.Value is ScalarValue sv && sv.Value is int i) id = i; - if (item.Name == "Name" && item.Value is ScalarValue sv2 && sv2.Value is string s) name = s; - } - eventId = new EventId(id, name); - } + eventId = new EventId(id, name); + } - var level = LevelConvert.ToExtensionsLevel(logEvent.Level); - var slv = new SerilogLogValues(logEvent.MessageTemplate, logEvent.Properties); + var level = LevelConvert.ToExtensionsLevel(logEvent.Level); + var slv = new SerilogLogValues(logEvent.MessageTemplate, logEvent.Properties); - foreach (var provider in _providers.Providers) - { - var logger = provider.CreateLogger(categoryName); + foreach (var provider in _providers.Providers) + { + var logger = provider.CreateLogger(categoryName); - logger.Log( - level, - eventId, - slv, - logEvent.Exception, - (s, e) => s.ToString()); - } + logger.Log( + level, + eventId, + slv, + logEvent.Exception, + (s, e) => s.ToString()); } + } - public void Dispose() - { - _providers.Dispose(); - } + public void Dispose() + { + _providers.Dispose(); } } diff --git a/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLogValues.cs b/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLogValues.cs index c6f8058..20253f8 100644 --- a/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLogValues.cs +++ b/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLogValues.cs @@ -13,50 +13,47 @@ // limitations under the License. using Serilog.Events; -using System; using System.Collections; -using System.Collections.Generic; -namespace Serilog.Extensions.Logging +namespace Serilog.Extensions.Logging; + +readonly struct SerilogLogValues : IReadOnlyList> { - readonly struct SerilogLogValues : IReadOnlyList> + // Note, this struct is only used in a very limited context internally, so we ignore + // the possibility of fields being null via the default struct initialization. + + readonly MessageTemplate _messageTemplate; + readonly IReadOnlyDictionary _properties; + readonly KeyValuePair[] _values; + + public SerilogLogValues(MessageTemplate messageTemplate, IReadOnlyDictionary properties) { - // Note, this struct is only used in a very limited context internally, so we ignore - // the possibility of fields being null via the default struct initialization. + _messageTemplate = messageTemplate ?? throw new ArgumentNullException(nameof(messageTemplate)); - private readonly MessageTemplate _messageTemplate; - private readonly IReadOnlyDictionary _properties; - private readonly KeyValuePair[] _values; + // The dictionary is needed for rendering through the message template + _properties = properties ?? throw new ArgumentNullException(nameof(properties)); - public SerilogLogValues(MessageTemplate messageTemplate, IReadOnlyDictionary properties) + // The array is needed because the IReadOnlyList interface expects indexed access + _values = new KeyValuePair[_properties.Count + 1]; + var i = 0; + foreach (var p in properties) { - _messageTemplate = messageTemplate ?? throw new ArgumentNullException(nameof(messageTemplate)); - - // The dictionary is needed for rendering through the message template - _properties = properties ?? throw new ArgumentNullException(nameof(properties)); - - // The array is needed because the IReadOnlyList interface expects indexed access - _values = new KeyValuePair[_properties.Count + 1]; - var i = 0; - foreach (var p in properties) - { - _values[i] = new KeyValuePair(p.Key, (p.Value is ScalarValue sv) ? sv.Value : p.Value); - ++i; - } - _values[i] = new KeyValuePair("{OriginalFormat}", _messageTemplate.Text); + _values[i] = new KeyValuePair(p.Key, (p.Value is ScalarValue sv) ? sv.Value : p.Value); + ++i; } + _values[i] = new KeyValuePair("{OriginalFormat}", _messageTemplate.Text); + } - public KeyValuePair this[int index] - { - get => _values[index]; - } + public KeyValuePair this[int index] + { + get => _values[index]; + } - public int Count => _properties.Count + 1; + public int Count => _properties.Count + 1; - public IEnumerator> GetEnumerator() => ((IEnumerable>)_values).GetEnumerator(); + public IEnumerator> GetEnumerator() => ((IEnumerable>)_values).GetEnumerator(); - public override string ToString() => _messageTemplate.Render(_properties); + public override string ToString() => _messageTemplate.Render(_properties); - IEnumerator IEnumerable.GetEnumerator() => _values.GetEnumerator(); - } + IEnumerator IEnumerable.GetEnumerator() => _values.GetEnumerator(); } diff --git a/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLogger.cs b/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLogger.cs index 1b1f895..edcef30 100644 --- a/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLogger.cs +++ b/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLogger.cs @@ -2,173 +2,190 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using Microsoft.Extensions.Logging; -using System; -using System.Collections.Generic; -using System.Linq; using Serilog.Core; using Serilog.Events; using FrameworkLogger = Microsoft.Extensions.Logging.ILogger; using System.Reflection; using Serilog.Debugging; +using System.Collections.Concurrent; -namespace Serilog.Extensions.Logging +namespace Serilog.Extensions.Logging; + +class SerilogLogger : FrameworkLogger { - class SerilogLogger : FrameworkLogger + internal static readonly ConcurrentDictionary DestructureDictionary = new(); + internal static readonly ConcurrentDictionary StringifyDictionary = new(); + + internal static string GetKeyWithoutFirstSymbol(ConcurrentDictionary source, string key) { - readonly SerilogLoggerProvider _provider; - readonly ILogger _logger; + if (source.TryGetValue(key, out var value)) + return value; + if (source.Count < 1000) + return source.GetOrAdd(key, k => k.Substring(1)); + return key.Substring(1); + } - static readonly CachingMessageTemplateParser MessageTemplateParser = new CachingMessageTemplateParser(); + readonly SerilogLoggerProvider _provider; + readonly ILogger _logger; - // It's rare to see large event ids, as they are category-specific - static readonly LogEventProperty[] LowEventIdValues = Enumerable.Range(0, 48) - .Select(n => new LogEventProperty("Id", new ScalarValue(n))) - .ToArray(); + static readonly CachingMessageTemplateParser MessageTemplateParser = new(); - public SerilogLogger( - SerilogLoggerProvider provider, - ILogger logger = null, - string name = null) - { - _provider = provider ?? throw new ArgumentNullException(nameof(provider)); - _logger = logger; + // It's rare to see large event ids, as they are category-specific + static readonly LogEventProperty[] LowEventIdValues = Enumerable.Range(0, 48) + .Select(n => new LogEventProperty("Id", new ScalarValue(n))) + .ToArray(); - // If a logger was passed, the provider has already added itself as an enricher - _logger = _logger ?? Serilog.Log.Logger.ForContext(new[] { provider }); + public SerilogLogger( + SerilogLoggerProvider provider, + ILogger? logger = null, + string? name = null) + { + _provider = provider ?? throw new ArgumentNullException(nameof(provider)); - if (name != null) - { - _logger = _logger.ForContext(Constants.SourceContextPropertyName, name); - } - } + // If a logger was passed, the provider has already added itself as an enricher + _logger = logger ?? Serilog.Log.Logger.ForContext(new[] { provider }); - public bool IsEnabled(LogLevel logLevel) + if (name != null) { - return _logger.IsEnabled(LevelConvert.ToSerilogLevel(logLevel)); + _logger = _logger.ForContext(Constants.SourceContextPropertyName, name); } + } + + public bool IsEnabled(LogLevel logLevel) + { + return logLevel != LogLevel.None && _logger.IsEnabled(LevelConvert.ToSerilogLevel(logLevel)); + } - public IDisposable BeginScope(TState state) + public IDisposable BeginScope(TState state) where TState : notnull + { + return _provider.BeginScope(state); + } + + public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter) + { + if (logLevel == LogLevel.None) { - return _provider.BeginScope(state); + return; } - - public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) + var level = LevelConvert.ToSerilogLevel(logLevel); + if (!_logger.IsEnabled(level)) { - var level = LevelConvert.ToSerilogLevel(logLevel); - if (!_logger.IsEnabled(level)) - { - return; - } - - try - { - Write(level, eventId, state, exception, formatter); - } - catch (Exception ex) - { - SelfLog.WriteLine($"Failed to write event through {typeof(SerilogLogger).Name}: {ex}"); - } + return; } - void Write(LogEventLevel level, EventId eventId, TState state, Exception exception, Func formatter) + LogEvent? evt = null; + try { - var logger = _logger; - string messageTemplate = null; + evt = PrepareWrite(level, eventId, state, exception, formatter); + } + catch (Exception ex) + { + SelfLog.WriteLine($"Failed to write event through {nameof(SerilogLogger)}: {ex}"); + } - var properties = new List(); + // Do not swallow exceptions from here because Serilog takes care of them in case of WriteTo and throws them back to the caller in case of AuditTo. + if (evt != null) + _logger.Write(evt); + } - if (state is IEnumerable> structure) - { - foreach (var property in structure) - { - if (property.Key == SerilogLoggerProvider.OriginalFormatPropertyName && property.Value is string value) - { - messageTemplate = value; - } - else if (property.Key.StartsWith("@")) - { - if (logger.BindProperty(property.Key.Substring(1), property.Value, true, out var destructured)) - properties.Add(destructured); - } - else if (property.Key.StartsWith("$")) - { - if (logger.BindProperty(property.Key.Substring(1), property.Value?.ToString(), true, out var stringified)) - properties.Add(stringified); - } - else - { - if (logger.BindProperty(property.Key, property.Value, false, out var bound)) - properties.Add(bound); - } - } + LogEvent PrepareWrite(LogEventLevel level, EventId eventId, TState state, Exception? exception, Func formatter) + { + string? messageTemplate = null; - var stateType = state.GetType(); - var stateTypeInfo = stateType.GetTypeInfo(); - // Imperfect, but at least eliminates `1 names - if (messageTemplate == null && !stateTypeInfo.IsGenericType) - { - messageTemplate = "{" + stateType.Name + ":l}"; - if (logger.BindProperty(stateType.Name, AsLoggableValue(state, formatter), false, out var stateTypeProperty)) - properties.Add(stateTypeProperty); - } - } + var properties = new List(); - if (messageTemplate == null) + if (state is IEnumerable> structure) + { + foreach (var property in structure) { - string propertyName = null; - if (state != null) + if (property.Key == SerilogLoggerProvider.OriginalFormatPropertyName && property.Value is string value) { - propertyName = "State"; - messageTemplate = "{State:l}"; + messageTemplate = value; } - else if (formatter != null) + else if (property.Key.StartsWith("@")) { - propertyName = "Message"; - messageTemplate = "{Message:l}"; + if (_logger.BindProperty(GetKeyWithoutFirstSymbol(DestructureDictionary, property.Key), property.Value, true, out var destructured)) + properties.Add(destructured); } - - if (propertyName != null) + else if (property.Key.StartsWith("$")) + { + if (_logger.BindProperty(GetKeyWithoutFirstSymbol(StringifyDictionary, property.Key), property.Value?.ToString(), true, out var stringified)) + properties.Add(stringified); + } + else { - if (logger.BindProperty(propertyName, AsLoggableValue(state, formatter), false, out var property)) - properties.Add(property); + if (_logger.BindProperty(property.Key, property.Value, false, out var bound)) + properties.Add(bound); } } - if (eventId.Id != 0 || eventId.Name != null) - properties.Add(CreateEventIdProperty(eventId)); - - var parsedTemplate = MessageTemplateParser.Parse(messageTemplate ?? ""); - var evt = new LogEvent(DateTimeOffset.Now, level, exception, parsedTemplate, properties); - logger.Write(evt); - } - - static object AsLoggableValue(TState state, Func formatter) - { - object sobj = state; - if (formatter != null) - sobj = formatter(state, null); - return sobj; + var stateType = state.GetType(); + var stateTypeInfo = stateType.GetTypeInfo(); + // Imperfect, but at least eliminates `1 names + if (messageTemplate == null && !stateTypeInfo.IsGenericType) + { + messageTemplate = "{" + stateType.Name + ":l}"; + if (_logger.BindProperty(stateType.Name, AsLoggableValue(state, formatter), false, out var stateTypeProperty)) + properties.Add(stateTypeProperty); + } } - internal static LogEventProperty CreateEventIdProperty(EventId eventId) + if (messageTemplate == null) { - var properties = new List(2); - - if (eventId.Id != 0) + string? propertyName = null; + if (state != null) { - if (eventId.Id >= 0 && eventId.Id < LowEventIdValues.Length) - // Avoid some allocations - properties.Add(LowEventIdValues[eventId.Id]); - else - properties.Add(new LogEventProperty("Id", new ScalarValue(eventId.Id))); + propertyName = "State"; + messageTemplate = "{State:l}"; + } + // `formatter` was originally accepted as nullable, so despite the new annotation, this check should still + // be made. + else if (formatter != null!) + { + propertyName = "Message"; + messageTemplate = "{Message:l}"; } - if (eventId.Name != null) + if (propertyName != null) { - properties.Add(new LogEventProperty("Name", new ScalarValue(eventId.Name))); + if (_logger.BindProperty(propertyName, AsLoggableValue(state, formatter!), false, out var property)) + properties.Add(property); } + } + + if (eventId.Id != 0 || eventId.Name != null) + properties.Add(CreateEventIdProperty(eventId)); + + var parsedTemplate = MessageTemplateParser.Parse(messageTemplate ?? ""); + return new LogEvent(DateTimeOffset.Now, level, exception, parsedTemplate, properties); + } + + static object? AsLoggableValue(TState state, Func? formatter) + { + object? stateObj = state; + if (formatter != null) + stateObj = formatter(state, null); + return stateObj; + } + + internal static LogEventProperty CreateEventIdProperty(EventId eventId) + { + var properties = new List(2); + + if (eventId.Id != 0) + { + if (eventId.Id >= 0 && eventId.Id < LowEventIdValues.Length) + // Avoid some allocations + properties.Add(LowEventIdValues[eventId.Id]); + else + properties.Add(new LogEventProperty("Id", new ScalarValue(eventId.Id))); + } - return new LogEventProperty("EventId", new StructureValue(properties)); + if (eventId.Name != null) + { + properties.Add(new LogEventProperty("Name", new ScalarValue(eventId.Name))); } + + return new LogEventProperty("EventId", new StructureValue(properties)); } } diff --git a/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLoggerFactory.cs b/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLoggerFactory.cs index cf25f06..b85dbcb 100644 --- a/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLoggerFactory.cs +++ b/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLoggerFactory.cs @@ -1,4 +1,4 @@ -// Copyright 2019 Serilog Contributors +// Copyright 2019 Serilog Contributors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,65 +12,63 @@ // See the License for the specific language governing permissions and // limitations under the License. -using System; using Microsoft.Extensions.Logging; using Serilog.Debugging; -namespace Serilog.Extensions.Logging +namespace Serilog.Extensions.Logging; + +/// +/// A complete Serilog-backed implementation of the .NET Core logging infrastructure. +/// +public class SerilogLoggerFactory : ILoggerFactory { + readonly LoggerProviderCollection? _providerCollection; + readonly SerilogLoggerProvider _provider; + /// - /// A complete Serilog-backed implementation of the .NET Core logging infrastructure. + /// Initializes a new instance of the class. /// - public class SerilogLoggerFactory : ILoggerFactory + /// The Serilog logger; if not supplied, the static will be used. + /// When true, dispose when the framework disposes the provider. If the + /// logger is not specified but is true, the method will be + /// called on the static class instead. + /// A , for use with WriteTo.Providers(). + public SerilogLoggerFactory(ILogger? logger = null, bool dispose = false, LoggerProviderCollection? providerCollection = null) { - readonly LoggerProviderCollection _providerCollection; - readonly SerilogLoggerProvider _provider; - - /// - /// Initializes a new instance of the class. - /// - /// The Serilog logger; if not supplied, the static will be used. - /// When true, dispose when the framework disposes the provider. If the - /// logger is not specified but is true, the method will be - /// called on the static class instead. - /// A , for use with WriteTo.Providers(). - public SerilogLoggerFactory(ILogger logger = null, bool dispose = false, LoggerProviderCollection providerCollection = null) - { - _provider = new SerilogLoggerProvider(logger, dispose); - _providerCollection = providerCollection; - } + _provider = new SerilogLoggerProvider(logger, dispose); + _providerCollection = providerCollection; + } - /// - /// Disposes the provider. - /// - public void Dispose() - { - _provider.Dispose(); - } + /// + /// Disposes the provider. + /// + public void Dispose() + { + _provider.Dispose(); + } - /// - /// Creates a new instance. - /// - /// The category name for messages produced by the logger. - /// - /// The . - /// - public Microsoft.Extensions.Logging.ILogger CreateLogger(string categoryName) - { - return _provider.CreateLogger(categoryName); - } + /// + /// Creates a new instance. + /// + /// The category name for messages produced by the logger. + /// + /// The . + /// + public Microsoft.Extensions.Logging.ILogger CreateLogger(string categoryName) + { + return _provider.CreateLogger(categoryName); + } - /// - /// Adds an to the logging system. - /// - /// The . - public void AddProvider(ILoggerProvider provider) - { - if (provider == null) throw new ArgumentNullException(nameof(provider)); - if (_providerCollection != null) - _providerCollection.AddProvider(provider); - else - SelfLog.WriteLine("Ignoring added logger provider {0}", provider); - } + /// + /// Adds an to the logging system. + /// + /// The . + public void AddProvider(ILoggerProvider provider) + { + if (provider == null) throw new ArgumentNullException(nameof(provider)); + if (_providerCollection != null) + _providerCollection.AddProvider(provider); + else + SelfLog.WriteLine("Ignoring added logger provider {0}", provider); } } diff --git a/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLoggerProvider.cs b/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLoggerProvider.cs index d51c2c0..3fead38 100644 --- a/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLoggerProvider.cs +++ b/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLoggerProvider.cs @@ -1,102 +1,98 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System; using Microsoft.Extensions.Logging; using Serilog.Core; using Serilog.Events; using FrameworkLogger = Microsoft.Extensions.Logging.ILogger; -using System.Collections.Generic; -using System.Threading; using Serilog.Context; -namespace Serilog.Extensions.Logging +namespace Serilog.Extensions.Logging; + +/// +/// An that pipes events through Serilog. +/// +[ProviderAlias("Serilog")] +public class SerilogLoggerProvider : ILoggerProvider, ILogEventEnricher { + internal const string OriginalFormatPropertyName = "{OriginalFormat}"; + internal const string ScopePropertyName = "Scope"; + + // May be null; if it is, Log.Logger will be lazily used + readonly ILogger? _logger; + readonly Action? _dispose; + /// - /// An that pipes events through Serilog. + /// Construct a . /// - [ProviderAlias("Serilog")] - public class SerilogLoggerProvider : ILoggerProvider, ILogEventEnricher + /// A Serilog logger to pipe events through; if null, the static class will be used. + /// If true, the provided logger or static log class will be disposed/closed when the provider is disposed. + public SerilogLoggerProvider(ILogger? logger = null, bool dispose = false) { - internal const string OriginalFormatPropertyName = "{OriginalFormat}"; - internal const string ScopePropertyName = "Scope"; - - // May be null; if it is, Log.Logger will be lazily used - readonly ILogger _logger; - readonly Action _dispose; + if (logger != null) + _logger = logger.ForContext(new[] { this }); - /// - /// Construct a . - /// - /// A Serilog logger to pipe events through; if null, the static class will be used. - /// If true, the provided logger or static log class will be disposed/closed when the provider is disposed. - public SerilogLoggerProvider(ILogger logger = null, bool dispose = false) + if (dispose) { if (logger != null) - _logger = logger.ForContext(new[] { this }); - - if (dispose) - { - if (logger != null) - _dispose = () => (logger as IDisposable)?.Dispose(); - else - _dispose = Log.CloseAndFlush; - } + _dispose = () => (logger as IDisposable)?.Dispose(); + else + _dispose = Log.CloseAndFlush; } + } - /// - public FrameworkLogger CreateLogger(string name) - { - return new SerilogLogger(this, _logger, name); - } + /// + public FrameworkLogger CreateLogger(string name) + { + return new SerilogLogger(this, _logger, name); + } - /// - public IDisposable BeginScope(T state) - { - if (CurrentScope != null) - return new SerilogLoggerScope(this, state); + /// + public IDisposable BeginScope(T state) + { + if (CurrentScope != null) + return new SerilogLoggerScope(this, state); - // The outermost scope pushes and pops the Serilog `LogContext` - once - // this enricher is on the stack, the `CurrentScope` property takes care - // of the rest of the `BeginScope()` stack. - var popSerilogContext = LogContext.Push(this); - return new SerilogLoggerScope(this, state, popSerilogContext); - } + // The outermost scope pushes and pops the Serilog `LogContext` - once + // this enricher is on the stack, the `CurrentScope` property takes care + // of the rest of the `BeginScope()` stack. + var popSerilogContext = LogContext.Push(this); + return new SerilogLoggerScope(this, state, popSerilogContext); + } - /// - public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory) + /// + public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory) + { + List? scopeItems = null; + for (var scope = CurrentScope; scope != null; scope = scope.Parent) { - List scopeItems = null; - for (var scope = CurrentScope; scope != null; scope = scope.Parent) - { - scope.EnrichAndCreateScopeItem(logEvent, propertyFactory, out LogEventPropertyValue scopeItem); + scope.EnrichAndCreateScopeItem(logEvent, propertyFactory, out var scopeItem); - if (scopeItem != null) - { - scopeItems = scopeItems ?? new List(); - scopeItems.Add(scopeItem); - } - } - - if (scopeItems != null) + if (scopeItem != null) { - scopeItems.Reverse(); - logEvent.AddPropertyIfAbsent(new LogEventProperty(ScopePropertyName, new SequenceValue(scopeItems))); + scopeItems ??= new List(); + scopeItems.Add(scopeItem); } } - readonly AsyncLocal _value = new AsyncLocal(); - - internal SerilogLoggerScope CurrentScope + if (scopeItems != null) { - get => _value.Value; - set => _value.Value = value; + scopeItems.Reverse(); + logEvent.AddPropertyIfAbsent(new LogEventProperty(ScopePropertyName, new SequenceValue(scopeItems))); } + } - /// - public void Dispose() - { - _dispose?.Invoke(); - } + readonly AsyncLocal _value = new(); + + internal SerilogLoggerScope? CurrentScope + { + get => _value.Value; + set => _value.Value = value; + } + + /// + public void Dispose() + { + _dispose?.Invoke(); } } diff --git a/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLoggerScope.cs b/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLoggerScope.cs index dd0803c..9d13190 100644 --- a/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLoggerScope.cs +++ b/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLoggerScope.cs @@ -1,98 +1,109 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System; -using System.Collections.Generic; using Serilog.Core; using Serilog.Events; -namespace Serilog.Extensions.Logging +namespace Serilog.Extensions.Logging; + +class SerilogLoggerScope : IDisposable { - class SerilogLoggerScope : IDisposable + const string NoName = "None"; + + readonly SerilogLoggerProvider _provider; + readonly object? _state; + readonly IDisposable? _chainedDisposable; + + // An optimization only, no problem if there are data races on this. + bool _disposed; + + public SerilogLoggerScope(SerilogLoggerProvider provider, object? state, IDisposable? chainedDisposable = null) { - const string NoName = "None"; + _provider = provider; + _state = state; - readonly SerilogLoggerProvider _provider; - readonly object _state; - readonly IDisposable _chainedDisposable; + Parent = _provider.CurrentScope; + _provider.CurrentScope = this; + _chainedDisposable = chainedDisposable; + } - // An optimization only, no problem if there are data races on this. - bool _disposed; + public SerilogLoggerScope? Parent { get; } - public SerilogLoggerScope(SerilogLoggerProvider provider, object state, IDisposable chainedDisposable = null) + public void Dispose() + { + if (!_disposed) { - _provider = provider; - _state = state; + _disposed = true; - Parent = _provider.CurrentScope; - _provider.CurrentScope = this; - _chainedDisposable = chainedDisposable; + // In case one of the parent scopes has been disposed out-of-order, don't + // just blindly reinstate our own parent. + for (var scan = _provider.CurrentScope; scan != null; scan = scan.Parent) + { + if (ReferenceEquals(scan, this)) + _provider.CurrentScope = Parent; + } + + _chainedDisposable?.Dispose(); } + } - public SerilogLoggerScope Parent { get; } - - public void Dispose() + public void EnrichAndCreateScopeItem(LogEvent logEvent, ILogEventPropertyFactory propertyFactory, out LogEventPropertyValue? scopeItem) + { + void AddProperty(KeyValuePair stateProperty) { - if (!_disposed) + var key = stateProperty.Key; + var destructureObject = false; + var value = stateProperty.Value; + + if (key.StartsWith("@")) { - _disposed = true; + key = SerilogLogger.GetKeyWithoutFirstSymbol(SerilogLogger.DestructureDictionary, key); + destructureObject = true; + } + else if (key.StartsWith("$")) + { + key = SerilogLogger.GetKeyWithoutFirstSymbol(SerilogLogger.StringifyDictionary, key); + value = value?.ToString(); + } - // In case one of the parent scopes has been disposed out-of-order, don't - // just blindly reinstate our own parent. - for (var scan = _provider.CurrentScope; scan != null; scan = scan.Parent) - { - if (ReferenceEquals(scan, this)) - _provider.CurrentScope = Parent; - } + var property = propertyFactory.CreateProperty(key, value, destructureObject); + logEvent.AddPropertyIfAbsent(property); + } - _chainedDisposable?.Dispose(); - } + if (_state == null) + { + scopeItem = null; + return; } - public void EnrichAndCreateScopeItem(LogEvent logEvent, ILogEventPropertyFactory propertyFactory, out LogEventPropertyValue scopeItem) + // Eliminates boxing of Dictionary.Enumerator for the most common use case + if (_state is Dictionary dictionary) { - if (_state == null) - { - scopeItem = null; - return; - } + scopeItem = null; // Unless it's `FormattedLogValues`, these are treated as property bags rather than scope items. - if (_state is IEnumerable> stateProperties) + foreach (var stateProperty in dictionary) { - scopeItem = null; // Unless it's `FormattedLogValues`, these are treated as property bags rather than scope items. - - foreach (var stateProperty in stateProperties) - { - if (stateProperty.Key == SerilogLoggerProvider.OriginalFormatPropertyName && stateProperty.Value is string) - { - scopeItem = new ScalarValue(_state.ToString()); - continue; - } - - var key = stateProperty.Key; - var destructureObject = false; - var value = stateProperty.Value; - - if (key.StartsWith("@")) - { - key = key.Substring(1); - destructureObject = true; - } - - if (key.StartsWith("$")) - { - key = key.Substring(1); - value = value?.ToString(); - } - - var property = propertyFactory.CreateProperty(key, value, destructureObject); - logEvent.AddPropertyIfAbsent(property); - } + if (stateProperty.Key == SerilogLoggerProvider.OriginalFormatPropertyName && stateProperty.Value is string) + scopeItem = new ScalarValue(_state.ToString()); + else + AddProperty(stateProperty); } - else + } + else if (_state is IEnumerable> stateProperties) + { + scopeItem = null; // Unless it's `FormattedLogValues`, these are treated as property bags rather than scope items. + + foreach (var stateProperty in stateProperties) { - scopeItem = propertyFactory.CreateProperty(NoName, _state).Value; + if (stateProperty.Key == SerilogLoggerProvider.OriginalFormatPropertyName && stateProperty.Value is string) + scopeItem = new ScalarValue(_state.ToString()); + else + AddProperty(stateProperty); } } + else + { + scopeItem = propertyFactory.CreateProperty(NoName, _state).Value; + } } } diff --git a/src/Serilog.Extensions.Logging/LoggerSinkConfigurationExtensions.cs b/src/Serilog.Extensions.Logging/LoggerSinkConfigurationExtensions.cs index 85064db..afbea79 100644 --- a/src/Serilog.Extensions.Logging/LoggerSinkConfigurationExtensions.cs +++ b/src/Serilog.Extensions.Logging/LoggerSinkConfigurationExtensions.cs @@ -1,4 +1,4 @@ -// Copyright 2019 Serilog Contributors +// Copyright 2019 Serilog Contributors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,38 +12,36 @@ // See the License for the specific language governing permissions and // limitations under the License. -using System; using Serilog.Configuration; using Serilog.Core; using Serilog.Events; using Serilog.Extensions.Logging; -namespace Serilog +namespace Serilog; + +/// +/// Extensions for . +/// +public static class LoggerSinkConfigurationExtensions { /// - /// Extensions for . + /// Write Serilog events to the providers in . /// - public static class LoggerSinkConfigurationExtensions + /// The `WriteTo` object. + /// A to write events to. + /// The minimum level for + /// events passed through the sink. Ignored when is specified. + /// A switch allowing the pass-through minimum level + /// to be changed at runtime. + /// A to allow method chaining. + public static LoggerConfiguration Providers( + this LoggerSinkConfiguration configuration, + LoggerProviderCollection providers, + LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum, + LoggingLevelSwitch? levelSwitch = null) { - /// - /// Write Serilog events to the providers in . - /// - /// The `WriteTo` object. - /// A to write events to. - /// The minimum level for - /// events passed through the sink. Ignored when is specified. - /// A switch allowing the pass-through minimum level - /// to be changed at runtime. - /// A to allow method chaining. - public static LoggerConfiguration Providers( - this LoggerSinkConfiguration configuration, - LoggerProviderCollection providers, - LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum, - LoggingLevelSwitch levelSwitch = null) - { - if (configuration == null) throw new ArgumentNullException(nameof(configuration)); - if (providers == null) throw new ArgumentNullException(nameof(providers)); - return configuration.Sink(new LoggerProviderCollectionSink(providers), restrictedToMinimumLevel, levelSwitch); - } + if (configuration == null) throw new ArgumentNullException(nameof(configuration)); + if (providers == null) throw new ArgumentNullException(nameof(providers)); + return configuration.Sink(new LoggerProviderCollectionSink(providers), restrictedToMinimumLevel, levelSwitch); } } diff --git a/src/Serilog.Extensions.Logging/Properties/AssemblyInfo.cs b/src/Serilog.Extensions.Logging/Properties/AssemblyInfo.cs index 37885a5..50afb45 100644 --- a/src/Serilog.Extensions.Logging/Properties/AssemblyInfo.cs +++ b/src/Serilog.Extensions.Logging/Properties/AssemblyInfo.cs @@ -1,7 +1,12 @@ -using System.Reflection; +global using System; +global using System.Collections.Generic; +global using System.Linq; +global using System.Threading; + +using System.Reflection; using System.Runtime.CompilerServices; -[assembly: AssemblyVersion("2.0.0.0")] +[assembly: AssemblyVersion("7.0.0.0")] [assembly: InternalsVisibleTo("Serilog.Extensions.Logging.Tests, PublicKey=" + "0024000004800000940000000602000000240000525341310004000001000100fb8d13fd344a1c" + diff --git a/src/Serilog.Extensions.Logging/Serilog.Extensions.Logging.csproj b/src/Serilog.Extensions.Logging/Serilog.Extensions.Logging.csproj index f6d4bc1..33d764b 100644 --- a/src/Serilog.Extensions.Logging/Serilog.Extensions.Logging.csproj +++ b/src/Serilog.Extensions.Logging/Serilog.Extensions.Logging.csproj @@ -2,33 +2,37 @@ Low-level Serilog provider for Microsoft.Extensions.Logging - 3.1.0 + + 7.0.0 Microsoft;Serilog Contributors - netstandard2.0 - true + + net462;netstandard2.0;netstandard2.1;net6.0;net7.0 true - Serilog.Extensions.Logging - ../../assets/Serilog.snk - true - true - Serilog.Extensions.Logging serilog;Microsoft.Extensions.Logging serilog-extension-nuget.png https://github.com/serilog/serilog-extensions-logging Apache-2.0 - https://github.com/serilog/serilog-extensions-logging - git false Serilog + git + embedded + true + true + True + README.md + + NU5118 - - - - - - + + + + + + + diff --git a/src/Serilog.Extensions.Logging/SerilogLoggerFactoryExtensions.cs b/src/Serilog.Extensions.Logging/SerilogLoggerFactoryExtensions.cs index 5292603..b9eb557 100644 --- a/src/Serilog.Extensions.Logging/SerilogLoggerFactoryExtensions.cs +++ b/src/Serilog.Extensions.Logging/SerilogLoggerFactoryExtensions.cs @@ -1,36 +1,34 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System; using Microsoft.Extensions.Logging; using Serilog.Extensions.Logging; -namespace Serilog +namespace Serilog; + +/// +/// Extends with Serilog configuration methods. +/// +public static class SerilogLoggerFactoryExtensions { /// - /// Extends with Serilog configuration methods. + /// Add Serilog to the logging pipeline. /// - public static class SerilogLoggerFactoryExtensions + /// The logger factory to configure. + /// The Serilog logger; if not supplied, the static will be used. + /// When true, dispose when the framework disposes the provider. If the + /// logger is not specified but is true, the method will be + /// called on the static class instead. + /// Reference to the supplied . + public static ILoggerFactory AddSerilog( + this ILoggerFactory factory, + ILogger? logger = null, + bool dispose = false) { - /// - /// Add Serilog to the logging pipeline. - /// - /// The logger factory to configure. - /// The Serilog logger; if not supplied, the static will be used. - /// When true, dispose when the framework disposes the provider. If the - /// logger is not specified but is true, the method will be - /// called on the static class instead. - /// Reference to the supplied . - public static ILoggerFactory AddSerilog( - this ILoggerFactory factory, - ILogger logger = null, - bool dispose = false) - { - if (factory == null) throw new ArgumentNullException(nameof(factory)); + if (factory == null) throw new ArgumentNullException(nameof(factory)); - factory.AddProvider(new SerilogLoggerProvider(logger, dispose)); + factory.AddProvider(new SerilogLoggerProvider(logger, dispose)); - return factory; - } + return factory; } -} \ No newline at end of file +} diff --git a/src/Serilog.Extensions.Logging/SerilogLoggingBuilderExtensions.cs b/src/Serilog.Extensions.Logging/SerilogLoggingBuilderExtensions.cs index 2cf0008..1757de0 100644 --- a/src/Serilog.Extensions.Logging/SerilogLoggingBuilderExtensions.cs +++ b/src/Serilog.Extensions.Logging/SerilogLoggingBuilderExtensions.cs @@ -1,4 +1,4 @@ -// Copyright 2017 Serilog Contributors +// Copyright 2017 Serilog Contributors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,43 +12,41 @@ // See the License for the specific language governing permissions and // limitations under the License. -using System; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Serilog.Extensions.Logging; -namespace Serilog +namespace Serilog; + +/// +/// Extends with Serilog configuration methods. +/// +public static class SerilogLoggingBuilderExtensions { /// - /// Extends with Serilog configuration methods. + /// Add Serilog to the logging pipeline. /// - public static class SerilogLoggingBuilderExtensions + /// The to add logging provider to. + /// The Serilog logger; if not supplied, the static will be used. + /// When true, dispose when the framework disposes the provider. If the + /// logger is not specified but is true, the method will be + /// called on the static class instead. + /// Reference to the supplied . + public static ILoggingBuilder AddSerilog(this ILoggingBuilder builder, ILogger? logger = null, bool dispose = false) { - /// - /// Add Serilog to the logging pipeline. - /// - /// The to add logging provider to. - /// The Serilog logger; if not supplied, the static will be used. - /// When true, dispose when the framework disposes the provider. If the - /// logger is not specified but is true, the method will be - /// called on the static class instead. - /// Reference to the supplied . - public static ILoggingBuilder AddSerilog(this ILoggingBuilder builder, ILogger logger = null, bool dispose = false) - { - if (builder == null) throw new ArgumentNullException(nameof(builder)); + if (builder == null) throw new ArgumentNullException(nameof(builder)); - if (dispose) - { - builder.Services.AddSingleton(services => new SerilogLoggerProvider(logger, true)); - } - else - { - builder.AddProvider(new SerilogLoggerProvider(logger)); - } + if (dispose) + { + builder.Services.AddSingleton(services => new SerilogLoggerProvider(logger, true)); + } + else + { + builder.AddProvider(new SerilogLoggerProvider(logger)); + } - builder.AddFilter(null, LogLevel.Trace); + builder.AddFilter(null, LogLevel.Trace); - return builder; - } + return builder; } } diff --git a/test/Serilog.Extensions.Logging.Benchmarks/LogEventBenchmark.cs b/test/Serilog.Extensions.Logging.Benchmarks/LogEventBenchmark.cs new file mode 100644 index 0000000..995d462 --- /dev/null +++ b/test/Serilog.Extensions.Logging.Benchmarks/LogEventBenchmark.cs @@ -0,0 +1,65 @@ +// Copyright 2019 Serilog Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Running; +using Microsoft.Extensions.Logging; +using Xunit; +using IMelLogger = Microsoft.Extensions.Logging.ILogger; + +#pragma warning disable xUnit1013 // Public method should be marked as test + +namespace Serilog.Extensions.Logging.Benchmarks +{ + [MemoryDiagnoser] + public class LogEventBenchmark + { + private class Person + { + public string? Name { get; set; } + public int Age { get; set; } + public override string ToString() => "Fixed text"; + } + + readonly IMelLogger _melLogger; + readonly Person _bob, _alice; + + public LogEventBenchmark() + { + var underlyingLogger = new LoggerConfiguration().CreateLogger(); + _melLogger = new SerilogLoggerProvider(underlyingLogger).CreateLogger(GetType().FullName!); + _bob = new Person { Name = "Bob", Age = 42 }; + _alice = new Person { Name = "Alice", Age = 42 }; + } + + [Fact] + public void Benchmark() + { + BenchmarkRunner.Run(); + } + + [Benchmark] + public void LogInformation() + { + _melLogger.LogInformation("Hi {@User} from {$Me}", _bob, _alice); + } + + [Benchmark] + public void LogInformationScoped() + { + using (var scope = _melLogger.BeginScope("Hi {@User} from {$Me}", _bob, _alice)) + _melLogger.LogInformation("Hi"); + } + } +} diff --git a/test/Serilog.Extensions.Logging.Benchmarks/LogEventConstructionBenchmark.cs b/test/Serilog.Extensions.Logging.Benchmarks/LogEventConstructionBenchmark.cs index 51cae2e..415867a 100644 --- a/test/Serilog.Extensions.Logging.Benchmarks/LogEventConstructionBenchmark.cs +++ b/test/Serilog.Extensions.Logging.Benchmarks/LogEventConstructionBenchmark.cs @@ -1,4 +1,4 @@ -// Copyright 2019 Serilog Contributors +// Copyright 2019 Serilog Contributors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,7 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -using System; using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Running; using Microsoft.Extensions.Logging; @@ -21,83 +20,82 @@ using Serilog.Extensions.Logging.Benchmarks.Support; using Xunit; -namespace Serilog.Extensions.Logging.Benchmarks +namespace Serilog.Extensions.Logging.Benchmarks; + +[MemoryDiagnoser] +public class LogEventConstructionBenchmark { - [MemoryDiagnoser] - public class LogEventConstructionBenchmark + readonly IMelLogger _melLogger; + readonly ILogger _serilogContextualLogger; + readonly CapturingSink _sink; + const int LowId = 10, HighId = 101; + const string Template = "This is an event"; + + public LogEventConstructionBenchmark() { - readonly IMelLogger _melLogger; - readonly ILogger _serilogContextualLogger; - readonly CapturingSink _sink; - const int LowId = 10, HighId = 101; - const string Template = "This is an event"; + _sink = new CapturingSink(); + var underlyingLogger = new LoggerConfiguration().WriteTo.Sink(_sink).CreateLogger(); + _serilogContextualLogger = underlyingLogger.ForContext(); + _melLogger = new SerilogLoggerProvider(underlyingLogger).CreateLogger(GetType().FullName!); + } - public LogEventConstructionBenchmark() + static void VerifyEventId(LogEvent? evt, int? expectedId) + { + if (evt == null) throw new ArgumentNullException(nameof(evt)); + if (expectedId == null) { - _sink = new CapturingSink(); - var underlyingLogger = new LoggerConfiguration().WriteTo.Sink(_sink).CreateLogger(); - _serilogContextualLogger = underlyingLogger.ForContext(); - _melLogger = new SerilogLoggerProvider(underlyingLogger).CreateLogger(GetType().FullName); + Assert.False(evt.Properties.TryGetValue("EventId", out _)); } - - static void VerifyEventId(LogEvent evt, int? expectedId) + else { - if (evt == null) throw new ArgumentNullException(nameof(evt)); - if (expectedId == null) - { - Assert.False(evt.Properties.TryGetValue("EventId", out _)); - } - else - { - Assert.True(evt.Properties.TryGetValue("EventId", out var eventIdValue)); - var structure = Assert.IsType(eventIdValue); - var idValue = Assert.Single(structure.Properties, p => p.Name == "Id")?.Value; - var scalar = Assert.IsType(idValue); - Assert.Equal(expectedId.Value, scalar.Value); - } + Assert.True(evt.Properties.TryGetValue("EventId", out var eventIdValue)); + var structure = Assert.IsType(eventIdValue); + var idValue = Assert.Single(structure.Properties, p => p.Name == "Id")?.Value; + var scalar = Assert.IsType(idValue); + Assert.Equal(expectedId.Value, scalar.Value); } + } - [Fact] - public void Verify() - { - VerifyEventId(Native(), null); - VerifyEventId(NoId(), null); - VerifyEventId(LowNumbered(), LowId); - VerifyEventId(HighNumbered(), HighId); - } + [Fact] + public void Verify() + { + VerifyEventId(Native(), null); + VerifyEventId(NoId(), null); + VerifyEventId(LowNumbered(), LowId); + VerifyEventId(HighNumbered(), HighId); + } - [Fact] - public void Benchmark() - { - BenchmarkRunner.Run(); - } + [Fact] + public void Benchmark() + { + BenchmarkRunner.Run(); + } - [Benchmark(Baseline = true)] - public LogEvent Native() - { - _serilogContextualLogger.Information(Template); - return _sink.Collect(); - } + [Benchmark(Baseline = true)] + public LogEvent? Native() + { + _serilogContextualLogger.Information(Template); + return _sink.Collect(); + } - [Benchmark] - public LogEvent NoId() - { - _melLogger.LogInformation(Template); - return _sink.Collect(); - } + [Benchmark] + public LogEvent? NoId() + { + _melLogger.LogInformation(Template); + return _sink.Collect(); + } - [Benchmark] - public LogEvent LowNumbered() - { - _melLogger.LogInformation(LowId, Template); - return _sink.Collect(); - } + [Benchmark] + public LogEvent? LowNumbered() + { + _melLogger.LogInformation(LowId, Template); + return _sink.Collect(); + } - [Benchmark] - public LogEvent HighNumbered() - { - _melLogger.LogInformation(HighId, Template); - return _sink.Collect(); - } + [Benchmark] + public LogEvent? HighNumbered() + { + _melLogger.LogInformation(HighId, Template); + return _sink.Collect(); } } diff --git a/test/Serilog.Extensions.Logging.Benchmarks/Serilog.Extensions.Logging.Benchmarks.csproj b/test/Serilog.Extensions.Logging.Benchmarks/Serilog.Extensions.Logging.Benchmarks.csproj index 7840f21..24fdb78 100644 --- a/test/Serilog.Extensions.Logging.Benchmarks/Serilog.Extensions.Logging.Benchmarks.csproj +++ b/test/Serilog.Extensions.Logging.Benchmarks/Serilog.Extensions.Logging.Benchmarks.csproj @@ -1,8 +1,8 @@ - + - netcoreapp2.2 - true + net7.0 + enable @@ -10,17 +10,10 @@ - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - + + + + diff --git a/test/Serilog.Extensions.Logging.Benchmarks/Support/CapturingSink.cs b/test/Serilog.Extensions.Logging.Benchmarks/Support/CapturingSink.cs index 3913561..555ea88 100644 --- a/test/Serilog.Extensions.Logging.Benchmarks/Support/CapturingSink.cs +++ b/test/Serilog.Extensions.Logging.Benchmarks/Support/CapturingSink.cs @@ -1,4 +1,4 @@ -// Copyright 2019 Serilog Contributors +// Copyright 2019 Serilog Contributors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -15,22 +15,21 @@ using Serilog.Core; using Serilog.Events; -namespace Serilog.Extensions.Logging.Benchmarks.Support +namespace Serilog.Extensions.Logging.Benchmarks.Support; + +class CapturingSink : ILogEventSink { - class CapturingSink : ILogEventSink - { - LogEvent _emitted; + LogEvent? _emitted; - public void Emit(LogEvent logEvent) - { - _emitted = logEvent; - } + public void Emit(LogEvent logEvent) + { + _emitted = logEvent; + } - public LogEvent Collect() - { - var collected = _emitted; - _emitted = null; - return collected; - } + public LogEvent? Collect() + { + var collected = _emitted; + _emitted = null; + return collected; } } diff --git a/test/Serilog.Extensions.Logging.Tests/ApiApprovalTests.cs b/test/Serilog.Extensions.Logging.Tests/ApiApprovalTests.cs new file mode 100644 index 0000000..68575f6 --- /dev/null +++ b/test/Serilog.Extensions.Logging.Tests/ApiApprovalTests.cs @@ -0,0 +1,26 @@ +#if NET7_0 + +using PublicApiGenerator; +using Shouldly; +using Xunit; + +namespace Serilog.Extensions.Logging.Tests; + +public class ApiApprovalTests +{ + [Fact] + public void PublicApi_Should_Not_Change_Unintentionally() + { + var assembly = typeof(LoggerSinkConfigurationExtensions).Assembly; + var publicApi = assembly.GeneratePublicApi( + new() + { + IncludeAssemblyAttributes = false, + ExcludeAttributes = new[] { "System.Diagnostics.DebuggerDisplayAttribute" }, + }); + + publicApi.ShouldMatchApproved(options => options.WithFilenameGenerator((_, _, fileType, fileExtension) => $"{assembly.GetName().Name!}.{fileType}.{fileExtension}")); + } +} + +#endif diff --git a/test/Serilog.Extensions.Logging.Tests/LoggerProviderCollectionSinkTests.cs b/test/Serilog.Extensions.Logging.Tests/LoggerProviderCollectionSinkTests.cs index 0be1b4c..dad17b5 100644 --- a/test/Serilog.Extensions.Logging.Tests/LoggerProviderCollectionSinkTests.cs +++ b/test/Serilog.Extensions.Logging.Tests/LoggerProviderCollectionSinkTests.cs @@ -1,85 +1,83 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System; using Microsoft.Extensions.Logging; using Serilog.Extensions.Logging.Tests.Support; using Xunit; -namespace Serilog.Extensions.Logging.Tests +namespace Serilog.Extensions.Logging.Tests; + +public class LoggerProviderCollectionSinkTests { - public class LoggerProviderCollectionSinkTests + const string Name = "test"; + const string TestMessage = "This is a test"; + + static Tuple SetUp(LogLevel logLevel) + { + var providers = new LoggerProviderCollection(); + var provider = new ExtensionsProvider(logLevel); + providers.AddProvider(provider); + var serilogLogger = new LoggerConfiguration() + .WriteTo.Providers(providers) + .MinimumLevel.Is(LevelConvert.ToSerilogLevel(logLevel)) + .CreateLogger(); + + var logger = (SerilogLogger)new SerilogLoggerProvider(serilogLogger).CreateLogger(Name); + + return new Tuple(logger, provider); + } + + [Fact] + public void LogsCorrectLevel() + { + var (logger, sink) = SetUp(LogLevel.Trace); + + logger.Log(LogLevel.Trace, 0, TestMessage, null!, null!); + logger.Log(LogLevel.Debug, 0, TestMessage, null!, null!); + logger.Log(LogLevel.Information, 0, TestMessage, null!, null!); + logger.Log(LogLevel.Warning, 0, TestMessage, null!, null!); + logger.Log(LogLevel.Error, 0, TestMessage, null!, null!); + logger.Log(LogLevel.Critical, 0, TestMessage, null!, null!); + + Assert.Equal(6, sink.Writes.Count); + Assert.Equal(LogLevel.Trace, sink.Writes[0].logLevel); + Assert.Equal(LogLevel.Debug, sink.Writes[1].logLevel); + Assert.Equal(LogLevel.Information, sink.Writes[2].logLevel); + Assert.Equal(LogLevel.Warning, sink.Writes[3].logLevel); + Assert.Equal(LogLevel.Error, sink.Writes[4].logLevel); + Assert.Equal(LogLevel.Critical, sink.Writes[5].logLevel); + } + + [Fact] + public void LogsCorrectEventId() { - const string Name = "test"; - const string TestMessage = "This is a test"; - - static Tuple SetUp(LogLevel logLevel) - { - var providers = new LoggerProviderCollection(); - var provider = new ExtensionsProvider(logLevel); - providers.AddProvider(provider); - var serilogLogger = new LoggerConfiguration() - .WriteTo.Providers(providers) - .MinimumLevel.Is(LevelConvert.ToSerilogLevel(logLevel)) - .CreateLogger(); - - var logger = (SerilogLogger)new SerilogLoggerProvider(serilogLogger).CreateLogger(Name); - - return new Tuple(logger, provider); - } - - [Fact] - public void LogsCorrectLevel() - { - var (logger, sink) = SetUp(LogLevel.Trace); - - logger.Log(LogLevel.Trace, 0, TestMessage, null, null); - logger.Log(LogLevel.Debug, 0, TestMessage, null, null); - logger.Log(LogLevel.Information, 0, TestMessage, null, null); - logger.Log(LogLevel.Warning, 0, TestMessage, null, null); - logger.Log(LogLevel.Error, 0, TestMessage, null, null); - logger.Log(LogLevel.Critical, 0, TestMessage, null, null); - - Assert.Equal(6, sink.Writes.Count); - Assert.Equal(LogLevel.Trace, sink.Writes[0].logLevel); - Assert.Equal(LogLevel.Debug, sink.Writes[1].logLevel); - Assert.Equal(LogLevel.Information, sink.Writes[2].logLevel); - Assert.Equal(LogLevel.Warning, sink.Writes[3].logLevel); - Assert.Equal(LogLevel.Error, sink.Writes[4].logLevel); - Assert.Equal(LogLevel.Critical, sink.Writes[5].logLevel); - } - - [Fact] - public void LogsCorrectEventId() - { - var (logger, sink) = SetUp(LogLevel.Trace); - - logger.Log(LogLevel.Trace, new EventId(1, nameof(LogLevel.Trace)), TestMessage, null, null); - logger.Log(LogLevel.Debug, new EventId(2, nameof(LogLevel.Debug)), TestMessage, null, null); - logger.Log(LogLevel.Information, new EventId(3, nameof(LogLevel.Information)), TestMessage, null, null); - logger.Log(LogLevel.Warning, new EventId(4, nameof(LogLevel.Warning)), TestMessage, null, null); - logger.Log(LogLevel.Error, new EventId(5, nameof(LogLevel.Error)), TestMessage, null, null); - logger.Log(LogLevel.Critical, new EventId(6, nameof(LogLevel.Critical)), TestMessage, null, null); - - Assert.Equal(6, sink.Writes.Count); - - Assert.Equal(1, sink.Writes[0].eventId.Id); - Assert.Equal(nameof(LogLevel.Trace), sink.Writes[0].eventId.Name); - - Assert.Equal(2, sink.Writes[1].eventId.Id); - Assert.Equal(nameof(LogLevel.Debug), sink.Writes[1].eventId.Name); - - Assert.Equal(3, sink.Writes[2].eventId.Id); - Assert.Equal(nameof(LogLevel.Information), sink.Writes[2].eventId.Name); - - Assert.Equal(4, sink.Writes[3].eventId.Id); - Assert.Equal(nameof(LogLevel.Warning), sink.Writes[3].eventId.Name); - - Assert.Equal(5, sink.Writes[4].eventId.Id); - Assert.Equal(nameof(LogLevel.Error), sink.Writes[4].eventId.Name); - - Assert.Equal(6, sink.Writes[5].eventId.Id); - Assert.Equal(nameof(LogLevel.Critical), sink.Writes[5].eventId.Name); - } + var (logger, sink) = SetUp(LogLevel.Trace); + + logger.Log(LogLevel.Trace, new EventId(1, nameof(LogLevel.Trace)), TestMessage, null!, null!); + logger.Log(LogLevel.Debug, new EventId(2, nameof(LogLevel.Debug)), TestMessage, null!, null!); + logger.Log(LogLevel.Information, new EventId(3, nameof(LogLevel.Information)), TestMessage, null!, null!); + logger.Log(LogLevel.Warning, new EventId(4, nameof(LogLevel.Warning)), TestMessage, null!, null!); + logger.Log(LogLevel.Error, new EventId(5, nameof(LogLevel.Error)), TestMessage, null!, null!); + logger.Log(LogLevel.Critical, new EventId(6, nameof(LogLevel.Critical)), TestMessage, null!, null!); + + Assert.Equal(6, sink.Writes.Count); + + Assert.Equal(1, sink.Writes[0].eventId.Id); + Assert.Equal(nameof(LogLevel.Trace), sink.Writes[0].eventId.Name); + + Assert.Equal(2, sink.Writes[1].eventId.Id); + Assert.Equal(nameof(LogLevel.Debug), sink.Writes[1].eventId.Name); + + Assert.Equal(3, sink.Writes[2].eventId.Id); + Assert.Equal(nameof(LogLevel.Information), sink.Writes[2].eventId.Name); + + Assert.Equal(4, sink.Writes[3].eventId.Id); + Assert.Equal(nameof(LogLevel.Warning), sink.Writes[3].eventId.Name); + + Assert.Equal(5, sink.Writes[4].eventId.Id); + Assert.Equal(nameof(LogLevel.Error), sink.Writes[4].eventId.Name); + + Assert.Equal(6, sink.Writes[5].eventId.Id); + Assert.Equal(nameof(LogLevel.Critical), sink.Writes[5].eventId.Name); } } diff --git a/test/Serilog.Extensions.Logging.Tests/Serilog.Extensions.Logging.Tests.csproj b/test/Serilog.Extensions.Logging.Tests/Serilog.Extensions.Logging.Tests.csproj index e1a144a..94c27dc 100644 --- a/test/Serilog.Extensions.Logging.Tests/Serilog.Extensions.Logging.Tests.csproj +++ b/test/Serilog.Extensions.Logging.Tests/Serilog.Extensions.Logging.Tests.csproj @@ -1,13 +1,8 @@ - netcoreapp2.0;net472 - Serilog.Extensions.Logging.Tests - ../../assets/Serilog.snk - true - true - Serilog.Extensions.Logging.Tests - true + net7.0;net472 + enable @@ -15,9 +10,11 @@ - - - + + + + + diff --git a/test/Serilog.Extensions.Logging.Tests/Serilog.Extensions.Logging.approved.txt b/test/Serilog.Extensions.Logging.Tests/Serilog.Extensions.Logging.approved.txt new file mode 100644 index 0000000..83cb966 --- /dev/null +++ b/test/Serilog.Extensions.Logging.Tests/Serilog.Extensions.Logging.approved.txt @@ -0,0 +1,46 @@ +namespace Serilog.Extensions.Logging +{ + public static class LevelConvert + { + public static Microsoft.Extensions.Logging.LogLevel ToExtensionsLevel(Serilog.Events.LogEventLevel logEventLevel) { } + public static Serilog.Events.LogEventLevel ToSerilogLevel(Microsoft.Extensions.Logging.LogLevel logLevel) { } + } + public class LoggerProviderCollection : System.IDisposable + { + public LoggerProviderCollection() { } + public System.Collections.Generic.IEnumerable Providers { get; } + public void AddProvider(Microsoft.Extensions.Logging.ILoggerProvider provider) { } + public void Dispose() { } + } + public class SerilogLoggerFactory : Microsoft.Extensions.Logging.ILoggerFactory, System.IDisposable + { + public SerilogLoggerFactory(Serilog.ILogger? logger = null, bool dispose = false, Serilog.Extensions.Logging.LoggerProviderCollection? providerCollection = null) { } + public void AddProvider(Microsoft.Extensions.Logging.ILoggerProvider provider) { } + public Microsoft.Extensions.Logging.ILogger CreateLogger(string categoryName) { } + public void Dispose() { } + } + [Microsoft.Extensions.Logging.ProviderAlias("Serilog")] + public class SerilogLoggerProvider : Microsoft.Extensions.Logging.ILoggerProvider, Serilog.Core.ILogEventEnricher, System.IDisposable + { + public SerilogLoggerProvider(Serilog.ILogger? logger = null, bool dispose = false) { } + public System.IDisposable BeginScope(T state) { } + public Microsoft.Extensions.Logging.ILogger CreateLogger(string name) { } + public void Dispose() { } + public void Enrich(Serilog.Events.LogEvent logEvent, Serilog.Core.ILogEventPropertyFactory propertyFactory) { } + } +} +namespace Serilog +{ + public static class LoggerSinkConfigurationExtensions + { + public static Serilog.LoggerConfiguration Providers(this Serilog.Configuration.LoggerSinkConfiguration configuration, Serilog.Extensions.Logging.LoggerProviderCollection providers, Serilog.Events.LogEventLevel restrictedToMinimumLevel = 0, Serilog.Core.LoggingLevelSwitch? levelSwitch = null) { } + } + public static class SerilogLoggerFactoryExtensions + { + public static Microsoft.Extensions.Logging.ILoggerFactory AddSerilog(this Microsoft.Extensions.Logging.ILoggerFactory factory, Serilog.ILogger? logger = null, bool dispose = false) { } + } + public static class SerilogLoggingBuilderExtensions + { + public static Microsoft.Extensions.Logging.ILoggingBuilder AddSerilog(this Microsoft.Extensions.Logging.ILoggingBuilder builder, Serilog.ILogger? logger = null, bool dispose = false) { } + } +} \ No newline at end of file diff --git a/test/Serilog.Extensions.Logging.Tests/SerilogLogValuesTests.cs b/test/Serilog.Extensions.Logging.Tests/SerilogLogValuesTests.cs index d5a777e..a1f3025 100644 --- a/test/Serilog.Extensions.Logging.Tests/SerilogLogValuesTests.cs +++ b/test/Serilog.Extensions.Logging.Tests/SerilogLogValuesTests.cs @@ -1,53 +1,50 @@ using Serilog.Events; using Serilog.Parsing; -using System.Collections.Generic; -using System.Linq; using Xunit; -namespace Serilog.Extensions.Logging.Tests +namespace Serilog.Extensions.Logging.Tests; + +public class SerilogLogValuesTests { - public class SerilogLogValuesTests + [Fact] + public void OriginalFormatIsExposed() { - [Fact] - public void OriginalFormatIsExposed() - { - const string format = "Hello, {Name}!"; - var mt = new MessageTemplateParser().Parse(format); - var lv = new SerilogLogValues(mt, new Dictionary()); - var kvp = lv.Single(); - Assert.Equal("{OriginalFormat}", kvp.Key); - Assert.Equal(format, kvp.Value); - } + const string format = "Hello, {Name}!"; + var mt = new MessageTemplateParser().Parse(format); + var lv = new SerilogLogValues(mt, new Dictionary()); + var kvp = lv.Single(); + Assert.Equal("{OriginalFormat}", kvp.Key); + Assert.Equal(format, kvp.Value); + } - [Fact] - public void ScalarPropertiesAreSimplified() - { - const string name = "Scalar"; - var scalar = 15; - var lv = new SerilogLogValues(MessageTemplate.Empty, new Dictionary { [name] = new ScalarValue(scalar) }); - var kvp = lv.Single(p => p.Key == name); - var sv = Assert.IsType(kvp.Value); - Assert.Equal(scalar, sv); - } + [Fact] + public void ScalarPropertiesAreSimplified() + { + const string name = "Scalar"; + var scalar = 15; + var lv = new SerilogLogValues(MessageTemplate.Empty, new Dictionary { [name] = new ScalarValue(scalar) }); + var kvp = lv.Single(p => p.Key == name); + var sv = Assert.IsType(kvp.Value); + Assert.Equal(scalar, sv); + } - [Fact] - public void NonscalarPropertiesAreWrapped() - { - const string name = "Sequence"; - var seq = new SequenceValue(Enumerable.Empty()); - var lv = new SerilogLogValues(MessageTemplate.Empty, new Dictionary { [name] = seq }); - var kvp = lv.Single(p => p.Key == name); - var sv = Assert.IsType(kvp.Value); - Assert.Equal(seq, sv); - } + [Fact] + public void NonscalarPropertiesAreWrapped() + { + const string name = "Sequence"; + var seq = new SequenceValue(Enumerable.Empty()); + var lv = new SerilogLogValues(MessageTemplate.Empty, new Dictionary { [name] = seq }); + var kvp = lv.Single(p => p.Key == name); + var sv = Assert.IsType(kvp.Value); + Assert.Equal(seq, sv); + } - [Fact] - public void MessageTemplatesAreRendered() - { - const string format = "Hello, {Name}!"; - var mt = new MessageTemplateParser().Parse(format); - var lv = new SerilogLogValues(mt, new Dictionary { ["Name"] = new ScalarValue("World") }); - Assert.Equal("Hello, \"World\"!", lv.ToString()); - } + [Fact] + public void MessageTemplatesAreRendered() + { + const string format = "Hello, {Name}!"; + var mt = new MessageTemplateParser().Parse(format); + var lv = new SerilogLogValues(mt, new Dictionary { ["Name"] = new ScalarValue("World") }); + Assert.Equal("Hello, \"World\"!", lv.ToString()); } } diff --git a/test/Serilog.Extensions.Logging.Tests/SerilogLoggerTests.cs b/test/Serilog.Extensions.Logging.Tests/SerilogLoggerTests.cs index f37e9eb..94da8bb 100644 --- a/test/Serilog.Extensions.Logging.Tests/SerilogLoggerTests.cs +++ b/test/Serilog.Extensions.Logging.Tests/SerilogLoggerTests.cs @@ -1,454 +1,496 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System; using System.Collections; using Serilog.Events; using Microsoft.Extensions.Logging; -using System.Collections.Generic; -using System.IO; -using System.Linq; using Serilog.Debugging; using Serilog.Extensions.Logging.Tests.Support; using Xunit; +using Serilog.Core; -namespace Serilog.Extensions.Logging.Tests +namespace Serilog.Extensions.Logging.Tests; + +public class SerilogLoggerTest { - public class SerilogLoggerTest + const string Name = "test"; + const string TestMessage = "This is a test"; + + static Tuple SetUp(LogLevel logLevel) { - const string Name = "test"; - const string TestMessage = "This is a test"; + var sink = new SerilogSink(); - static Tuple SetUp(LogLevel logLevel) - { - var sink = new SerilogSink(); + var serilogLogger = new LoggerConfiguration() + .WriteTo.Sink(sink) + .MinimumLevel.Is(LevelConvert.ToSerilogLevel(logLevel)) + .CreateLogger(); - var serilogLogger = new LoggerConfiguration() - .WriteTo.Sink(sink) - .MinimumLevel.Is(LevelConvert.ToSerilogLevel(logLevel)) - .CreateLogger(); + var provider = new SerilogLoggerProvider(serilogLogger); + var logger = (SerilogLogger)provider.CreateLogger(Name); - var provider = new SerilogLoggerProvider(serilogLogger); - var logger = (SerilogLogger)provider.CreateLogger(Name); + return new Tuple(logger, sink); + } - return new Tuple(logger, sink); - } + [Fact] + public void LogsWhenNullFilterGiven() + { + var (logger, sink) = SetUp(LogLevel.Trace); - [Fact] - public void LogsWhenNullFilterGiven() - { - var (logger, sink) = SetUp(LogLevel.Trace); + logger.Log(LogLevel.Information, 0, TestMessage, null!, null!); - logger.Log(LogLevel.Information, 0, TestMessage, null, null); + Assert.Single(sink.Writes); + } - Assert.Equal(1, sink.Writes.Count); - } + [Fact] + public void LogsCorrectLevel() + { + var (logger, sink) = SetUp(LogLevel.Trace); + + logger.Log(LogLevel.Trace, 0, TestMessage, null!, null!); + logger.Log(LogLevel.Debug, 0, TestMessage, null!, null!); + logger.Log(LogLevel.Information, 0, TestMessage, null!, null!); + logger.Log(LogLevel.Warning, 0, TestMessage, null!, null!); + logger.Log(LogLevel.Error, 0, TestMessage, null!, null!); + logger.Log(LogLevel.Critical, 0, TestMessage, null!, null!); + logger.Log(LogLevel.None, 0, TestMessage, null!, null!); + + Assert.Equal(6, sink.Writes.Count); + Assert.Equal(LogEventLevel.Verbose, sink.Writes[0].Level); + Assert.Equal(LogEventLevel.Debug, sink.Writes[1].Level); + Assert.Equal(LogEventLevel.Information, sink.Writes[2].Level); + Assert.Equal(LogEventLevel.Warning, sink.Writes[3].Level); + Assert.Equal(LogEventLevel.Error, sink.Writes[4].Level); + Assert.Equal(LogEventLevel.Fatal, sink.Writes[5].Level); + } - [Fact] - public void LogsCorrectLevel() - { - var (logger, sink) = SetUp(LogLevel.Trace); - - logger.Log(LogLevel.Trace, 0, TestMessage, null, null); - logger.Log(LogLevel.Debug, 0, TestMessage, null, null); - logger.Log(LogLevel.Information, 0, TestMessage, null, null); - logger.Log(LogLevel.Warning, 0, TestMessage, null, null); - logger.Log(LogLevel.Error, 0, TestMessage, null, null); - logger.Log(LogLevel.Critical, 0, TestMessage, null, null); - - Assert.Equal(6, sink.Writes.Count); - Assert.Equal(LogEventLevel.Verbose, sink.Writes[0].Level); - Assert.Equal(LogEventLevel.Debug, sink.Writes[1].Level); - Assert.Equal(LogEventLevel.Information, sink.Writes[2].Level); - Assert.Equal(LogEventLevel.Warning, sink.Writes[3].Level); - Assert.Equal(LogEventLevel.Error, sink.Writes[4].Level); - Assert.Equal(LogEventLevel.Fatal, sink.Writes[5].Level); - } - [Theory] - [InlineData(LogLevel.Trace, LogLevel.Trace, 1)] - [InlineData(LogLevel.Trace, LogLevel.Debug, 1)] - [InlineData(LogLevel.Trace, LogLevel.Information, 1)] - [InlineData(LogLevel.Trace, LogLevel.Warning, 1)] - [InlineData(LogLevel.Trace, LogLevel.Error, 1)] - [InlineData(LogLevel.Trace, LogLevel.Critical, 1)] - [InlineData(LogLevel.Debug, LogLevel.Trace, 0)] - [InlineData(LogLevel.Debug, LogLevel.Debug, 1)] - [InlineData(LogLevel.Debug, LogLevel.Information, 1)] - [InlineData(LogLevel.Debug, LogLevel.Warning, 1)] - [InlineData(LogLevel.Debug, LogLevel.Error, 1)] - [InlineData(LogLevel.Debug, LogLevel.Critical, 1)] - [InlineData(LogLevel.Information, LogLevel.Trace, 0)] - [InlineData(LogLevel.Information, LogLevel.Debug, 0)] - [InlineData(LogLevel.Information, LogLevel.Information, 1)] - [InlineData(LogLevel.Information, LogLevel.Warning, 1)] - [InlineData(LogLevel.Information, LogLevel.Error, 1)] - [InlineData(LogLevel.Information, LogLevel.Critical, 1)] - [InlineData(LogLevel.Warning, LogLevel.Trace, 0)] - [InlineData(LogLevel.Warning, LogLevel.Debug, 0)] - [InlineData(LogLevel.Warning, LogLevel.Information, 0)] - [InlineData(LogLevel.Warning, LogLevel.Warning, 1)] - [InlineData(LogLevel.Warning, LogLevel.Error, 1)] - [InlineData(LogLevel.Warning, LogLevel.Critical, 1)] - [InlineData(LogLevel.Error, LogLevel.Trace, 0)] - [InlineData(LogLevel.Error, LogLevel.Debug, 0)] - [InlineData(LogLevel.Error, LogLevel.Information, 0)] - [InlineData(LogLevel.Error, LogLevel.Warning, 0)] - [InlineData(LogLevel.Error, LogLevel.Error, 1)] - [InlineData(LogLevel.Error, LogLevel.Critical, 1)] - [InlineData(LogLevel.Critical, LogLevel.Trace, 0)] - [InlineData(LogLevel.Critical, LogLevel.Debug, 0)] - [InlineData(LogLevel.Critical, LogLevel.Information, 0)] - [InlineData(LogLevel.Critical, LogLevel.Warning, 0)] - [InlineData(LogLevel.Critical, LogLevel.Error, 0)] - [InlineData(LogLevel.Critical, LogLevel.Critical, 1)] - public void LogsWhenEnabled(LogLevel minLevel, LogLevel logLevel, int expected) - { - var (logger, sink) = SetUp(minLevel); + [Theory] + [InlineData(LogLevel.Trace, true)] + [InlineData(LogLevel.Debug, true)] + [InlineData(LogLevel.Information, true)] + [InlineData(LogLevel.Warning, true)] + [InlineData(LogLevel.Error, true)] + [InlineData(LogLevel.Critical, true)] + [InlineData(LogLevel.None, false)] + public void IsEnabledCorrect(LogLevel logLevel, bool isEnabled) + { + var (logger, _) = SetUp(LogLevel.Trace); - logger.Log(logLevel, 0, TestMessage, null, null); + Assert.Equal(isEnabled, logger.IsEnabled(logLevel)); + } - Assert.Equal(expected, sink.Writes.Count); - } + [Theory] + [InlineData(LogLevel.Trace, LogLevel.Trace, 1)] + [InlineData(LogLevel.Trace, LogLevel.Debug, 1)] + [InlineData(LogLevel.Trace, LogLevel.Information, 1)] + [InlineData(LogLevel.Trace, LogLevel.Warning, 1)] + [InlineData(LogLevel.Trace, LogLevel.Error, 1)] + [InlineData(LogLevel.Trace, LogLevel.Critical, 1)] + [InlineData(LogLevel.Trace, LogLevel.None, 0)] + [InlineData(LogLevel.Debug, LogLevel.Trace, 0)] + [InlineData(LogLevel.Debug, LogLevel.Debug, 1)] + [InlineData(LogLevel.Debug, LogLevel.Information, 1)] + [InlineData(LogLevel.Debug, LogLevel.Warning, 1)] + [InlineData(LogLevel.Debug, LogLevel.Error, 1)] + [InlineData(LogLevel.Debug, LogLevel.Critical, 1)] + [InlineData(LogLevel.Debug, LogLevel.None, 0)] + [InlineData(LogLevel.Information, LogLevel.Trace, 0)] + [InlineData(LogLevel.Information, LogLevel.Debug, 0)] + [InlineData(LogLevel.Information, LogLevel.Information, 1)] + [InlineData(LogLevel.Information, LogLevel.Warning, 1)] + [InlineData(LogLevel.Information, LogLevel.Error, 1)] + [InlineData(LogLevel.Information, LogLevel.Critical, 1)] + [InlineData(LogLevel.Information, LogLevel.None, 0)] + [InlineData(LogLevel.Warning, LogLevel.Trace, 0)] + [InlineData(LogLevel.Warning, LogLevel.Debug, 0)] + [InlineData(LogLevel.Warning, LogLevel.Information, 0)] + [InlineData(LogLevel.Warning, LogLevel.Warning, 1)] + [InlineData(LogLevel.Warning, LogLevel.Error, 1)] + [InlineData(LogLevel.Warning, LogLevel.Critical, 1)] + [InlineData(LogLevel.Warning, LogLevel.None, 0)] + [InlineData(LogLevel.Error, LogLevel.Trace, 0)] + [InlineData(LogLevel.Error, LogLevel.Debug, 0)] + [InlineData(LogLevel.Error, LogLevel.Information, 0)] + [InlineData(LogLevel.Error, LogLevel.Warning, 0)] + [InlineData(LogLevel.Error, LogLevel.Error, 1)] + [InlineData(LogLevel.Error, LogLevel.Critical, 1)] + [InlineData(LogLevel.Error, LogLevel.None, 0)] + [InlineData(LogLevel.Critical, LogLevel.Trace, 0)] + [InlineData(LogLevel.Critical, LogLevel.Debug, 0)] + [InlineData(LogLevel.Critical, LogLevel.Information, 0)] + [InlineData(LogLevel.Critical, LogLevel.Warning, 0)] + [InlineData(LogLevel.Critical, LogLevel.Error, 0)] + [InlineData(LogLevel.Critical, LogLevel.Critical, 1)] + [InlineData(LogLevel.Critical, LogLevel.None, 0)] + public void LogsWhenEnabled(LogLevel minLevel, LogLevel logLevel, int expected) + { + var (logger, sink) = SetUp(minLevel); - [Fact] - public void LogsCorrectMessage() - { - var (logger, sink) = SetUp(LogLevel.Trace); + logger.Log(logLevel, 0, TestMessage, null!, null!); - logger.Log(LogLevel.Information, 0, null, null, null); - logger.Log(LogLevel.Information, 0, TestMessage, null, null); - logger.Log(LogLevel.Information, 0, null, null, (_, __) => TestMessage); + Assert.Equal(expected, sink.Writes.Count); + } - Assert.Equal(3, sink.Writes.Count); + [Fact] + public void LogsCorrectMessage() + { + var (logger, sink) = SetUp(LogLevel.Trace); - Assert.Equal(1, sink.Writes[0].Properties.Count); - Assert.Empty(sink.Writes[0].RenderMessage()); + logger.Log(LogLevel.Information, 0, null!, null!, null!); + logger.Log(LogLevel.Information, 0, TestMessage, null!, null!); + logger.Log(LogLevel.Information, 0, null!, null!, (_, __) => TestMessage); - Assert.Equal(2, sink.Writes[1].Properties.Count); - Assert.True(sink.Writes[1].Properties.ContainsKey("State")); - Assert.Equal(TestMessage, sink.Writes[1].RenderMessage()); + Assert.Equal(3, sink.Writes.Count); - Assert.Equal(2, sink.Writes[2].Properties.Count); - Assert.True(sink.Writes[2].Properties.ContainsKey("Message")); - Assert.Equal(TestMessage, sink.Writes[2].RenderMessage()); - } + Assert.Equal(1, sink.Writes[0].Properties.Count); + Assert.Empty(sink.Writes[0].RenderMessage()); - [Fact] - public void CarriesException() - { - var (logger, sink) = SetUp(LogLevel.Trace); + Assert.Equal(2, sink.Writes[1].Properties.Count); + Assert.True(sink.Writes[1].Properties.ContainsKey("State")); + Assert.Equal(TestMessage, sink.Writes[1].RenderMessage()); - var exception = new Exception(); + Assert.Equal(2, sink.Writes[2].Properties.Count); + Assert.True(sink.Writes[2].Properties.ContainsKey("Message")); + Assert.Equal(TestMessage, sink.Writes[2].RenderMessage()); + } - logger.Log(LogLevel.Information, 0, "Test", exception, null); + [Fact] + public void CarriesException() + { + var (logger, sink) = SetUp(LogLevel.Trace); - Assert.Equal(1, sink.Writes.Count); - Assert.Same(exception, sink.Writes[0].Exception); - } + var exception = new Exception(); - [Fact] - public void SingleScopeProperty() - { - var (logger, sink) = SetUp(LogLevel.Trace); + logger.Log(LogLevel.Information, 0, "Test", exception, null!); - using (logger.BeginScope(new FoodScope("pizza"))) - { - logger.Log(LogLevel.Information, 0, TestMessage, null, null); - } + Assert.Single(sink.Writes); + Assert.Same(exception, sink.Writes[0].Exception); + } - Assert.Equal(1, sink.Writes.Count); - Assert.True(sink.Writes[0].Properties.ContainsKey("Name")); - Assert.Equal("\"pizza\"", sink.Writes[0].Properties["Name"].ToString()); - } + [Fact] + public void SingleScopeProperty() + { + var (logger, sink) = SetUp(LogLevel.Trace); - [Fact] - public void StringifyScopeProperty() + using (logger.BeginScope(new FoodScope("pizza"))) { - var (logger, sink) = SetUp(LogLevel.Trace); + logger.Log(LogLevel.Information, 0, TestMessage, null!, null!); + } - using (logger.BeginScope("{$values}", new [] { 1, 2, 3, 4 })) - { - logger.Log(LogLevel.Information, 0, TestMessage, null, null); - } + Assert.Single(sink.Writes); + Assert.True(sink.Writes[0].Properties.ContainsKey("Name")); + Assert.Equal("\"pizza\"", sink.Writes[0].Properties["Name"].ToString()); + } - Assert.Equal(1, sink.Writes.Count); - Assert.True(sink.Writes[0].Properties.ContainsKey("values")); - Assert.Equal("\"System.Int32[]\"", sink.Writes[0].Properties["values"].ToString()); - } + [Fact] + public void StringifyScopeProperty() + { + var (logger, sink) = SetUp(LogLevel.Trace); - [Fact] - public void NestedScopeSameProperty() + using (logger.BeginScope("{$values}", new [] { 1, 2, 3, 4 })) { - var (logger, sink) = SetUp(LogLevel.Trace); + logger.Log(LogLevel.Information, 0, TestMessage, null!, null!); + } - using (logger.BeginScope(new FoodScope("avocado"))) - { - using (logger.BeginScope(new FoodScope("bacon"))) - { - logger.Log(LogLevel.Information, 0, TestMessage, null, null); - } - } + Assert.Single(sink.Writes); + Assert.True(sink.Writes[0].Properties.ContainsKey("values")); + Assert.Equal("\"System.Int32[]\"", sink.Writes[0].Properties["values"].ToString()); + } - // Should retain the property of the most specific scope - Assert.Equal(1, sink.Writes.Count); - Assert.True(sink.Writes[0].Properties.ContainsKey("Name")); - Assert.Equal("\"bacon\"", sink.Writes[0].Properties["Name"].ToString()); - } + [Fact] + public void NestedScopeSameProperty() + { + var (logger, sink) = SetUp(LogLevel.Trace); - [Fact] - public void NestedScopesDifferentProperties() + using (logger.BeginScope(new FoodScope("avocado"))) { - var (logger, sink) = SetUp(LogLevel.Trace); - - using (logger.BeginScope(new FoodScope("spaghetti"))) + using (logger.BeginScope(new FoodScope("bacon"))) { - using (logger.BeginScope(new LuckyScope(7))) - { - logger.Log(LogLevel.Information, 0, TestMessage, null, null); - } + logger.Log(LogLevel.Information, 0, TestMessage, null!, null!); } - - Assert.Equal(1, sink.Writes.Count); - Assert.True(sink.Writes[0].Properties.ContainsKey("Name")); - Assert.Equal("\"spaghetti\"", sink.Writes[0].Properties["Name"].ToString()); - Assert.True(sink.Writes[0].Properties.ContainsKey("LuckyNumber")); - Assert.Equal("7", sink.Writes[0].Properties["LuckyNumber"].ToString()); } - [Fact] - public void CarriesMessageTemplateProperties() + // Should retain the property of the most specific scope + Assert.Single(sink.Writes); + Assert.True(sink.Writes[0].Properties.ContainsKey("Name")); + Assert.Equal("\"bacon\"", sink.Writes[0].Properties["Name"].ToString()); + } + + [Fact] + public void NestedScopesDifferentProperties() + { + var (logger, sink) = SetUp(LogLevel.Trace); + + using (logger.BeginScope(new FoodScope("spaghetti"))) { - var selfLog = new StringWriter(); - SelfLog.Enable(selfLog); + using (logger.BeginScope(new LuckyScope(7))) + { + logger.Log(LogLevel.Information, 0, TestMessage, null!, null!); + } + } - var (logger, sink) = SetUp(LogLevel.Trace); + Assert.Single(sink.Writes); + Assert.True(sink.Writes[0].Properties.ContainsKey("Name")); + Assert.Equal("\"spaghetti\"", sink.Writes[0].Properties["Name"].ToString()); + Assert.True(sink.Writes[0].Properties.ContainsKey("LuckyNumber")); + Assert.Equal("7", sink.Writes[0].Properties["LuckyNumber"].ToString()); + } - logger.LogInformation("Hello, {Recipient}", "World"); + [Fact] + public void CarriesMessageTemplateProperties() + { + var selfLog = new StringWriter(); + SelfLog.Enable(selfLog); - Assert.True(sink.Writes[0].Properties.ContainsKey("Recipient")); - Assert.Equal("\"World\"", sink.Writes[0].Properties["Recipient"].ToString()); - Assert.Equal("Hello, {Recipient}", sink.Writes[0].MessageTemplate.Text); + var (logger, sink) = SetUp(LogLevel.Trace); - SelfLog.Disable(); - Assert.Empty(selfLog.ToString()); - } + logger.LogInformation("Hello, {Recipient}", "World"); - [Fact] - public void CarriesMessageTemplatePropertiesWhenStringificationIsUsed() - { - var selfLog = new StringWriter(); - SelfLog.Enable(selfLog); - var (logger, sink) = SetUp(LogLevel.Trace); - var array = new[] { 1, 2, 3, 4 }; + Assert.True(sink.Writes[0].Properties.ContainsKey("Recipient")); + Assert.Equal("\"World\"", sink.Writes[0].Properties["Recipient"].ToString()); + Assert.Equal("Hello, {Recipient}", sink.Writes[0].MessageTemplate.Text); - logger.LogInformation("{$array}", array); + SelfLog.Disable(); + Assert.Empty(selfLog.ToString()); + } - Assert.True(sink.Writes[0].Properties.ContainsKey("array")); - Assert.Equal("\"System.Int32[]\"", sink.Writes[0].Properties["array"].ToString()); - Assert.Equal("{$array}", sink.Writes[0].MessageTemplate.Text); + [Fact] + public void CarriesMessageTemplatePropertiesWhenStringificationIsUsed() + { + var selfLog = new StringWriter(); + SelfLog.Enable(selfLog); + var (logger, sink) = SetUp(LogLevel.Trace); + var array = new[] { 1, 2, 3, 4 }; - SelfLog.Disable(); - Assert.Empty(selfLog.ToString()); - } + logger.LogInformation("{$array}", array); - [Fact] - public void CarriesEventIdIfNonzero() - { - var (logger, sink) = SetUp(LogLevel.Trace); + Assert.True(sink.Writes[0].Properties.ContainsKey("array")); + Assert.Equal("\"System.Int32[]\"", sink.Writes[0].Properties["array"].ToString()); + Assert.Equal("{$array}", sink.Writes[0].MessageTemplate.Text); - const int expected = 42; + SelfLog.Disable(); + Assert.Empty(selfLog.ToString()); + } - logger.Log(LogLevel.Information, expected, "Test", null, null); + [Fact] + public void CarriesEventIdIfNonzero() + { + var (logger, sink) = SetUp(LogLevel.Trace); - Assert.Equal(1, sink.Writes.Count); + const int expected = 42; - var eventId = (StructureValue) sink.Writes[0].Properties["EventId"]; - var id = (ScalarValue) eventId.Properties.Single(p => p.Name == "Id").Value; - Assert.Equal(42, id.Value); - } + logger.Log(LogLevel.Information, expected, "Test", null!, null!); - [Fact] - public void WhenDisposeIsFalseProvidedLoggerIsNotDisposed() - { - var logger = new DisposeTrackingLogger(); - // ReSharper disable once RedundantArgumentDefaultValue - var provider = new SerilogLoggerProvider(logger, false); - provider.Dispose(); - Assert.False(logger.IsDisposed); - } + Assert.Single(sink.Writes); - [Fact] - public void WhenDisposeIsTrueProvidedLoggerIsDisposed() - { - var logger = new DisposeTrackingLogger(); - var provider = new SerilogLoggerProvider(logger, true); - provider.Dispose(); - Assert.True(logger.IsDisposed); - } + var eventId = (StructureValue) sink.Writes[0].Properties["EventId"]; + var id = (ScalarValue) eventId.Properties.Single(p => p.Name == "Id").Value; + Assert.Equal(42, id.Value); + } - [Fact] - public void BeginScopeDestructuresObjectsWhenDestructurerIsUsedInMessageTemplate() - { - var (logger, sink) = SetUp(LogLevel.Trace); + [Fact] + public void WhenDisposeIsFalseProvidedLoggerIsNotDisposed() + { + var logger = new DisposeTrackingLogger(); + // ReSharper disable once RedundantArgumentDefaultValue + var provider = new SerilogLoggerProvider(logger, false); + provider.Dispose(); + Assert.False(logger.IsDisposed); + } - using (logger.BeginScope("{@Person}", new Person { FirstName = "John", LastName = "Smith" })) - { - logger.Log(LogLevel.Information, 0, TestMessage, null, null); - } + [Fact] + public void WhenDisposeIsTrueProvidedLoggerIsDisposed() + { + var logger = new DisposeTrackingLogger(); + var provider = new SerilogLoggerProvider(logger, true); + provider.Dispose(); + Assert.True(logger.IsDisposed); + } - Assert.Equal(1, sink.Writes.Count); - Assert.True(sink.Writes[0].Properties.ContainsKey("Person")); + [Fact] + public void BeginScopeDestructuresObjectsWhenDestructurerIsUsedInMessageTemplate() + { + var (logger, sink) = SetUp(LogLevel.Trace); - var person = (StructureValue)sink.Writes[0].Properties["Person"]; - var firstName = (ScalarValue)person.Properties.Single(p => p.Name == "FirstName").Value; - var lastName = (ScalarValue)person.Properties.Single(p => p.Name == "LastName").Value; - Assert.Equal("John", firstName.Value); - Assert.Equal("Smith", lastName.Value); + using (logger.BeginScope("{@Person}", new Person { FirstName = "John", LastName = "Smith" })) + { + logger.Log(LogLevel.Information, 0, TestMessage, null!, null!); } - [Fact] - public void BeginScopeDestructuresObjectsWhenDestructurerIsUsedInDictionary() - { - var (logger, sink) = SetUp(LogLevel.Trace); - - using (logger.BeginScope(new Dictionary {{ "@Person", new Person { FirstName = "John", LastName = "Smith" }}})) - { - logger.Log(LogLevel.Information, 0, TestMessage, null, null); - } + Assert.Single(sink.Writes); + Assert.True(sink.Writes[0].Properties.ContainsKey("Person")); - Assert.Equal(1, sink.Writes.Count); - Assert.True(sink.Writes[0].Properties.ContainsKey("Person")); + var person = (StructureValue)sink.Writes[0].Properties["Person"]; + var firstName = (ScalarValue)person.Properties.Single(p => p.Name == "FirstName").Value; + var lastName = (ScalarValue)person.Properties.Single(p => p.Name == "LastName").Value; + Assert.Equal("John", firstName.Value); + Assert.Equal("Smith", lastName.Value); + } - var person = (StructureValue)sink.Writes[0].Properties["Person"]; - var firstName = (ScalarValue)person.Properties.Single(p => p.Name == "FirstName").Value; - var lastName = (ScalarValue)person.Properties.Single(p => p.Name == "LastName").Value; - Assert.Equal("John", firstName.Value); - Assert.Equal("Smith", lastName.Value); - } + [Fact] + public void BeginScopeDestructuresObjectsWhenDestructurerIsUsedInDictionary() + { + var (logger, sink) = SetUp(LogLevel.Trace); - [Fact] - public void BeginScopeDoesNotModifyKeyWhenDestructurerIsNotUsedInMessageTemplate() + using (logger.BeginScope(new Dictionary {{ "@Person", new Person { FirstName = "John", LastName = "Smith" }}})) { - var (logger, sink) = SetUp(LogLevel.Trace); + logger.Log(LogLevel.Information, 0, TestMessage, null!, null!); + } - using (logger.BeginScope("{FirstName}", "John")) - { - logger.Log(LogLevel.Information, 0, TestMessage, null, null); - } + Assert.Single(sink.Writes); + Assert.True(sink.Writes[0].Properties.ContainsKey("Person")); - Assert.Equal(1, sink.Writes.Count); - Assert.True(sink.Writes[0].Properties.ContainsKey("FirstName")); - } + var person = (StructureValue)sink.Writes[0].Properties["Person"]; + var firstName = (ScalarValue)person.Properties.Single(p => p.Name == "FirstName").Value; + var lastName = (ScalarValue)person.Properties.Single(p => p.Name == "LastName").Value; + Assert.Equal("John", firstName.Value); + Assert.Equal("Smith", lastName.Value); + } + + [Fact] + public void BeginScopeDoesNotModifyKeyWhenDestructurerIsNotUsedInMessageTemplate() + { + var (logger, sink) = SetUp(LogLevel.Trace); - [Fact] - public void BeginScopeDoesNotModifyKeyWhenDestructurerIsNotUsedInDictionary() + using (logger.BeginScope("{FirstName}", "John")) { - var (logger, sink) = SetUp(LogLevel.Trace); + logger.Log(LogLevel.Information, 0, TestMessage, null!, null!); + } - using (logger.BeginScope(new Dictionary { { "FirstName", "John"}})) - { - logger.Log(LogLevel.Information, 0, TestMessage, null, null); - } + Assert.Single(sink.Writes); + Assert.True(sink.Writes[0].Properties.ContainsKey("FirstName")); + } - Assert.Equal(1, sink.Writes.Count); - Assert.True(sink.Writes[0].Properties.ContainsKey("FirstName")); - } + [Fact] + public void BeginScopeDoesNotModifyKeyWhenDestructurerIsNotUsedInDictionary() + { + var (logger, sink) = SetUp(LogLevel.Trace); - [Fact] - public void NamedScopesAreCaptured() + using (logger.BeginScope(new Dictionary { { "FirstName", "John"}})) { - var (logger, sink) = SetUp(LogLevel.Trace); + logger.Log(LogLevel.Information, 0, TestMessage, null!, null!); + } - using (logger.BeginScope("Outer")) - using (logger.BeginScope("Inner")) - { - logger.Log(LogLevel.Information, 0, TestMessage, null, null); - } + Assert.Single(sink.Writes); + Assert.True(sink.Writes[0].Properties.ContainsKey("FirstName")); + } - Assert.Equal(1, sink.Writes.Count); + [Fact] + public void NamedScopesAreCaptured() + { + var (logger, sink) = SetUp(LogLevel.Trace); - Assert.True(sink.Writes[0].Properties.TryGetValue(SerilogLoggerProvider.ScopePropertyName, out var scopeValue)); - var sequence = Assert.IsType(scopeValue); - var items = sequence.Elements.Select(e => Assert.IsType(e).Value).Cast().ToArray(); - Assert.Equal(2, items.Length); - Assert.Equal("Outer", items[0]); - Assert.Equal("Inner", items[1]); + using (logger.BeginScope("Outer")) + using (logger.BeginScope("Inner")) + { + logger.Log(LogLevel.Information, 0, TestMessage, null!, null!); } - class FoodScope : IEnumerable> - { - readonly string _name; + Assert.Single(sink.Writes); - public FoodScope(string name) - { - _name = name; - } + Assert.True(sink.Writes[0].Properties.TryGetValue(SerilogLoggerProvider.ScopePropertyName, out var scopeValue)); + var sequence = Assert.IsType(scopeValue); + var items = sequence.Elements.Select(e => Assert.IsType(e).Value).Cast().ToArray(); + Assert.Equal(2, items.Length); + Assert.Equal("Outer", items[0]); + Assert.Equal("Inner", items[1]); + } - public IEnumerator> GetEnumerator() - { - yield return new KeyValuePair("Name", _name); - } + class FoodScope : IEnumerable> + { + readonly string _name; - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } + public FoodScope(string name) + { + _name = name; } - class LuckyScope : IEnumerable> + public IEnumerator> GetEnumerator() { - readonly int _luckyNumber; + yield return new KeyValuePair("Name", _name); + } - public LuckyScope(int luckyNumber) - { - _luckyNumber = luckyNumber; - } + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } - public IEnumerator> GetEnumerator() - { - yield return new KeyValuePair("LuckyNumber", _luckyNumber); - } + class LuckyScope : IEnumerable> + { + readonly int _luckyNumber; - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } + public LuckyScope(int luckyNumber) + { + _luckyNumber = luckyNumber; } - class Person + public IEnumerator> GetEnumerator() { - // ReSharper disable once UnusedAutoPropertyAccessor.Local - public string FirstName { get; set; } - - // ReSharper disable once UnusedAutoPropertyAccessor.Local - public string LastName { get; set; } + yield return new KeyValuePair("LuckyNumber", _luckyNumber); } - [Theory] - [InlineData(1)] - [InlineData(10)] - [InlineData(48)] - [InlineData(100)] - public void LowAndHighNumberedEventIdsAreMapped(int id) + IEnumerator IEnumerable.GetEnumerator() { - var orig = new EventId(id, "test"); - var mapped = SerilogLogger.CreateEventIdProperty(orig); - var value = Assert.IsType(mapped.Value); - Assert.Equal(2, value.Properties.Count); - var idValue = value.Properties.Single(p => p.Name == "Id").Value; - var scalar = Assert.IsType(idValue); - Assert.Equal(id, scalar.Value); + return GetEnumerator(); } - - [Fact] - public void MismatchedMessageTemplateParameterCountIsHandled() - { - var (logger, sink) = SetUp(LogLevel.Trace); + } - logger.LogInformation("Some test message with {Two} {Properties}", "OneProperty"); + class Person + { + // ReSharper disable once UnusedAutoPropertyAccessor.Local + public string? FirstName { get; set; } + + // ReSharper disable once UnusedAutoPropertyAccessor.Local + public string? LastName { get; set; } + } + + [Theory] + [InlineData(1)] + [InlineData(10)] + [InlineData(48)] + [InlineData(100)] + public void LowAndHighNumberedEventIdsAreMapped(int id) + { + var orig = new EventId(id, "test"); + var mapped = SerilogLogger.CreateEventIdProperty(orig); + var value = Assert.IsType(mapped.Value); + Assert.Equal(2, value.Properties.Count); + var idValue = value.Properties.Single(p => p.Name == "Id").Value; + var scalar = Assert.IsType(idValue); + Assert.Equal(id, scalar.Value); + } + + [Fact] + public void MismatchedMessageTemplateParameterCountIsHandled() + { + var (logger, sink) = SetUp(LogLevel.Trace); + + logger.LogInformation("Some test message with {Two} {Properties}", "OneProperty"); - Assert.Equal(0, sink.Writes.Count); + Assert.Empty(sink.Writes); + } + + [Fact] + public void ExceptionFromAuditSinkIsUnhandled() + { + var serilogLogger = new LoggerConfiguration() + .AuditTo.Sink(new MySink()) + .CreateLogger(); + + var provider = new SerilogLoggerProvider(serilogLogger); + var logger = provider.CreateLogger(Name); + + var ex = Assert.Throws(() => logger.LogInformation("Normal text")); + Assert.IsType(ex.InnerException); + Assert.Equal("Oops", ex.InnerException.Message); + } + + private class MySink : ILogEventSink + { + public void Emit(LogEvent logEvent) + { + throw new NotImplementedException("Oops"); } } } diff --git a/test/Serilog.Extensions.Logging.Tests/Support/DisposeTrackingLogger.cs b/test/Serilog.Extensions.Logging.Tests/Support/DisposeTrackingLogger.cs index d833c0c..8effbfc 100644 --- a/test/Serilog.Extensions.Logging.Tests/Support/DisposeTrackingLogger.cs +++ b/test/Serilog.Extensions.Logging.Tests/Support/DisposeTrackingLogger.cs @@ -1,354 +1,352 @@ -using System; -using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using Serilog.Core; using Serilog.Events; -namespace Serilog.Extensions.Logging.Tests.Support +namespace Serilog.Extensions.Logging.Tests.Support; + +sealed class DisposeTrackingLogger : ILogger, IDisposable { - public class DisposeTrackingLogger : ILogger, IDisposable - { - public bool IsDisposed { get; set; } - - public ILogger ForContext(ILogEventEnricher enricher) - { - return new LoggerConfiguration().CreateLogger(); - } - - public ILogger ForContext(IEnumerable enrichers) - { - return new LoggerConfiguration().CreateLogger(); - } - - public ILogger ForContext(string propertyName, object value, bool destructureObjects = false) - { - return new LoggerConfiguration().CreateLogger(); - } - - public ILogger ForContext() - { - return new LoggerConfiguration().CreateLogger(); - } - - public ILogger ForContext(Type source) - { - return new LoggerConfiguration().CreateLogger(); - } - - public void Write(LogEvent logEvent) - { - } - - public void Write(LogEventLevel level, string messageTemplate) - { - } - - public void Write(LogEventLevel level, string messageTemplate, T propertyValue) - { - } - - public void Write(LogEventLevel level, string messageTemplate, T0 propertyValue0, T1 propertyValue1) - { - } - - public void Write(LogEventLevel level, string messageTemplate, T0 propertyValue0, T1 propertyValue1, - T2 propertyValue2) - { - } - - public void Write(LogEventLevel level, string messageTemplate, params object[] propertyValues) - { - } - - public void Write(LogEventLevel level, Exception exception, string messageTemplate) - { - } - - public void Write(LogEventLevel level, Exception exception, string messageTemplate, T propertyValue) - { - } - - public void Write(LogEventLevel level, Exception exception, string messageTemplate, T0 propertyValue0, - T1 propertyValue1) - { - } - - public void Write(LogEventLevel level, Exception exception, string messageTemplate, T0 propertyValue0, - T1 propertyValue1, T2 propertyValue2) - { - } - - public void Write(LogEventLevel level, Exception exception, string messageTemplate, params object[] propertyValues) - { - } - - public bool IsEnabled(LogEventLevel level) - { - return false; - } - - public void Verbose(string messageTemplate) - { - } - - public void Verbose(string messageTemplate, T propertyValue) - { - } - - public void Verbose(string messageTemplate, T0 propertyValue0, T1 propertyValue1) - { - } - - public void Verbose(string messageTemplate, T0 propertyValue0, T1 propertyValue1, T2 propertyValue2) - { - } - - public void Verbose(string messageTemplate, params object[] propertyValues) - { - } - - public void Verbose(Exception exception, string messageTemplate) - { - } - - public void Verbose(Exception exception, string messageTemplate, T propertyValue) - { - } - - public void Verbose(Exception exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1) - { - } - - public void Verbose(Exception exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1, - T2 propertyValue2) - { - } - - public void Verbose(Exception exception, string messageTemplate, params object[] propertyValues) - { - } - - public void Debug(string messageTemplate) - { - } - - public void Debug(string messageTemplate, T propertyValue) - { - } - - public void Debug(string messageTemplate, T0 propertyValue0, T1 propertyValue1) - { - } - - public void Debug(string messageTemplate, T0 propertyValue0, T1 propertyValue1, T2 propertyValue2) - { - } - - public void Debug(string messageTemplate, params object[] propertyValues) - { - } - - public void Debug(Exception exception, string messageTemplate) - { - } - - public void Debug(Exception exception, string messageTemplate, T propertyValue) - { - } - - public void Debug(Exception exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1) - { - } - - public void Debug(Exception exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1, - T2 propertyValue2) - { - } - - public void Debug(Exception exception, string messageTemplate, params object[] propertyValues) - { - } - - public void Information(string messageTemplate) - { - } - - public void Information(string messageTemplate, T propertyValue) - { - } - - public void Information(string messageTemplate, T0 propertyValue0, T1 propertyValue1) - { - } - - public void Information(string messageTemplate, T0 propertyValue0, T1 propertyValue1, T2 propertyValue2) - { - } - - public void Information(string messageTemplate, params object[] propertyValues) - { - } - - public void Information(Exception exception, string messageTemplate) - { - } - - public void Information(Exception exception, string messageTemplate, T propertyValue) - { - } - - public void Information(Exception exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1) - { - } - - public void Information(Exception exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1, - T2 propertyValue2) - { - } - - public void Information(Exception exception, string messageTemplate, params object[] propertyValues) - { - } - - public void Warning(string messageTemplate) - { - } - - public void Warning(string messageTemplate, T propertyValue) - { - } - - public void Warning(string messageTemplate, T0 propertyValue0, T1 propertyValue1) - { - } - - public void Warning(string messageTemplate, T0 propertyValue0, T1 propertyValue1, T2 propertyValue2) - { - } - - public void Warning(string messageTemplate, params object[] propertyValues) - { - } - - public void Warning(Exception exception, string messageTemplate) - { - } - - public void Warning(Exception exception, string messageTemplate, T propertyValue) - { - } - - public void Warning(Exception exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1) - { - } - - public void Warning(Exception exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1, - T2 propertyValue2) - { - } - - public void Warning(Exception exception, string messageTemplate, params object[] propertyValues) - { - } - - public void Error(string messageTemplate) - { - } - - public void Error(string messageTemplate, T propertyValue) - { - } - - public void Error(string messageTemplate, T0 propertyValue0, T1 propertyValue1) - { - } - - public void Error(string messageTemplate, T0 propertyValue0, T1 propertyValue1, T2 propertyValue2) - { - } - - public void Error(string messageTemplate, params object[] propertyValues) - { - } - - public void Error(Exception exception, string messageTemplate) - { - } - - public void Error(Exception exception, string messageTemplate, T propertyValue) - { - } - - public void Error(Exception exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1) - { - } - - public void Error(Exception exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1, - T2 propertyValue2) - { - } - - public void Error(Exception exception, string messageTemplate, params object[] propertyValues) - { - } - - public void Fatal(string messageTemplate) - { - } - - public void Fatal(string messageTemplate, T propertyValue) - { - } - - public void Fatal(string messageTemplate, T0 propertyValue0, T1 propertyValue1) - { - } - - public void Fatal(string messageTemplate, T0 propertyValue0, T1 propertyValue1, T2 propertyValue2) - { - } - - public void Fatal(string messageTemplate, params object[] propertyValues) - { - } - - public void Fatal(Exception exception, string messageTemplate) - { - } - - public void Fatal(Exception exception, string messageTemplate, T propertyValue) - { - } - - public void Fatal(Exception exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1) - { - } - - public void Fatal(Exception exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1, - T2 propertyValue2) - { - } - - public void Fatal(Exception exception, string messageTemplate, params object[] propertyValues) - { - } - - public bool BindMessageTemplate(string messageTemplate, object[] propertyValues, out MessageTemplate parsedTemplate, - out IEnumerable boundProperties) - { - parsedTemplate = null; - boundProperties = null; - return false; - } - - public bool BindProperty(string propertyName, object value, bool destructureObjects, out LogEventProperty property) - { - property = null; - return false; - } - - public void Dispose() - { - IsDisposed = true; - } + public bool IsDisposed { get; private set; } + + public ILogger ForContext(ILogEventEnricher enricher) + { + return new LoggerConfiguration().CreateLogger(); + } + + public ILogger ForContext(IEnumerable enrichers) + { + return new LoggerConfiguration().CreateLogger(); + } + + public ILogger ForContext(string propertyName, object? value, bool destructureObjects = false) + { + return new LoggerConfiguration().CreateLogger(); + } + + public ILogger ForContext() + { + return new LoggerConfiguration().CreateLogger(); + } + + public ILogger ForContext(Type source) + { + return new LoggerConfiguration().CreateLogger(); + } + + public void Write(LogEvent logEvent) + { + } + + public void Write(LogEventLevel level, string messageTemplate) + { + } + + public void Write(LogEventLevel level, string messageTemplate, T propertyValue) + { + } + + public void Write(LogEventLevel level, string messageTemplate, T0 propertyValue0, T1 propertyValue1) + { + } + + public void Write(LogEventLevel level, string messageTemplate, T0 propertyValue0, T1 propertyValue1, + T2 propertyValue2) + { + } + + public void Write(LogEventLevel level, string messageTemplate, params object?[]? propertyValues) + { + } + + public void Write(LogEventLevel level, Exception? exception, string messageTemplate) + { + } + + public void Write(LogEventLevel level, Exception? exception, string messageTemplate, T propertyValue) + { + } + + public void Write(LogEventLevel level, Exception? exception, string messageTemplate, T0 propertyValue0, + T1 propertyValue1) + { + } + + public void Write(LogEventLevel level, Exception? exception, string messageTemplate, T0 propertyValue0, + T1 propertyValue1, T2 propertyValue2) + { + } + + public void Write(LogEventLevel level, Exception? exception, string messageTemplate, params object?[]? propertyValues) + { + } + + public bool IsEnabled(LogEventLevel level) + { + return false; + } + + public void Verbose(string messageTemplate) + { + } + + public void Verbose(string messageTemplate, T propertyValue) + { + } + + public void Verbose(string messageTemplate, T0 propertyValue0, T1 propertyValue1) + { + } + + public void Verbose(string messageTemplate, T0 propertyValue0, T1 propertyValue1, T2 propertyValue2) + { + } + + public void Verbose(string messageTemplate, params object?[]? propertyValues) + { + } + + public void Verbose(Exception? exception, string messageTemplate) + { + } + + public void Verbose(Exception? exception, string messageTemplate, T propertyValue) + { + } + + public void Verbose(Exception? exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1) + { + } + + public void Verbose(Exception? exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1, + T2 propertyValue2) + { + } + + public void Verbose(Exception? exception, string messageTemplate, params object?[]? propertyValues) + { + } + + public void Debug(string messageTemplate) + { + } + + public void Debug(string messageTemplate, T propertyValue) + { + } + + public void Debug(string messageTemplate, T0 propertyValue0, T1 propertyValue1) + { + } + + public void Debug(string messageTemplate, T0 propertyValue0, T1 propertyValue1, T2 propertyValue2) + { + } + + public void Debug(string messageTemplate, params object?[]? propertyValues) + { + } + + public void Debug(Exception? exception, string messageTemplate) + { + } + + public void Debug(Exception? exception, string messageTemplate, T propertyValue) + { + } + + public void Debug(Exception? exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1) + { + } + + public void Debug(Exception? exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1, + T2 propertyValue2) + { + } + + public void Debug(Exception? exception, string messageTemplate, params object?[]? propertyValues) + { + } + + public void Information(string messageTemplate) + { + } + + public void Information(string messageTemplate, T propertyValue) + { + } + + public void Information(string messageTemplate, T0 propertyValue0, T1 propertyValue1) + { + } + + public void Information(string messageTemplate, T0 propertyValue0, T1 propertyValue1, T2 propertyValue2) + { + } + + public void Information(string messageTemplate, params object?[]? propertyValues) + { + } + + public void Information(Exception? exception, string messageTemplate) + { + } + + public void Information(Exception? exception, string messageTemplate, T propertyValue) + { + } + + public void Information(Exception? exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1) + { + } + + public void Information(Exception? exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1, + T2 propertyValue2) + { + } + + public void Information(Exception? exception, string messageTemplate, params object?[]? propertyValues) + { + } + + public void Warning(string messageTemplate) + { + } + + public void Warning(string messageTemplate, T propertyValue) + { + } + + public void Warning(string messageTemplate, T0 propertyValue0, T1 propertyValue1) + { + } + + public void Warning(string messageTemplate, T0 propertyValue0, T1 propertyValue1, T2 propertyValue2) + { + } + + public void Warning(string messageTemplate, params object?[]? propertyValues) + { + } + + public void Warning(Exception? exception, string messageTemplate) + { + } + + public void Warning(Exception? exception, string messageTemplate, T propertyValue) + { + } + + public void Warning(Exception? exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1) + { + } + + public void Warning(Exception? exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1, + T2 propertyValue2) + { + } + + public void Warning(Exception? exception, string messageTemplate, params object?[]? propertyValues) + { + } + + public void Error(string messageTemplate) + { + } + + public void Error(string messageTemplate, T propertyValue) + { + } + + public void Error(string messageTemplate, T0 propertyValue0, T1 propertyValue1) + { + } + + public void Error(string messageTemplate, T0 propertyValue0, T1 propertyValue1, T2 propertyValue2) + { + } + + public void Error(string messageTemplate, params object?[]? propertyValues) + { + } + + public void Error(Exception? exception, string messageTemplate) + { + } + + public void Error(Exception? exception, string messageTemplate, T propertyValue) + { + } + + public void Error(Exception? exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1) + { + } + + public void Error(Exception? exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1, + T2 propertyValue2) + { + } + + public void Error(Exception? exception, string messageTemplate, params object?[]? propertyValues) + { + } + + public void Fatal(string messageTemplate) + { + } + + public void Fatal(string messageTemplate, T propertyValue) + { + } + + public void Fatal(string messageTemplate, T0 propertyValue0, T1 propertyValue1) + { + } + + public void Fatal(string messageTemplate, T0 propertyValue0, T1 propertyValue1, T2 propertyValue2) + { + } + + public void Fatal(string messageTemplate, params object?[]? propertyValues) + { + } + + public void Fatal(Exception? exception, string messageTemplate) + { + } + + public void Fatal(Exception? exception, string messageTemplate, T propertyValue) + { + } + + public void Fatal(Exception? exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1) + { + } + + public void Fatal(Exception? exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1, + T2 propertyValue2) + { + } + + public void Fatal(Exception? exception, string messageTemplate, params object?[]? propertyValues) + { + } + + public bool BindMessageTemplate(string messageTemplate, object?[]? propertyValues, [NotNullWhen(true)] out MessageTemplate? parsedTemplate, + [NotNullWhen(true)] out IEnumerable? boundProperties) + { + parsedTemplate = null; + boundProperties = null; + return false; + } + + public bool BindProperty(string? propertyName, object? value, bool destructureObjects, [NotNullWhen(true)] out LogEventProperty? property) + { + property = null; + return false; + } + + public void Dispose() + { + IsDisposed = true; } } diff --git a/test/Serilog.Extensions.Logging.Tests/Support/ExtensionsProvider.cs b/test/Serilog.Extensions.Logging.Tests/Support/ExtensionsProvider.cs index 583293c..a27d0bd 100644 --- a/test/Serilog.Extensions.Logging.Tests/Support/ExtensionsProvider.cs +++ b/test/Serilog.Extensions.Logging.Tests/Support/ExtensionsProvider.cs @@ -1,45 +1,42 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System; -using System.Collections.Generic; using Microsoft.Extensions.Logging; -using Serilog.Events; -namespace Serilog.Extensions.Logging.Tests.Support +namespace Serilog.Extensions.Logging.Tests.Support; + +sealed class ExtensionsProvider : ILoggerProvider, Microsoft.Extensions.Logging.ILogger { - public class ExtensionsProvider : ILoggerProvider, Microsoft.Extensions.Logging.ILogger + readonly LogLevel _enabledLevel; + + public List<(LogLevel logLevel, EventId eventId, object? state, Exception? exception, string message)> Writes { get; } = new(); + + public ExtensionsProvider(LogLevel enabledLevel) + { + _enabledLevel = enabledLevel; + } + + public Microsoft.Extensions.Logging.ILogger CreateLogger(string categoryName) + { + return this; + } + + public IDisposable BeginScope(TState state) where TState: notnull + { + return this; + } + + public bool IsEnabled(LogLevel logLevel) + { + return _enabledLevel <= logLevel; + } + + public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter) + { + Writes.Add((logLevel, eventId, state, exception, formatter(state, exception))); + } + + public void Dispose() { - private readonly LogLevel enabledLevel; - public List<(LogLevel logLevel, EventId eventId, object state, Exception exception, string message)> Writes { get; set; } = new List<(LogLevel logLevel, EventId eventId, object state, Exception exception, string message)>(); - - public ExtensionsProvider(LogLevel enabledLevel) - { - this.enabledLevel = enabledLevel; - } - - public Microsoft.Extensions.Logging.ILogger CreateLogger(string categoryName) - { - return this; - } - - public IDisposable BeginScope(TState state) - { - return this; - } - - public bool IsEnabled(LogLevel logLevel) - { - return enabledLevel <= logLevel; - } - - public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) - { - Writes.Add((logLevel, eventId, state, exception, formatter(state, exception))); - } - - public void Dispose() - { - } } -} \ No newline at end of file +} diff --git a/test/Serilog.Extensions.Logging.Tests/Support/SerilogSink.cs b/test/Serilog.Extensions.Logging.Tests/Support/SerilogSink.cs index cd7d5d7..d26aac5 100644 --- a/test/Serilog.Extensions.Logging.Tests/Support/SerilogSink.cs +++ b/test/Serilog.Extensions.Logging.Tests/Support/SerilogSink.cs @@ -1,19 +1,17 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System.Collections.Generic; using Serilog.Core; using Serilog.Events; -namespace Serilog.Extensions.Logging.Tests.Support +namespace Serilog.Extensions.Logging.Tests.Support; + +public class SerilogSink : ILogEventSink { - public class SerilogSink : ILogEventSink - { - public List Writes { get; set; } = new List(); + public List Writes { get; set; } = new(); - public void Emit(LogEvent logEvent) - { - Writes.Add(logEvent); - } + public void Emit(LogEvent logEvent) + { + Writes.Add(logEvent); } -} \ No newline at end of file +}