diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..e482c23 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,41 @@ +# If this file is renamed, the incrementing run attempt number will be reset. + +name: CI + +on: + push: + branches: [ "dev", "main" ] + pull_request: + branches: [ "dev", "main" ] + +env: + CI_BUILD_NUMBER_BASE: ${{ github.run_number }} + CI_TARGET_BRANCH: ${{ github.head_ref || github.ref_name }} + +jobs: + build: + + runs-on: windows-latest + + permissions: + contents: write + + steps: + - uses: actions/checkout@v4 + - name: Setup + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 9.0.x + - name: Compute build number + shell: bash + run: | + echo "CI_BUILD_NUMBER=$(($CI_BUILD_NUMBER_BASE+130))" >> $GITHUB_ENV + - name: Build and Publish + env: + DOTNET_CLI_TELEMETRY_OPTOUT: true + NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + shell: pwsh + run: | + ./Build.ps1 + diff --git a/Build.ps1 b/Build.ps1 index d8abfad..2e09e11 100644 --- a/Build.ps1 +++ b/Build.ps1 @@ -1,56 +1,79 @@ -echo "build: Build started" +Write-Output "build: Build started" Push-Location $PSScriptRoot +Write-Output "build: Tool versions follow" + +dotnet --version +dotnet --list-sdks + if(Test-Path .\artifacts) { - echo "build: Cleaning .\artifacts" - Remove-Item .\artifacts -Force -Recurse + Write-Output "build: Cleaning ./artifacts" + Remove-Item ./artifacts -Force -Recurse } & dotnet restore --no-cache -$branch = @{ $true = $env:APPVEYOR_REPO_BRANCH; $false = $(git symbolic-ref --short -q HEAD) }[$env:APPVEYOR_REPO_BRANCH -ne $NULL]; -$revision = @{ $true = "{0:00000}" -f [convert]::ToInt32("0" + $env:APPVEYOR_BUILD_NUMBER, 10); $false = "local" }[$env:APPVEYOR_BUILD_NUMBER -ne $NULL]; -$suffix = @{ $true = ""; $false = "$($branch.Substring(0, [math]::Min(10,$branch.Length)))-$revision"}[$branch -eq "main" -and $revision -ne "local"] +$dbp = [Xml] (Get-Content .\Directory.Build.props) +$versionPrefix = $dbp.Project.PropertyGroup.VersionPrefix -echo "build: Version suffix is $suffix" +Write-Output "build: Package version prefix is $versionPrefix" -foreach ($src in ls src/*) { - Push-Location $src +$branch = @{ $true = $env:CI_TARGET_BRANCH; $false = $(git symbolic-ref --short -q HEAD) }[$NULL -ne $env:CI_TARGET_BRANCH]; +$revision = @{ $true = "{0:00000}" -f [convert]::ToInt32("0" + $env:CI_BUILD_NUMBER, 10); $false = "local" }[$NULL -ne $env:CI_BUILD_NUMBER]; +$suffix = @{ $true = ""; $false = "$($branch.Substring(0, [math]::Min(10,$branch.Length)) -replace '([^a-zA-Z0-9\-]*)', '')-$revision"}[$branch -eq "main" -and $revision -ne "local"] - echo "build: Packaging project in $src" +Write-Output "build: Package version suffix is $suffix" - if ($suffix) { - & dotnet pack -c Release -o ..\..\artifacts --version-suffix=$suffix - } else { - & dotnet pack -c Release -o ..\..\artifacts - } - - if($LASTEXITCODE -ne 0) { exit 1 } - - Pop-Location +if ($suffix) { + & dotnet build -c Release --version-suffix=$suffix /p:ContinuousIntegrationBuild=true +} else { + & dotnet build -c Release /p:ContinuousIntegrationBuild=true } -foreach ($test in ls test/*.PerformanceTests) { - Push-Location $test +if($LASTEXITCODE -ne 0) { throw "Build failed" } - echo "build: Building performance test project in $test" +foreach ($src in Get-ChildItem src/*) { + Push-Location $src - & dotnet build -c Release - if($LASTEXITCODE -ne 0) { exit 2 } + Write-Output "build: Packaging project in $src" + if ($suffix) { + & dotnet pack -c Release --no-build --no-restore -o ../../artifacts --version-suffix=$suffix + } else { + & dotnet pack -c Release --no-build --no-restore -o ../../artifacts + } + if($LASTEXITCODE -ne 0) { throw "Packaging failed" } Pop-Location } -foreach ($test in ls test/*.Tests) { +foreach ($test in Get-ChildItem test/*.Tests) { Push-Location $test - echo "build: Testing project in $test" + Write-Output "build: Testing project in $test" & dotnet test -c Release - if($LASTEXITCODE -ne 0) { exit 3 } + if($LASTEXITCODE -ne 0) { throw "Testing failed" } Pop-Location } Pop-Location + +if ($env:NUGET_API_KEY) { + # GitHub Actions will only supply this to branch builds and not PRs. We publish + # builds from any branch this action targets (i.e. main and dev). + + Write-Output "build: Publishing NuGet packages" + + foreach ($nupkg in Get-ChildItem artifacts/*.nupkg) { + & dotnet nuget push -k $env:NUGET_API_KEY -s https://api.nuget.org/v3/index.json "$nupkg" + if($LASTEXITCODE -ne 0) { throw "Publishing failed" } + } + + if (!($suffix)) { + Write-Output "build: Creating release for version $versionPrefix" + + iex "gh release create v$versionPrefix --title v$versionPrefix --generate-notes $(get-item ./artifacts/*.nupkg) $(get-item ./artifacts/*.snupkg)" + } +} diff --git a/Directory.Build.props b/Directory.Build.props new file mode 100644 index 0000000..b6d027b --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,8 @@ + + + 9.0.0 + latest + enable + enable + + diff --git a/README.md b/README.md index 970535f..ba65a83 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Seq.Extensions.Logging [![Build status](https://ci.appveyor.com/api/projects/status/h7r1hv3cpd6e2ou3?svg=true)](https://ci.appveyor.com/project/datalust/seq-extensions-logging) [![NuGet Pre Release](https://img.shields.io/nuget/vpre/Seq.Extensions.Logging.svg)](https://nuget.org/packages/Seq.Extensions.Logging) +# Seq.Extensions.Logging [![NuGet Pre Release](https://img.shields.io/nuget/vpre/Seq.Extensions.Logging.svg)](https://nuget.org/packages/Seq.Extensions.Logging) [Seq](https://datalust.co/seq) is a flexible self-hosted back-end for the ASP.NET Core logging subsystem (_Microsoft.Extensions.Logging_). Log events generated by the framework and application code are sent over HTTP to a Seq server, where the structured data associated with each event is used for powerful filtering, correlation, and analysis. @@ -126,6 +126,17 @@ builder.Logging.Configure(opts => { }); ``` +### Enrichment + +You can add additional structured data to events being sent to Seq by specifying _enricher_ callbacks in the `AddSeq()` method: + +```csharp + .AddSeq(enrichers: [evt => evt.AddOrUpdateProperty( + "ThreadId", + Environment.CurrentManagedThreadId) + ])) +``` + ### Troubleshooting > Nothing showed up, what can I do? @@ -170,5 +181,5 @@ you're targeting `net8.0`, target a 8.* version of _Seq.Extensions.Logging_ for ### Credits -This package is based on a subset of the powerful [Serilog](https://serilog.net) library. Not all of the options supported by the Serilog and Seq client libraries are present in -the _Seq.Extensions.Logging_ package. +This package is based on a subset of [Serilog](https://serilog.net). Not all of the options supported by the Serilog and Seq client +libraries are present in the _Seq.Extensions.Logging_ package. diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index 05aacc5..0000000 --- a/appveyor.yml +++ /dev/null @@ -1,15 +0,0 @@ -version: '{build}' -skip_tags: true -image: Visual Studio 2022 -build_script: -- pwsh: ./Build.ps1 -test: off -artifacts: -- path: artifacts/Seq.Extensions.Logging.*.nupkg -deploy: -- provider: NuGet - api_key: - secure: Urj/cvXeFTl4NjNLrRJUZ2EpSak7Jq9JfswqrNBeDpFiTjkpjzsm3CgoOr8gRSBU - skip_symbols: true - on: - branch: /^(main|dev)$/ diff --git a/example/ConsoleExample/ConsoleExample.csproj b/example/ConsoleExample/ConsoleExample.csproj index dacb017..b9f28d4 100644 --- a/example/ConsoleExample/ConsoleExample.csproj +++ b/example/ConsoleExample/ConsoleExample.csproj @@ -2,15 +2,15 @@ Exe - net8.0 + net9.0 enable enable - - - + + + diff --git a/example/WebExample/WebExample.csproj b/example/WebExample/WebExample.csproj index 3fecad7..525bd2e 100644 --- a/example/WebExample/WebExample.csproj +++ b/example/WebExample/WebExample.csproj @@ -1,7 +1,7 @@ - net8.0 + net9.0 enable enable diff --git a/global.json b/global.json index 501e79a..d5bf446 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "8.0.100", + "version": "9.0.100", "rollForward": "latestFeature" } } \ No newline at end of file diff --git a/seq-extensions-logging.sln b/seq-extensions-logging.sln index 846b6fb..096dfc5 100644 --- a/seq-extensions-logging.sln +++ b/seq-extensions-logging.sln @@ -6,13 +6,13 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{0069BFD6-B0E EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "global", "global", "{B0D573D8-1AE2-4C5C-817A-95815067452E}" ProjectSection(SolutionItems) = preProject - appveyor.yml = appveyor.yml - Build.ps1 = Build.ps1 LICENSE = LICENSE README.md = README.md global.json = global.json .gitattributes = .gitattributes .gitignore = .gitignore + Directory.Build.props = Directory.Build.props + Build.ps1 = Build.ps1 EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{D54DE844-AC36-4872-928F-5F573D41ACAE}" @@ -27,6 +27,13 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebExample", "example\WebEx EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConsoleExample", "example\ConsoleExample\ConsoleExample.csproj", "{3C7C4F1B-7BB1-48F6-BC9E-0A81F632458B}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".github", ".github", "{98FB80CE-7AEF-4FA3-88D0-9812529BC15F}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{8BCBFBF7-24E1-491A-956E-1AC7E1183270}" + ProjectSection(SolutionItems) = preProject + .github\workflows\ci.yml = .github\workflows\ci.yml + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -58,6 +65,7 @@ Global {64A3F2C5-D71E-44A6-8DC7-99474765B32E} = {D54DE844-AC36-4872-928F-5F573D41ACAE} {FC44EC1F-AD83-4522-99B4-4B309B8CC78A} = {1C72E771-7C72-4A30-A408-5CE66E792ADF} {3C7C4F1B-7BB1-48F6-BC9E-0A81F632458B} = {1C72E771-7C72-4A30-A408-5CE66E792ADF} + {8BCBFBF7-24E1-491A-956E-1AC7E1183270} = {98FB80CE-7AEF-4FA3-88D0-9812529BC15F} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {A6DD7789-D2A5-43A4-A4FE-E138166E15C7} diff --git a/seq-extensions-logging.sln.DotSettings b/seq-extensions-logging.sln.DotSettings index 6942af0..989d8b8 100644 --- a/seq-extensions-logging.sln.DotSettings +++ b/seq-extensions-logging.sln.DotSettings @@ -1,5 +1,7 @@  <Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb"><ExtraRule Prefix="" Suffix="" Style="AaBb" /></Policy> + <Policy><Descriptor Staticness="Instance" AccessRightKinds="Private" Description="Instance fields (private)"><ElementKinds><Kind Name="FIELD" /><Kind Name="READONLY_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb"><ExtraRule Prefix="" Suffix="" Style="AaBb" /></Policy></Policy> + True True True True diff --git a/src/Seq.Extensions.Logging/Microsoft/Extensions/Logging/SeqLoggerExtensions.cs b/src/Seq.Extensions.Logging/Microsoft/Extensions/Logging/SeqLoggerExtensions.cs index 13a2922..afdbca0 100644 --- a/src/Seq.Extensions.Logging/Microsoft/Extensions/Logging/SeqLoggerExtensions.cs +++ b/src/Seq.Extensions.Logging/Microsoft/Extensions/Logging/SeqLoggerExtensions.cs @@ -1,4 +1,5 @@ -using Microsoft.Extensions.Configuration; +using System.Diagnostics.CodeAnalysis; +using Microsoft.Extensions.Configuration; using Serilog.Core; using Seq.Extensions.Logging; using Serilog.Events; @@ -7,6 +8,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging.Configuration; using Serilog.Sinks.PeriodicBatching; +// ReSharper disable UnusedMember.Global namespace Microsoft.Extensions.Logging; @@ -28,7 +30,7 @@ public static ILoggerFactory AddSeq(this ILoggerFactory loggerFactory, IConfigur if (loggerFactory == null) throw new ArgumentNullException(nameof(loggerFactory)); if (configuration == null) throw new ArgumentNullException(nameof(configuration)); - if (TryCreateProvider(configuration, LogLevel.Information, out var provider)) + if (TryCreateProvider(configuration, LogLevel.Information, [], out var provider)) loggerFactory.AddProvider(provider); return loggerFactory; @@ -42,62 +44,66 @@ public static ILoggerFactory AddSeq(this ILoggerFactory loggerFactory, IConfigur /// A Seq API key to authenticate or tag messages from the logger. /// The level below which events will be suppressed (the default is ). /// A dictionary mapping logger name prefixes to minimum logging levels. + /// A collection of enrichers to apply. /// A logger factory to allow further configuration. public static ILoggerFactory AddSeq( this ILoggerFactory loggerFactory, string serverUrl = LocalServerUrl, - string apiKey = null, + string? apiKey = null, LogLevel minimumLevel = LogLevel.Information, - IDictionary levelOverrides = null) + IDictionary? levelOverrides = null, + IEnumerable>? enrichers = null) { if (loggerFactory == null) throw new ArgumentNullException(nameof(loggerFactory)); if (serverUrl == null) throw new ArgumentNullException(nameof(serverUrl)); - var provider = CreateProvider(serverUrl, apiKey, minimumLevel, levelOverrides); + var provider = CreateProvider(serverUrl, apiKey, minimumLevel, levelOverrides, enrichers); loggerFactory.AddProvider(provider); return loggerFactory; } /// - /// Adds a Seq logger. + /// Adds a Seq logger configured from the supplied configuration section. /// /// The logging builder. - /// The Seq server URL; the default is http://localhost:5341. - /// A Seq API key to authenticate or tag messages from the logger. + /// A configuration section with details of the Seq server connection. /// A logging builder to allow further configuration. public static ILoggingBuilder AddSeq( this ILoggingBuilder loggingBuilder, - string serverUrl = LocalServerUrl, - string apiKey = null) + IConfigurationSection configuration) { if (loggingBuilder == null) throw new ArgumentNullException(nameof(loggingBuilder)); - if (serverUrl == null) throw new ArgumentNullException(nameof(serverUrl)); + if (configuration == null) throw new ArgumentNullException(nameof(configuration)); - loggingBuilder.Services.AddSingleton(s => - { - var opts = s.GetService>(); - var provider = CreateProvider(opts?.Configuration, serverUrl, apiKey); - return provider; - }); + if (TryCreateProvider(configuration, LevelAlias.Minimum, Array.Empty>(), out var provider)) + loggingBuilder.Services.AddSingleton(_ => provider); return loggingBuilder; } /// - /// Adds a Seq logger configured from the supplied configuration section. + /// Adds a Seq logger. /// /// The logging builder. - /// A configuration section with details of the Seq server connection. + /// The Seq server URL; the default is http://localhost:5341. + /// A Seq API key to authenticate or tag messages from the logger. + /// A collection of enrichers to apply. /// A logging builder to allow further configuration. public static ILoggingBuilder AddSeq( this ILoggingBuilder loggingBuilder, - IConfigurationSection configuration) + string serverUrl = LocalServerUrl, + string? apiKey = null, + IEnumerable>? enrichers = null) { if (loggingBuilder == null) throw new ArgumentNullException(nameof(loggingBuilder)); - if (configuration == null) throw new ArgumentNullException(nameof(configuration)); + if (serverUrl == null) throw new ArgumentNullException(nameof(serverUrl)); - if (TryCreateProvider(configuration, LevelAlias.Minimum, out var provider)) - loggingBuilder.Services.AddSingleton(_ => provider); + loggingBuilder.Services.AddSingleton(s => + { + var opts = s.GetService>(); + var provider = CreateProvider(opts?.Configuration, serverUrl, apiKey, enrichers); + return provider; + }); return loggingBuilder; } @@ -105,7 +111,8 @@ public static ILoggingBuilder AddSeq( static bool TryCreateProvider( IConfigurationSection configuration, LogLevel defaultMinimumLevel, - out SerilogLoggerProvider provider) + IEnumerable> enrichers, + [NotNullWhen(true)] out SerilogLoggerProvider? provider) { var serverUrl = configuration["ServerUrl"]; if (string.IsNullOrWhiteSpace(serverUrl)) @@ -131,28 +138,28 @@ static bool TryCreateProvider( } var levelOverrides = new Dictionary(); - foreach (var overr in configuration.GetSection("LevelOverride").GetChildren()) + foreach (var levelOverride in configuration.GetSection("LevelOverride").GetChildren()) { - LogLevel value; - if (!Enum.TryParse(overr.Value, out value)) + if (!Enum.TryParse(levelOverride.Value, out LogLevel value)) { - SelfLog.WriteLine("The level override setting `{0}` for `{1}` is invalid", overr.Value, overr.Key); + SelfLog.WriteLine("The level override setting `{0}` for `{1}` is invalid", levelOverride.Value, levelOverride.Key); continue; } - levelOverrides[overr.Key] = value; + levelOverrides[levelOverride.Key] = value; } - provider = CreateProvider(serverUrl, apiKey, minimumLevel, levelOverrides); + provider = CreateProvider(serverUrl, apiKey, minimumLevel, levelOverrides, enrichers); return true; } static SerilogLoggerProvider CreateProvider( - IConfiguration configuration, - string defaultServerUrl, - string defaultApiKey) + IConfiguration? configuration, + string? defaultServerUrl, + string? defaultApiKey, + IEnumerable>? enrichers) { - string serverUrl = null, apiKey = null; + string? serverUrl = null, apiKey = null; if (configuration != null) { serverUrl = configuration["ServerUrl"]; @@ -165,31 +172,32 @@ static SerilogLoggerProvider CreateProvider( if (string.IsNullOrWhiteSpace(apiKey)) apiKey = defaultApiKey; - return CreateProvider(serverUrl, apiKey, LevelAlias.Minimum, null); + return CreateProvider(serverUrl, apiKey, LevelAlias.Minimum, null, enrichers); } static SerilogLoggerProvider CreateProvider( - string serverUrl, - string apiKey, + string? serverUrl, + string? apiKey, LogLevel minimumLevel, - IDictionary levelOverrides) + IDictionary? levelOverrides, + IEnumerable>? enrichers) { var levelSwitch = new LoggingLevelSwitch(minimumLevel); var sink = new SeqSink( - serverUrl, + serverUrl!, apiKey, 256 * 1024, new ControlledLevelSwitch(levelSwitch), null); - LevelOverrideMap overrideMap = null; + LevelOverrideMap? overrideMap = null; if (levelOverrides != null && levelOverrides.Count != 0) { var overrides = new Dictionary(); foreach (var levelOverride in levelOverrides) { - overrides.Add(levelOverride.Key, new LoggingLevelSwitch(levelOverride.Value)); + overrides[levelOverride.Key] = new LoggingLevelSwitch(levelOverride.Value); } overrideMap = new LevelOverrideMap(overrides, levelSwitch); @@ -201,7 +209,7 @@ static SerilogLoggerProvider CreateProvider( Period = TimeSpan.FromSeconds(2), }); - var logger = new Logger(levelSwitch, batchingSink, batchingSink.Dispose, overrideMap); + var logger = new Logger(batchingSink, new Enricher(enrichers ?? []), batchingSink.Dispose, levelSwitch, overrideMap); var provider = new SerilogLoggerProvider(logger); return provider; } diff --git a/src/Seq.Extensions.Logging/Seq.Extensions.Logging.csproj b/src/Seq.Extensions.Logging/Seq.Extensions.Logging.csproj index 7e42e7b..bdf66ca 100644 --- a/src/Seq.Extensions.Logging/Seq.Extensions.Logging.csproj +++ b/src/Seq.Extensions.Logging/Seq.Extensions.Logging.csproj @@ -2,9 +2,8 @@ Add centralized structured log collection to ASP.NET Core apps with one line of code. - 8.0.0 Datalust and Contributors - netstandard2.0;net6.0;net8.0 + netstandard2.0;net8.0;net9.0 true true ../../asset/seqext.snk @@ -14,17 +13,15 @@ icon.png https://github.com/datalust/seq-extensions-logging Apache-2.0 - latest README.md - enable - - - + + + diff --git a/src/Seq.Extensions.Logging/Seq/Extensions/Logging/Enricher.cs b/src/Seq.Extensions.Logging/Seq/Extensions/Logging/Enricher.cs new file mode 100644 index 0000000..5254031 --- /dev/null +++ b/src/Seq.Extensions.Logging/Seq/Extensions/Logging/Enricher.cs @@ -0,0 +1,36 @@ +using Serilog.Core; +using Serilog.Core.Enrichers; +using Serilog.Events; + +namespace Seq.Extensions.Logging; + +class Enricher: ILogEventEnricher +{ + readonly ILogEventEnricher _builtIn = new SafeAggregateEnricher([new ExceptionDataEnricher()]); + + public Enricher(IEnumerable>? enrichers = null) + { + _enrichers = (enrichers ?? Array.Empty>()).ToArray(); + } + + readonly Action[] _enrichers; + + public void Enrich(LogEvent logEvent, ILogEventPropertyValueFactory propertyFactory) + { + _builtIn.Enrich(logEvent, propertyFactory); + + var enriching = new EnrichingEvent(logEvent, propertyFactory); + + foreach (var enricher in _enrichers) + { + try + { + enricher(enriching); + } + catch (Exception ex) + { + SelfLog.WriteLine("Exception {0} caught while enriching {1}.", ex, logEvent); + } + } + } +} diff --git a/src/Seq.Extensions.Logging/Seq/Extensions/Logging/EnrichingEvent.cs b/src/Seq.Extensions.Logging/Seq/Extensions/Logging/EnrichingEvent.cs new file mode 100644 index 0000000..d94050d --- /dev/null +++ b/src/Seq.Extensions.Logging/Seq/Extensions/Logging/EnrichingEvent.cs @@ -0,0 +1,39 @@ +using Serilog.Core; +using Serilog.Events; + +namespace Seq.Extensions.Logging; + +/// +/// The input to an enricher. +/// +public readonly struct EnrichingEvent +{ + internal EnrichingEvent(LogEvent logEvent, ILogEventPropertyValueFactory propertyFactory) + { + _propertyFactory = propertyFactory; + LogEvent = logEvent; + } + + readonly ILogEventPropertyValueFactory _propertyFactory; + + internal LogEvent LogEvent { get; } + + /// + /// Add a property to the event if not already present, otherwise, update its value. + /// + public void AddOrUpdateProperty(string propertyName, object propertyValue, bool serialize = false) + { + LogEvent.AddOrUpdateProperty(propertyName, _propertyFactory.CreatePropertyValue(propertyValue, serialize)); + } + + /// + /// Add a property to the event if not already present. + /// + public void AddPropertyIfAbsent(string propertyName, object propertyValue, bool serialize = false) + { + if (LogEvent.Properties.ContainsKey(propertyName)) + return; + + AddOrUpdateProperty(propertyName, propertyValue, serialize); + } +} diff --git a/src/Seq.Extensions.Logging/Seq/Extensions/Logging/ExceptionDataEnricher.cs b/src/Seq.Extensions.Logging/Seq/Extensions/Logging/ExceptionDataEnricher.cs index f748c31..a38f7f2 100644 --- a/src/Seq.Extensions.Logging/Seq/Extensions/Logging/ExceptionDataEnricher.cs +++ b/src/Seq.Extensions.Logging/Seq/Extensions/Logging/ExceptionDataEnricher.cs @@ -20,7 +20,7 @@ namespace Seq.Extensions.Logging; class ExceptionDataEnricher : ILogEventEnricher { - public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory) + public void Enrich(LogEvent logEvent, ILogEventPropertyValueFactory propertyFactory) { var exceptionData = logEvent.Exception?.GetBaseException().Data; if (exceptionData == null || exceptionData.Count == 0) @@ -29,10 +29,8 @@ public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory) var data = exceptionData .Cast() .Where(e => e.Key is string) - .Select(e => propertyFactory.CreateProperty((string)e.Key, e.Value)); + .Select(e => new LogEventProperty((string)e.Key, propertyFactory.CreatePropertyValue(e.Value))); - var property = new LogEventProperty("ExceptionData", new StructureValue(data)); - - logEvent.AddPropertyIfAbsent(property); + logEvent.AddPropertyIfAbsent("ExceptionData", new StructureValue(data)); } -} \ No newline at end of file +} diff --git a/src/Seq.Extensions.Logging/Seq/Extensions/Logging/JsonSafeString.cs b/src/Seq.Extensions.Logging/Seq/Extensions/Logging/JsonSafeString.cs index 1ea9bbd..9f70290 100644 --- a/src/Seq.Extensions.Logging/Seq/Extensions/Logging/JsonSafeString.cs +++ b/src/Seq.Extensions.Logging/Seq/Extensions/Logging/JsonSafeString.cs @@ -1,6 +1,4 @@ -#nullable enable - -namespace Seq.Extensions.Logging; +namespace Seq.Extensions.Logging; /// /// A wrapper type that marks a string as containing valid JSON that's safe to include as-is in log events. diff --git a/src/Seq.Extensions.Logging/Seq/Extensions/Logging/SelfLog.cs b/src/Seq.Extensions.Logging/Seq/Extensions/Logging/SelfLog.cs index 4209e39..b5549e5 100644 --- a/src/Seq.Extensions.Logging/Seq/Extensions/Logging/SelfLog.cs +++ b/src/Seq.Extensions.Logging/Seq/Extensions/Logging/SelfLog.cs @@ -20,7 +20,7 @@ namespace Seq.Extensions.Logging; /// public static class SelfLog { - static Action _output; + static Action? _output; /// /// Set the output mechanism for self-log messages. @@ -69,7 +69,7 @@ public static void Disable() /// The name is historical; because this is used from third-party sink packages, removing the "Line" /// suffix as would seem sensible isn't worth the breakage. /// - public static void WriteLine(string format, object arg0 = null, object arg1 = null, object arg2 = null) + public static void WriteLine(string format, object? arg0 = null, object? arg1 = null, object? arg2 = null) { var o = _output; diff --git a/src/Seq.Extensions.Logging/Serilog/Core/Enrichers/FixedPropertyEnricher.cs b/src/Seq.Extensions.Logging/Serilog/Core/Enrichers/FixedPropertyEnricher.cs index d0e7e85..6141447 100644 --- a/src/Seq.Extensions.Logging/Serilog/Core/Enrichers/FixedPropertyEnricher.cs +++ b/src/Seq.Extensions.Logging/Serilog/Core/Enrichers/FixedPropertyEnricher.cs @@ -22,13 +22,12 @@ class FixedPropertyEnricher : ILogEventEnricher public FixedPropertyEnricher(LogEventProperty logEventProperty) { - if (logEventProperty == null) throw new ArgumentNullException(nameof(logEventProperty)); _logEventProperty = logEventProperty; } - public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory) + public void Enrich(LogEvent logEvent, ILogEventPropertyValueFactory propertyFactory) { if (logEvent == null) throw new ArgumentNullException(nameof(logEvent)); - logEvent.AddPropertyIfAbsent(_logEventProperty); + logEvent.AddPropertyIfAbsent(_logEventProperty.Name, _logEventProperty.Value); } } \ No newline at end of file diff --git a/src/Seq.Extensions.Logging/Serilog/Core/Enrichers/PropertyEnricher.cs b/src/Seq.Extensions.Logging/Serilog/Core/Enrichers/PropertyEnricher.cs index e42e217..ba2e6dd 100644 --- a/src/Seq.Extensions.Logging/Serilog/Core/Enrichers/PropertyEnricher.cs +++ b/src/Seq.Extensions.Logging/Serilog/Core/Enrichers/PropertyEnricher.cs @@ -49,11 +49,11 @@ public PropertyEnricher(string name, object value, bool destructureObjects = fal /// /// The log event to enrich. /// Factory for creating new properties to add to the event. - public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory) + public void Enrich(LogEvent logEvent, ILogEventPropertyValueFactory propertyFactory) { if (logEvent == null) throw new ArgumentNullException(nameof(logEvent)); if (propertyFactory == null) throw new ArgumentNullException(nameof(propertyFactory)); - var property = propertyFactory.CreateProperty(_name, _value, _destructureObjects); - logEvent.AddPropertyIfAbsent(property); + var propertyValue = propertyFactory.CreatePropertyValue(_value, _destructureObjects); + logEvent.AddPropertyIfAbsent(_name, propertyValue); } } \ No newline at end of file diff --git a/src/Seq.Extensions.Logging/Serilog/Core/Enrichers/SafeAggregateEnricher.cs b/src/Seq.Extensions.Logging/Serilog/Core/Enrichers/SafeAggregateEnricher.cs index fd8e497..8fe2098 100644 --- a/src/Seq.Extensions.Logging/Serilog/Core/Enrichers/SafeAggregateEnricher.cs +++ b/src/Seq.Extensions.Logging/Serilog/Core/Enrichers/SafeAggregateEnricher.cs @@ -27,7 +27,7 @@ public SafeAggregateEnricher(IEnumerable enrichers) _enrichers = enrichers.ToArray(); } - public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory) + public void Enrich(LogEvent logEvent, ILogEventPropertyValueFactory propertyFactory) { foreach (var enricher in _enrichers) { diff --git a/src/Seq.Extensions.Logging/Serilog/Core/IDestructuringPolicy.cs b/src/Seq.Extensions.Logging/Serilog/Core/IDestructuringPolicy.cs index 8f96760..4e418f7 100644 --- a/src/Seq.Extensions.Logging/Serilog/Core/IDestructuringPolicy.cs +++ b/src/Seq.Extensions.Logging/Serilog/Core/IDestructuringPolicy.cs @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +using System.Diagnostics.CodeAnalysis; using Serilog.Events; namespace Serilog.Core; @@ -29,5 +30,5 @@ interface IDestructuringPolicy /// Recursively apply policies to destructure additional values. /// The destructured value, or null. /// True if the value could be destructured under this policy. - bool TryDestructure(object value, ILogEventPropertyValueFactory propertyValueFactory, out LogEventPropertyValue result); + bool TryDestructure(object value, ILogEventPropertyValueFactory propertyValueFactory, [NotNullWhen(true)] out LogEventPropertyValue? result); } \ No newline at end of file diff --git a/src/Seq.Extensions.Logging/Serilog/Core/ILogEventEnricher.cs b/src/Seq.Extensions.Logging/Serilog/Core/ILogEventEnricher.cs index 334d404..54b1af0 100644 --- a/src/Seq.Extensions.Logging/Serilog/Core/ILogEventEnricher.cs +++ b/src/Seq.Extensions.Logging/Serilog/Core/ILogEventEnricher.cs @@ -26,5 +26,5 @@ interface ILogEventEnricher /// /// The log event to enrich. /// Factory for creating new properties to add to the event. - void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory); + void Enrich(LogEvent logEvent, ILogEventPropertyValueFactory propertyFactory); } \ No newline at end of file diff --git a/src/Seq.Extensions.Logging/Serilog/Core/ILogEventPropertyValueFactory.cs b/src/Seq.Extensions.Logging/Serilog/Core/ILogEventPropertyValueFactory.cs index f662122..2051ed7 100644 --- a/src/Seq.Extensions.Logging/Serilog/Core/ILogEventPropertyValueFactory.cs +++ b/src/Seq.Extensions.Logging/Serilog/Core/ILogEventPropertyValueFactory.cs @@ -17,5 +17,5 @@ interface ILogEventPropertyValueFactory /// then the value will be converted to a structure; otherwise, unknown types will /// be converted to scalars, which are generally stored as strings. /// The value. - LogEventPropertyValue CreatePropertyValue(object value, bool destructureObjects = false); + LogEventPropertyValue CreatePropertyValue(object? value, bool destructureObjects = false); } \ No newline at end of file diff --git a/src/Seq.Extensions.Logging/Serilog/Core/IScalarConversionPolicy.cs b/src/Seq.Extensions.Logging/Serilog/Core/IScalarConversionPolicy.cs index 819925b..947fbce 100644 --- a/src/Seq.Extensions.Logging/Serilog/Core/IScalarConversionPolicy.cs +++ b/src/Seq.Extensions.Logging/Serilog/Core/IScalarConversionPolicy.cs @@ -1,4 +1,5 @@ -using Serilog.Events; +using System.Diagnostics.CodeAnalysis; +using Serilog.Events; namespace Serilog.Core; @@ -15,5 +16,5 @@ interface IScalarConversionPolicy /// Recursively apply policies to convert additional values. /// The converted value, or null. /// True if the value could be converted under this policy. - bool TryConvertToScalar(object value, ILogEventPropertyValueFactory propertyValueFactory, out ScalarValue result); + bool TryConvertToScalar(object value, ILogEventPropertyValueFactory propertyValueFactory, [NotNullWhen(true)] out ScalarValue? result); } \ No newline at end of file diff --git a/src/Seq.Extensions.Logging/Serilog/Core/Logger.cs b/src/Seq.Extensions.Logging/Serilog/Core/Logger.cs index 0c64b5c..cd42280 100644 --- a/src/Seq.Extensions.Logging/Serilog/Core/Logger.cs +++ b/src/Seq.Extensions.Logging/Serilog/Core/Logger.cs @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +using System.Diagnostics.CodeAnalysis; using Serilog.Core.Enrichers; using Seq.Extensions.Logging; using Serilog.Events; @@ -29,33 +30,22 @@ namespace Serilog.Core; /// sealed class Logger : ILogEventSink, IDisposable { - static readonly object[] NoPropertyValues = new object[0]; - - readonly MessageTemplateProcessor _messageTemplateProcessor = new MessageTemplateProcessor(new PropertyValueConverter(10, int.MaxValue)); + readonly MessageTemplateProcessor _messageTemplateProcessor = new(new PropertyValueConverter(10, int.MaxValue)); readonly ILogEventSink _sink; - readonly Action _dispose; + readonly Action? _dispose; readonly ILogEventEnricher _enricher; - readonly LoggingLevelSwitch _levelSwitch; - readonly LevelOverrideMap _overrideMap; - - internal Logger( - LoggingLevelSwitch levelSwitch, - ILogEventSink sink, - Action dispose = null, - LevelOverrideMap overrideMap = null) - : this(sink, new ExceptionDataEnricher(), dispose, levelSwitch, overrideMap) - { - } + readonly LoggingLevelSwitch? _levelSwitch; + readonly LevelOverrideMap? _overrideMap; // The messageTemplateProcessor, sink and enricher are required. Argument checks are dropped because // throwing from here breaks the logger's no-throw contract, and callers are all in this file anyway. - Logger( + internal Logger( ILogEventSink sink, ILogEventEnricher enricher, - Action dispose = null, - LoggingLevelSwitch levelSwitch = null, - LevelOverrideMap overrideMap = null) + Action? dispose = null, + LoggingLevelSwitch? levelSwitch = null, + LevelOverrideMap? overrideMap = null) { _sink = sink; _dispose = dispose; @@ -71,7 +61,7 @@ internal Logger( /// A logger that will enrich log events as specified. public Logger ForContext(ILogEventEnricher enricher) { - if (enricher == null) + if (enricher == null!) return this; // No context here, so little point writing to SelfLog. return new Logger( @@ -89,7 +79,7 @@ public Logger ForContext(ILogEventEnricher enricher) /// A logger that will enrich log events as specified. public Logger ForContext(IEnumerable enrichers) { - if (enrichers == null) + if (enrichers == null!) return this; // No context here, so little point writing to SelfLog. return ForContext(new SafeAggregateEnricher(enrichers)); @@ -103,7 +93,7 @@ public Logger ForContext(IEnumerable enrichers) /// If true, the value will be serialized as a structured /// object if possible; if false, the object will be recorded as a scalar or simple array. /// A logger that will enrich log events as specified. - public Logger ForContext(string propertyName, object value, bool destructureObjects = false) + public Logger ForContext(string propertyName, object? value, bool destructureObjects = false) { if (!LogEventProperty.IsValidName(propertyName)) { @@ -120,8 +110,7 @@ public Logger ForContext(string propertyName, object value, bool destructureObje var levelSwitch = _levelSwitch; if (_overrideMap != null && propertyName == Constants.SourceContextPropertyName) { - var context = value as string; - if (context != null) + if (value is string context) _overrideMap.GetEffectiveLevel(context, out levelSwitch); } @@ -141,23 +130,12 @@ public Logger ForContext(string propertyName, object value, bool destructureObje /// A logger that will enrich log events as specified. public Logger ForContext(Type source) { - if (source == null) + if (source == null!) return this; // Little point in writing to SelfLog here because we don't have any contextual information return ForContext(Constants.SourceContextPropertyName, source.FullName); } - /// - /// Create a logger that marks log events as being from the specified - /// source type. - /// - /// Type generating log messages in the context. - /// A logger that will enrich log events as specified. - public Logger ForContext() - { - return ForContext(typeof(TSource)); - } - /// /// Determine if events at the specified level will be passed through /// to the log sinks. @@ -166,9 +144,7 @@ public Logger ForContext() /// True if the level is enabled; otherwise, false. public bool IsEnabled(LogLevel level) { - - return _levelSwitch == null || - (int)level >= (int)_levelSwitch.MinimumLevel; + return level != LogLevel.None && (_levelSwitch == null || level >= _levelSwitch.MinimumLevel); } /// @@ -177,7 +153,7 @@ public bool IsEnabled(LogLevel level) /// The event to write. public void Write(LogEvent logEvent) { - if (logEvent == null) return; + if (logEvent == null!) return; if (!IsEnabled(logEvent.Level)) return; Dispatch(logEvent); } @@ -227,9 +203,9 @@ void Dispatch(LogEvent logEvent) /// // -> "Hello, World!" /// } /// - 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) { - if (messageTemplate == null) + if (messageTemplate == null!) { parsedTemplate = null; boundProperties = null; @@ -251,7 +227,7 @@ public bool BindMessageTemplate(string messageTemplate, object[] propertyValues, /// object if possible; if false, the object will be recorded as a scalar or simple array. /// The resulting property. /// methods never throw exceptions). - 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) { if (!LogEventProperty.IsValidName(propertyName)) { diff --git a/src/Seq.Extensions.Logging/Serilog/Core/LoggingLevelSwitch.cs b/src/Seq.Extensions.Logging/Serilog/Core/LoggingLevelSwitch.cs index 06ca227..1075298 100644 --- a/src/Seq.Extensions.Logging/Serilog/Core/LoggingLevelSwitch.cs +++ b/src/Seq.Extensions.Logging/Serilog/Core/LoggingLevelSwitch.cs @@ -41,7 +41,7 @@ public LoggingLevelSwitch(LogLevel initialMinimumLevel = LogLevel.Information) // so needs to be used judiciously in the logging pipeline. public LogLevel MinimumLevel { - get { return _minimumLevel; } - set { _minimumLevel = value; } + get => _minimumLevel; + set => _minimumLevel = value; } } \ No newline at end of file diff --git a/src/Seq.Extensions.Logging/Serilog/Core/Pipeline/MessageTemplateCache.cs b/src/Seq.Extensions.Logging/Serilog/Core/Pipeline/MessageTemplateCache.cs index 743fa7d..0dd6e02 100644 --- a/src/Seq.Extensions.Logging/Serilog/Core/Pipeline/MessageTemplateCache.cs +++ b/src/Seq.Extensions.Logging/Serilog/Core/Pipeline/MessageTemplateCache.cs @@ -19,32 +19,27 @@ namespace Serilog.Core.Pipeline; class MessageTemplateCache { - readonly MessageTemplateParser _innerParser; - readonly Dictionary _templates = new Dictionary(); - readonly object _templatesLock = new object(); + readonly Dictionary _templates = new(); + readonly object _templatesLock = new(); const int MaxCacheItems = 1000; const int MaxCachedTemplateLength = 1024; - public MessageTemplateCache(MessageTemplateParser innerParser) - { - if (innerParser == null) throw new ArgumentNullException(nameof(innerParser)); - _innerParser = innerParser; - } public MessageTemplate Parse(string messageTemplate) { if (messageTemplate == null) throw new ArgumentNullException(nameof(messageTemplate)); if (messageTemplate.Length > MaxCachedTemplateLength) - return _innerParser.Parse(messageTemplate); + return MessageTemplateParser.Parse(messageTemplate); - MessageTemplate result; lock (_templatesLock) - if (_templates.TryGetValue(messageTemplate, out result)) - return result; + { + if (_templates.TryGetValue(messageTemplate, out var found)) + return found; + } - result = _innerParser.Parse(messageTemplate); + var result = MessageTemplateParser.Parse(messageTemplate); lock (_templatesLock) { diff --git a/src/Seq.Extensions.Logging/Serilog/Events/DictionaryValue.cs b/src/Seq.Extensions.Logging/Serilog/Events/DictionaryValue.cs index be73fd6..3edd6fa 100644 --- a/src/Seq.Extensions.Logging/Serilog/Events/DictionaryValue.cs +++ b/src/Seq.Extensions.Logging/Serilog/Events/DictionaryValue.cs @@ -42,7 +42,7 @@ public DictionaryValue(IEnumerableA format string applied to the value, or null. /// A format provider to apply to the value, or null to use the default. /// . - public override void Render(TextWriter output, string format = null, IFormatProvider formatProvider = null) + public override void Render(TextWriter output, string? format = null, IFormatProvider? formatProvider = null) { if (output == null) throw new ArgumentNullException(nameof(output)); diff --git a/src/Seq.Extensions.Logging/Serilog/Events/LogEvent.cs b/src/Seq.Extensions.Logging/Serilog/Events/LogEvent.cs index c65b50d..42f7685 100644 --- a/src/Seq.Extensions.Logging/Serilog/Events/LogEvent.cs +++ b/src/Seq.Extensions.Logging/Serilog/Events/LogEvent.cs @@ -23,8 +23,8 @@ namespace Serilog.Events; class LogEvent { readonly Dictionary _properties; - ActivityTraceId _traceId; - ActivitySpanId _spanId; + readonly ActivityTraceId _traceId; + readonly ActivitySpanId _spanId; /// /// Construct a new . @@ -39,22 +39,19 @@ class LogEvent public LogEvent( DateTimeOffset timestamp, LogLevel level, - Exception exception, + Exception? exception, MessageTemplate messageTemplate, - IEnumerable properties, + Dictionary properties, ActivityTraceId traceId, ActivitySpanId spanId) { - if (properties == null) throw new ArgumentNullException(nameof(properties)); _traceId = traceId; _spanId = spanId; Timestamp = timestamp; Level = level; Exception = exception; - MessageTemplate = messageTemplate ?? throw new ArgumentNullException(nameof(messageTemplate)); - _properties = new Dictionary(); - foreach (var p in properties) - AddOrUpdateProperty(p); + MessageTemplate = messageTemplate; + _properties = properties; } /// @@ -88,7 +85,7 @@ public LogEvent( /// /// The output. /// Supplies culture-specific formatting information, or null. - public void RenderMessage(TextWriter output, IFormatProvider formatProvider = null) + public void RenderMessage(TextWriter output, IFormatProvider? formatProvider = null) { MessageTemplate.Render(Properties, output, formatProvider); } @@ -98,7 +95,7 @@ public void RenderMessage(TextWriter output, IFormatProvider formatProvider = nu /// with the event, and return the result. /// /// Supplies culture-specific formatting information, or null. - public string RenderMessage(IFormatProvider formatProvider = null) + public string RenderMessage(IFormatProvider? formatProvider = null) { return MessageTemplate.Render(Properties, formatProvider); } @@ -111,38 +108,24 @@ public string RenderMessage(IFormatProvider formatProvider = null) /// /// An exception associated with the event, or null. /// - public Exception Exception { get; } + public Exception? Exception { get; } /// /// Add a property to the event if not already present, otherwise, update its value. /// - /// The property to add or update. /// - public void AddOrUpdateProperty(LogEventProperty property) + public void AddOrUpdateProperty(string propertyName, LogEventPropertyValue propertyValue) { - if (property == null) throw new ArgumentNullException(nameof(property)); - _properties[property.Name] = property.Value; + _properties[propertyName] = propertyValue; } /// /// Add a property to the event if not already present. /// - /// The property to add. /// - public void AddPropertyIfAbsent(LogEventProperty property) + public void AddPropertyIfAbsent(string propertyName, LogEventPropertyValue propertyValue) { - if (property == null) throw new ArgumentNullException(nameof(property)); - if (!_properties.ContainsKey(property.Name)) - _properties.Add(property.Name, property.Value); - } - - /// - /// Remove a property from the event, if present. Otherwise no action - /// is performed. - /// - /// The name of the property to remove. - public void RemovePropertyIfPresent(string propertyName) - { - _properties.Remove(propertyName); + if (!_properties.ContainsKey(propertyName)) + _properties.Add(propertyName, propertyValue); } } diff --git a/src/Seq.Extensions.Logging/Serilog/Events/LogEventPropertyValue.cs b/src/Seq.Extensions.Logging/Serilog/Events/LogEventPropertyValue.cs index 98c9e51..17e9df5 100644 --- a/src/Seq.Extensions.Logging/Serilog/Events/LogEventPropertyValue.cs +++ b/src/Seq.Extensions.Logging/Serilog/Events/LogEventPropertyValue.cs @@ -27,7 +27,7 @@ abstract class LogEventPropertyValue : IFormattable /// A format string applied to the value, or null. /// A format provider to apply to the value, or null to use the default. /// . - public abstract void Render(TextWriter output, string format = null, IFormatProvider formatProvider = null); + public abstract void Render(TextWriter output, string? format = null, IFormatProvider? formatProvider = null); /// /// Returns a string that represents the current object. @@ -52,7 +52,7 @@ public override string ToString() /// The provider to use to format the value.-or- A null reference /// (Nothing in Visual Basic) to obtain the numeric format information from the current locale /// setting of the operating system. 2 - public string ToString(string format, IFormatProvider formatProvider) + public string ToString(string? format, IFormatProvider? formatProvider) { var output = new StringWriter(); Render(output, format, formatProvider); diff --git a/src/Seq.Extensions.Logging/Serilog/Events/MessageTemplate.cs b/src/Seq.Extensions.Logging/Serilog/Events/MessageTemplate.cs index 6c73bf3..68fcb0e 100644 --- a/src/Seq.Extensions.Logging/Serilog/Events/MessageTemplate.cs +++ b/src/Seq.Extensions.Logging/Serilog/Events/MessageTemplate.cs @@ -29,15 +29,6 @@ class MessageTemplate // Optimisation for when the template is bound to // property values. - /// - /// Construct a message template using manually-defined text and property tokens. - /// - /// The text and property tokens defining the template. - public MessageTemplate(IEnumerable tokens) - : this(string.Join("", tokens), tokens) - { - } - /// /// Construct a message template using manually-defined text and property tokens. /// @@ -46,10 +37,9 @@ public MessageTemplate(IEnumerable tokens) /// The text and property tokens defining the template. public MessageTemplate(string text, IEnumerable tokens) { - if (text == null) throw new ArgumentNullException(nameof(text)); if (tokens == null) throw new ArgumentNullException(nameof(tokens)); - Text = text; + Text = text ?? throw new ArgumentNullException(nameof(text)); _tokens = tokens.ToArray(); var propertyTokens = GetElementsOfTypeToArray(_tokens); @@ -82,14 +72,13 @@ public MessageTemplate(string text, IEnumerable tokens) /// /// Similar to , but faster. /// - static TResult[] GetElementsOfTypeToArray(object[] tokens) + static TResult[] GetElementsOfTypeToArray(MessageTemplateToken[] tokens) where TResult : class { var result = new List(tokens.Length / 2); for (var i = 0; i < tokens.Length; i++) { - var token = tokens[i] as TResult; - if (token != null) + if (tokens[i] is TResult token) { result.Add(token); } @@ -116,9 +105,9 @@ public override string ToString() /// public IEnumerable Tokens => _tokens; - internal PropertyToken[] NamedProperties { get; } + internal PropertyToken[]? NamedProperties { get; } - internal PropertyToken[] PositionalProperties { get; } + internal PropertyToken[]? PositionalProperties { get; } /// /// Convert the message template into a textual message, given the @@ -129,7 +118,7 @@ public override string ToString() /// The message created from the template and properties. If the /// properties are mismatched with the template, the template will be /// returned with incomplete substitution. - public string Render(IReadOnlyDictionary properties, IFormatProvider formatProvider = null) + public string Render(IReadOnlyDictionary properties, IFormatProvider? formatProvider = null) { var writer = new StringWriter(formatProvider); Render(properties, writer, formatProvider); @@ -145,7 +134,7 @@ public string Render(IReadOnlyDictionary properti /// properties are mismatched with the template, the template will be /// returned with incomplete substitution. /// Supplies culture-specific formatting information, or null. - public void Render(IReadOnlyDictionary properties, TextWriter output, IFormatProvider formatProvider = null) + public void Render(IReadOnlyDictionary properties, TextWriter output, IFormatProvider? formatProvider = null) { foreach (var token in _tokens) { diff --git a/src/Seq.Extensions.Logging/Serilog/Events/ScalarValue.cs b/src/Seq.Extensions.Logging/Serilog/Events/ScalarValue.cs index a67285f..679504e 100644 --- a/src/Seq.Extensions.Logging/Serilog/Events/ScalarValue.cs +++ b/src/Seq.Extensions.Logging/Serilog/Events/ScalarValue.cs @@ -26,7 +26,7 @@ class ScalarValue : LogEventPropertyValue /// value. /// /// The value, which may be null. - public ScalarValue(object value) + public ScalarValue(object? value) { Value = value; } @@ -34,7 +34,7 @@ public ScalarValue(object value) /// /// The value, which may be null. /// - public object Value { get; } + public object? Value { get; } /// /// Render the value to the output. @@ -43,7 +43,7 @@ public ScalarValue(object value) /// A format string applied to the value, or null. /// A format provider to apply to the value, or null to use the default. /// . - public override void Render(TextWriter output, string format = null, IFormatProvider formatProvider = null) + public override void Render(TextWriter output, string? format = null, IFormatProvider? formatProvider = null) { if (output == null) throw new ArgumentNullException(nameof(output)); @@ -53,8 +53,7 @@ public override void Render(TextWriter output, string format = null, IFormatProv return; } - var s = Value as string; - if (s != null) + if (Value is string s) { if (format != "l") { @@ -69,18 +68,14 @@ public override void Render(TextWriter output, string format = null, IFormatProv return; } - if (formatProvider != null) + var custom = (ICustomFormatter?)formatProvider?.GetFormat(typeof(ICustomFormatter)); + if (custom != null) { - var custom = (ICustomFormatter)formatProvider.GetFormat(typeof(ICustomFormatter)); - if (custom != null) - { - output.Write(custom.Format(format, Value, formatProvider)); - return; - } + output.Write(custom.Format(format, Value, formatProvider)); + return; } - var f = Value as IFormattable; - if (f != null) + if (Value is IFormattable f) { output.Write(f.ToString(format, formatProvider ?? CultureInfo.InvariantCulture)); } @@ -95,7 +90,7 @@ public override void Render(TextWriter output, string format = null, IFormatProv /// /// The instance to compare with. /// True if the instances are equal; otherwise, false. - public override bool Equals(object obj) + public override bool Equals(object? obj) { var sv = obj as ScalarValue; if (sv == null) return false; diff --git a/src/Seq.Extensions.Logging/Serilog/Events/SequenceValue.cs b/src/Seq.Extensions.Logging/Serilog/Events/SequenceValue.cs index 94cfdde..f066928 100644 --- a/src/Seq.Extensions.Logging/Serilog/Events/SequenceValue.cs +++ b/src/Seq.Extensions.Logging/Serilog/Events/SequenceValue.cs @@ -44,7 +44,7 @@ public SequenceValue(IEnumerable elements) /// A format string applied to the value, or null. /// A format provider to apply to the value, or null to use the default. /// . - public override void Render(TextWriter output, string format = null, IFormatProvider formatProvider = null) + public override void Render(TextWriter output, string? format = null, IFormatProvider? formatProvider = null) { if (output == null) throw new ArgumentNullException(nameof(output)); diff --git a/src/Seq.Extensions.Logging/Serilog/Events/StructureValue.cs b/src/Seq.Extensions.Logging/Serilog/Events/StructureValue.cs index 978874c..f56211b 100644 --- a/src/Seq.Extensions.Logging/Serilog/Events/StructureValue.cs +++ b/src/Seq.Extensions.Logging/Serilog/Events/StructureValue.cs @@ -28,7 +28,7 @@ class StructureValue : LogEventPropertyValue /// structure. /// The properties of the structure. /// - public StructureValue(IEnumerable properties, string typeTag = null) + public StructureValue(IEnumerable properties, string? typeTag = null) { if (properties == null) throw new ArgumentNullException(nameof(properties)); TypeTag = typeTag; @@ -39,7 +39,7 @@ public StructureValue(IEnumerable properties, string typeTag = /// A piece of metadata describing the "type" of the /// structure, or null. /// - public string TypeTag { get; } + public string? TypeTag { get; } /// /// The properties of the structure. @@ -56,7 +56,7 @@ public StructureValue(IEnumerable properties, string typeTag = /// A format string applied to the value, or null. /// A format provider to apply to the value, or null to use the default. /// . - public override void Render(TextWriter output, string format = null, IFormatProvider formatProvider = null) + public override void Render(TextWriter output, string? format = null, IFormatProvider? formatProvider = null) { if (output == null) throw new ArgumentNullException(nameof(output)); @@ -83,7 +83,7 @@ public override void Render(TextWriter output, string format = null, IFormatProv output.Write(" }"); } - static void Render(TextWriter output, LogEventProperty property, IFormatProvider formatProvider = null) + static void Render(TextWriter output, LogEventProperty property, IFormatProvider? formatProvider = null) { output.Write(property.Name); output.Write(": "); diff --git a/src/Seq.Extensions.Logging/Serilog/Extensions/Logging/SerilogLogger.cs b/src/Seq.Extensions.Logging/Serilog/Extensions/Logging/SerilogLogger.cs index 4405aee..a43a449 100644 --- a/src/Seq.Extensions.Logging/Serilog/Extensions/Logging/SerilogLogger.cs +++ b/src/Seq.Extensions.Logging/Serilog/Extensions/Logging/SerilogLogger.cs @@ -9,7 +9,6 @@ using System.Reflection; using Serilog.Parsing; -#nullable enable // ReSharper disable ConditionIsAlwaysTrueOrFalse // ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract // ReSharper disable ConstantNullCoalescingCondition @@ -21,8 +20,6 @@ class SerilogLogger : FrameworkLogger readonly SerilogLoggerProvider _provider; readonly Logger _logger; - static readonly MessageTemplateParser MessageTemplateParser = new(); - public SerilogLogger( SerilogLoggerProvider provider, Logger logger, @@ -31,7 +28,7 @@ public SerilogLogger( if (logger == null) throw new ArgumentNullException(nameof(logger)); _provider = provider ?? throw new ArgumentNullException(nameof(provider)); - _logger = logger.ForContext(new[] { provider }); + _logger = logger.ForContext([provider]); if (name != null) { @@ -56,10 +53,9 @@ public void Log(LogLevel logLevel, EventId eventId, TState state, Except return; } - var logger = _logger; string? messageTemplate = null; - var properties = new List(); + var properties = new Dictionary(); if (state is IEnumerable> structure) { @@ -71,13 +67,13 @@ public void Log(LogLevel logLevel, EventId eventId, TState state, Except } else if (property.Key.StartsWith("@")) { - if (logger.BindProperty(property.Key.Substring(1), property.Value, true, out var destructured)) - properties.Add(destructured); + if (_logger.BindProperty(property.Key.Substring(1), property.Value, true, out var destructured)) + properties.Add(destructured.Name, destructured.Value); } else { - if (logger.BindProperty(property.Key, property.Value, false, out var bound)) - properties.Add(bound); + if (_logger.BindProperty(property.Key, property.Value, false, out var bound)) + properties.Add(bound.Name, bound.Value); } } @@ -87,8 +83,8 @@ public void Log(LogLevel logLevel, EventId eventId, TState state, Except if (messageTemplate == null && !stateTypeInfo.IsGenericType) { messageTemplate = "{" + stateType.Name + ":l}"; - if (logger.BindProperty(stateType.Name, AsLoggableValue(state, formatter), false, out var stateTypeProperty)) - properties.Add(stateTypeProperty); + if (_logger.BindProperty(stateType.Name, AsLoggableValue(state, formatter), false, out var stateTypeProperty)) + properties.Add(stateTypeProperty.Name, stateTypeProperty.Value); } } @@ -108,18 +104,18 @@ public void Log(LogLevel logLevel, EventId eventId, TState state, Except if (propertyName != null) { - if (logger.BindProperty(propertyName, AsLoggableValue(state, formatter!), false, out var property)) - properties.Add(property); + if (_logger.BindProperty(propertyName, AsLoggableValue(state, formatter!), false, out var property)) + properties.Add(property.Name, property.Value); } } if (eventId.Id != 0 || eventId.Name != null) - properties.Add(CreateEventIdProperty(eventId)); + properties["EventId"] = CreateEventIdPropertyValue(eventId); var parsedTemplate = MessageTemplateParser.Parse(messageTemplate ?? ""); var currentActivity = Activity.Current; var evt = new LogEvent(DateTimeOffset.Now, logLevel, exception, parsedTemplate, properties, currentActivity?.TraceId ?? default, currentActivity?.SpanId ?? default); - logger.Write(evt); + _logger.Write(evt); } static object? AsLoggableValue(TState state, Func formatter) @@ -130,7 +126,7 @@ public void Log(LogLevel logLevel, EventId eventId, TState state, Except return stateObject; } - static LogEventProperty CreateEventIdProperty(EventId eventId) + static LogEventPropertyValue CreateEventIdPropertyValue(EventId eventId) { var properties = new List(2); @@ -144,6 +140,6 @@ static LogEventProperty CreateEventIdProperty(EventId eventId) properties.Add(new LogEventProperty("Name", new ScalarValue(eventId.Name))); } - return new LogEventProperty("EventId", new StructureValue(properties)); + return new StructureValue(properties); } } \ No newline at end of file diff --git a/src/Seq.Extensions.Logging/Serilog/Extensions/Logging/SerilogLoggerProvider.cs b/src/Seq.Extensions.Logging/Serilog/Extensions/Logging/SerilogLoggerProvider.cs index 851f263..13a3080 100644 --- a/src/Seq.Extensions.Logging/Serilog/Extensions/Logging/SerilogLoggerProvider.cs +++ b/src/Seq.Extensions.Logging/Serilog/Extensions/Logging/SerilogLoggerProvider.cs @@ -20,7 +20,7 @@ class SerilogLoggerProvider : ILoggerProvider, ILogEventEnricher, ISupportExtern readonly Logger _logger; readonly Action _dispose; - IExternalScopeProvider _scopeProvider; + IExternalScopeProvider? _scopeProvider; /// /// Construct a . @@ -32,7 +32,7 @@ public SerilogLoggerProvider(Logger logger) throw new ArgumentNullException(nameof(logger)); _dispose = logger.Dispose; - _logger = logger.ForContext(new[] { this }); + _logger = logger.ForContext([this]); } /// @@ -41,13 +41,13 @@ public FrameworkLogger CreateLogger(string name) return new SerilogLogger(this, _logger, name); } - public IDisposable BeginScope(T state) + public IDisposable? BeginScope(T state) { return _scopeProvider?.Push(state); } /// - public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory) + public void Enrich(LogEvent logEvent, ILogEventPropertyValueFactory propertyFactory) { var scopeItems = new List(); @@ -63,7 +63,7 @@ public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory) if (scopeItems.Count > 0) { - logEvent.AddPropertyIfAbsent(new LogEventProperty(ScopePropertyName, new SequenceValue(scopeItems))); + logEvent.AddPropertyIfAbsent(ScopePropertyName, new SequenceValue(scopeItems)); } } diff --git a/src/Seq.Extensions.Logging/Serilog/Extensions/Logging/SerilogLoggerScope.cs b/src/Seq.Extensions.Logging/Serilog/Extensions/Logging/SerilogLoggerScope.cs index 17133d9..3cb5a64 100644 --- a/src/Seq.Extensions.Logging/Serilog/Extensions/Logging/SerilogLoggerScope.cs +++ b/src/Seq.Extensions.Logging/Serilog/Extensions/Logging/SerilogLoggerScope.cs @@ -9,9 +9,7 @@ namespace Serilog.Extensions.Logging; static class SerilogLoggerScope { - const string NoName = "None"; - - public static void EnrichAndCreateScopeItem(object state, LogEvent logEvent, ILogEventPropertyFactory propertyFactory, out LogEventPropertyValue scopeItem) + public static void EnrichAndCreateScopeItem(object? state, LogEvent logEvent, ILogEventPropertyValueFactory propertyFactory, out LogEventPropertyValue? scopeItem) { if (state == null) { @@ -39,13 +37,13 @@ public static void EnrichAndCreateScopeItem(object state, LogEvent logEvent, ILo destructureObject = true; } - var property = propertyFactory.CreateProperty(key, stateProperty.Value, destructureObject); - logEvent.AddOrUpdateProperty(property); + var property = propertyFactory.CreatePropertyValue(stateProperty.Value, destructureObject); + logEvent.AddOrUpdateProperty(key, property); } } else { - scopeItem = propertyFactory.CreateProperty(NoName, state).Value; + scopeItem = propertyFactory.CreatePropertyValue(state); } } } \ No newline at end of file diff --git a/src/Seq.Extensions.Logging/Serilog/Formatting/Json/JsonValueFormatter.cs b/src/Seq.Extensions.Logging/Serilog/Formatting/Json/JsonValueFormatter.cs index a5ea602..baaf155 100644 --- a/src/Seq.Extensions.Logging/Serilog/Formatting/Json/JsonValueFormatter.cs +++ b/src/Seq.Extensions.Logging/Serilog/Formatting/Json/JsonValueFormatter.cs @@ -25,7 +25,7 @@ namespace Serilog.Formatting.Json; /// class JsonValueFormatter : LogEventPropertyValueVisitor { - readonly string _typeTagName; + readonly string? _typeTagName; const string DefaultTypeTagName = "_typeTag"; @@ -36,7 +36,7 @@ class JsonValueFormatter : LogEventPropertyValueVisitor /// the property name to use for the Serilog field /// in the resulting JSON. If null, no type tag field will be written. The default is /// "_typeTag". - public JsonValueFormatter(string typeTagName = DefaultTypeTagName) + public JsonValueFormatter(string? typeTagName = DefaultTypeTagName) { _typeTagName = typeTagName; } @@ -136,7 +136,7 @@ protected override bool VisitDictionaryValue(TextWriter state, DictionaryValue d { state.Write(delim); delim = ","; - WriteQuotedJsonString((element.Key.Value ?? "null").ToString(), state); + WriteQuotedJsonString((element.Key.Value ?? "null").ToString()!, state); state.Write(':'); Visit(state, element.Value); } @@ -152,7 +152,7 @@ protected override bool VisitDictionaryValue(TextWriter state, DictionaryValue d /// /// The value to write. /// The output - static void FormatLiteralValue(object value, TextWriter output) + static void FormatLiteralValue(object? value, TextWriter output) { if (value == null) { @@ -200,7 +200,7 @@ static void FormatLiteralValue(object value, TextWriter output) if (value is char) { - FormatStringValue(value.ToString(), output); + FormatStringValue(value.ToString()!, output); return; } @@ -274,7 +274,7 @@ static void FormatTimeSpanValue(TimeSpan value, TextWriter output) static void FormatLiteralObjectValue(object value, TextWriter output) { if (value == null) throw new ArgumentNullException(nameof(value)); - FormatStringValue(value.ToString(), output); + FormatStringValue(value.ToString()!, output); } static void FormatStringValue(string str, TextWriter output) diff --git a/src/Seq.Extensions.Logging/Serilog/Parameters/DepthLimiter.cs b/src/Seq.Extensions.Logging/Serilog/Parameters/DepthLimiter.cs index a51eeda..c11788d 100644 --- a/src/Seq.Extensions.Logging/Serilog/Parameters/DepthLimiter.cs +++ b/src/Seq.Extensions.Logging/Serilog/Parameters/DepthLimiter.cs @@ -34,19 +34,19 @@ public DepthLimiter(int currentDepth, int maximumDepth, PropertyValueConverter p _propertyValueConverter = propertyValueConverter; } - public LogEventPropertyValue CreatePropertyValue(object value, Destructuring destructuring) + public LogEventPropertyValue CreatePropertyValue(object? value, Destructuring destructuring) { return DefaultIfMaximumDepth() ?? _propertyValueConverter.CreatePropertyValue(value, destructuring, _currentDepth + 1); } - public LogEventPropertyValue CreatePropertyValue(object value, bool destructureObjects = false) + public LogEventPropertyValue CreatePropertyValue(object? value, bool destructureObjects = false) { return DefaultIfMaximumDepth() ?? _propertyValueConverter.CreatePropertyValue(value, destructureObjects, _currentDepth + 1); } - LogEventPropertyValue DefaultIfMaximumDepth() + LogEventPropertyValue? DefaultIfMaximumDepth() { if (_currentDepth == _maximumDestructuringDepth) { diff --git a/src/Seq.Extensions.Logging/Serilog/Parameters/GetablePropertyFinder.cs b/src/Seq.Extensions.Logging/Serilog/Parameters/GettablePropertyFinder.cs similarity index 88% rename from src/Seq.Extensions.Logging/Serilog/Parameters/GetablePropertyFinder.cs rename to src/Seq.Extensions.Logging/Serilog/Parameters/GettablePropertyFinder.cs index d192f0a..d392a05 100644 --- a/src/Seq.Extensions.Logging/Serilog/Parameters/GetablePropertyFinder.cs +++ b/src/Seq.Extensions.Logging/Serilog/Parameters/GettablePropertyFinder.cs @@ -16,7 +16,7 @@ namespace Serilog.Parameters; -static class GetablePropertyFinder +static class GettablePropertyFinder { internal static IEnumerable GetPropertiesRecursive(this Type type) { @@ -27,7 +27,7 @@ internal static IEnumerable GetPropertiesRecursive(this Type type) while (currentTypeInfo.AsType() != typeof(object)) { var unseenProperties = currentTypeInfo.DeclaredProperties.Where(p => p.CanRead && - p.GetMethod.IsPublic && !p.GetMethod.IsStatic && + p.GetMethod!.IsPublic && !p.GetMethod.IsStatic && (p.Name != "Item" || p.GetIndexParameters().Length == 0) && !seenNames.Contains(p.Name)); foreach (var propertyInfo in unseenProperties) @@ -36,7 +36,7 @@ internal static IEnumerable GetPropertiesRecursive(this Type type) yield return propertyInfo; } - currentTypeInfo = currentTypeInfo.BaseType.GetTypeInfo(); + currentTypeInfo = currentTypeInfo.BaseType!.GetTypeInfo(); } } } \ No newline at end of file diff --git a/src/Seq.Extensions.Logging/Serilog/Parameters/MessageTemplateProcessor.cs b/src/Seq.Extensions.Logging/Serilog/Parameters/MessageTemplateProcessor.cs index cd37220..f982ebd 100644 --- a/src/Seq.Extensions.Logging/Serilog/Parameters/MessageTemplateProcessor.cs +++ b/src/Seq.Extensions.Logging/Serilog/Parameters/MessageTemplateProcessor.cs @@ -15,13 +15,12 @@ using Serilog.Core; using Serilog.Core.Pipeline; using Serilog.Events; -using Serilog.Parsing; namespace Serilog.Parameters; -class MessageTemplateProcessor : ILogEventPropertyFactory +class MessageTemplateProcessor : ILogEventPropertyFactory, ILogEventPropertyValueFactory { - readonly MessageTemplateCache _parser = new MessageTemplateCache(new MessageTemplateParser()); + readonly MessageTemplateCache _parser = new(); readonly PropertyBinder _propertyBinder; readonly PropertyValueConverter _propertyValueConverter; @@ -31,13 +30,22 @@ public MessageTemplateProcessor(PropertyValueConverter propertyValueConverter) _propertyBinder = new PropertyBinder(_propertyValueConverter); } - public void Process(string messageTemplate, object[] messageTemplateParameters, out MessageTemplate parsedTemplate, out IEnumerable properties) + public void Process( + string messageTemplate, + object?[]? messageTemplateParameters, + out MessageTemplate parsedTemplate, + out IEnumerable properties) { parsedTemplate = _parser.Parse(messageTemplate); properties = _propertyBinder.ConstructProperties(parsedTemplate, messageTemplateParameters); } - public LogEventProperty CreateProperty(string name, object value, bool destructureObjects = false) + public LogEventPropertyValue CreatePropertyValue(object? value, bool destructureObjects = false) + { + return _propertyValueConverter.CreatePropertyValue(value, destructureObjects); + } + + public LogEventProperty CreateProperty(string name, object? value, bool destructureObjects = false) { return _propertyValueConverter.CreateProperty(name, value, destructureObjects); } diff --git a/src/Seq.Extensions.Logging/Serilog/Parameters/PropertyBinder.cs b/src/Seq.Extensions.Logging/Serilog/Parameters/PropertyBinder.cs index 2d74cbb..aa99d49 100644 --- a/src/Seq.Extensions.Logging/Serilog/Parameters/PropertyBinder.cs +++ b/src/Seq.Extensions.Logging/Serilog/Parameters/PropertyBinder.cs @@ -23,7 +23,7 @@ class PropertyBinder { readonly PropertyValueConverter _valueConverter; - static readonly LogEventProperty[] NoProperties = new LogEventProperty[0]; + static readonly LogEventProperty[] NoProperties = []; public PropertyBinder(PropertyValueConverter valueConverter) { @@ -38,7 +38,7 @@ public PropertyBinder(PropertyValueConverter valueConverter) /// represented in the message template. /// A list of properties; if the template is malformed then /// this will be empty. - public IEnumerable ConstructProperties(MessageTemplate messageTemplate, object[] messageTemplateParameters) + public IEnumerable ConstructProperties(MessageTemplate messageTemplate, object?[]? messageTemplateParameters) { if (messageTemplateParameters == null || messageTemplateParameters.Length == 0) { @@ -54,18 +54,17 @@ public IEnumerable ConstructProperties(MessageTemplate message return ConstructNamedProperties(messageTemplate, messageTemplateParameters); } - IEnumerable ConstructPositionalProperties(MessageTemplate template, object[] messageTemplateParameters) + IEnumerable ConstructPositionalProperties(MessageTemplate template, object?[] messageTemplateParameters) { var positionalProperties = template.PositionalProperties; - if (positionalProperties.Length != messageTemplateParameters.Length) + if (positionalProperties!.Length != messageTemplateParameters.Length) SelfLog.WriteLine("Positional property count does not match parameter count: {0}", template); - var result = new LogEventProperty[messageTemplateParameters.Length]; + var result = new LogEventProperty?[messageTemplateParameters.Length]; foreach (var property in positionalProperties) { - int position; - if (property.TryGetPositionalValue(out position)) + if (property.TryGetPositionalValue(out var position)) { if (position < 0 || position >= messageTemplateParameters.Length) SelfLog.WriteLine("Unassigned positional value {0} in: {1}", position, template); @@ -87,14 +86,14 @@ IEnumerable ConstructPositionalProperties(MessageTemplate temp if (next != result.Length) Array.Resize(ref result, next); - return result; + return result!; } - IEnumerable ConstructNamedProperties(MessageTemplate template, object[] messageTemplateParameters) + IEnumerable ConstructNamedProperties(MessageTemplate template, object?[] messageTemplateParameters) { var namedProperties = template.NamedProperties; if (namedProperties == null) - return Enumerable.Empty(); + return []; var matchedRun = namedProperties.Length; if (namedProperties.Length != messageTemplateParameters.Length) @@ -106,7 +105,7 @@ IEnumerable ConstructNamedProperties(MessageTemplate template, var result = new LogEventProperty[messageTemplateParameters.Length]; for (var i = 0; i < matchedRun; ++i) { - var property = template.NamedProperties[i]; + var property = template.NamedProperties![i]; var value = messageTemplateParameters[i]; result[i] = ConstructProperty(property, value); } @@ -119,7 +118,7 @@ IEnumerable ConstructNamedProperties(MessageTemplate template, return result; } - LogEventProperty ConstructProperty(PropertyToken propertyToken, object value) + LogEventProperty ConstructProperty(PropertyToken propertyToken, object? value) { return new LogEventProperty( propertyToken.PropertyName, diff --git a/src/Seq.Extensions.Logging/Serilog/Parameters/PropertyValueConverter.cs b/src/Seq.Extensions.Logging/Serilog/Parameters/PropertyValueConverter.cs index cc59350..069afab 100644 --- a/src/Seq.Extensions.Logging/Serilog/Parameters/PropertyValueConverter.cs +++ b/src/Seq.Extensions.Logging/Serilog/Parameters/PropertyValueConverter.cs @@ -29,8 +29,8 @@ namespace Serilog.Parameters; // writing a log event (roughly) in control of the cost of recording that event. partial class PropertyValueConverter : ILogEventPropertyFactory, ILogEventPropertyValueFactory { - readonly HashSet _builtInScalarTypes = new() - { + readonly HashSet _builtInScalarTypes = + [ typeof(bool), typeof(char), typeof(byte), typeof(short), typeof(ushort), typeof(int), typeof(uint), @@ -38,12 +38,12 @@ partial class PropertyValueConverter : ILogEventPropertyFactory, ILogEventProper typeof(string), typeof(DateTime), typeof(DateTimeOffset), typeof(TimeSpan), typeof(Guid), typeof(Uri) - }; + ]; - static IEnumerable SeqExtensionsLoggingScalarTypes() => new[] - { + static IEnumerable SeqExtensionsLoggingScalarTypes() => + [ typeof(JsonSafeString) - }; + ]; readonly IDestructuringPolicy[] _destructuringPolicies; readonly IScalarConversionPolicy[] _scalarConversionPolicies; @@ -62,32 +62,32 @@ public PropertyValueConverter( _propagateExceptions = false; _maximumStringLength = maximumStringLength; - _scalarConversionPolicies = new IScalarConversionPolicy[] - { + _scalarConversionPolicies = + [ new SimpleScalarConversionPolicy(_builtInScalarTypes.Concat(SeqExtensionsLoggingScalarTypes())), new NullableScalarConversionPolicy(), new EnumScalarConversionPolicy(), - new ByteArrayScalarConversionPolicy(), - }; + new ByteArrayScalarConversionPolicy() + ]; - _destructuringPolicies = new IDestructuringPolicy[] - { + _destructuringPolicies = + [ new DelegateDestructuringPolicy(), new ReflectionTypesScalarDestructuringPolicy() - }; + ]; } - public LogEventProperty CreateProperty(string name, object value, bool destructureObjects = false) + public LogEventProperty CreateProperty(string name, object? value, bool destructureObjects = false) { return new LogEventProperty(name, CreatePropertyValue(value, destructureObjects)); } - public LogEventPropertyValue CreatePropertyValue(object value, bool destructureObjects = false) + public LogEventPropertyValue CreatePropertyValue(object? value, bool destructureObjects = false) { return CreatePropertyValue(value, destructureObjects, 1); } - public LogEventPropertyValue CreatePropertyValue(object value, Destructuring destructuring) + public LogEventPropertyValue CreatePropertyValue(object? value, Destructuring destructuring) { try { @@ -104,7 +104,7 @@ public LogEventPropertyValue CreatePropertyValue(object value, Destructuring des } } - LogEventPropertyValue CreatePropertyValue(object value, bool destructureObjects, int depth) + LogEventPropertyValue CreatePropertyValue(object? value, bool destructureObjects, int depth) { return CreatePropertyValue( value, @@ -114,7 +114,7 @@ LogEventPropertyValue CreatePropertyValue(object value, bool destructureObjects, depth); } - LogEventPropertyValue CreatePropertyValue(object value, Destructuring destructuring, int depth) + LogEventPropertyValue CreatePropertyValue(object? value, Destructuring destructuring, int depth) { if (value == null) return new ScalarValue(null); @@ -129,8 +129,7 @@ LogEventPropertyValue CreatePropertyValue(object value, Destructuring destructur if (destructuring == Destructuring.Destructure) { - var stringValue = value as string; - if (stringValue != null) + if (value is string stringValue) { value = TruncateIfNecessary(stringValue); } @@ -138,8 +137,7 @@ LogEventPropertyValue CreatePropertyValue(object value, Destructuring destructur foreach (var scalarConversionPolicy in _scalarConversionPolicies) { - ScalarValue converted; - if (scalarConversionPolicy.TryConvertToScalar(value, limiter, out converted)) + if (scalarConversionPolicy.TryConvertToScalar(value, limiter, out var converted)) return converted; } @@ -147,14 +145,12 @@ LogEventPropertyValue CreatePropertyValue(object value, Destructuring destructur { foreach (var destructuringPolicy in _destructuringPolicies) { - LogEventPropertyValue result; - if (destructuringPolicy.TryDestructure(value, limiter, out result)) + if (destructuringPolicy.TryDestructure(value, limiter, out var result)) return result; } } - var enumerable = value as IEnumerable; - if (enumerable != null) + if (value is IEnumerable enumerable) { // Only dictionaries with 'scalar' keys are permitted, as // more complex keys may not serialize to unique values for @@ -167,10 +163,10 @@ LogEventPropertyValue CreatePropertyValue(object value, Destructuring destructur if (IsValueTypeDictionary(valueType)) { var typeInfo = typeof(KeyValuePair<,>).MakeGenericType(valueType.GenericTypeArguments).GetTypeInfo(); - var keyProperty = typeInfo.GetDeclaredProperty("Key"); - var valueProperty = typeInfo.GetDeclaredProperty("Value"); + var keyProperty = typeInfo.GetDeclaredProperty("Key")!; + var valueProperty = typeInfo.GetDeclaredProperty("Value")!; - return new DictionaryValue(enumerable.Cast() + return new DictionaryValue(enumerable.Cast() .Select(kvp => new KeyValuePair( (ScalarValue)limiter.CreatePropertyValue(keyProperty.GetValue(kvp), destructuring), limiter.CreatePropertyValue(valueProperty.GetValue(kvp), destructuring))) @@ -198,7 +194,7 @@ LogEventPropertyValue CreatePropertyValue(object value, Destructuring destructur LogEventPropertyValue Stringify(object value) { - var stringified = value.ToString(); + var stringified = value.ToString()!; var truncated = TruncateIfNecessary(stringified); return new ScalarValue(truncated); } @@ -230,7 +226,7 @@ IEnumerable GetProperties(object value, ILogEventPropertyValue { foreach (var prop in value.GetType().GetPropertiesRecursive()) { - object propValue; + object? propValue; try { propValue = prop.GetValue(value); diff --git a/src/Seq.Extensions.Logging/Serilog/Parsing/MessageTemplateParser.cs b/src/Seq.Extensions.Logging/Serilog/Parsing/MessageTemplateParser.cs index c7d5208..c4812be 100644 --- a/src/Seq.Extensions.Logging/Serilog/Parsing/MessageTemplateParser.cs +++ b/src/Seq.Extensions.Logging/Serilog/Parsing/MessageTemplateParser.cs @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +using System.Diagnostics.CodeAnalysis; using System.Text; using Serilog.Events; @@ -21,7 +22,7 @@ namespace Serilog.Parsing; /// Parses message template strings into sequences of text or property /// tokens. /// -class MessageTemplateParser +static class MessageTemplateParser { /// /// Parse the supplied message template. @@ -31,7 +32,7 @@ class MessageTemplateParser /// is not syntactically valid, text tokens will be returned. The parser /// will make a best effort to extract valid property tokens even in the /// presence of parsing issues. - public MessageTemplate Parse(string messageTemplate) + public static MessageTemplate Parse(string messageTemplate) { if (messageTemplate == null) throw new ArgumentNullException(nameof(messageTemplate)); @@ -42,7 +43,7 @@ static IEnumerable Tokenize(string messageTemplate) { if (messageTemplate.Length == 0) { - yield return new TextToken("", 0); + yield return new TextToken(""); yield break; } @@ -77,7 +78,7 @@ static MessageTemplateToken ParsePropertyToken(int startAt, string messageTempla if (startAt == messageTemplate.Length || messageTemplate[startAt] != '}') { next = startAt; - return new TextToken(messageTemplate.Substring(first, next - first), first); + return new TextToken(messageTemplate.Substring(first, next - first)); } next = startAt + 1; @@ -85,25 +86,23 @@ static MessageTemplateToken ParsePropertyToken(int startAt, string messageTempla var rawText = messageTemplate.Substring(first, next - first); var tagContent = rawText.Substring(1, next - (first + 2)); if (tagContent.Length == 0) - return new TextToken(rawText, first); + return new TextToken(rawText); - string propertyNameAndDestructuring, format, alignment; - if (!TrySplitTagContent(tagContent, out propertyNameAndDestructuring, out format, out alignment)) - return new TextToken(rawText, first); + if (!TrySplitTagContent(tagContent, out var propertyNameAndDestructuring, out var format, out var alignment)) + return new TextToken(rawText); var propertyName = propertyNameAndDestructuring; - Destructuring destructuring; - if (TryGetDestructuringHint(propertyName[0], out destructuring)) + if (TryGetDestructuringHint(propertyName[0], out var destructuring)) propertyName = propertyName.Substring(1); if (propertyName.Length == 0) - return new TextToken(rawText, first); + return new TextToken(rawText); for (var i = 0; i < propertyName.Length; ++i) { var c = propertyName[i]; if (!IsValidInPropertyName(c)) - return new TextToken(rawText, first); + return new TextToken(rawText); } if (format != null) @@ -112,7 +111,7 @@ static MessageTemplateToken ParsePropertyToken(int startAt, string messageTempla { var c = format[i]; if (!IsValidInFormat(c)) - return new TextToken(rawText, first); + return new TextToken(rawText); } } @@ -123,19 +122,19 @@ static MessageTemplateToken ParsePropertyToken(int startAt, string messageTempla { var c = alignment[i]; if (!IsValidInAlignment(c)) - return new TextToken(rawText, first); + return new TextToken(rawText); } var lastDash = alignment.LastIndexOf('-'); if (lastDash > 0) - return new TextToken(rawText, first); + return new TextToken(rawText); var width = lastDash == -1 ? int.Parse(alignment) : int.Parse(alignment.Substring(1)); if (width == 0) - return new TextToken(rawText, first); + return new TextToken(rawText); var direction = lastDash == -1 ? AlignmentDirection.Right : @@ -149,11 +148,10 @@ static MessageTemplateToken ParsePropertyToken(int startAt, string messageTempla rawText, format, alignmentValue, - destructuring, - first); + destructuring); } - static bool TrySplitTagContent(string tagContent, out string propertyNameAndDestructuring, out string format, out string alignment) + static bool TrySplitTagContent(string tagContent, [NotNullWhen(true)] out string? propertyNameAndDestructuring, out string? format, out string? alignment) { var formatDelim = tagContent.IndexOf(':'); var alignmentDelim = tagContent.IndexOf(','); @@ -299,6 +297,6 @@ static TextToken ParseTextToken(int startAt, string messageTemplate, out int nex } while (startAt < messageTemplate.Length); next = startAt; - return new TextToken(accum.ToString(), first); + return new TextToken(accum.ToString()); } } \ No newline at end of file diff --git a/src/Seq.Extensions.Logging/Serilog/Parsing/MessageTemplateToken.cs b/src/Seq.Extensions.Logging/Serilog/Parsing/MessageTemplateToken.cs index 9b5e8c5..661cba4 100644 --- a/src/Seq.Extensions.Logging/Serilog/Parsing/MessageTemplateToken.cs +++ b/src/Seq.Extensions.Logging/Serilog/Parsing/MessageTemplateToken.cs @@ -21,31 +21,11 @@ namespace Serilog.Parsing; /// abstract class MessageTemplateToken { - /// - /// Construct a . - /// - /// The token's start index in the template. - protected MessageTemplateToken(int startIndex) - { - StartIndex = startIndex; - } - - /// - /// The token's start index in the template. - /// - // ReSharper disable once UnusedAutoPropertyAccessor.Global - public int StartIndex { get; } - - /// - /// The token's length. - /// - public abstract int Length { get; } - /// /// Render the token to the output. /// /// Properties that may be represented by the token. /// Output for the rendered string. /// Supplies culture-specific formatting information, or null. - public abstract void Render(IReadOnlyDictionary properties, TextWriter output, IFormatProvider formatProvider = null); + public abstract void Render(IReadOnlyDictionary properties, TextWriter output, IFormatProvider? formatProvider = null); } \ No newline at end of file diff --git a/src/Seq.Extensions.Logging/Serilog/Parsing/PropertyToken.cs b/src/Seq.Extensions.Logging/Serilog/Parsing/PropertyToken.cs index 94a1322..ce6df4a 100644 --- a/src/Seq.Extensions.Logging/Serilog/Parsing/PropertyToken.cs +++ b/src/Seq.Extensions.Logging/Serilog/Parsing/PropertyToken.cs @@ -34,10 +34,8 @@ class PropertyToken : MessageTemplateToken /// The format applied to the property, if any. /// The alignment applied to the property, if any. /// The destructuring strategy applied to the property, if any. - /// The token's start index in the template. /// - public PropertyToken(string propertyName, string rawText, string format = null, Alignment? alignment = null, Destructuring destructuring = Destructuring.Default, int startIndex = -1) - : base(startIndex) + public PropertyToken(string propertyName, string rawText, string? format = null, Alignment? alignment = null, Destructuring destructuring = Destructuring.Default) { PropertyName = propertyName ?? throw new ArgumentNullException(nameof(propertyName)); Format = format; @@ -52,18 +50,13 @@ public PropertyToken(string propertyName, string rawText, string format = null, } } - /// - /// The token's length. - /// - public override int Length => _rawText.Length; - /// /// Render the token to the output. /// /// Properties that may be represented by the token. /// Output for the rendered string. /// Supplies culture-specific formatting information, or null. - public override void Render(IReadOnlyDictionary properties, TextWriter output, IFormatProvider formatProvider = null) + public override void Render(IReadOnlyDictionary properties, TextWriter output, IFormatProvider? formatProvider = null) { if (properties == null) throw new ArgumentNullException(nameof(properties)); if (output == null) throw new ArgumentNullException(nameof(output)); @@ -106,7 +99,7 @@ public override void Render(IReadOnlyDictionary p /// /// Format applied to the property. /// - public string Format { get; } + public string? Format { get; } /// /// Alignment applied to the property. @@ -142,7 +135,7 @@ public bool TryGetPositionalValue(out int position) /// true if the specified object is equal to the current object; otherwise, false. /// /// The object to compare with the current object. 2 - public override bool Equals(object obj) + public override bool Equals(object? obj) { return obj is PropertyToken pt && pt.Destructuring == Destructuring && diff --git a/src/Seq.Extensions.Logging/Serilog/Parsing/TextToken.cs b/src/Seq.Extensions.Logging/Serilog/Parsing/TextToken.cs index d1d5722..996f804 100644 --- a/src/Seq.Extensions.Logging/Serilog/Parsing/TextToken.cs +++ b/src/Seq.Extensions.Logging/Serilog/Parsing/TextToken.cs @@ -25,26 +25,18 @@ class TextToken : MessageTemplateToken /// Construct a . /// /// The text of the token. - /// The token's start index in the template. - /// - public TextToken(string text, int startIndex = -1) : base(startIndex) + public TextToken(string text) { - if (text == null) throw new ArgumentNullException(nameof(text)); Text = text; } - /// - /// The token's length. - /// - public override int Length => Text.Length; - /// /// Render the token to the output. /// /// Properties that may be represented by the token. /// Output for the rendered string. /// Supplies culture-specific formatting information, or null. - public override void Render(IReadOnlyDictionary properties, TextWriter output, IFormatProvider formatProvider = null) + public override void Render(IReadOnlyDictionary properties, TextWriter output, IFormatProvider? formatProvider = null) { if (output == null) throw new ArgumentNullException(nameof(output)); output.Write(Text); @@ -57,7 +49,7 @@ public override void Render(IReadOnlyDictionary p /// true if the specified object is equal to the current object; otherwise, false. /// /// The object to compare with the current object. 2 - public override bool Equals(object obj) + public override bool Equals(object? obj) { var tt = obj as TextToken; return tt != null && tt.Text == Text; diff --git a/src/Seq.Extensions.Logging/Serilog/Policies/ByteArrayScalarConversionPolicy.cs b/src/Seq.Extensions.Logging/Serilog/Policies/ByteArrayScalarConversionPolicy.cs index 65d7412..ecf679a 100644 --- a/src/Seq.Extensions.Logging/Serilog/Policies/ByteArrayScalarConversionPolicy.cs +++ b/src/Seq.Extensions.Logging/Serilog/Policies/ByteArrayScalarConversionPolicy.cs @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +using System.Diagnostics.CodeAnalysis; using Serilog.Core; using Serilog.Events; @@ -24,7 +25,7 @@ class ByteArrayScalarConversionPolicy : IScalarConversionPolicy { const int MaximumByteArrayLength = 1024; - public bool TryConvertToScalar(object value, ILogEventPropertyValueFactory propertyValueFactory, out ScalarValue result) + public bool TryConvertToScalar(object value, ILogEventPropertyValueFactory propertyValueFactory, [NotNullWhen(true)] out ScalarValue? result) { var bytes = value as byte[]; if (bytes == null) diff --git a/src/Seq.Extensions.Logging/Serilog/Policies/DelegateDestructuringPolicy.cs b/src/Seq.Extensions.Logging/Serilog/Policies/DelegateDestructuringPolicy.cs index d629ce3..72f69f9 100644 --- a/src/Seq.Extensions.Logging/Serilog/Policies/DelegateDestructuringPolicy.cs +++ b/src/Seq.Extensions.Logging/Serilog/Policies/DelegateDestructuringPolicy.cs @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +using System.Diagnostics.CodeAnalysis; using Serilog.Core; using Serilog.Events; @@ -19,10 +20,9 @@ namespace Serilog.Policies; class DelegateDestructuringPolicy : IDestructuringPolicy { - public bool TryDestructure(object value, ILogEventPropertyValueFactory propertyValueFactory, out LogEventPropertyValue result) + public bool TryDestructure(object value, ILogEventPropertyValueFactory propertyValueFactory, [NotNullWhen(true)] out LogEventPropertyValue? result) { - var del = value as Delegate; - if (del != null) + if (value is Delegate del) { result = new ScalarValue(del.ToString()); return true; diff --git a/src/Seq.Extensions.Logging/Serilog/Policies/EnumScalarConversionPolicy.cs b/src/Seq.Extensions.Logging/Serilog/Policies/EnumScalarConversionPolicy.cs index caa99dd..72c175d 100644 --- a/src/Seq.Extensions.Logging/Serilog/Policies/EnumScalarConversionPolicy.cs +++ b/src/Seq.Extensions.Logging/Serilog/Policies/EnumScalarConversionPolicy.cs @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +using System.Diagnostics.CodeAnalysis; using Serilog.Core; using Serilog.Events; using System.Reflection; @@ -20,7 +21,7 @@ namespace Serilog.Policies; class EnumScalarConversionPolicy : IScalarConversionPolicy { - public bool TryConvertToScalar(object value, ILogEventPropertyValueFactory propertyValueFactory, out ScalarValue result) + public bool TryConvertToScalar(object value, ILogEventPropertyValueFactory propertyValueFactory, [NotNullWhen(true)] out ScalarValue? result) { if (value.GetType().GetTypeInfo().IsEnum) { diff --git a/src/Seq.Extensions.Logging/Serilog/Policies/NullableScalarConversionPolicy.cs b/src/Seq.Extensions.Logging/Serilog/Policies/NullableScalarConversionPolicy.cs index 352eef8..b22c4ea 100644 --- a/src/Seq.Extensions.Logging/Serilog/Policies/NullableScalarConversionPolicy.cs +++ b/src/Seq.Extensions.Logging/Serilog/Policies/NullableScalarConversionPolicy.cs @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +using System.Diagnostics.CodeAnalysis; using Serilog.Core; using Serilog.Events; @@ -19,7 +20,7 @@ namespace Serilog.Policies; class NullableScalarConversionPolicy : IScalarConversionPolicy { - public bool TryConvertToScalar(object value, ILogEventPropertyValueFactory propertyValueFactory, out ScalarValue result) + public bool TryConvertToScalar(object value, ILogEventPropertyValueFactory propertyValueFactory, [NotNullWhen(true)] out ScalarValue? result) { var type = value.GetType(); if (!type.IsConstructedGenericType || type.GetGenericTypeDefinition() != typeof(Nullable<>)) diff --git a/src/Seq.Extensions.Logging/Serilog/Policies/ReflectionTypesScalarDestructuringPolicy.cs b/src/Seq.Extensions.Logging/Serilog/Policies/ReflectionTypesScalarDestructuringPolicy.cs index 4cd3d2b..ea9c36d 100644 --- a/src/Seq.Extensions.Logging/Serilog/Policies/ReflectionTypesScalarDestructuringPolicy.cs +++ b/src/Seq.Extensions.Logging/Serilog/Policies/ReflectionTypesScalarDestructuringPolicy.cs @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +using System.Diagnostics.CodeAnalysis; using System.Reflection; using Serilog.Core; using Serilog.Events; @@ -20,11 +21,11 @@ namespace Serilog.Policies; class ReflectionTypesScalarDestructuringPolicy : IDestructuringPolicy { - public bool TryDestructure(object value, ILogEventPropertyValueFactory propertyValueFactory, out LogEventPropertyValue result) + public bool TryDestructure(object value, ILogEventPropertyValueFactory propertyValueFactory, [NotNullWhen(true)] out LogEventPropertyValue? result) { // These types and their subclasses are property-laden and deep; // most sinks will convert them to strings. - if (value is Type || value is MemberInfo) + if (value is Type or MemberInfo) { result = new ScalarValue(value); return true; diff --git a/src/Seq.Extensions.Logging/Serilog/Policies/SimpleScalarConversionPolicy.cs b/src/Seq.Extensions.Logging/Serilog/Policies/SimpleScalarConversionPolicy.cs index e216ff4..b94020c 100644 --- a/src/Seq.Extensions.Logging/Serilog/Policies/SimpleScalarConversionPolicy.cs +++ b/src/Seq.Extensions.Logging/Serilog/Policies/SimpleScalarConversionPolicy.cs @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +using System.Diagnostics.CodeAnalysis; using Serilog.Core; using Serilog.Events; @@ -23,10 +24,10 @@ class SimpleScalarConversionPolicy : IScalarConversionPolicy public SimpleScalarConversionPolicy(IEnumerable scalarTypes) { - _scalarTypes = new HashSet(scalarTypes); + _scalarTypes = [..scalarTypes]; } - public bool TryConvertToScalar(object value, ILogEventPropertyValueFactory propertyValueFactory, out ScalarValue result) + public bool TryConvertToScalar(object value, ILogEventPropertyValueFactory propertyValueFactory, [NotNullWhen(true)] out ScalarValue? result) { if (_scalarTypes.Contains(value.GetType())) { diff --git a/src/Seq.Extensions.Logging/Serilog/Sinks/PeriodicBatching/BoundedConcurrentQueue.cs b/src/Seq.Extensions.Logging/Serilog/Sinks/PeriodicBatching/BoundedConcurrentQueue.cs index 6b28e73..6b11352 100644 --- a/src/Seq.Extensions.Logging/Serilog/Sinks/PeriodicBatching/BoundedConcurrentQueue.cs +++ b/src/Seq.Extensions.Logging/Serilog/Sinks/PeriodicBatching/BoundedConcurrentQueue.cs @@ -13,6 +13,7 @@ // limitations under the License. using System.Collections.Concurrent; +using System.Diagnostics.CodeAnalysis; namespace Serilog.Sinks.PeriodicBatching; @@ -20,7 +21,7 @@ class BoundedConcurrentQueue { const int Unbounded = -1; - readonly ConcurrentQueue _queue = new ConcurrentQueue(); + readonly ConcurrentQueue _queue = new(); readonly int _queueLimit; int _counter; @@ -33,9 +34,7 @@ public BoundedConcurrentQueue(int? queueLimit = null) _queueLimit = queueLimit ?? Unbounded; } - public int Count => _queue.Count; - - public bool TryDequeue(out T item) + public bool TryDequeue([MaybeNullWhen(false)] out T item) { if (_queueLimit == Unbounded) return _queue.TryDequeue(out item); diff --git a/src/Seq.Extensions.Logging/Serilog/Sinks/PeriodicBatching/PeriodicBatchingSink.cs b/src/Seq.Extensions.Logging/Serilog/Sinks/PeriodicBatching/PeriodicBatchingSink.cs index 5bd84e0..a3e33b9 100644 --- a/src/Seq.Extensions.Logging/Serilog/Sinks/PeriodicBatching/PeriodicBatchingSink.cs +++ b/src/Seq.Extensions.Logging/Serilog/Sinks/PeriodicBatching/PeriodicBatchingSink.cs @@ -30,7 +30,7 @@ namespace Serilog.Sinks.PeriodicBatching; /// that want to change this behavior need to either implement from scratch, or /// embed retry logic in the batch emitting functions. /// -class PeriodicBatchingSink : ILogEventSink, IDisposable, IBatchedLogEventSink +sealed class PeriodicBatchingSink : ILogEventSink, IDisposable { /// /// Constant used with legacy constructor to indicate that the internal queue shouldn't be limited. @@ -43,9 +43,9 @@ class PeriodicBatchingSink : ILogEventSink, IDisposable, IBatchedLogEventSink readonly bool _eagerlyEmitFirstEvent; readonly BoundedConcurrentQueue _queue; readonly BatchedConnectionStatus _status; - readonly Queue _waitingBatch = new Queue(); + readonly Queue _waitingBatch = new(); - readonly object _stateLock = new object(); + readonly object _stateLock = new(); readonly PortableTimer _timer; @@ -60,25 +60,18 @@ class PeriodicBatchingSink : ILogEventSink, IDisposable, IBatchedLogEventSink /// it will dispose this object if possible. /// Options controlling behavior of the sink. public PeriodicBatchingSink(IBatchedLogEventSink batchedSink, PeriodicBatchingSinkOptions options) - : this(options) { - _batchedLogEventSink = batchedSink ?? throw new ArgumentNullException(nameof(batchedSink)); - } - - PeriodicBatchingSink(PeriodicBatchingSinkOptions options) - { - if (options == null) throw new ArgumentNullException(nameof(options)); - if (options.BatchSizeLimit <= 0) throw new ArgumentOutOfRangeException(nameof(options), "The batch size limit must be greater than zero."); if (options.Period <= TimeSpan.Zero) throw new ArgumentOutOfRangeException(nameof(options), "The period must be greater than zero."); _batchSizeLimit = options.BatchSizeLimit; + _batchedLogEventSink = batchedSink ?? throw new ArgumentNullException(nameof(batchedSink)); _queue = new BoundedConcurrentQueue(options.QueueLimit); _status = new BatchedConnectionStatus(options.Period); _eagerlyEmitFirstEvent = options.EagerlyEmitFirstEvent; - _timer = new PortableTimer(cancel => OnTick()); + _timer = new PortableTimer(_ => OnTick()); } void CloseAndFlush() @@ -97,8 +90,7 @@ void CloseAndFlush() // so we prevent possible deadlocks here for sync-over-async downstream implementations ResetSyncContextAndWait(OnTick); - if (_batchedLogEventSink != this) - (_batchedLogEventSink as IDisposable)?.Dispose(); + (_batchedLogEventSink as IDisposable)?.Dispose(); } static void ResetSyncContextAndWait(Func taskFactory) @@ -121,44 +113,9 @@ static void ResetSyncContextAndWait(Func taskFactory) /// 2 public void Dispose() { - Dispose(true); - } - - /// - /// Free resources held by the sink. - /// - /// If true, called because the object is being disposed; if false, - /// the object is being disposed from the finalizer. - protected virtual void Dispose(bool disposing) - { - if (!disposing) return; CloseAndFlush(); } - /// - /// Emit a batch of log events, running to completion synchronously. - /// - /// The events to emit. - /// Override either or , - /// not both. - protected virtual void EmitBatch(IEnumerable events) - { - } - - /// - /// Emit a batch of log events, running asynchronously. - /// - /// The events to emit. - /// Override either or , - /// not both. -#pragma warning disable 1998 - protected virtual async Task EmitBatchAsync(IEnumerable events) -#pragma warning restore 1998 - { - // ReSharper disable once MethodHasAsyncOverload - EmitBatch(events); - } - async Task OnTick() { try @@ -169,8 +126,7 @@ async Task OnTick() while (_waitingBatch.Count < _batchSizeLimit && _queue.TryDequeue(out var next)) { - if (CanInclude(next)) - _waitingBatch.Enqueue(next); + _waitingBatch.Enqueue(next); } if (_waitingBatch.Count == 0) @@ -261,43 +217,4 @@ public void Emit(LogEvent logEvent) _queue.TryEnqueue(logEvent); } - - /// - /// Determine whether a queued log event should be included in the batch. If - /// an override returns false, the event will be dropped. - /// - /// An event to test for inclusion. - /// True if the event should be included in the batch; otherwise, false. - // ReSharper disable once UnusedParameter.Global - protected virtual bool CanInclude(LogEvent logEvent) - { - return true; - } - - /// - /// Allows derived sinks to perform periodic work without requiring additional threads - /// or timers (thus avoiding additional flush/shut-down complexity). - /// - /// Override either or , - /// not both. - protected virtual void OnEmptyBatch() - { - } - - /// - /// Allows derived sinks to perform periodic work without requiring additional threads - /// or timers (thus avoiding additional flush/shut-down complexity). - /// - /// Override either or , - /// not both. -#pragma warning disable 1998 - protected virtual async Task OnEmptyBatchAsync() -#pragma warning restore 1998 - { - // ReSharper disable once MethodHasAsyncOverload - OnEmptyBatch(); - } - - Task IBatchedLogEventSink.EmitBatchAsync(IEnumerable batch) => EmitBatchAsync(batch); - Task IBatchedLogEventSink.OnEmptyBatchAsync() => OnEmptyBatchAsync(); } \ No newline at end of file diff --git a/src/Seq.Extensions.Logging/Serilog/Sinks/PeriodicBatching/PortableTimer.cs b/src/Seq.Extensions.Logging/Serilog/Sinks/PeriodicBatching/PortableTimer.cs index c9a3211..ab2c498 100644 --- a/src/Seq.Extensions.Logging/Serilog/Sinks/PeriodicBatching/PortableTimer.cs +++ b/src/Seq.Extensions.Logging/Serilog/Sinks/PeriodicBatching/PortableTimer.cs @@ -18,10 +18,10 @@ namespace Serilog.Sinks.PeriodicBatching; class PortableTimer : IDisposable { - readonly object _stateLock = new object(); + readonly object _stateLock = new(); readonly Func _onTick; - readonly CancellationTokenSource _cancel = new CancellationTokenSource(); + readonly CancellationTokenSource _cancel = new(); readonly Timer _timer; diff --git a/src/Seq.Extensions.Logging/Serilog/Sinks/Seq/ControlledLevelSwitch.cs b/src/Seq.Extensions.Logging/Serilog/Sinks/Seq/ControlledLevelSwitch.cs index 1096f8d..612b27b 100644 --- a/src/Seq.Extensions.Logging/Serilog/Sinks/Seq/ControlledLevelSwitch.cs +++ b/src/Seq.Extensions.Logging/Serilog/Sinks/Seq/ControlledLevelSwitch.cs @@ -27,10 +27,10 @@ class ControlledLevelSwitch { // If non-null, then background level checks will be performed; set either through the constructor // or in response to a level specification from the server. Never set to null after being made non-null. - LoggingLevelSwitch _controlledSwitch; + LoggingLevelSwitch? _controlledSwitch; LogLevel? _originalLevel; - public ControlledLevelSwitch(LoggingLevelSwitch controlledSwitch = null) + public ControlledLevelSwitch(LoggingLevelSwitch? controlledSwitch = null) { _controlledSwitch = controlledSwitch; } diff --git a/src/Seq.Extensions.Logging/Serilog/Sinks/Seq/ExponentialBackoffConnectionSchedule.cs b/src/Seq.Extensions.Logging/Serilog/Sinks/Seq/ExponentialBackoffConnectionSchedule.cs deleted file mode 100644 index ec9322a..0000000 --- a/src/Seq.Extensions.Logging/Serilog/Sinks/Seq/ExponentialBackoffConnectionSchedule.cs +++ /dev/null @@ -1,72 +0,0 @@ -// Serilog.Sinks.Seq Copyright 2016 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. - -namespace Serilog.Sinks.Seq; - -/// -/// Based on the BatchedConnectionStatus class from . -/// -class ExponentialBackoffConnectionSchedule -{ - static readonly TimeSpan MinimumBackoffPeriod = TimeSpan.FromSeconds(5); - static readonly TimeSpan MaximumBackoffInterval = TimeSpan.FromMinutes(10); - - readonly TimeSpan _period; - - int _failuresSinceSuccessfulConnection; - - public ExponentialBackoffConnectionSchedule(TimeSpan period) - { - if (period < TimeSpan.Zero) throw new ArgumentOutOfRangeException(nameof(period), "The connection retry period must be a positive timespan"); - - _period = period; - } - - public void MarkSuccess() - { - _failuresSinceSuccessfulConnection = 0; - } - - public void MarkFailure() - { - ++_failuresSinceSuccessfulConnection; - } - - public TimeSpan NextInterval - { - get - { - // Available, and first failure, just try the batch interval - if (_failuresSinceSuccessfulConnection <= 1) return _period; - - // Second failure, start ramping up the interval - first 2x, then 4x, ... - var backoffFactor = Math.Pow(2, (_failuresSinceSuccessfulConnection - 1)); - - // If the period is ridiculously short, give it a boost so we get some - // visible backoff. - var backoffPeriod = Math.Max(_period.Ticks, MinimumBackoffPeriod.Ticks); - - // The "ideal" interval - var backedOff = (long)(backoffPeriod * backoffFactor); - - // Capped to the maximum interval - var cappedBackoff = Math.Min(MaximumBackoffInterval.Ticks, backedOff); - - // Unless that's shorter than the base interval, in which case we'll just apply the period - var actual = Math.Max(_period.Ticks, cappedBackoff); - - return TimeSpan.FromTicks(actual); - } - } -} \ No newline at end of file diff --git a/src/Seq.Extensions.Logging/Serilog/Sinks/Seq/SeqSink.cs b/src/Seq.Extensions.Logging/Serilog/Sinks/Seq/SeqSink.cs index e049d89..ed6993a 100644 --- a/src/Seq.Extensions.Logging/Serilog/Sinks/Seq/SeqSink.cs +++ b/src/Seq.Extensions.Logging/Serilog/Sinks/Seq/SeqSink.cs @@ -26,7 +26,7 @@ class SeqSink : IBatchedLogEventSink, IDisposable static readonly TimeSpan RequiredLevelCheckInterval = TimeSpan.FromMinutes(2); - readonly string _apiKey; + readonly string? _apiKey; readonly long? _eventBodyLimitBytes; readonly HttpClient _httpClient; @@ -35,10 +35,10 @@ class SeqSink : IBatchedLogEventSink, IDisposable public SeqSink( string serverUrl, - string apiKey, + string? apiKey, long? eventBodyLimitBytes, - ControlledLevelSwitch controlledSwitch, - HttpMessageHandler messageHandler) + ControlledLevelSwitch? controlledSwitch, + HttpMessageHandler? messageHandler) { if (serverUrl == null) throw new ArgumentNullException(nameof(serverUrl)); _controlledSwitch = controlledSwitch ?? throw new ArgumentNullException(nameof(controlledSwitch)); @@ -60,7 +60,7 @@ public async Task OnEmptyBatchAsync() if (_controlledSwitch.IsActive && _nextRequiredLevelCheckUtc < DateTime.UtcNow) { - await EmitBatchAsync(Enumerable.Empty()); + await EmitBatchAsync([]); } } diff --git a/test/Seq.Extensions.Logging.Tests/Seq.Extensions.Logging.Tests.csproj b/test/Seq.Extensions.Logging.Tests/Seq.Extensions.Logging.Tests.csproj index 238fe34..267380a 100644 --- a/test/Seq.Extensions.Logging.Tests/Seq.Extensions.Logging.Tests.csproj +++ b/test/Seq.Extensions.Logging.Tests/Seq.Extensions.Logging.Tests.csproj @@ -1,12 +1,11 @@  - net48;net8.0 + net48;net8.0;net9.0 ../../asset/seqext.snk true true Tests - latest @@ -14,12 +13,13 @@ - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/test/Seq.Extensions.Logging.Tests/Seq/Extensions/Logging/EnricherTests.cs b/test/Seq.Extensions.Logging.Tests/Seq/Extensions/Logging/EnricherTests.cs new file mode 100644 index 0000000..18e64fd --- /dev/null +++ b/test/Seq.Extensions.Logging.Tests/Seq/Extensions/Logging/EnricherTests.cs @@ -0,0 +1,48 @@ +using Serilog.Parameters; +using Serilog.Events; +using Seq.Extensions.Logging; +using Tests.Support; +using Xunit; + +namespace Tests.Seq.Extensions.Logging; + +public class EnricherTests +{ + [Fact] + public void EnrichersAreAppliedInOrder() + { + var evt = Some.EmptyLogEvent(); + + new Enricher([ + enrichingEvent => enrichingEvent.AddPropertyIfAbsent("A", 1), + enrichingEvent => enrichingEvent.AddPropertyIfAbsent("A", 2), + enrichingEvent => enrichingEvent.AddOrUpdateProperty("B", 1), + enrichingEvent => enrichingEvent.AddOrUpdateProperty("B", 2), + ]) + .Enrich( + evt, + new PropertyValueConverter(int.MaxValue, int.MaxValue) + ); + + Assert.Equal(1, ((ScalarValue)evt.Properties["A"]).Value); + Assert.Equal(2, ((ScalarValue)evt.Properties["B"]).Value); + } + + [Fact] + public void FailingEnricherIsHandled() + { + var evt = Some.EmptyLogEvent(); + + new Enricher([ + enrichingEvent => enrichingEvent.AddOrUpdateProperty("A", 1), + _ => throw new Exception("Enricher Failed"), + enrichingEvent => enrichingEvent.AddOrUpdateProperty("A", 2), + ]) + .Enrich( + evt, + new PropertyValueConverter(int.MaxValue, int.MaxValue) + ); + + Assert.Equal(2, ((ScalarValue)evt.Properties["A"]).Value); + } +} \ No newline at end of file diff --git a/test/Seq.Extensions.Logging.Tests/Seq/Extensions/Logging/EnrichingEventTests.cs b/test/Seq.Extensions.Logging.Tests/Seq/Extensions/Logging/EnrichingEventTests.cs new file mode 100644 index 0000000..5e05746 --- /dev/null +++ b/test/Seq.Extensions.Logging.Tests/Seq/Extensions/Logging/EnrichingEventTests.cs @@ -0,0 +1,38 @@ +using Xunit; +using Serilog.Parameters; +using Serilog.Events; +using Seq.Extensions.Logging; +using Tests.Support; + +namespace Tests.Seq.Extensions.Logging; + +public class EnrichingEventTests +{ + [Fact] + public void AddPropertyIfAbsentAddsProperties() + { + var enriching = new EnrichingEvent( + Some.EmptyLogEvent(), + new PropertyValueConverter(int.MaxValue, int.MaxValue) + ); + + enriching.AddPropertyIfAbsent("A", false); + enriching.AddPropertyIfAbsent("A", true); + + Assert.Equal(false, ((ScalarValue)enriching.LogEvent.Properties["A"]).Value); + } + + [Fact] + public void AddOrUpdatePropertyAddsProperties() + { + var enriching = new EnrichingEvent( + Some.EmptyLogEvent(), + new PropertyValueConverter(int.MaxValue, int.MaxValue) + ); + + enriching.AddOrUpdateProperty("A", false); + enriching.AddOrUpdateProperty("A", true); + + Assert.Equal(true, ((ScalarValue)enriching.LogEvent.Properties["A"]).Value); + } +} diff --git a/test/Seq.Extensions.Logging.Tests/Seq/Extensions/Logging/ExceptionDataEnricherTests.cs b/test/Seq.Extensions.Logging.Tests/Seq/Extensions/Logging/ExceptionDataEnricherTests.cs index 0d1676b..e598ca9 100644 --- a/test/Seq.Extensions.Logging.Tests/Seq/Extensions/Logging/ExceptionDataEnricherTests.cs +++ b/test/Seq.Extensions.Logging.Tests/Seq/Extensions/Logging/ExceptionDataEnricherTests.cs @@ -1,7 +1,5 @@ using Seq.Extensions.Logging; using Serilog.Events; -using System; -using System.Linq; using Tests.Support; using Xunit; @@ -16,16 +14,16 @@ public void WhenNoDataIsPresentNoPropertyIsAdded() var exception = new Exception(); var evt = Some.ErrorEvent(exception); - enricher.Enrich(evt, Some.PropertyFactory()); + enricher.Enrich(evt, Some.PropertyValueFactory()); - Assert.Equal(0, evt.Properties.Count); + Assert.Empty(evt.Properties); } [Fact] public void WhenDataIsPresentThePropertyIsAdded() { var enricher = new ExceptionDataEnricher(); - var exception = new Exception() + var exception = new Exception { Data = { @@ -35,9 +33,9 @@ public void WhenDataIsPresentThePropertyIsAdded() }; var evt = Some.ErrorEvent(exception); - enricher.Enrich(evt, Some.PropertyFactory()); + enricher.Enrich(evt, Some.PropertyValueFactory()); - Assert.Equal(1, evt.Properties.Count); + Assert.Single(evt.Properties); var data = evt.Properties["ExceptionData"]; var value = Assert.IsType(data); Assert.Equal(2, value.Properties.Count); diff --git a/test/Seq.Extensions.Logging.Tests/Serilog/Extensions/Logging/SerilogLoggerTests.cs b/test/Seq.Extensions.Logging.Tests/Serilog/Extensions/Logging/SerilogLoggerTests.cs index 1df84d1..fe7ac69 100644 --- a/test/Seq.Extensions.Logging.Tests/Serilog/Extensions/Logging/SerilogLoggerTests.cs +++ b/test/Seq.Extensions.Logging.Tests/Serilog/Extensions/Logging/SerilogLoggerTests.cs @@ -1,14 +1,10 @@ // 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.Diagnostics; -using System.IO; -using System.Linq; using Xunit; using Serilog.Extensions.Logging; using Seq.Extensions.Logging; @@ -30,11 +26,11 @@ static SerilogLoggerTests() const string Name = "test"; const string TestMessage = "This is a test"; - static (SerilogLogger logger, SerilogSink sink) SetUp(LogLevel logLevel) + static (SerilogLogger logger, SerilogSink sink) SetUp(LogLevel logLevel, params Action[] enrichers) { var sink = new SerilogSink(); - var l = new global::Serilog.Core.Logger(new global::Serilog.Core.LoggingLevelSwitch(logLevel), sink); + var l = new global::Serilog.Core.Logger(sink, new Enricher(enrichers), null, new global::Serilog.Core.LoggingLevelSwitch(logLevel)); var provider = new SerilogLoggerProvider(l); provider.SetScopeProvider(new LoggerExternalScopeProvider()); @@ -111,6 +107,19 @@ public void LogsCorrectLevel() [InlineData(LogLevel.Critical, LogLevel.Warning, 0)] [InlineData(LogLevel.Critical, LogLevel.Error, 0)] [InlineData(LogLevel.Critical, LogLevel.Critical, 1)] + [InlineData(LogLevel.None, LogLevel.Trace, 0)] + [InlineData(LogLevel.None, LogLevel.Debug, 0)] + [InlineData(LogLevel.None, LogLevel.Information, 0)] + [InlineData(LogLevel.None, LogLevel.Warning, 0)] + [InlineData(LogLevel.None, LogLevel.Error, 0)] + [InlineData(LogLevel.None, LogLevel.Critical, 0)] + [InlineData(LogLevel.None, LogLevel.None, 0)] + [InlineData(LogLevel.Critical, LogLevel.None, 0)] + [InlineData(LogLevel.Error, LogLevel.None, 0)] + [InlineData(LogLevel.Warning, LogLevel.None, 0)] + [InlineData(LogLevel.Information, LogLevel.None, 0)] + [InlineData(LogLevel.Debug, LogLevel.None, 0)] + [InlineData(LogLevel.Trace, LogLevel.None, 0)] public void LogsWhenEnabled(LogLevel minLevel, LogLevel logLevel, int expected) { var (logger, sink) = SetUp(minLevel); @@ -125,13 +134,13 @@ public void LogsCorrectMessage() { var (logger, sink) = SetUp(LogLevel.Trace); - logger.Log(LogLevel.Information, 0, null, null, null!); + logger.Log(LogLevel.Information, 0, null, null, null!); logger.Log(LogLevel.Information, 0, TestMessage, null, null!); - logger.Log(LogLevel.Information, 0, null, null, (_, _) => TestMessage); + logger.Log(LogLevel.Information, 0, null, null, (_, _) => TestMessage); Assert.Equal(3, sink.Writes.Count); - Assert.Equal(1, sink.Writes[0].Properties.Count); + Assert.Single(sink.Writes[0].Properties); Assert.Empty(sink.Writes[0].RenderMessage()); Assert.Equal(2, sink.Writes[1].Properties.Count); @@ -225,7 +234,13 @@ public void CarriesMessageTemplateProperties() Assert.Equal("Hello, {Recipient}", sink.Writes[0].MessageTemplate.Text); SelfLog.Disable(); - Assert.Empty(selfLog.ToString()); + + var selfLogContent = selfLog.ToString(); + if (!string.IsNullOrEmpty(selfLogContent)) + { + // Test failures are hard to diagnose without the full SelfLog entry. + throw new Exception(selfLogContent); + } } [Fact] @@ -244,6 +259,22 @@ public void CarriesEventIdIfNonzero() Assert.Equal(42, id.Value); } + [Fact] + public void OverridesStateEventIdIfSpecified() + { + var (logger, sink) = SetUp(LogLevel.Trace); + + const int expected = 3; + + logger.Log[]>(LogLevel.Information, expected, state: [new("EventId", "Something")], exception: null, formatter: (_, _) => ""); + + Assert.Single(sink.Writes); + + var eventId = (StructureValue)sink.Writes[0].Properties["EventId"]; + var id = (ScalarValue)eventId.Properties.Single(p => p.Name == "Id").Value; + Assert.Equal(expected, id.Value); + } + [Fact] public void BeginScopeDestructuresObjectsWhenDestructurerIsUsedInMessageTemplate() { @@ -357,6 +388,21 @@ public void CurrentActivityIsCapturedAtLogEventCreation() Assert.Equal(activity.SpanId, single.SpanId); } + [Fact] + public void EnrichersAreApplied() + { + var (logger, sink) = SetUp( + LogLevel.Trace, + evt => evt.AddPropertyIfAbsent("EnrichedScalar", true), + evt => evt.AddPropertyIfAbsent("EnrichedObject", new { a = 1 }, true) + ); + + logger.Log(LogLevel.Information, 0, TestMessage, null, null!); + + Assert.Equal(true, ((ScalarValue)sink.Writes[0].Properties["EnrichedScalar"]).Value); + Assert.Equal(1, ((ScalarValue)((StructureValue)sink.Writes[0].Properties["EnrichedObject"]).Properties[0].Value).Value); + } + class FoodScope : IEnumerable> { readonly string _name; @@ -400,8 +446,8 @@ IEnumerator IEnumerable.GetEnumerator() class Person { // ReSharper disable once UnusedAutoPropertyAccessor.Local - public string FirstName { get; set; } + public string? FirstName { get; set; } // ReSharper disable once UnusedAutoPropertyAccessor.Local - public string LastName { get; set; } + public string? LastName { get; set; } } } \ No newline at end of file diff --git a/test/Seq.Extensions.Logging.Tests/Serilog/Extensions/Logging/Support/SerilogSink.cs b/test/Seq.Extensions.Logging.Tests/Serilog/Extensions/Logging/Support/SerilogSink.cs index baa8cb8..3205cc7 100644 --- a/test/Seq.Extensions.Logging.Tests/Serilog/Extensions/Logging/Support/SerilogSink.cs +++ b/test/Seq.Extensions.Logging.Tests/Serilog/Extensions/Logging/Support/SerilogSink.cs @@ -1,7 +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.Collections.Generic; using Serilog.Core; using Serilog.Events; using Xunit; diff --git a/test/Seq.Extensions.Logging.Tests/Serilog/Sinks/BatchedConnectionStatusTests.cs b/test/Seq.Extensions.Logging.Tests/Serilog/Sinks/BatchedConnectionStatusTests.cs index 366dd4d..29fe89d 100644 --- a/test/Seq.Extensions.Logging.Tests/Serilog/Sinks/BatchedConnectionStatusTests.cs +++ b/test/Seq.Extensions.Logging.Tests/Serilog/Sinks/BatchedConnectionStatusTests.cs @@ -12,12 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. -using System; using System.Globalization; -using Xunit; using Serilog.Sinks.PeriodicBatching; +using Xunit; -namespace Seq.Extensions.Logging.Tests.Serilog.Sinks; +namespace Tests.Serilog.Sinks; public class BatchedConnectionStatusTests { diff --git a/test/Seq.Extensions.Logging.Tests/Serilog/Sinks/Seq/ControlledLevelSwitchTests.cs b/test/Seq.Extensions.Logging.Tests/Serilog/Sinks/Seq/ControlledLevelSwitchTests.cs index ace60a5..4243d88 100644 --- a/test/Seq.Extensions.Logging.Tests/Serilog/Sinks/Seq/ControlledLevelSwitchTests.cs +++ b/test/Seq.Extensions.Logging.Tests/Serilog/Sinks/Seq/ControlledLevelSwitchTests.cs @@ -20,8 +20,7 @@ public void WhenTheServerSendsALevelTheSwitchIsAdjusted() [Fact] public void WhenTheServerSendsNoLevelTheSwitchIsNotInitiallyAdjusted() { - var lls = new LoggingLevelSwitch(LogLevel.Warning); - lls.MinimumLevel = LogLevel.Critical; + var lls = new LoggingLevelSwitch(LogLevel.Critical); var cls = new ControlledLevelSwitch(lls); cls.Update(null); Assert.Equal(LogLevel.Critical, lls.MinimumLevel); @@ -40,14 +39,14 @@ public void WhenTheServerSendsNoLevelTheSwitchIsResetIfPreviouslyAdjusted() [Fact] public void WithNoSwitchToControlAllEventsAreIncluded() { - var cls = new ControlledLevelSwitch(null); + var cls = new ControlledLevelSwitch(); Assert.True(cls.IsIncluded(Some.DebugEvent())); } [Fact] public void WithNoSwitchToControlEventsAreStillFiltered() { - var cls = new ControlledLevelSwitch(null); + var cls = new ControlledLevelSwitch(); cls.Update(LogLevel.Warning); Assert.True(cls.IsIncluded(Some.ErrorEvent())); Assert.False(cls.IsIncluded(Some.InformationEvent())); @@ -56,7 +55,7 @@ public void WithNoSwitchToControlEventsAreStillFiltered() [Fact] public void WithNoSwitchToControlAllEventsAreIncludedAfterReset() { - var cls = new ControlledLevelSwitch(null); + var cls = new ControlledLevelSwitch(); cls.Update(LogLevel.Warning); cls.Update(null); Assert.True(cls.IsIncluded(Some.DebugEvent())); @@ -77,7 +76,7 @@ public void WhenNotControllingASwitchTheControllerIsNotActive() } [Fact] - public void AfterServerControlhTheControllerIsAlwaysActive() + public void AfterServerControlTheControllerIsAlwaysActive() { var cls = new ControlledLevelSwitch(); diff --git a/test/Seq.Extensions.Logging.Tests/Serilog/Sinks/Seq/SeqPayloadFormatterTests.cs b/test/Seq.Extensions.Logging.Tests/Serilog/Sinks/Seq/SeqPayloadFormatterTests.cs index 1157956..85f521d 100644 --- a/test/Seq.Extensions.Logging.Tests/Serilog/Sinks/Seq/SeqPayloadFormatterTests.cs +++ b/test/Seq.Extensions.Logging.Tests/Serilog/Sinks/Seq/SeqPayloadFormatterTests.cs @@ -1,8 +1,9 @@ -using Serilog.Sinks.Seq; +using Seq.Extensions.Logging; +using Serilog.Sinks.Seq; using Tests.Support; using Xunit; -namespace Seq.Extensions.Logging.Tests.Serilog.Sinks.Seq; +namespace Tests.Serilog.Sinks.Seq; public class SeqPayloadFormatterTests { @@ -11,7 +12,7 @@ public void JsonSafeStringPropertiesAreIncludedAsIs() { const string json = "{\"A\": 42}"; var evt = Some.LogEvent("The answer is {Answer}", new JsonSafeString(json)); - var payload = SeqPayloadFormatter.FormatCompactPayload(new[] { evt }, null); + var payload = SeqPayloadFormatter.FormatCompactPayload([evt], null); Assert.Contains("\"Answer\":{\"A\": 42}", payload); } @@ -19,7 +20,7 @@ public void JsonSafeStringPropertiesAreIncludedAsIs() public void DefaultJsonSafeStringsDoNotCorruptPayload() { var evt = Some.LogEvent("The answer is {Answer}", (JsonSafeString)default); - var payload = SeqPayloadFormatter.FormatCompactPayload(new[] { evt }, null); + var payload = SeqPayloadFormatter.FormatCompactPayload([evt], null); Assert.Contains("\"Answer\":\"\"", payload); } } \ No newline at end of file diff --git a/test/Seq.Extensions.Logging.Tests/Support/Some.cs b/test/Seq.Extensions.Logging.Tests/Support/Some.cs index b26719d..5f6147a 100644 --- a/test/Seq.Extensions.Logging.Tests/Support/Some.cs +++ b/test/Seq.Extensions.Logging.Tests/Support/Some.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Threading; -using Serilog.Events; +using Serilog.Events; using Xunit.Sdk; using Serilog.Core; using Microsoft.Extensions.Logging; @@ -12,33 +9,45 @@ namespace Tests.Support; static class Some { - public static LogEvent LogEvent(string messageTemplate, params object[] propertyValues) + public static LogEvent EmptyLogEvent() + { + return new LogEvent( + default, + default, + null, + new MessageTemplate("", []), + new(), + default, + default + ); + } + + public static LogEvent LogEvent(string messageTemplate, params object?[] propertyValues) { return LogEvent(null, messageTemplate, propertyValues); } - public static LogEvent LogEvent(Exception exception, string messageTemplate, params object[] propertyValues) + public static LogEvent LogEvent(Exception? exception, string messageTemplate, params object?[] propertyValues) { return LogEvent(LogLevel.Information, exception, messageTemplate, propertyValues); } - public static ILogEventPropertyFactory PropertyFactory() + public static ILogEventPropertyValueFactory PropertyValueFactory() { return new PropertyValueConverter(10, 1024); } - public static LogEvent LogEvent(LogLevel level, Exception exception, string messageTemplate, params object[] propertyValues) + public static LogEvent LogEvent(LogLevel level, Exception? exception, string messageTemplate, params object?[] propertyValues) { - var log = new Logger(null, null, null); - MessageTemplate template; - IEnumerable properties; + var log = new Logger(null!, null!); #pragma warning disable Serilog004 // Constant MessageTemplate verifier - if (!log.BindMessageTemplate(messageTemplate, propertyValues, out template, out properties)) + // ReSharper disable once ConvertIfStatementToReturnStatement + if (!log.BindMessageTemplate(messageTemplate, propertyValues, out var template, out var properties)) #pragma warning restore Serilog004 // Constant MessageTemplate verifier { throw new XunitException("Template could not be bound."); } - return new LogEvent(DateTimeOffset.Now, level, exception, template, properties, default, default); + return new LogEvent(DateTimeOffset.Now, level, exception, template, properties.ToDictionary(p => p.Name, p => p.Value), default, default); } public static LogEvent DebugEvent() @@ -51,7 +60,7 @@ public static LogEvent InformationEvent() return LogEvent(LogLevel.Information, null, "Information event"); } - public static LogEvent ErrorEvent(Exception exception = null) + public static LogEvent ErrorEvent(Exception? exception = null) { return LogEvent(LogLevel.Error, exception, "Error event"); }