From 504fc5ca55bc1dc3860e63bd65eadd3dd15d25af Mon Sep 17 00:00:00 2001 From: Ivan Maximov Date: Mon, 25 Mar 2024 11:15:49 +0300 Subject: [PATCH] Support for ITuple --- README.md | 72 ++++++++++--------- .../Extensions/Logging/SerilogLoggerScope.cs | 12 ++++ .../Serilog.Extensions.Logging.csproj | 15 ++++ .../SerilogLoggerScopeTests.cs | 5 +- 4 files changed, 70 insertions(+), 34 deletions(-) diff --git a/README.md b/README.md index 81884fa..ff1278c 100644 --- a/README.md +++ b/README.md @@ -28,41 +28,41 @@ using Serilog; public class Startup { - public Startup(IHostingEnvironment env) - { - Log.Logger = new LoggerConfiguration() - .Enrich.FromLogContext() - .WriteTo.Console() - .CreateLogger(); - - // Other startup code + public Startup(IHostingEnvironment env) + { + Log.Logger = new LoggerConfiguration() + .Enrich.FromLogContext() + .WriteTo.Console() + .CreateLogger(); + + // Other startup code ``` **Finally, for .NET Core 2.0+**, in your `Startup` class's `Configure()` method, remove the existing logger configuration entries and call `AddSerilog()` on the provided `loggingBuilder`. ```csharp - public void ConfigureServices(IServiceCollection services) - { - services.AddLogging(loggingBuilder => - loggingBuilder.AddSerilog(dispose: true)); +public void ConfigureServices(IServiceCollection services) +{ + services.AddLogging(loggingBuilder => + loggingBuilder.AddSerilog(dispose: true)); - // Other services ... - } + // Other services ... +} ``` **For .NET Core 1.0 or 1.1**, in your `Startup` class's `Configure()` method, remove the existing logger configuration entries and call `AddSerilog()` on the provided `loggerFactory`. ``` - public void Configure(IApplicationBuilder app, - IHostingEnvironment env, - ILoggerFactory loggerfactory, - IApplicationLifetime appLifetime) - { - loggerfactory.AddSerilog(); - - // Ensure any buffered events are sent at shutdown - appLifetime.ApplicationStopped.Register(Log.CloseAndFlush); +public void Configure(IApplicationBuilder app, + IHostingEnvironment env, + ILoggerFactory loggerfactory, + IApplicationLifetime appLifetime) +{ + loggerfactory.AddSerilog(); + + // Ensure any buffered events are sent at shutdown + appLifetime.ApplicationStopped.Register(Log.CloseAndFlush); ``` That's it! With the level bumped up a little you should see log output like: @@ -87,10 +87,10 @@ _Serilog.Extensions.Logging_ captures the `ILogger`'s log category, but it's not To include the log category in the final written messages, add the `{SourceContext}` named hole to a customised `outputTemplate` parameter value when configuring the relevant sink(s). For example: ```csharp - .WriteTo.Console( - outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3}] {SourceContext}: {Message:lj}{NewLine}{Exception}") - .WriteTo.File("log.txt", - outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {SourceContext}: {Message:lj}{NewLine}{Exception}") +.WriteTo.Console( + outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3}] {SourceContext}: {Message:lj}{NewLine}{Exception}") +.WriteTo.File("log.txt", + outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {SourceContext}: {Message:lj}{NewLine}{Exception}") ``` ### Notes on Log Scopes @@ -103,7 +103,8 @@ _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: ```csharp -using (_logger.BeginScope("Transaction")) { +using (_logger.BeginScope("Transaction")) +{ _logger.LogInformation("Beginning..."); _logger.LogInformation("Completed in {DurationMs}ms...", 30); } @@ -116,7 +117,8 @@ If you simply want to add a "bag" of additional properties to your log events, h ```csharp // WRONG! Prefer the dictionary or value tuple approach below instead -using (_logger.BeginScope("TransactionId: {TransactionId}, ResponseJson: {ResponseJson}", 12345, jsonString)) { +using (_logger.BeginScope("TransactionId: {TransactionId}, ResponseJson: {ResponseJson}", 12345, jsonString)) +{ _logger.LogInformation("Completed in {DurationMs}ms...", 30); } // Example JSON output: @@ -138,11 +140,13 @@ Moreover, the template string within `BeginScope` is rather arbitrary when all y 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: ```csharp -var scopeProps = new Dictionary { +var scopeProps = new Dictionary +{ { "TransactionId", 12345 }, { "ResponseJson", jsonString }, }; -using (_logger.BeginScope(scopeProps) { +using (_logger.BeginScope(scopeProps) +{ _logger.LogInformation("Transaction completed in {DurationMs}ms...", 30); } // Example JSON output: @@ -157,10 +161,12 @@ using (_logger.BeginScope(scopeProps) { // } ``` -Alternatively provide a `ValueTuple` to this method, where `Item1` is the property name and `Item2` is the property value. Note that `T2` _must_ be `object?`. +Alternatively provide a `ValueTuple` to this method, where `Item1` is the property name and `Item2` is the property value. +Note that `T2` _must_ be `object?` if your target platform is net462 or netstandard2.0. ```csharp -using (_logger.BeginScope(("TransactionId", (object?)12345)) { +using (_logger.BeginScope(("TransactionId", 12345)) +{ _logger.LogInformation("Transaction completed in {DurationMs}ms...", 30); } // Example JSON output: diff --git a/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLoggerScope.cs b/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLoggerScope.cs index 4d4a2c1..69d5dc0 100644 --- a/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLoggerScope.cs +++ b/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLoggerScope.cs @@ -99,6 +99,17 @@ void AddProperty(string key, object? value) AddProperty(stateProperty.Key, stateProperty.Value); } } +#if FEATURE_ITUPLE + else if (_state is System.Runtime.CompilerServices.ITuple tuple && tuple.Length == 2 && tuple[0] is string s) + { + scopeItem = null; // Unless it's `FormattedLogValues`, these are treated as property bags rather than scope items. + + if (s == SerilogLoggerProvider.OriginalFormatPropertyName && tuple[1] is string) + scopeItem = new ScalarValue(_state.ToString()); + else + AddProperty(s, tuple[1]); + } +#else else if (_state is ValueTuple tuple) { scopeItem = null; // Unless it's `FormattedLogValues`, these are treated as property bags rather than scope items. @@ -108,6 +119,7 @@ void AddProperty(string key, object? value) else AddProperty(tuple.Item1, tuple.Item2); } +#endif else { scopeItem = propertyFactory.CreateProperty(NoName, _state).Value; diff --git a/src/Serilog.Extensions.Logging/Serilog.Extensions.Logging.csproj b/src/Serilog.Extensions.Logging/Serilog.Extensions.Logging.csproj index a197a45..457818f 100644 --- a/src/Serilog.Extensions.Logging/Serilog.Extensions.Logging.csproj +++ b/src/Serilog.Extensions.Logging/Serilog.Extensions.Logging.csproj @@ -35,4 +35,19 @@ + + $(DefineConstants);FEATURE_ITUPLE + + + + $(DefineConstants);FEATURE_ITUPLE + + + + $(DefineConstants);FEATURE_ITUPLE + + + + $(DefineConstants);FEATURE_ITUPLE + diff --git a/test/Serilog.Extensions.Logging.Tests/SerilogLoggerScopeTests.cs b/test/Serilog.Extensions.Logging.Tests/SerilogLoggerScopeTests.cs index 442d737..1a212c2 100644 --- a/test/Serilog.Extensions.Logging.Tests/SerilogLoggerScopeTests.cs +++ b/test/Serilog.Extensions.Logging.Tests/SerilogLoggerScopeTests.cs @@ -81,8 +81,11 @@ public void EnrichWithTupleStringObject() var (loggerProvider, logEventPropertyFactory, logEvent) = SetUp(); - +#if NET48 var state = (propertyName, (object)expectedValue); +#else + var state = (propertyName, expectedValue); +#endif var loggerScope = new SerilogLoggerScope(loggerProvider, state);