From e25ed7ddfd0c664bd3a7e9cdbeeec6f87ff12964 Mon Sep 17 00:00:00 2001 From: Nicholas Blumhardt Date: Tue, 2 Nov 2021 07:38:56 +1000 Subject: [PATCH 01/22] Dev version bump [skip ci] --- .../Serilog.Extensions.Logging.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Serilog.Extensions.Logging/Serilog.Extensions.Logging.csproj b/src/Serilog.Extensions.Logging/Serilog.Extensions.Logging.csproj index f6d4bc1..2318516 100644 --- a/src/Serilog.Extensions.Logging/Serilog.Extensions.Logging.csproj +++ b/src/Serilog.Extensions.Logging/Serilog.Extensions.Logging.csproj @@ -2,7 +2,7 @@ Low-level Serilog provider for Microsoft.Extensions.Logging - 3.1.0 + 3.1.1 Microsoft;Serilog Contributors netstandard2.0 true From b02968d617f36034a1f764671d3c55d1b6082b82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Luthi?= Date: Wed, 30 Mar 2022 01:09:08 +0200 Subject: [PATCH 02/22] Observe Microsoft.Extensions.Logging.LogLevel.None Before this commit: `LogLevel.None` is converted to `LogEventLevel.Fatal` and events with a `None` level are logged as fatal. After this commit: `LogLevel.None` is observed and logs and events with a `None` level are ignored. The `None` level is documented as such: > Not used for writing log messages. Specifies that a logging category should not write any messages. Note: this erroneous behaviour was seen in a real-world scenario: 1. `BuildOrgConnectUri CoreClass ()` is [logged as `TraceEventType.Start `][1] 2. `TraceEventType.Start` is [converted to `LogLevel.None`][2] 3. `LogLevel.None ` is [converted to `LogEventLevel.Fatal`][3] As a result, `BuildOrgConnectUri CoreClass ()` is logged as fatal whereas it should have been ignored. [1]: https://github.com/microsoft/PowerPlatform-DataverseServiceClient/blob/0.6.1/src/GeneralTools/DataverseClient/Client/ConnectionService.cs#L3207 [2]: https://github.com/microsoft/PowerPlatform-DataverseServiceClient/blob/0.6.1/src/GeneralTools/DataverseClient/Client/DataverseTraceLogger.cs#L675 [3]: https://github.com/serilog/serilog-extensions-logging/blob/dev/src/Serilog.Extensions.Logging/Extensions/Logging/LevelConvert.cs#L39 --- .../Extensions/Logging/SerilogLogger.cs | 6 ++++- .../SerilogLoggerTests.cs | 23 +++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLogger.cs b/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLogger.cs index 1b1f895..80ba75f 100644 --- a/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLogger.cs +++ b/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLogger.cs @@ -44,7 +44,7 @@ public SerilogLogger( public bool IsEnabled(LogLevel logLevel) { - return _logger.IsEnabled(LevelConvert.ToSerilogLevel(logLevel)); + return logLevel != LogLevel.None && _logger.IsEnabled(LevelConvert.ToSerilogLevel(logLevel)); } public IDisposable BeginScope(TState state) @@ -54,6 +54,10 @@ public IDisposable BeginScope(TState state) public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) { + if (logLevel == LogLevel.None) + { + return; + } var level = LevelConvert.ToSerilogLevel(logLevel); if (!_logger.IsEnabled(level)) { diff --git a/test/Serilog.Extensions.Logging.Tests/SerilogLoggerTests.cs b/test/Serilog.Extensions.Logging.Tests/SerilogLoggerTests.cs index f37e9eb..b60e7f2 100644 --- a/test/Serilog.Extensions.Logging.Tests/SerilogLoggerTests.cs +++ b/test/Serilog.Extensions.Logging.Tests/SerilogLoggerTests.cs @@ -55,6 +55,7 @@ public void LogsCorrectLevel() 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); @@ -65,6 +66,22 @@ public void LogsCorrectLevel() Assert.Equal(LogEventLevel.Fatal, sink.Writes[5].Level); } + + [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); + + Assert.Equal(isEnabled, logger.IsEnabled(logLevel)); + } + [Theory] [InlineData(LogLevel.Trace, LogLevel.Trace, 1)] [InlineData(LogLevel.Trace, LogLevel.Debug, 1)] @@ -72,36 +89,42 @@ public void LogsCorrectLevel() [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); From 5bd2cc04f9cf7e69dda6ae09993a8d3484daab0d Mon Sep 17 00:00:00 2001 From: Simon Cropp Date: Sat, 4 Feb 2023 06:24:20 +1100 Subject: [PATCH 03/22] build against 2022 --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 628e491..c755a69 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: From 1269b852564017d3f74cdb9d663455ca46ca2e13 Mon Sep 17 00:00:00 2001 From: Ivan Maximov Date: Sat, 18 Feb 2023 09:35:10 +0300 Subject: [PATCH 04/22] Little cleanup --- Build.ps1 | 4 ++-- README.md | 9 +++++---- samples/Sample/Sample.csproj | 1 - .../Extensions/Logging/CachingMessageTemplateParser.cs | 2 +- .../Extensions/Logging/SerilogLogger.cs | 2 +- .../Extensions/Logging/SerilogLoggerFactory.cs | 2 +- .../Extensions/Logging/SerilogLoggerScope.cs | 2 +- .../LoggerSinkConfigurationExtensions.cs | 2 +- .../Serilog.Extensions.Logging.csproj | 2 +- .../SerilogLoggerTests.cs | 4 ++-- 10 files changed, 15 insertions(+), 15 deletions(-) diff --git a/Build.ps1 b/Build.ps1 index 41fbf5d..0687e20 100644 --- a/Build.ps1 +++ b/Build.ps1 @@ -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/README.md b/README.md index 905fc55..9649d58 100644 --- a/README.md +++ b/README.md @@ -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 }, diff --git a/samples/Sample/Sample.csproj b/samples/Sample/Sample.csproj index c557b8a..8b1487b 100644 --- a/samples/Sample/Sample.csproj +++ b/samples/Sample/Sample.csproj @@ -13,7 +13,6 @@ - diff --git a/src/Serilog.Extensions.Logging/Extensions/Logging/CachingMessageTemplateParser.cs b/src/Serilog.Extensions.Logging/Extensions/Logging/CachingMessageTemplateParser.cs index d47e84f..ca3c254 100644 --- a/src/Serilog.Extensions.Logging/Extensions/Logging/CachingMessageTemplateParser.cs +++ b/src/Serilog.Extensions.Logging/Extensions/Logging/CachingMessageTemplateParser.cs @@ -23,7 +23,7 @@ namespace Serilog.Extensions.Logging class CachingMessageTemplateParser { readonly MessageTemplateParser _innerParser = new MessageTemplateParser(); - + readonly object _templatesLock = new object(); readonly Hashtable _templates = new Hashtable(); diff --git a/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLogger.cs b/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLogger.cs index 80ba75f..78d72dd 100644 --- a/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLogger.cs +++ b/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLogger.cs @@ -103,7 +103,7 @@ void Write(LogEventLevel level, EventId eventId, TState state, Exception { if (logger.BindProperty(property.Key, property.Value, false, out var bound)) properties.Add(bound); - } + } } var stateType = state.GetType(); diff --git a/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLoggerFactory.cs b/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLoggerFactory.cs index cf25f06..3ee0760 100644 --- a/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLoggerFactory.cs +++ b/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLoggerFactory.cs @@ -25,7 +25,7 @@ public class SerilogLoggerFactory : ILoggerFactory { readonly LoggerProviderCollection _providerCollection; readonly SerilogLoggerProvider _provider; - + /// /// Initializes a new instance of the class. /// diff --git a/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLoggerScope.cs b/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLoggerScope.cs index dd0803c..e911611 100644 --- a/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLoggerScope.cs +++ b/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLoggerScope.cs @@ -30,7 +30,7 @@ public SerilogLoggerScope(SerilogLoggerProvider provider, object state, IDisposa } public SerilogLoggerScope Parent { get; } - + public void Dispose() { if (!_disposed) diff --git a/src/Serilog.Extensions.Logging/LoggerSinkConfigurationExtensions.cs b/src/Serilog.Extensions.Logging/LoggerSinkConfigurationExtensions.cs index 85064db..9e78511 100644 --- a/src/Serilog.Extensions.Logging/LoggerSinkConfigurationExtensions.cs +++ b/src/Serilog.Extensions.Logging/LoggerSinkConfigurationExtensions.cs @@ -38,7 +38,7 @@ public static class LoggerSinkConfigurationExtensions public static LoggerConfiguration Providers( this LoggerSinkConfiguration configuration, LoggerProviderCollection providers, - LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum, + LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum, LoggingLevelSwitch levelSwitch = null) { if (configuration == null) throw new ArgumentNullException(nameof(configuration)); diff --git a/src/Serilog.Extensions.Logging/Serilog.Extensions.Logging.csproj b/src/Serilog.Extensions.Logging/Serilog.Extensions.Logging.csproj index 2318516..8ec5392 100644 --- a/src/Serilog.Extensions.Logging/Serilog.Extensions.Logging.csproj +++ b/src/Serilog.Extensions.Logging/Serilog.Extensions.Logging.csproj @@ -23,7 +23,7 @@ - + diff --git a/test/Serilog.Extensions.Logging.Tests/SerilogLoggerTests.cs b/test/Serilog.Extensions.Logging.Tests/SerilogLoggerTests.cs index b60e7f2..d3a0eb5 100644 --- a/test/Serilog.Extensions.Logging.Tests/SerilogLoggerTests.cs +++ b/test/Serilog.Extensions.Logging.Tests/SerilogLoggerTests.cs @@ -334,7 +334,7 @@ public void BeginScopeDestructuresObjectsWhenDestructurerIsUsedInMessageTemplate 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); @@ -463,7 +463,7 @@ public void LowAndHighNumberedEventIdsAreMapped(int id) var scalar = Assert.IsType(idValue); Assert.Equal(id, scalar.Value); } - + [Fact] public void MismatchedMessageTemplateParameterCountIsHandled() { From 2e5bec9a9cb305a05a825fadae5a6c8bd10b2bfc Mon Sep 17 00:00:00 2001 From: Ivan Maximov Date: Sat, 18 Feb 2023 12:13:58 +0300 Subject: [PATCH 05/22] Eliminates boxing of Dictionary.Enumerator for the most common use case --- .../Extensions/Logging/SerilogLoggerScope.cs | 63 ++++++++++++------- 1 file changed, 39 insertions(+), 24 deletions(-) diff --git a/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLoggerScope.cs b/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLoggerScope.cs index dd0803c..deb8ace 100644 --- a/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLoggerScope.cs +++ b/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLoggerScope.cs @@ -30,7 +30,7 @@ public SerilogLoggerScope(SerilogLoggerProvider provider, object state, IDisposa } public SerilogLoggerScope Parent { get; } - + public void Dispose() { if (!_disposed) @@ -51,42 +51,57 @@ public void Dispose() public void EnrichAndCreateScopeItem(LogEvent logEvent, ILogEventPropertyFactory propertyFactory, out LogEventPropertyValue scopeItem) { + void AddProperty(KeyValuePair stateProperty) + { + 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 (_state == null) { scopeItem = null; return; } - if (_state is IEnumerable> stateProperties) + // Eliminates boxing of Dictionary.Enumerator for the most common use case + if (_state is Dictionary dictionary) + { + scopeItem = null; // Unless it's `FormattedLogValues`, these are treated as property bags rather than scope items. + + foreach (var stateProperty in dictionary) + { + if (stateProperty.Key == SerilogLoggerProvider.OriginalFormatPropertyName && stateProperty.Value is string) + scopeItem = new ScalarValue(_state.ToString()); + else + AddProperty(stateProperty); + } + } + 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) { 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); + else + AddProperty(stateProperty); } } else From eb5d7ce6fdb31c131bf1eb0ab31020473a78f4c5 Mon Sep 17 00:00:00 2001 From: Ivan Maximov Date: Sat, 4 Mar 2023 11:24:05 +0300 Subject: [PATCH 06/22] Migrate to NET7 and C#11 --- .editorconfig | 23 + Directory.Build.props | 13 + Directory.Build.targets | 3 + global.json | 7 - samples/Sample/Program.cs | 134 ++-- samples/Sample/Sample.csproj | 9 +- serilog-extensions-logging.sln | 8 +- serilog-extensions-logging.sln.DotSettings | 12 - .../Logging/CachingMessageTemplateParser.cs | 68 +- .../Extensions/Logging/LevelConvert.cs | 101 ++- .../Logging/LoggerProviderCollection.cs | 73 +- .../Logging/LoggerProviderCollectionSink.cs | 84 +- .../Extensions/Logging/SerilogLogValues.cs | 63 +- .../Extensions/Logging/SerilogLogger.cs | 250 +++--- .../Logging/SerilogLoggerFactory.cs | 100 ++- .../Logging/SerilogLoggerProvider.cs | 136 ++-- .../Extensions/Logging/SerilogLoggerScope.cs | 159 ++-- .../LoggerSinkConfigurationExtensions.cs | 48 +- .../Serilog.Extensions.Logging.csproj | 19 +- .../SerilogLoggerFactoryExtensions.cs | 44 +- .../SerilogLoggingBuilderExtensions.cs | 54 +- .../LogEventConstructionBenchmark.cs | 132 ++-- ...rilog.Extensions.Logging.Benchmarks.csproj | 17 +- .../Support/CapturingSink.cs | 29 +- .../LoggerProviderCollectionSinkTests.cs | 146 ++-- .../Serilog.Extensions.Logging.Tests.csproj | 17 +- .../SerilogLogValuesTests.cs | 81 +- .../SerilogLoggerTests.cs | 723 +++++++++--------- .../Support/DisposeTrackingLogger.cs | 693 +++++++++-------- .../Support/ExtensionsProvider.cs | 70 +- .../Support/SerilogSink.cs | 20 +- 31 files changed, 1645 insertions(+), 1691 deletions(-) create mode 100644 .editorconfig create mode 100644 Directory.Build.props create mode 100644 Directory.Build.targets delete mode 100644 global.json delete mode 100644 serilog-extensions-logging.sln.DotSettings 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/Directory.Build.props b/Directory.Build.props new file mode 100644 index 0000000..3883932 --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,13 @@ + + + + latest + True + true + $(MSBuildThisFileDirectory)assets/Serilog.snk + true + enable + enable + + + \ No newline at end of file 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/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..9ba6bf0 100644 --- a/samples/Sample/Program.cs +++ b/samples/Sample/Program.cs @@ -1,79 +1,77 @@ -using System; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Serilog; using Serilog.Extensions.Logging; -namespace Sample +namespace Sample; + +public class Program { - public class Program + public static void Main(string[] args) { - public static void Main(string[] args) + // 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(); + + var services = new ServiceCollection(); + + 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); + + return factory; + }); + + services.AddLogging(l => l.AddConsole()); + + 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); + + 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); + } + + using (logger.BeginScope("Main")) { - // 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(); - - var services = new ServiceCollection(); - - 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); - - return factory; - }); - - services.AddLogging(l => l.AddConsole()); - - 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); - - 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); - } - - 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); - - 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); - - serviceProvider.Dispose(); + 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); + + 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); + + serviceProvider.Dispose(); } } diff --git a/samples/Sample/Sample.csproj b/samples/Sample/Sample.csproj index 8b1487b..44ba8e2 100644 --- a/samples/Sample/Sample.csproj +++ b/samples/Sample/Sample.csproj @@ -1,10 +1,9 @@  - netcoreapp2.0 + net7 Sample Exe - Sample @@ -12,9 +11,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 deleted file mode 100644 index c2fd9da..0000000 --- a/serilog-extensions-logging.sln.DotSettings +++ /dev/null @@ -1,12 +0,0 @@ - - True - True - True - True - True - True - True - True - True - True - 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 ca3c254..294f201 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 MessageTemplateParser _innerParser = new(); - readonly object _templatesLock = new object(); - readonly Hashtable _templates = new Hashtable(); + readonly object _templatesLock = new(); + readonly Hashtable _templates = new(); - const int MaxCacheItems = 1000; - const int MaxCachedTemplateLength = 1024; + const int MaxCacheItems = 1000; + const int MaxCachedTemplateLength = 1024; - public MessageTemplate Parse(string messageTemplate) - { - if (messageTemplate == null) throw new ArgumentNullException(nameof(messageTemplate)); - - 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..7f1923d 100644 --- a/src/Serilog.Extensions.Logging/Extensions/Logging/LevelConvert.cs +++ b/src/Serilog.Extensions.Logging/Extensions/Logging/LevelConvert.cs @@ -17,65 +17,64 @@ // 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) + switch (logLevel) { - 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; - } + 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; } + } - /// - /// 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) + { + switch (logEventLevel) { - 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; - } + 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; } } } 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..2a3a586 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 = null; + 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..930f457 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. + + private readonly MessageTemplate _messageTemplate; + private readonly IReadOnlyDictionary _properties; + private 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 78d72dd..f76e75c 100644 --- a/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLogger.cs +++ b/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLogger.cs @@ -2,177 +2,173 @@ // 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; -namespace Serilog.Extensions.Logging +namespace Serilog.Extensions.Logging; + +class SerilogLogger : FrameworkLogger { - class SerilogLogger : FrameworkLogger - { - readonly SerilogLoggerProvider _provider; - readonly ILogger _logger; + readonly SerilogLoggerProvider _provider; + readonly ILogger _logger; - static readonly CachingMessageTemplateParser MessageTemplateParser = new CachingMessageTemplateParser(); + static readonly CachingMessageTemplateParser MessageTemplateParser = new(); - // 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(); + // 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(); - public SerilogLogger( - SerilogLoggerProvider provider, - ILogger logger = null, - string name = null) - { - _provider = provider ?? throw new ArgumentNullException(nameof(provider)); - _logger = logger; + public SerilogLogger( + SerilogLoggerProvider provider, + ILogger? logger = null, + string? name = null) + { + _provider = provider ?? throw new ArgumentNullException(nameof(provider)); + _logger = logger!; - // If a logger was passed, the provider has already added itself as an enricher - _logger = _logger ?? Serilog.Log.Logger.ForContext(new[] { provider }); + // If a logger was passed, the provider has already added itself as an enricher + _logger ??= Serilog.Log.Logger.ForContext(new[] { provider }); - if (name != null) - { - _logger = _logger.ForContext(Constants.SourceContextPropertyName, name); - } + if (name != null) + { + _logger = _logger.ForContext(Constants.SourceContextPropertyName, name); } + } - public bool IsEnabled(LogLevel logLevel) + public bool IsEnabled(LogLevel logLevel) + { + return logLevel != LogLevel.None && _logger.IsEnabled(LevelConvert.ToSerilogLevel(logLevel)); + } + + public IDisposable BeginScope(TState state) + { + return _provider.BeginScope(state); + } + + public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) + { + if (logLevel == LogLevel.None) { - return logLevel != LogLevel.None && _logger.IsEnabled(LevelConvert.ToSerilogLevel(logLevel)); + return; } - - public IDisposable BeginScope(TState state) + var level = LevelConvert.ToSerilogLevel(logLevel); + if (!_logger.IsEnabled(level)) { - return _provider.BeginScope(state); + return; } - public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) + try { - if (logLevel == LogLevel.None) - { - return; - } - 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}"); - } + Write(level, eventId, state, exception, formatter); } - - void Write(LogEventLevel level, EventId eventId, TState state, Exception exception, Func formatter) + catch (Exception ex) { - var logger = _logger; - string messageTemplate = null; + SelfLog.WriteLine($"Failed to write event through {typeof(SerilogLogger).Name}: {ex}"); + } + } - var properties = new List(); + void Write(LogEventLevel level, EventId eventId, TState state, Exception exception, Func formatter) + { + var logger = _logger; + string? messageTemplate = null; - 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); - } - } + var properties = new List(); - var stateType = state.GetType(); - var stateTypeInfo = stateType.GetTypeInfo(); - // Imperfect, but at least eliminates `1 names - if (messageTemplate == null && !stateTypeInfo.IsGenericType) + if (state is IEnumerable> structure) + { + foreach (var property in structure) + { + if (property.Key == SerilogLoggerProvider.OriginalFormatPropertyName && property.Value is string value) { - messageTemplate = "{" + stateType.Name + ":l}"; - if (logger.BindProperty(stateType.Name, AsLoggableValue(state, formatter), false, out var stateTypeProperty)) - properties.Add(stateTypeProperty); + messageTemplate = value; } - } - - if (messageTemplate == null) - { - string propertyName = null; - if (state != null) + else if (property.Key.StartsWith("@")) { - propertyName = "State"; - messageTemplate = "{State:l}"; + if (logger.BindProperty(property.Key.Substring(1), property.Value, true, out var destructured)) + properties.Add(destructured); } - else if (formatter != null) + else if (property.Key.StartsWith("$")) { - propertyName = "Message"; - messageTemplate = "{Message:l}"; + if (logger.BindProperty(property.Key.Substring(1), property.Value?.ToString(), true, out var stringified)) + properties.Add(stringified); } - - if (propertyName != null) + 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}"; + } + 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 ?? ""); + 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; + } - return new LogEventProperty("EventId", new StructureValue(properties)); + 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))); } + + 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 3ee0760..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..2a46739 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 LogEventPropertyValue? 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 deb8ace..6d6621f 100644 --- a/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLoggerScope.cs +++ b/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLoggerScope.cs @@ -1,113 +1,110 @@ // 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"; + const string NoName = "None"; - readonly SerilogLoggerProvider _provider; - readonly object _state; - readonly IDisposable _chainedDisposable; + readonly SerilogLoggerProvider _provider; + readonly object? _state; + readonly IDisposable? _chainedDisposable; - // An optimization only, no problem if there are data races on this. - bool _disposed; + // An optimization only, no problem if there are data races on this. + bool _disposed; - public SerilogLoggerScope(SerilogLoggerProvider provider, object state, IDisposable chainedDisposable = null) - { - _provider = provider; - _state = state; + public SerilogLoggerScope(SerilogLoggerProvider provider, object? state, IDisposable? chainedDisposable = null) + { + _provider = provider; + _state = state; - Parent = _provider.CurrentScope; - _provider.CurrentScope = this; - _chainedDisposable = chainedDisposable; - } + Parent = _provider.CurrentScope; + _provider.CurrentScope = this; + _chainedDisposable = chainedDisposable; + } - public SerilogLoggerScope Parent { get; } + public SerilogLoggerScope Parent { get; } - public void Dispose() + public void Dispose() + { + if (!_disposed) { - if (!_disposed) - { - _disposed = true; - - // 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; - } + _disposed = true; - _chainedDisposable?.Dispose(); + // 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 void EnrichAndCreateScopeItem(LogEvent logEvent, ILogEventPropertyFactory propertyFactory, out LogEventPropertyValue scopeItem) + public void EnrichAndCreateScopeItem(LogEvent logEvent, ILogEventPropertyFactory propertyFactory, out LogEventPropertyValue? scopeItem) + { + void AddProperty(KeyValuePair stateProperty) { - void AddProperty(KeyValuePair stateProperty) - { - 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); - } + var key = stateProperty.Key; + var destructureObject = false; + var value = stateProperty.Value; - if (_state == null) + if (key.StartsWith("@")) { - scopeItem = null; - return; + key = key.Substring(1); + destructureObject = true; } - // Eliminates boxing of Dictionary.Enumerator for the most common use case - if (_state is Dictionary dictionary) + if (key.StartsWith("$")) { - scopeItem = null; // Unless it's `FormattedLogValues`, these are treated as property bags rather than scope items. - - foreach (var stateProperty in dictionary) - { - if (stateProperty.Key == SerilogLoggerProvider.OriginalFormatPropertyName && stateProperty.Value is string) - scopeItem = new ScalarValue(_state.ToString()); - else - AddProperty(stateProperty); - } + key = key.Substring(1); + value = value?.ToString(); } - else if (_state is IEnumerable> stateProperties) + + var property = propertyFactory.CreateProperty(key, value, destructureObject); + logEvent.AddPropertyIfAbsent(property); + } + + if (_state == null) + { + scopeItem = null; + return; + } + + // Eliminates boxing of Dictionary.Enumerator for the most common use case + if (_state is Dictionary dictionary) + { + scopeItem = null; // Unless it's `FormattedLogValues`, these are treated as property bags rather than scope items. + + 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()); - else - AddProperty(stateProperty); - } + 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 9e78511..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/Serilog.Extensions.Logging.csproj b/src/Serilog.Extensions.Logging/Serilog.Extensions.Logging.csproj index 8ec5392..9f4dec5 100644 --- a/src/Serilog.Extensions.Logging/Serilog.Extensions.Logging.csproj +++ b/src/Serilog.Extensions.Logging/Serilog.Extensions.Logging.csproj @@ -5,28 +5,25 @@ 3.1.1 Microsoft;Serilog Contributors netstandard2.0 - true 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 - - - + + 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/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..30050c0 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,7 @@ - + - netcoreapp2.2 - true + net7 @@ -10,17 +9,13 @@ - - + + 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/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..0680497 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,7 @@ - netcoreapp2.0;net472 - Serilog.Extensions.Logging.Tests - ../../assets/Serilog.snk - true - true - Serilog.Extensions.Logging.Tests - true + net7;net472 @@ -15,9 +9,12 @@ - - - + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + 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 d3a0eb5..fcaf1f8 100644 --- a/test/Serilog.Extensions.Logging.Tests/SerilogLoggerTests.cs +++ b/test/Serilog.Extensions.Logging.Tests/SerilogLoggerTests.cs @@ -1,477 +1,472 @@ -// 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; -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); - 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); - } + [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); - [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); + Assert.Equal(isEnabled, logger.IsEnabled(logLevel)); + } - Assert.Equal(isEnabled, logger.IsEnabled(logLevel)); - } + [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); - [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); + logger.Log(logLevel, 0, TestMessage, null!, null!); - logger.Log(logLevel, 0, TestMessage, null, null); + Assert.Equal(expected, sink.Writes.Count); + } - Assert.Equal(expected, sink.Writes.Count); - } + [Fact] + public void LogsCorrectMessage() + { + var (logger, sink) = SetUp(LogLevel.Trace); - [Fact] - public void LogsCorrectMessage() - { - var (logger, sink) = SetUp(LogLevel.Trace); + logger.Log(LogLevel.Information, 0, null!, null!, null!); + logger.Log(LogLevel.Information, 0, TestMessage, null!, null!); + logger.Log(LogLevel.Information, 0, null!, null!, (_, __) => TestMessage); - 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(3, sink.Writes.Count); - Assert.Equal(3, sink.Writes.Count); + Assert.Equal(1, sink.Writes[0].Properties.Count); + Assert.Empty(sink.Writes[0].RenderMessage()); - Assert.Equal(1, sink.Writes[0].Properties.Count); - Assert.Empty(sink.Writes[0].RenderMessage()); + 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(2, sink.Writes[1].Properties.Count); - Assert.True(sink.Writes[1].Properties.ContainsKey("State")); - Assert.Equal(TestMessage, sink.Writes[1].RenderMessage()); + 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(2, sink.Writes[2].Properties.Count); - Assert.True(sink.Writes[2].Properties.ContainsKey("Message")); - Assert.Equal(TestMessage, sink.Writes[2].RenderMessage()); - } + [Fact] + public void CarriesException() + { + var (logger, sink) = SetUp(LogLevel.Trace); - [Fact] - public void CarriesException() - { - var (logger, sink) = SetUp(LogLevel.Trace); + var exception = new Exception(); - var exception = new Exception(); + logger.Log(LogLevel.Information, 0, "Test", exception, null!); - logger.Log(LogLevel.Information, 0, "Test", exception, null); + Assert.Single(sink.Writes); + Assert.Same(exception, sink.Writes[0].Exception); + } - Assert.Equal(1, sink.Writes.Count); - Assert.Same(exception, sink.Writes[0].Exception); - } + [Fact] + public void SingleScopeProperty() + { + var (logger, sink) = SetUp(LogLevel.Trace); - [Fact] - public void SingleScopeProperty() + using (logger.BeginScope(new FoodScope("pizza"))) { - var (logger, sink) = SetUp(LogLevel.Trace); + logger.Log(LogLevel.Information, 0, TestMessage, null!, null!); + } - using (logger.BeginScope(new FoodScope("pizza"))) - { - 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("Name")); - Assert.Equal("\"pizza\"", sink.Writes[0].Properties["Name"].ToString()); - } + [Fact] + public void StringifyScopeProperty() + { + var (logger, sink) = SetUp(LogLevel.Trace); - [Fact] - public void StringifyScopeProperty() + 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("{$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("values")); + Assert.Equal("\"System.Int32[]\"", sink.Writes[0].Properties["values"].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 NestedScopeSameProperty() + { + var (logger, sink) = SetUp(LogLevel.Trace); - [Fact] - public void NestedScopeSameProperty() + using (logger.BeginScope(new FoodScope("avocado"))) { - var (logger, sink) = SetUp(LogLevel.Trace); - - using (logger.BeginScope(new FoodScope("avocado"))) + using (logger.BeginScope(new FoodScope("bacon"))) { - using (logger.BeginScope(new FoodScope("bacon"))) - { - logger.Log(LogLevel.Information, 0, TestMessage, null, null); - } + logger.Log(LogLevel.Information, 0, TestMessage, null!, null!); } - - // 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 NestedScopesDifferentProperties() - { - var (logger, sink) = SetUp(LogLevel.Trace); + // 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"))) + using (logger.BeginScope(new FoodScope("spaghetti"))) + { + using (logger.BeginScope(new LuckyScope(7))) { - 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() - { - var selfLog = new StringWriter(); - SelfLog.Enable(selfLog); - - 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); + Assert.Single(sink.Writes); + Assert.True(sink.Writes[0].Properties.ContainsKey("Person")); - using (logger.BeginScope(new Dictionary {{ "@Person", new Person { FirstName = "John", LastName = "Smith" }}})) - { - logger.Log(LogLevel.Information, 0, TestMessage, null, null); - } + 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); + } - Assert.Equal(1, sink.Writes.Count); - Assert.True(sink.Writes[0].Properties.ContainsKey("Person")); + [Fact] + public void BeginScopeDestructuresObjectsWhenDestructurerIsUsedInDictionary() + { + 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(new Dictionary {{ "@Person", new Person { FirstName = "John", LastName = "Smith" }}})) + { + logger.Log(LogLevel.Information, 0, TestMessage, null!, null!); } - [Fact] - public void BeginScopeDoesNotModifyKeyWhenDestructurerIsNotUsedInMessageTemplate() - { - var (logger, sink) = SetUp(LogLevel.Trace); + Assert.Single(sink.Writes); + Assert.True(sink.Writes[0].Properties.ContainsKey("Person")); - using (logger.BeginScope("{FirstName}", "John")) - { - logger.Log(LogLevel.Information, 0, TestMessage, null, null); - } + 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); + } - Assert.Equal(1, sink.Writes.Count); - Assert.True(sink.Writes[0].Properties.ContainsKey("FirstName")); - } + [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); + class Person + { + // ReSharper disable once UnusedAutoPropertyAccessor.Local + public string? FirstName { get; set; } - logger.LogInformation("Some test message with {Two} {Properties}", "OneProperty"); + // ReSharper disable once UnusedAutoPropertyAccessor.Local + public string? LastName { get; set; } + } - Assert.Equal(0, sink.Writes.Count); - } + [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.Empty(sink.Writes); } } diff --git a/test/Serilog.Extensions.Logging.Tests/Support/DisposeTrackingLogger.cs b/test/Serilog.Extensions.Logging.Tests/Support/DisposeTrackingLogger.cs index d833c0c..7c194d1 100644 --- a/test/Serilog.Extensions.Logging.Tests/Support/DisposeTrackingLogger.cs +++ b/test/Serilog.Extensions.Logging.Tests/Support/DisposeTrackingLogger.cs @@ -1,354 +1,351 @@ -using System; -using System.Collections.Generic; using Serilog.Core; using Serilog.Events; -namespace Serilog.Extensions.Logging.Tests.Support +namespace Serilog.Extensions.Logging.Tests.Support; + +public 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; 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; } } diff --git a/test/Serilog.Extensions.Logging.Tests/Support/ExtensionsProvider.cs b/test/Serilog.Extensions.Logging.Tests/Support/ExtensionsProvider.cs index 583293c..1fc2bb0 100644 --- a/test/Serilog.Extensions.Logging.Tests/Support/ExtensionsProvider.cs +++ b/test/Serilog.Extensions.Logging.Tests/Support/ExtensionsProvider.cs @@ -1,45 +1,41 @@ // 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; + +public class ExtensionsProvider : ILoggerProvider, Microsoft.Extensions.Logging.ILogger { - public class ExtensionsProvider : ILoggerProvider, Microsoft.Extensions.Logging.ILogger + private readonly LogLevel enabledLevel; + public List<(LogLevel logLevel, EventId eventId, object? state, Exception exception, string message)> Writes { get; set; } = new(); + + 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() { - 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 +} From ef975c86aca617ac34b89b8c7e9f2943d3b6f255 Mon Sep 17 00:00:00 2001 From: Ivan Maximov Date: Sat, 4 Mar 2023 11:33:23 +0300 Subject: [PATCH 07/22] fix --- .../Serilog.Extensions.Logging.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Serilog.Extensions.Logging/Serilog.Extensions.Logging.csproj b/src/Serilog.Extensions.Logging/Serilog.Extensions.Logging.csproj index 9f4dec5..c37ff7b 100644 --- a/src/Serilog.Extensions.Logging/Serilog.Extensions.Logging.csproj +++ b/src/Serilog.Extensions.Logging/Serilog.Extensions.Logging.csproj @@ -17,7 +17,7 @@ true true True - ..\..\README.md + README.md From 6b6af9cf9be4f9cf7b7403c1bb03233d90ee3449 Mon Sep 17 00:00:00 2001 From: Ivan Maximov Date: Mon, 6 Mar 2023 18:59:39 +0300 Subject: [PATCH 08/22] Cache trimmed keys for @ and $ --- .../Extensions/Logging/SerilogLogger.cs | 8 ++- .../Extensions/Logging/SerilogLoggerScope.cs | 8 +-- .../LogEventBenchmark.cs | 63 +++++++++++++++++++ 3 files changed, 73 insertions(+), 6 deletions(-) create mode 100644 test/Serilog.Extensions.Logging.Benchmarks/LogEventBenchmark.cs diff --git a/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLogger.cs b/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLogger.cs index 78d72dd..e6e306e 100644 --- a/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLogger.cs +++ b/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLogger.cs @@ -10,11 +10,15 @@ using FrameworkLogger = Microsoft.Extensions.Logging.ILogger; using System.Reflection; using Serilog.Debugging; +using System.Collections.Concurrent; namespace Serilog.Extensions.Logging { class SerilogLogger : FrameworkLogger { + internal static readonly ConcurrentDictionary DestructureDictionary = new ConcurrentDictionary(); + internal static readonly ConcurrentDictionary StringifyDictionary = new ConcurrentDictionary(); + readonly SerilogLoggerProvider _provider; readonly ILogger _logger; @@ -91,12 +95,12 @@ void Write(LogEventLevel level, EventId eventId, TState state, Exception } else if (property.Key.StartsWith("@")) { - if (logger.BindProperty(property.Key.Substring(1), property.Value, true, out var destructured)) + if (logger.BindProperty(DestructureDictionary.GetOrAdd(property.Key, k => k.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)) + if (logger.BindProperty(StringifyDictionary.GetOrAdd(property.Key, k => k.Substring(1)), property.Value?.ToString(), true, out var stringified)) properties.Add(stringified); } else diff --git a/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLoggerScope.cs b/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLoggerScope.cs index deb8ace..4712327 100644 --- a/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLoggerScope.cs +++ b/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLoggerScope.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections.Concurrent; using System.Collections.Generic; using Serilog.Core; using Serilog.Events; @@ -59,13 +60,12 @@ void AddProperty(KeyValuePair stateProperty) if (key.StartsWith("@")) { - key = key.Substring(1); + key = SerilogLogger.DestructureDictionary.GetOrAdd(key, k => k.Substring(1)); destructureObject = true; } - - if (key.StartsWith("$")) + else if (key.StartsWith("$")) { - key = key.Substring(1); + key = SerilogLogger.StringifyDictionary.GetOrAdd(key, k => k.Substring(1)); value = value?.ToString(); } diff --git a/test/Serilog.Extensions.Logging.Benchmarks/LogEventBenchmark.cs b/test/Serilog.Extensions.Logging.Benchmarks/LogEventBenchmark.cs new file mode 100644 index 0000000..82e4bad --- /dev/null +++ b/test/Serilog.Extensions.Logging.Benchmarks/LogEventBenchmark.cs @@ -0,0 +1,63 @@ +// 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; + +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"); + } + } +} From a50d9e6d41a88f07682f79505f8ba100cee75941 Mon Sep 17 00:00:00 2001 From: Ivan Maximov Date: Mon, 6 Mar 2023 19:08:59 +0300 Subject: [PATCH 09/22] 1 --- .../Serilog.Extensions.Logging.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Serilog.Extensions.Logging/Serilog.Extensions.Logging.csproj b/src/Serilog.Extensions.Logging/Serilog.Extensions.Logging.csproj index c37ff7b..27d593b 100644 --- a/src/Serilog.Extensions.Logging/Serilog.Extensions.Logging.csproj +++ b/src/Serilog.Extensions.Logging/Serilog.Extensions.Logging.csproj @@ -4,7 +4,7 @@ Low-level Serilog provider for Microsoft.Extensions.Logging 3.1.1 Microsoft;Serilog Contributors - netstandard2.0 + netstandard2.0 true serilog;Microsoft.Extensions.Logging serilog-extension-nuget.png From 8bdcc81378261355f7fe12850b7ccd2cdc824dd6 Mon Sep 17 00:00:00 2001 From: Ivan Maximov Date: Thu, 9 Mar 2023 14:22:30 +0300 Subject: [PATCH 10/22] fix --- .../Extensions/Logging/SerilogLogger.cs | 251 +++++++++--------- 1 file changed, 125 insertions(+), 126 deletions(-) diff --git a/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLogger.cs b/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLogger.cs index 6a08ac9..a478473 100644 --- a/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLogger.cs +++ b/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLogger.cs @@ -9,171 +9,170 @@ 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 ConcurrentDictionary(); - internal static readonly ConcurrentDictionary StringifyDictionary = new ConcurrentDictionary(); + internal static readonly ConcurrentDictionary DestructureDictionary = new ConcurrentDictionary(); + internal static readonly ConcurrentDictionary StringifyDictionary = new ConcurrentDictionary(); - readonly SerilogLoggerProvider _provider; - readonly ILogger _logger; + readonly SerilogLoggerProvider _provider; + readonly ILogger _logger; - static readonly CachingMessageTemplateParser MessageTemplateParser = new(); + static readonly CachingMessageTemplateParser MessageTemplateParser = new(); - // 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(); + // 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(); - public SerilogLogger( - SerilogLoggerProvider provider, - ILogger? logger = null, - string? name = null) - { - _provider = provider ?? throw new ArgumentNullException(nameof(provider)); - _logger = logger!; + public SerilogLogger( + SerilogLoggerProvider provider, + ILogger? logger = null, + string? name = null) + { + _provider = provider ?? throw new ArgumentNullException(nameof(provider)); + _logger = logger!; - // If a logger was passed, the provider has already added itself as an enricher - _logger ??= Serilog.Log.Logger.ForContext(new[] { provider }); + // If a logger was passed, the provider has already added itself as an enricher + _logger ??= Serilog.Log.Logger.ForContext(new[] { provider }); - if (name != null) - { - _logger = _logger.ForContext(Constants.SourceContextPropertyName, name); - } + if (name != null) + { + _logger = _logger.ForContext(Constants.SourceContextPropertyName, name); } + } - public bool IsEnabled(LogLevel logLevel) + public bool IsEnabled(LogLevel logLevel) + { + return logLevel != LogLevel.None && _logger.IsEnabled(LevelConvert.ToSerilogLevel(logLevel)); + } + + public IDisposable BeginScope(TState state) + { + return _provider.BeginScope(state); + } + + public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) + { + if (logLevel == LogLevel.None) { - return logLevel != LogLevel.None && _logger.IsEnabled(LevelConvert.ToSerilogLevel(logLevel)); + return; } - - public IDisposable BeginScope(TState state) + var level = LevelConvert.ToSerilogLevel(logLevel); + if (!_logger.IsEnabled(level)) { - return _provider.BeginScope(state); + return; } - public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) + try { - if (logLevel == LogLevel.None) - { - return; - } - 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}"); - } + Write(level, eventId, state, exception, formatter); } - - void Write(LogEventLevel level, EventId eventId, TState state, Exception exception, Func formatter) + catch (Exception ex) { - var logger = _logger; - string? messageTemplate = null; + SelfLog.WriteLine($"Failed to write event through {typeof(SerilogLogger).Name}: {ex}"); + } + } - var properties = new List(); + void Write(LogEventLevel level, EventId eventId, TState state, Exception exception, Func formatter) + { + var logger = _logger; + string? messageTemplate = null; - 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(DestructureDictionary.GetOrAdd(property.Key, k => k.Substring(1)), property.Value, true, out var destructured)) - properties.Add(destructured); - } - else if (property.Key.StartsWith("$")) - { - if (logger.BindProperty(StringifyDictionary.GetOrAdd(property.Key, k => k.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); - } - } + var properties = new List(); - var stateType = state.GetType(); - var stateTypeInfo = stateType.GetTypeInfo(); - // Imperfect, but at least eliminates `1 names - if (messageTemplate == null && !stateTypeInfo.IsGenericType) + if (state is IEnumerable> structure) + { + foreach (var property in structure) + { + if (property.Key == SerilogLoggerProvider.OriginalFormatPropertyName && property.Value is string value) { - messageTemplate = "{" + stateType.Name + ":l}"; - if (logger.BindProperty(stateType.Name, AsLoggableValue(state, formatter), false, out var stateTypeProperty)) - properties.Add(stateTypeProperty); + messageTemplate = value; } - } - - if (messageTemplate == null) - { - string? propertyName = null; - if (state != null) + else if (property.Key.StartsWith("@")) { - propertyName = "State"; - messageTemplate = "{State:l}"; + if (logger.BindProperty(DestructureDictionary.GetOrAdd(property.Key, k => k.Substring(1)), property.Value, true, out var destructured)) + properties.Add(destructured); } - else if (formatter != null) + else if (property.Key.StartsWith("$")) { - propertyName = "Message"; - messageTemplate = "{Message:l}"; + if (logger.BindProperty(StringifyDictionary.GetOrAdd(property.Key, k => k.Substring(1)), property.Value?.ToString(), true, out var stringified)) + properties.Add(stringified); } - - if (propertyName != null) + 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}"; + } + 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 ?? ""); + 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; + } - return new LogEventProperty("EventId", new StructureValue(properties)); + 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))); } + + if (eventId.Name != null) + { + properties.Add(new LogEventProperty("Name", new ScalarValue(eventId.Name))); + } + + return new LogEventProperty("EventId", new StructureValue(properties)); } } From 04a36b07892872e3f8b4995b36e31cc042e642ab Mon Sep 17 00:00:00 2001 From: Ivan Maximov Date: Thu, 9 Mar 2023 14:33:37 +0300 Subject: [PATCH 11/22] Add limit --- .../Extensions/Logging/SerilogLogger.cs | 17 +++++++++++++---- .../Extensions/Logging/SerilogLoggerScope.cs | 4 ++-- .../LogEventBenchmark.cs | 8 +++++--- 3 files changed, 20 insertions(+), 9 deletions(-) diff --git a/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLogger.cs b/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLogger.cs index a478473..9c79bcd 100644 --- a/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLogger.cs +++ b/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLogger.cs @@ -13,8 +13,17 @@ namespace Serilog.Extensions.Logging; class SerilogLogger : FrameworkLogger { - internal static readonly ConcurrentDictionary DestructureDictionary = new ConcurrentDictionary(); - internal static readonly ConcurrentDictionary StringifyDictionary = new ConcurrentDictionary(); + internal static readonly ConcurrentDictionary DestructureDictionary = new(); + internal static readonly ConcurrentDictionary StringifyDictionary = new(); + + internal static string GetKeyWithoutFirstSymbol(ConcurrentDictionary source, string key) + { + 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); + } readonly SerilogLoggerProvider _provider; readonly ILogger _logger; @@ -92,12 +101,12 @@ void Write(LogEventLevel level, EventId eventId, TState state, Exception } else if (property.Key.StartsWith("@")) { - if (logger.BindProperty(DestructureDictionary.GetOrAdd(property.Key, k => k.Substring(1)), property.Value, true, out var destructured)) + if (logger.BindProperty(GetKeyWithoutFirstSymbol(DestructureDictionary, property.Key), property.Value, true, out var destructured)) properties.Add(destructured); } else if (property.Key.StartsWith("$")) { - if (logger.BindProperty(StringifyDictionary.GetOrAdd(property.Key, k => k.Substring(1)), property.Value?.ToString(), true, out var stringified)) + if (logger.BindProperty(GetKeyWithoutFirstSymbol(StringifyDictionary, property.Key), property.Value?.ToString(), true, out var stringified)) properties.Add(stringified); } else diff --git a/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLoggerScope.cs b/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLoggerScope.cs index 39d9cdf..f2d6c8a 100644 --- a/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLoggerScope.cs +++ b/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLoggerScope.cs @@ -59,12 +59,12 @@ void AddProperty(KeyValuePair stateProperty) if (key.StartsWith("@")) { - key = SerilogLogger.DestructureDictionary.GetOrAdd(key, k => k.Substring(1)); + key = SerilogLogger.GetKeyWithoutFirstSymbol(SerilogLogger.DestructureDictionary, key); destructureObject = true; } else if (key.StartsWith("$")) { - key = SerilogLogger.StringifyDictionary.GetOrAdd(key, k => k.Substring(1)); + key = SerilogLogger.GetKeyWithoutFirstSymbol(SerilogLogger.StringifyDictionary, key); value = value?.ToString(); } diff --git a/test/Serilog.Extensions.Logging.Benchmarks/LogEventBenchmark.cs b/test/Serilog.Extensions.Logging.Benchmarks/LogEventBenchmark.cs index 82e4bad..995d462 100644 --- a/test/Serilog.Extensions.Logging.Benchmarks/LogEventBenchmark.cs +++ b/test/Serilog.Extensions.Logging.Benchmarks/LogEventBenchmark.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. @@ -18,6 +18,8 @@ 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] @@ -25,7 +27,7 @@ public class LogEventBenchmark { private class Person { - public string Name { get; set; } + public string? Name { get; set; } public int Age { get; set; } public override string ToString() => "Fixed text"; } @@ -36,7 +38,7 @@ private class Person public LogEventBenchmark() { var underlyingLogger = new LoggerConfiguration().CreateLogger(); - _melLogger = new SerilogLoggerProvider(underlyingLogger).CreateLogger(GetType().FullName); + _melLogger = new SerilogLoggerProvider(underlyingLogger).CreateLogger(GetType().FullName!); _bob = new Person { Name = "Bob", Age = 42 }; _alice = new Person { Name = "Alice", Age = 42 }; } From cec01f640f17b190f7c07e714cf64c98145fbd51 Mon Sep 17 00:00:00 2001 From: Ivan Maximov Date: Thu, 9 Mar 2023 20:56:13 +0300 Subject: [PATCH 12/22] Set default categoryName --- .../Extensions/Logging/LoggerProviderCollectionSink.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Serilog.Extensions.Logging/Extensions/Logging/LoggerProviderCollectionSink.cs b/src/Serilog.Extensions.Logging/Extensions/Logging/LoggerProviderCollectionSink.cs index 2a3a586..0febb52 100644 --- a/src/Serilog.Extensions.Logging/Extensions/Logging/LoggerProviderCollectionSink.cs +++ b/src/Serilog.Extensions.Logging/Extensions/Logging/LoggerProviderCollectionSink.cs @@ -29,7 +29,7 @@ public LoggerProviderCollectionSink(LoggerProviderCollection providers) public void Emit(LogEvent logEvent) { - string? categoryName = null; + string categoryName = "None"; EventId eventId = default; if (logEvent.Properties.TryGetValue("SourceContext", out var sourceContextProperty) && From 9fc84d126ba61e7f3aa6b2d965dac1aeddb40a7e Mon Sep 17 00:00:00 2001 From: Ivan Maximov Date: Fri, 10 Mar 2023 19:53:07 +0300 Subject: [PATCH 13/22] Do not swallow exception from Audit --- .../Extensions/Logging/SerilogLogger.cs | 9 ++++--- .../SerilogLoggerTests.cs | 24 +++++++++++++++++++ 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLogger.cs b/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLogger.cs index 9c79bcd..18aaed8 100644 --- a/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLogger.cs +++ b/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLogger.cs @@ -74,18 +74,20 @@ public void Log(LogLevel logLevel, EventId eventId, TState state, Except return; } + bool parsed = false; try { - Write(level, eventId, state, exception, formatter); + Write(level, eventId, state, exception, formatter, out parsed); } - catch (Exception ex) + catch (Exception ex) when (!parsed) { SelfLog.WriteLine($"Failed to write event through {typeof(SerilogLogger).Name}: {ex}"); } } - void Write(LogEventLevel level, EventId eventId, TState state, Exception exception, Func formatter) + void Write(LogEventLevel level, EventId eventId, TState state, Exception exception, Func formatter, out bool parsed) { + parsed = false; var logger = _logger; string? messageTemplate = null; @@ -153,6 +155,7 @@ void Write(LogEventLevel level, EventId eventId, TState state, Exception var parsedTemplate = MessageTemplateParser.Parse(messageTemplate ?? ""); var evt = new LogEvent(DateTimeOffset.Now, level, exception, parsedTemplate, properties); + parsed = true; logger.Write(evt); } diff --git a/test/Serilog.Extensions.Logging.Tests/SerilogLoggerTests.cs b/test/Serilog.Extensions.Logging.Tests/SerilogLoggerTests.cs index fcaf1f8..94da8bb 100644 --- a/test/Serilog.Extensions.Logging.Tests/SerilogLoggerTests.cs +++ b/test/Serilog.Extensions.Logging.Tests/SerilogLoggerTests.cs @@ -7,6 +7,7 @@ using Serilog.Debugging; using Serilog.Extensions.Logging.Tests.Support; using Xunit; +using Serilog.Core; namespace Serilog.Extensions.Logging.Tests; @@ -469,4 +470,27 @@ public void MismatchedMessageTemplateParameterCountIsHandled() 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"); + } + } } From 6da75a2b8608d965d130fee72deccd8f2d78a862 Mon Sep 17 00:00:00 2001 From: Ivan Maximov Date: Sat, 11 Mar 2023 08:45:16 +0300 Subject: [PATCH 14/22] rewrite --- .../Extensions/Logging/SerilogLogger.cs | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLogger.cs b/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLogger.cs index 18aaed8..1bf4bab 100644 --- a/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLogger.cs +++ b/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLogger.cs @@ -74,21 +74,23 @@ public void Log(LogLevel logLevel, EventId eventId, TState state, Except return; } - bool parsed = false; + LogEvent? evt = null; try { - Write(level, eventId, state, exception, formatter, out parsed); + evt = PrepareWrite(level, eventId, state, exception, formatter); } - catch (Exception ex) when (!parsed) + catch (Exception ex) { SelfLog.WriteLine($"Failed to write event through {typeof(SerilogLogger).Name}: {ex}"); } + + // 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); } - void Write(LogEventLevel level, EventId eventId, TState state, Exception exception, Func formatter, out bool parsed) + LogEvent PrepareWrite(LogEventLevel level, EventId eventId, TState state, Exception exception, Func formatter) { - parsed = false; - var logger = _logger; string? messageTemplate = null; var properties = new List(); @@ -103,17 +105,17 @@ void Write(LogEventLevel level, EventId eventId, TState state, Exception } else if (property.Key.StartsWith("@")) { - if (logger.BindProperty(GetKeyWithoutFirstSymbol(DestructureDictionary, property.Key), property.Value, true, out var destructured)) + if (_logger.BindProperty(GetKeyWithoutFirstSymbol(DestructureDictionary, property.Key), property.Value, true, out var destructured)) properties.Add(destructured); } else if (property.Key.StartsWith("$")) { - if (logger.BindProperty(GetKeyWithoutFirstSymbol(StringifyDictionary, property.Key), property.Value?.ToString(), true, out var stringified)) + if (_logger.BindProperty(GetKeyWithoutFirstSymbol(StringifyDictionary, property.Key), property.Value?.ToString(), true, out var stringified)) properties.Add(stringified); } else { - if (logger.BindProperty(property.Key, property.Value, false, out var bound)) + if (_logger.BindProperty(property.Key, property.Value, false, out var bound)) properties.Add(bound); } } @@ -124,7 +126,7 @@ void Write(LogEventLevel level, EventId eventId, TState state, Exception if (messageTemplate == null && !stateTypeInfo.IsGenericType) { messageTemplate = "{" + stateType.Name + ":l}"; - if (logger.BindProperty(stateType.Name, AsLoggableValue(state, formatter), false, out var stateTypeProperty)) + if (_logger.BindProperty(stateType.Name, AsLoggableValue(state, formatter), false, out var stateTypeProperty)) properties.Add(stateTypeProperty); } } @@ -145,7 +147,7 @@ void Write(LogEventLevel level, EventId eventId, TState state, Exception if (propertyName != null) { - if (logger.BindProperty(propertyName, AsLoggableValue(state, formatter!), false, out var property)) + if (_logger.BindProperty(propertyName, AsLoggableValue(state, formatter!), false, out var property)) properties.Add(property); } } @@ -154,9 +156,7 @@ void Write(LogEventLevel level, EventId eventId, TState state, Exception properties.Add(CreateEventIdProperty(eventId)); var parsedTemplate = MessageTemplateParser.Parse(messageTemplate ?? ""); - var evt = new LogEvent(DateTimeOffset.Now, level, exception, parsedTemplate, properties); - parsed = true; - logger.Write(evt); + return new LogEvent(DateTimeOffset.Now, level, exception, parsedTemplate, properties); } static object? AsLoggableValue(TState state, Func formatter) From c102f60a147af92375a45dbafab1eca78a9dbba3 Mon Sep 17 00:00:00 2001 From: Ivan Maximov Date: Mon, 13 Mar 2023 09:22:22 +0300 Subject: [PATCH 15/22] Add API approval test --- .../Extensions/Logging/LevelConvert.cs | 49 +++++++------------ .../ApiApprovalTests.cs | 26 ++++++++++ .../Serilog.Extensions.Logging.Tests.csproj | 2 + .../Serilog.Extensions.Logging.approved.txt | 46 +++++++++++++++++ 4 files changed, 91 insertions(+), 32 deletions(-) create mode 100644 test/Serilog.Extensions.Logging.Tests/ApiApprovalTests.cs create mode 100644 test/Serilog.Extensions.Logging.Tests/Serilog.Extensions.Logging.approved.txt diff --git a/src/Serilog.Extensions.Logging/Extensions/Logging/LevelConvert.cs b/src/Serilog.Extensions.Logging/Extensions/Logging/LevelConvert.cs index 7f1923d..c89c386 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. @@ -34,23 +34,15 @@ public static class LevelConvert /// different semantics. public static LogEventLevel ToSerilogLevel(LogLevel logLevel) { - switch (logLevel) + return logLevel switch { - 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, + }; } /// @@ -60,21 +52,14 @@ public static LogEventLevel ToSerilogLevel(LogLevel logLevel) /// The Microsoft.Extensions.Logging equivalent of . public static LogLevel ToExtensionsLevel(LogEventLevel logEventLevel) { - switch (logEventLevel) + return logEventLevel switch { - 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/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/Serilog.Extensions.Logging.Tests.csproj b/test/Serilog.Extensions.Logging.Tests/Serilog.Extensions.Logging.Tests.csproj index 0680497..7650199 100644 --- a/test/Serilog.Extensions.Logging.Tests/Serilog.Extensions.Logging.Tests.csproj +++ b/test/Serilog.Extensions.Logging.Tests/Serilog.Extensions.Logging.Tests.csproj @@ -15,6 +15,8 @@ runtime; build; native; contentfiles; analyzers; buildtransitive + + 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 From e51f56660ba908fe4d48a4382429c4a23dae1f4f Mon Sep 17 00:00:00 2001 From: Nicholas Blumhardt Date: Mon, 13 Mar 2023 16:39:52 +1000 Subject: [PATCH 16/22] Update appveyor.yml --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index c755a69..e6e8a2f 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -11,7 +11,7 @@ artifacts: deploy: - provider: NuGet api_key: - secure: kYR3BYzJm3wSFbFjPhgTzuDHHcE8ApoNUmHvJvunWZ39nyrqVk6J6srjzYVQ7/gX + secure: EN9f+XXE3fW+ebL4wxrIbafdtbNvRfddBN8UUixvctYh4qMBHzr1JdnM83QsM1zo skip_symbols: true on: branch: /^(master|dev)$/ From d76a80f2c55b611cd0283ec7124c36f2e402f5f8 Mon Sep 17 00:00:00 2001 From: Nicholas Blumhardt Date: Wed, 3 May 2023 14:51:20 +1000 Subject: [PATCH 17/22] v7.0 - pin to MEL v7, including matching target frameworks; update to Serilog v2.12 and apply annotations for nullable reference types --- Directory.Build.props | 3 +- README.md | 16 +-- appveyor.yml | 4 +- samples/Sample/Program.cs | 107 ++++++++---------- samples/Sample/Sample.csproj | 5 +- serilog-extensions-logging.sln.DotSettings | 2 + .../Logging/CachingMessageTemplateParser.cs | 2 +- .../Extensions/Logging/LevelConvert.cs | 6 +- .../Extensions/Logging/SerilogLogValues.cs | 18 +-- .../Extensions/Logging/SerilogLogger.cs | 23 ++-- .../Logging/SerilogLoggerProvider.cs | 6 +- .../Extensions/Logging/SerilogLoggerScope.cs | 4 +- .../Properties/AssemblyInfo.cs | 9 +- .../Serilog.Extensions.Logging.csproj | 10 +- ...rilog.Extensions.Logging.Benchmarks.csproj | 8 +- .../Serilog.Extensions.Logging.Tests.csproj | 8 +- .../Support/DisposeTrackingLogger.cs | 97 ++++++++-------- .../Support/ExtensionsProvider.cs | 15 +-- 18 files changed, 170 insertions(+), 173 deletions(-) create mode 100644 serilog-extensions-logging.sln.DotSettings diff --git a/Directory.Build.props b/Directory.Build.props index 3883932..2b620bc 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -7,7 +7,6 @@ $(MSBuildThisFileDirectory)assets/Serilog.snk true enable - enable - \ No newline at end of file + diff --git a/README.md b/README.md index 9649d58..4c67766 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 diff --git a/appveyor.yml b/appveyor.yml index e6e8a2f..3e4a484 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -14,12 +14,12 @@ deploy: 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/samples/Sample/Program.cs b/samples/Sample/Program.cs index 9ba6bf0..d70b9aa 100644 --- a/samples/Sample/Program.cs +++ b/samples/Sample/Program.cs @@ -3,75 +3,66 @@ using Serilog; using Serilog.Extensions.Logging; -namespace Sample; +// Creating a `LoggerProviderCollection` lets Serilog optionally write +// events through other dynamically-added MEL ILoggerProviders. +var providers = new LoggerProviderCollection(); -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(); - - 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 44ba8e2..ef89f43 100644 --- a/samples/Sample/Sample.csproj +++ b/samples/Sample/Sample.csproj @@ -1,9 +1,10 @@  - net7 + net7.0 Sample Exe + enable @@ -16,4 +17,4 @@ - \ No newline at end of file + diff --git a/serilog-extensions-logging.sln.DotSettings b/serilog-extensions-logging.sln.DotSettings new file mode 100644 index 0000000..23a0f99 --- /dev/null +++ b/serilog-extensions-logging.sln.DotSettings @@ -0,0 +1,2 @@ + + 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 294f201..8966ef7 100644 --- a/src/Serilog.Extensions.Logging/Extensions/Logging/CachingMessageTemplateParser.cs +++ b/src/Serilog.Extensions.Logging/Extensions/Logging/CachingMessageTemplateParser.cs @@ -38,7 +38,7 @@ public MessageTemplate Parse(string messageTemplate) // ReSharper disable once InconsistentlySynchronizedField // ignored warning because this is by design - var result = (MessageTemplate)_templates[messageTemplate]; + var result = (MessageTemplate?)_templates[messageTemplate]; if (result != null) return result; diff --git a/src/Serilog.Extensions.Logging/Extensions/Logging/LevelConvert.cs b/src/Serilog.Extensions.Logging/Extensions/Logging/LevelConvert.cs index c89c386..a77494e 100644 --- a/src/Serilog.Extensions.Logging/Extensions/Logging/LevelConvert.cs +++ b/src/Serilog.Extensions.Logging/Extensions/Logging/LevelConvert.cs @@ -27,9 +27,9 @@ public static class LevelConvert /// /// Convert to the equivalent Serilog . /// - /// A Microsoft.Extensions.Logging . + /// A Microsoft.Extensions.Logging . /// The Serilog equivalent of . - /// The value has no Serilog equivalent. It is mapped to + /// 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) @@ -46,7 +46,7 @@ public static LogEventLevel ToSerilogLevel(LogLevel logLevel) } /// - /// Convert to the equivalent Microsoft.Extensions.Logging . + /// Convert to the equivalent Microsoft.Extensions.Logging . /// /// A Serilog . /// The Microsoft.Extensions.Logging equivalent of . diff --git a/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLogValues.cs b/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLogValues.cs index 930f457..20253f8 100644 --- a/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLogValues.cs +++ b/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLogValues.cs @@ -17,14 +17,14 @@ 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. - private readonly MessageTemplate _messageTemplate; - private readonly IReadOnlyDictionary _properties; - private readonly KeyValuePair[] _values; + readonly MessageTemplate _messageTemplate; + readonly IReadOnlyDictionary _properties; + readonly KeyValuePair[] _values; public SerilogLogValues(MessageTemplate messageTemplate, IReadOnlyDictionary properties) { @@ -34,24 +34,24 @@ public SerilogLogValues(MessageTemplate messageTemplate, IReadOnlyDictionary interface expects indexed access - _values = new KeyValuePair[_properties.Count + 1]; + _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); + _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("{OriginalFormat}", _messageTemplate.Text); } - public KeyValuePair this[int index] + public KeyValuePair this[int index] { get => _values[index]; } 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); diff --git a/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLogger.cs b/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLogger.cs index 1bf4bab..edcef30 100644 --- a/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLogger.cs +++ b/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLogger.cs @@ -41,10 +41,9 @@ public SerilogLogger( string? name = null) { _provider = provider ?? throw new ArgumentNullException(nameof(provider)); - _logger = logger!; // If a logger was passed, the provider has already added itself as an enricher - _logger ??= Serilog.Log.Logger.ForContext(new[] { provider }); + _logger = logger ?? Serilog.Log.Logger.ForContext(new[] { provider }); if (name != null) { @@ -57,12 +56,12 @@ 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) + public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter) { if (logLevel == LogLevel.None) { @@ -81,7 +80,7 @@ public void Log(LogLevel logLevel, EventId eventId, TState state, Except } catch (Exception ex) { - SelfLog.WriteLine($"Failed to write event through {typeof(SerilogLogger).Name}: {ex}"); + SelfLog.WriteLine($"Failed to write event through {nameof(SerilogLogger)}: {ex}"); } // 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. @@ -89,7 +88,7 @@ public void Log(LogLevel logLevel, EventId eventId, TState state, Except _logger.Write(evt); } - LogEvent PrepareWrite(LogEventLevel level, EventId eventId, TState state, Exception exception, Func formatter) + LogEvent PrepareWrite(LogEventLevel level, EventId eventId, TState state, Exception? exception, Func formatter) { string? messageTemplate = null; @@ -139,7 +138,9 @@ LogEvent PrepareWrite(LogEventLevel level, EventId eventId, TState state propertyName = "State"; messageTemplate = "{State:l}"; } - else if (formatter != null) + // `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}"; @@ -159,12 +160,12 @@ LogEvent PrepareWrite(LogEventLevel level, EventId eventId, TState state return new LogEvent(DateTimeOffset.Now, level, exception, parsedTemplate, properties); } - static object? AsLoggableValue(TState state, Func formatter) + static object? AsLoggableValue(TState state, Func? formatter) { - object? sobj = state; + object? stateObj = state; if (formatter != null) - sobj = formatter(state, null!); - return sobj; + stateObj = formatter(state, null); + return stateObj; } internal static LogEventProperty CreateEventIdProperty(EventId eventId) diff --git a/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLoggerProvider.cs b/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLoggerProvider.cs index 2a46739..3fead38 100644 --- a/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLoggerProvider.cs +++ b/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLoggerProvider.cs @@ -66,7 +66,7 @@ public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory) 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) { @@ -82,9 +82,9 @@ public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory) } } - readonly AsyncLocal _value = new(); + readonly AsyncLocal _value = new(); - internal SerilogLoggerScope CurrentScope + internal SerilogLoggerScope? CurrentScope { get => _value.Value; set => _value.Value = value; diff --git a/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLoggerScope.cs b/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLoggerScope.cs index f2d6c8a..9d13190 100644 --- a/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLoggerScope.cs +++ b/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLoggerScope.cs @@ -1,8 +1,6 @@ // 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; @@ -29,7 +27,7 @@ public SerilogLoggerScope(SerilogLoggerProvider provider, object? state, IDispos _chainedDisposable = chainedDisposable; } - public SerilogLoggerScope Parent { get; } + public SerilogLoggerScope? Parent { get; } public void Dispose() { 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 27d593b..f9da570 100644 --- a/src/Serilog.Extensions.Logging/Serilog.Extensions.Logging.csproj +++ b/src/Serilog.Extensions.Logging/Serilog.Extensions.Logging.csproj @@ -2,9 +2,10 @@ Low-level Serilog provider for Microsoft.Extensions.Logging - 3.1.1 + 7.0.0 Microsoft;Serilog Contributors - netstandard2.0 + + net462;netstandard2.0;netstandard2.1;net6.0;net7.0 true serilog;Microsoft.Extensions.Logging serilog-extension-nuget.png @@ -24,8 +25,9 @@ - - + + + 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 30050c0..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,7 +1,8 @@ - net7 + net7.0 + enable @@ -10,10 +11,7 @@ - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - + 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 7650199..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,7 +1,8 @@ - net7;net472 + net7.0;net472 + enable @@ -10,10 +11,7 @@ - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/test/Serilog.Extensions.Logging.Tests/Support/DisposeTrackingLogger.cs b/test/Serilog.Extensions.Logging.Tests/Support/DisposeTrackingLogger.cs index 7c194d1..8effbfc 100644 --- a/test/Serilog.Extensions.Logging.Tests/Support/DisposeTrackingLogger.cs +++ b/test/Serilog.Extensions.Logging.Tests/Support/DisposeTrackingLogger.cs @@ -1,11 +1,12 @@ +using System.Diagnostics.CodeAnalysis; using Serilog.Core; using Serilog.Events; namespace Serilog.Extensions.Logging.Tests.Support; -public class DisposeTrackingLogger : ILogger, IDisposable +sealed class DisposeTrackingLogger : ILogger, IDisposable { - public bool IsDisposed { get; set; } + public bool IsDisposed { get; private set; } public ILogger ForContext(ILogEventEnricher enricher) { @@ -17,7 +18,7 @@ public ILogger ForContext(IEnumerable enrichers) return new LoggerConfiguration().CreateLogger(); } - public ILogger ForContext(string propertyName, object value, bool destructureObjects = false) + public ILogger ForContext(string propertyName, object? value, bool destructureObjects = false) { return new LoggerConfiguration().CreateLogger(); } @@ -53,29 +54,29 @@ public void Write(LogEventLevel level, string messageTemplate, T0 pr { } - public void Write(LogEventLevel level, string messageTemplate, params object[] propertyValues) + 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) { } - public void Write(LogEventLevel level, Exception exception, string messageTemplate, T propertyValue) + public void Write(LogEventLevel level, Exception? exception, string messageTemplate, T propertyValue) { } - public void Write(LogEventLevel level, Exception exception, string messageTemplate, T0 propertyValue0, + public void Write(LogEventLevel level, Exception? exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1) { } - public void Write(LogEventLevel level, Exception exception, string messageTemplate, T0 propertyValue0, + 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 void Write(LogEventLevel level, Exception? exception, string messageTemplate, params object?[]? propertyValues) { } @@ -100,28 +101,28 @@ public void Verbose(string messageTemplate, T0 propertyValue0, T1 pr { } - public void Verbose(string messageTemplate, params object[] propertyValues) + public void Verbose(string messageTemplate, params object?[]? propertyValues) { } - public void Verbose(Exception exception, string messageTemplate) + public void Verbose(Exception? exception, string messageTemplate) { } - public void Verbose(Exception exception, string messageTemplate, T propertyValue) + 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) { } - 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 Verbose(Exception? exception, string messageTemplate, params object?[]? propertyValues) { } @@ -141,28 +142,28 @@ public void Debug(string messageTemplate, T0 propertyValue0, T1 prop { } - public void Debug(string messageTemplate, params object[] propertyValues) + public void Debug(string messageTemplate, params object?[]? propertyValues) { } - public void Debug(Exception exception, string messageTemplate) + public void Debug(Exception? exception, string messageTemplate) { } - public void Debug(Exception exception, string messageTemplate, T propertyValue) + 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) { } - 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 Debug(Exception? exception, string messageTemplate, params object?[]? propertyValues) { } @@ -182,28 +183,28 @@ public void Information(string messageTemplate, T0 propertyValue0, T { } - public void Information(string messageTemplate, params object[] propertyValues) + public void Information(string messageTemplate, params object?[]? propertyValues) { } - public void Information(Exception exception, string messageTemplate) + public void Information(Exception? exception, string messageTemplate) { } - public void Information(Exception exception, string messageTemplate, T propertyValue) + 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) { } - 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 Information(Exception? exception, string messageTemplate, params object?[]? propertyValues) { } @@ -223,28 +224,28 @@ public void Warning(string messageTemplate, T0 propertyValue0, T1 pr { } - public void Warning(string messageTemplate, params object[] propertyValues) + public void Warning(string messageTemplate, params object?[]? propertyValues) { } - public void Warning(Exception exception, string messageTemplate) + public void Warning(Exception? exception, string messageTemplate) { } - public void Warning(Exception exception, string messageTemplate, T propertyValue) + 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) { } - 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 Warning(Exception? exception, string messageTemplate, params object?[]? propertyValues) { } @@ -264,28 +265,28 @@ public void Error(string messageTemplate, T0 propertyValue0, T1 prop { } - public void Error(string messageTemplate, params object[] propertyValues) + public void Error(string messageTemplate, params object?[]? propertyValues) { } - public void Error(Exception exception, string messageTemplate) + public void Error(Exception? exception, string messageTemplate) { } - public void Error(Exception exception, string messageTemplate, T propertyValue) + 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) { } - 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 Error(Exception? exception, string messageTemplate, params object?[]? propertyValues) { } @@ -305,40 +306,40 @@ public void Fatal(string messageTemplate, T0 propertyValue0, T1 prop { } - public void Fatal(string messageTemplate, params object[] propertyValues) + public void Fatal(string messageTemplate, params object?[]? propertyValues) { } - public void Fatal(Exception exception, string messageTemplate) + public void Fatal(Exception? exception, string messageTemplate) { } - public void Fatal(Exception exception, string messageTemplate, T propertyValue) + 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) { } - 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 void Fatal(Exception? exception, string messageTemplate, params object?[]? propertyValues) { } - public bool BindMessageTemplate(string messageTemplate, object[] propertyValues, out MessageTemplate? parsedTemplate, - out IEnumerable? boundProperties) + 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, out LogEventProperty? property) + public bool BindProperty(string? propertyName, object? value, bool destructureObjects, [NotNullWhen(true)] out LogEventProperty? property) { property = null; return false; diff --git a/test/Serilog.Extensions.Logging.Tests/Support/ExtensionsProvider.cs b/test/Serilog.Extensions.Logging.Tests/Support/ExtensionsProvider.cs index 1fc2bb0..a27d0bd 100644 --- a/test/Serilog.Extensions.Logging.Tests/Support/ExtensionsProvider.cs +++ b/test/Serilog.Extensions.Logging.Tests/Support/ExtensionsProvider.cs @@ -5,14 +5,15 @@ namespace Serilog.Extensions.Logging.Tests.Support; -public class ExtensionsProvider : ILoggerProvider, Microsoft.Extensions.Logging.ILogger +sealed class ExtensionsProvider : ILoggerProvider, Microsoft.Extensions.Logging.ILogger { - private readonly LogLevel enabledLevel; - public List<(LogLevel logLevel, EventId eventId, object? state, Exception exception, string message)> Writes { get; set; } = new(); + readonly LogLevel _enabledLevel; + + public List<(LogLevel logLevel, EventId eventId, object? state, Exception? exception, string message)> Writes { get; } = new(); public ExtensionsProvider(LogLevel enabledLevel) { - this.enabledLevel = enabledLevel; + _enabledLevel = enabledLevel; } public Microsoft.Extensions.Logging.ILogger CreateLogger(string categoryName) @@ -20,17 +21,17 @@ public Microsoft.Extensions.Logging.ILogger CreateLogger(string categoryName) return this; } - public IDisposable BeginScope(TState state) + public IDisposable BeginScope(TState state) where TState: notnull { return this; } public bool IsEnabled(LogLevel logLevel) { - return enabledLevel <= logLevel; + return _enabledLevel <= logLevel; } - public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) + public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter) { Writes.Add((logLevel, eventId, state, exception, formatter(state, exception))); } From d525717f3d3ff36f3d8720c43930ea69a9185f95 Mon Sep 17 00:00:00 2001 From: Nicholas Blumhardt Date: Wed, 3 May 2023 14:59:01 +1000 Subject: [PATCH 18/22] Build script branch name change --- Build.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Build.ps1 b/Build.ps1 index 0687e20..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" From 552e1428d008114defa92a0db65beb3ee941dffe Mon Sep 17 00:00:00 2001 From: Nicholas Blumhardt Date: Wed, 3 May 2023 15:03:30 +1000 Subject: [PATCH 19/22] Notes on version matching --- .../Serilog.Extensions.Logging.csproj | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Serilog.Extensions.Logging/Serilog.Extensions.Logging.csproj b/src/Serilog.Extensions.Logging/Serilog.Extensions.Logging.csproj index f9da570..af20b81 100644 --- a/src/Serilog.Extensions.Logging/Serilog.Extensions.Logging.csproj +++ b/src/Serilog.Extensions.Logging/Serilog.Extensions.Logging.csproj @@ -2,9 +2,11 @@ Low-level Serilog provider for Microsoft.Extensions.Logging + 7.0.0 Microsoft;Serilog Contributors - + net462;netstandard2.0;netstandard2.1;net6.0;net7.0 true serilog;Microsoft.Extensions.Logging @@ -26,6 +28,7 @@ + From 926d0f6ce2b609b2b528d38eeed75280d49e3450 Mon Sep 17 00:00:00 2001 From: Nicholas Blumhardt Date: Wed, 3 May 2023 15:06:40 +1000 Subject: [PATCH 20/22] Don't include untracked sources --- src/Serilog.Extensions.Logging/Serilog.Extensions.Logging.csproj | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Serilog.Extensions.Logging/Serilog.Extensions.Logging.csproj b/src/Serilog.Extensions.Logging/Serilog.Extensions.Logging.csproj index af20b81..09c1cc0 100644 --- a/src/Serilog.Extensions.Logging/Serilog.Extensions.Logging.csproj +++ b/src/Serilog.Extensions.Logging/Serilog.Extensions.Logging.csproj @@ -18,7 +18,6 @@ git embedded true - true True README.md From 4bb0c388a49aeb0da847aa27c0d3966403d7aff5 Mon Sep 17 00:00:00 2001 From: Nicholas Blumhardt Date: Wed, 3 May 2023 15:19:14 +1000 Subject: [PATCH 21/22] Try a different approach to avoiding spurious NuGet pack issues --- .../Serilog.Extensions.Logging.csproj | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Serilog.Extensions.Logging/Serilog.Extensions.Logging.csproj b/src/Serilog.Extensions.Logging/Serilog.Extensions.Logging.csproj index 09c1cc0..33d764b 100644 --- a/src/Serilog.Extensions.Logging/Serilog.Extensions.Logging.csproj +++ b/src/Serilog.Extensions.Logging/Serilog.Extensions.Logging.csproj @@ -18,8 +18,11 @@ git embedded true + true True README.md + + NU5118 From 5ba09d13e28b8aa16084e89aa82d7aace670bf51 Mon Sep 17 00:00:00 2001 From: Nicholas Blumhardt Date: Wed, 3 May 2023 18:49:44 +1000 Subject: [PATCH 22/22] Quick README update to note the versioning policy --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 4c67766..bbed04e 100644 --- a/README.md +++ b/README.md @@ -144,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).