diff --git a/.editorconfig b/.editorconfig index ed09cfcf..0feb7640 100644 --- a/.editorconfig +++ b/.editorconfig @@ -9,9 +9,6 @@ trim_trailing_whitespace = true [*.{csproj,json,config,yml}] indent_size = 2 -[*.sh] -end_of_line = lf - [*.{cmd,bat}] end_of_line = crlf @@ -43,7 +40,6 @@ csharp_space_between_method_call_name_and_opening_parenthesis = false csharp_space_between_method_call_parameter_list_parentheses = false csharp_space_between_method_declaration_empty_parameter_list_parentheses = false csharp_space_between_method_declaration_parameter_list_parentheses = false -csharp_space_between_parentheses = false csharp_style_conditional_delegate_call = true:suggestion csharp_style_expression_bodied_accessors = true:silent csharp_style_expression_bodied_constructors = false:silent diff --git a/.github/workflows/perftests.yml b/.github/workflows/perftests.yml new file mode 100644 index 00000000..d8bc1d45 --- /dev/null +++ b/.github/workflows/perftests.yml @@ -0,0 +1,25 @@ +name: Performance Tests + +on: + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +jobs: + build-and-perftest: + runs-on: windows-latest # Build on Windows to ensure .NET Framework targets + steps: + - uses: actions/checkout@v4 + + - name: Run build + run: ./Build.ps1 -SkipTests + shell: pwsh + + - name: Run performance tests + run: ./RunPerfTests.ps1 -Filter ${{ secrets.PERF_TESTS_FILTER }} + shell: pwsh + + - name: Upload perf test results artifact + uses: actions/upload-artifact@v4 + with: + name: perftestresults + path: artifacts\perftests diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3573faf2..abebc5f3 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -8,7 +8,7 @@ on: workflow_dispatch: jobs: - build-and-release: + build-perftest-and-release: runs-on: windows-latest # Build on Windows to ensure .NET Framework targets steps: - uses: actions/checkout@v4 @@ -35,6 +35,10 @@ jobs: run: ./Build.ps1 -SkipTests shell: pwsh + - name: Run performance tests + run: ./RunPerfTests.ps1 -Filter "*QuickBenchmarks*" + shell: pwsh + - name: Get last commit message id: last_commit if: success() && github.ref == 'refs/heads/main' @@ -47,34 +51,34 @@ jobs: env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - # Der Basisname der Dateien basierend auf der Versionsnummer $baseFileName = "Serilog.Sinks.MSSqlServer.${{ env.VERSION }}" - # Suche die exakten Dateipfade für .nupkg und .snupkg $nupkgFile = Get-ChildItem -Path "artifacts/$baseFileName*.nupkg" | Select-Object -First 1 $snupkgFile = Get-ChildItem -Path "artifacts/$baseFileName*.snupkg" | Select-Object -First 1 + $perfReportSinkFile = Get-ChildItem -Path "artifacts/perftests/Serilog.Sinks.MSSqlServer.PerformanceTests.SinkQuickBenchmarks-report.csv" ` + | Select-Object -First 1 + $perfReportAuditSinkFile = Get-ChildItem -Path "artifacts/perftests/Serilog.Sinks.MSSqlServer.PerformanceTests.AuditSinkQuickBenchmarks-report.csv" ` + | Select-Object -First 1 - # Überprüfe, ob beide Dateien gefunden wurden if (-not $nupkgFile) { Write-Error "nupkg file not found" ; exit 1 } if (-not $snupkgFile) { Write-Error "snupkg file not found" ; exit 1 } + if (-not $perfReportSinkFile) { Write-Error "Benchmark report for sink file not found" ; exit 1 } + if (-not $perfReportAuditSinkFile) { Write-Error "Benchmark report for audit sink file not found" ; exit 1 } - # Ersetze Backslashes durch Forward Slashes für GitHub CLI-Kompatibilität $nupkgFilePath = $nupkgFile.FullName -replace '\\', '/' $snupkgFilePath = $snupkgFile.FullName -replace '\\', '/' + $perfReportSinkFilePath = $perfReportSinkFile.FullName -replace '\\', '/' + $perfReportAuditSinkFilePath = $perfReportAuditSinkFile.FullName -replace '\\', '/' - # Ausgabe der Dateipfade zu Debugging-Zwecken - Write-Host "Uploading files: $nupkgFilePath, $snupkgFilePath" + Write-Host "Uploading files: $nupkgFilePath, $snupkgFilePath $perfReportSinkFilePath $perfReportAuditSinkFilePath" - # Erstelle das Release mit den genauen Dateipfaden gh release create v${{ env.VERSION }} ` --title "v${{ env.VERSION }}" ` --notes "$(Get-Content last_commit_message.txt)" ` - $nupkgFilePath $snupkgFilePath + $nupkgFilePath $snupkgFilePath $perfReportSinkFilePath $perfReportAuditSinkFilePath shell: pwsh - name: Publish to nuget.org - env: - NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }} run: | nuget push artifacts\*.nupkg -Source https://api.nuget.org/v3/index.json -ApiKey ${{ secrets.NUGET_API_KEY }} shell: pwsh diff --git a/.gitignore b/.gitignore index 4b3eeff0..9c1f0a18 100644 --- a/.gitignore +++ b/.gitignore @@ -17,6 +17,8 @@ build/ bld/ [Bb]in/ [Oo]bj/ +artifacts/ +BenchmarkDotNet.Artifacts/ # Roslyn cache directories *.ide/ @@ -125,7 +127,7 @@ publish/ # Publish Web Output *.[Pp]ublish.xml *.azurePubxml -# TODO: Comment the next line if you want to checkin your web deploy settings +# TODO: Comment the next line if you want to checkin your web deploy settings # but database connection strings (with potential passwords) will be unencrypted *.pubxml *.publishproj diff --git a/Build.ps1 b/Build.ps1 index 9beacfc8..9f020c6d 100644 --- a/Build.ps1 +++ b/Build.ps1 @@ -38,17 +38,6 @@ foreach ($src in Get-ChildItem "$PSScriptRoot/src" -Directory) { } if ($SkipTests -eq $false) { - foreach ($test in Get-ChildItem "$PSScriptRoot/test" -Filter "*.PerformanceTests" -Directory) { - Push-Location $test.FullName - - echo "build: Building performance test project in $($test.FullName)" - - & dotnet build -c Release - if ($LASTEXITCODE -ne 0) { exit 2 } - - Pop-Location - } - foreach ($test in Get-ChildItem "$PSScriptRoot/test" -Filter "*.Tests" -Directory) { Push-Location $test.FullName @@ -59,6 +48,15 @@ if ($SkipTests -eq $false) { Pop-Location } + + # The performance benchmark tests should at least build without errors during PR validation + $perfTestProjectPath = "$PSScriptRoot/test/Serilog.Sinks.MSSqlServer.PerformanceTests" + Push-Location "$perfTestProjectPath" + + echo "build: Building performance test project in $perfTestProjectPath" + & dotnet build -c Release + + Pop-Location } Pop-Location diff --git a/CHANGES.md b/CHANGES.md index 4be1cfc1..b104f85c 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,9 @@ +# 7.0.2 +* Fixed issue #580: Removed deprecated transitive dependency on Microsoft.NETCore.Targets by removing runtime identifier (thanks to @david-brink-talogy) +* Fixed issues #540 and #541 in README +* Added performance tests including a GitHub actions workflow +* Minor cleanups and fixes + # 7.0.1 * Fixed issue #567: .NET Framework assemblies were not built properly * Consolidated PR validation action workflows and updated some task versions diff --git a/Directory.Packages.props b/Directory.Packages.props index be2b31db..6ee1a16a 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -1,26 +1,27 @@ - - true - - - - - - - - - - - - - - - - - - - - - - - + + true + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/README.md b/README.md index 180117e8..3fc1eecd 100644 --- a/README.md +++ b/README.md @@ -187,8 +187,8 @@ CREATE TABLE [Logs] ( [Id] int IDENTITY(1,1) NOT NULL, [Message] nvarchar(max) NULL, [MessageTemplate] nvarchar(max) NULL, - [Level] nvarchar(128) NULL, - [TimeStamp] datetime NOT NULL, + [Level] nvarchar(max) NULL, + [TimeStamp] datetime NULL, [Exception] nvarchar(max) NULL, [Properties] nvarchar(max) NULL @@ -468,7 +468,7 @@ If `DataLength` is set to a value different to -1 longer text will be truncated. ### Level -This column stores the event level (Error, Information, etc.). For backwards-compatibility reasons it defaults to a length of 128 characters, but 12 characters is recommended. Alternately, the `StoreAsEnum` property can be set to `true` which causes the underlying level enum integer value to be stored as a SQL `tinyint` column. The `DataType` property can only be set to `nvarchar` or `tinyint`. Setting the `DataType` to `tinyint` is identical to setting `StoreAsEnum` to `true`. +This column stores the event level (Error, Information, etc.). For backwards-compatibility reasons it defaults to a length of `nvarchar(max)` characters, but 12 characters is recommended. Alternately, the `StoreAsEnum` property can be set to `true` which causes the underlying level enum integer value to be stored as a SQL `tinyint` column. The `DataType` property can only be set to `nvarchar` or `tinyint`. Setting the `DataType` to `tinyint` is identical to setting `StoreAsEnum` to `true`. ### TimeStamp diff --git a/RunPerfTests.ps1 b/RunPerfTests.ps1 new file mode 100644 index 00000000..fd3e4b15 --- /dev/null +++ b/RunPerfTests.ps1 @@ -0,0 +1,30 @@ +[CmdletBinding()] +param ( + [Parameter(Mandatory = $false)] + [string] + $Filter = "*" +) + +echo "perf: Performance tests started with Filter = $Filter" + +Push-Location $PSScriptRoot + +$artifactsPath = "$PSScriptRoot\artifacts\perftests" + +if (Test-Path "$artifactsPath") { + echo "perf: Cleaning $artifactsPath" + Remove-Item "$artifactsPath" -Force -Recurse +} + +New-Item -Path "$artifactsPath" -ItemType Directory + +$perfTestProjectPath = "$PSScriptRoot/test/Serilog.Sinks.MSSqlServer.PerformanceTests" +Push-Location "$perfTestProjectPath" + +echo "perf: Running performance test project in $perfTestProjectPath" +& dotnet run -c Release -- -f $Filter + +cp ".\BenchmarkDotNet.Artifacts\results\*.*" "$artifactsPath\" +Pop-Location + +Pop-Location diff --git a/serilog-sinks-mssqlserver.sln b/serilog-sinks-mssqlserver.sln index 2c4ed1be..9768fe57 100644 --- a/serilog-sinks-mssqlserver.sln +++ b/serilog-sinks-mssqlserver.sln @@ -33,6 +33,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution .github\workflows\pr-validation.yml = .github\workflows\pr-validation.yml README.md = README.md .github\workflows\release.yml = .github\workflows\release.yml + RunPerfTests.ps1 = RunPerfTests.ps1 + .github\workflows\perftests.yml = .github\workflows\perftests.yml EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NetStandardDemoLib", "sample\NetStandardDemo\NetStandardDemoLib\NetStandardDemoLib.csproj", "{8E69E31B-61C7-4175-B886-9C2078FCA477}" @@ -43,6 +45,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "NetStandardDemo", "NetStand EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AppConfigDemo", "sample\AppConfigDemo\AppConfigDemo.csproj", "{6BFE1D21-1442-4375-AB69-14160B906A64}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Serilog.Sinks.MSSqlServer.PerformanceTests", "test\Serilog.Sinks.MSSqlServer.PerformanceTests\Serilog.Sinks.MSSqlServer.PerformanceTests.csproj", "{106A6BAF-F8E4-408B-BB09-391330DA87F2}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -81,6 +85,10 @@ Global {6BFE1D21-1442-4375-AB69-14160B906A64}.Debug|Any CPU.Build.0 = Debug|Any CPU {6BFE1D21-1442-4375-AB69-14160B906A64}.Release|Any CPU.ActiveCfg = Release|Any CPU {6BFE1D21-1442-4375-AB69-14160B906A64}.Release|Any CPU.Build.0 = Release|Any CPU + {106A6BAF-F8E4-408B-BB09-391330DA87F2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {106A6BAF-F8E4-408B-BB09-391330DA87F2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {106A6BAF-F8E4-408B-BB09-391330DA87F2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {106A6BAF-F8E4-408B-BB09-391330DA87F2}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -95,6 +103,7 @@ Global {F908C46D-E72E-41E4-975D-73733294F93F} = {7B2B80DE-427A-4FEC-A7CE-7AD81FED73DE} {7B2B80DE-427A-4FEC-A7CE-7AD81FED73DE} = {AA346332-5BAF-47F1-B8FB-7600ED61265D} {6BFE1D21-1442-4375-AB69-14160B906A64} = {AA346332-5BAF-47F1-B8FB-7600ED61265D} + {106A6BAF-F8E4-408B-BB09-391330DA87F2} = {F02D6513-6F45-452E-85A0-41A872A2C1F8} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {AAA6BF8D-7B53-4A5F-A79A-D1B306383B45} diff --git a/src/Serilog.Sinks.MSSqlServer/Serilog.Sinks.MSSqlServer.csproj b/src/Serilog.Sinks.MSSqlServer/Serilog.Sinks.MSSqlServer.csproj index 8e82e129..a98903cb 100644 --- a/src/Serilog.Sinks.MSSqlServer/Serilog.Sinks.MSSqlServer.csproj +++ b/src/Serilog.Sinks.MSSqlServer/Serilog.Sinks.MSSqlServer.csproj @@ -2,7 +2,7 @@ A Serilog sink that writes events to Microsoft SQL Server and Azure SQL - 7.0.1 + 7.0.2 true 7.0.0 Michiel van Oudheusden;Christian Kadluba;Serilog Contributors @@ -25,7 +25,6 @@ true true snupkg - win false false false @@ -40,7 +39,6 @@ - @@ -76,6 +74,7 @@ + diff --git a/test/Serilog.Sinks.MSSqlServer.PerformanceTests/Misc/AuditSinkExtendedBenchmarks.cs b/test/Serilog.Sinks.MSSqlServer.PerformanceTests/Misc/AuditSinkExtendedBenchmarks.cs new file mode 100644 index 00000000..f45fb2b4 --- /dev/null +++ b/test/Serilog.Sinks.MSSqlServer.PerformanceTests/Misc/AuditSinkExtendedBenchmarks.cs @@ -0,0 +1,67 @@ +using System; +using System.Collections.Generic; +using System.Data; +using BenchmarkDotNet.Attributes; + +namespace Serilog.Sinks.MSSqlServer.PerformanceTests.Misc; + +[MemoryDiagnoser] +public class AuditSinkExtendedBenchmarks +{ + private const string _connectionString = @"Data Source=(localdb)\MSSQLLocalDB;Database=LogAuditExtPerfTest;Integrated Security=SSPI;Encrypt=False;"; + private const string _schemaName = "dbo"; + private const string _tableName = "LogEvents"; + private ILogger _log = null!; + private DateTimeOffset _additionalColumn7; + + + [Params("String One", "String Two")] + public string AdditionalColumn1 { get; set; } + + [Params(1, 2)] + public int AdditionalColumn2 { get; set; } + + + [GlobalSetup] + public void Setup() + { + var options = new ColumnOptions + { + AdditionalColumns = new List + { + new() { DataType = SqlDbType.NVarChar, ColumnName = "AdditionalColumn1", DataLength = 40 }, + new() { DataType = SqlDbType.Int, ColumnName = "AdditionalColumn2" }, + new() { DataType = SqlDbType.Int, ColumnName = "AdditionalColumn3" }, + new() { DataType = SqlDbType.Int, ColumnName = "AdditionalColumn4" }, + new() { DataType = SqlDbType.Int, ColumnName = "AdditionalColumn5" }, + new() { DataType = SqlDbType.Int, ColumnName = "AdditionalColumn6" }, + new() { DataType = SqlDbType.DateTimeOffset, ColumnName = "AdditionalColumn7" } + } + }; + options.Store.Add(StandardColumn.LogEvent); + _log = new LoggerConfiguration() + .AuditTo.MSSqlServer(_connectionString, + sinkOptions: new MSSqlServerSinkOptions + { + TableName = _tableName, + SchemaName = _schemaName, + AutoCreateSqlTable = true, + AutoCreateSqlDatabase = true + }, + appConfiguration: null, + restrictedToMinimumLevel: Serilog.Events.LogEventLevel.Verbose, + formatProvider: null, + columnOptions: options, + columnOptionsSection: null) + .CreateLogger(); + + _additionalColumn7 = new DateTimeOffset(2024, 01, 01, 00, 00, 00, TimeSpan.FromHours(1)); + } + + [Benchmark] + public void EmitComplexLogEvent() + { + _log.Information("Hello, {AdditionalColumn1} {AdditionalColumn2} {AdditionalColumn3} {AdditionalColumn4} {AdditionalColumn5} {AdditionalColumn6} {AdditionalColumn7}!", + AdditionalColumn1, AdditionalColumn2, 3, 4, 5, 6, _additionalColumn7); + } +} diff --git a/test/Serilog.Sinks.MSSqlServer.PerformanceTests/Misc/AuditSinkQuickBenchmarks.cs b/test/Serilog.Sinks.MSSqlServer.PerformanceTests/Misc/AuditSinkQuickBenchmarks.cs new file mode 100644 index 00000000..71a6c1c7 --- /dev/null +++ b/test/Serilog.Sinks.MSSqlServer.PerformanceTests/Misc/AuditSinkQuickBenchmarks.cs @@ -0,0 +1,46 @@ +using BenchmarkDotNet.Attributes; + +namespace Serilog.Sinks.MSSqlServer.PerformanceTests.Misc; + +[MemoryDiagnoser] +public class AuditSinkQuickBenchmarks +{ + private const string _connectionString = @"Data Source=(localdb)\MSSQLLocalDB;Database=LogAuditQuickPerfTest;Integrated Security=SSPI;Encrypt=False;"; + private const string _schemaName = "dbo"; + private const string _tableName = "LogEvents"; + private ILogger _log = null!; + + [GlobalSetup] + public void Setup() + { + var options = new ColumnOptions(); + options.Store.Add(StandardColumn.LogEvent); + _log = new LoggerConfiguration() + .AuditTo.MSSqlServer(_connectionString, + sinkOptions: new MSSqlServerSinkOptions + { + TableName = _tableName, + SchemaName = _schemaName, + AutoCreateSqlTable = true, + AutoCreateSqlDatabase = true + }, + appConfiguration: null, + restrictedToMinimumLevel: Serilog.Events.LogEventLevel.Verbose, + formatProvider: null, + columnOptions: options, + columnOptionsSection: null) + .CreateLogger(); + } + + [Benchmark] + public void EmitLogEvent() + { + _log.Information("Hello, {Name}!", "World"); + } + + [Benchmark] + public void IntProperties() + { + _log.Information("Hello, {A} {B} {C}!", 1, 2, 3); + } +} diff --git a/test/Serilog.Sinks.MSSqlServer.PerformanceTests/Misc/SinkExtendedBenchmarks.cs b/test/Serilog.Sinks.MSSqlServer.PerformanceTests/Misc/SinkExtendedBenchmarks.cs new file mode 100644 index 00000000..9e21e646 --- /dev/null +++ b/test/Serilog.Sinks.MSSqlServer.PerformanceTests/Misc/SinkExtendedBenchmarks.cs @@ -0,0 +1,67 @@ +using System; +using System.Collections.Generic; +using System.Data; +using BenchmarkDotNet.Attributes; + +namespace Serilog.Sinks.MSSqlServer.PerformanceTests.Misc; + +[MemoryDiagnoser] +public class SinkExtendedBenchmarks +{ + private const string _connectionString = @"Data Source=(localdb)\MSSQLLocalDB;Database=LogExtPerfTest;Integrated Security=SSPI;Encrypt=False;"; + private const string _schemaName = "dbo"; + private const string _tableName = "LogEvents"; + private ILogger _log = null!; + private DateTimeOffset _additionalColumn7; + + + [Params("String One", "String Two")] + public string AdditionalColumn1 { get; set; } + + [Params(1, 2)] + public int AdditionalColumn2 { get; set; } + + + [GlobalSetup] + public void Setup() + { + var options = new ColumnOptions + { + AdditionalColumns = new List + { + new() { DataType = SqlDbType.NVarChar, ColumnName = "AdditionalColumn1", DataLength = 40 }, + new() { DataType = SqlDbType.Int, ColumnName = "AdditionalColumn2" }, + new() { DataType = SqlDbType.Int, ColumnName = "AdditionalColumn3" }, + new() { DataType = SqlDbType.Int, ColumnName = "AdditionalColumn4" }, + new() { DataType = SqlDbType.Int, ColumnName = "AdditionalColumn5" }, + new() { DataType = SqlDbType.Int, ColumnName = "AdditionalColumn6" }, + new() { DataType = SqlDbType.DateTimeOffset, ColumnName = "AdditionalColumn7" } + } + }; + options.Store.Add(StandardColumn.LogEvent); + _log = new LoggerConfiguration() + .WriteTo.MSSqlServer(_connectionString, + sinkOptions: new MSSqlServerSinkOptions + { + TableName = _tableName, + SchemaName = _schemaName, + AutoCreateSqlTable = true, + AutoCreateSqlDatabase = true + }, + appConfiguration: null, + restrictedToMinimumLevel: Serilog.Events.LogEventLevel.Verbose, + formatProvider: null, + columnOptions: options, + columnOptionsSection: null) + .CreateLogger(); + + _additionalColumn7 = new DateTimeOffset(2024, 01, 01, 00, 00, 00, TimeSpan.FromHours(1)); + } + + [Benchmark] + public void EmitComplexLogEvent() + { + _log.Information("Hello, {AdditionalColumn1} {AdditionalColumn2} {AdditionalColumn3} {AdditionalColumn4} {AdditionalColumn5} {AdditionalColumn6} {AdditionalColumn7}!", + AdditionalColumn1, AdditionalColumn2,3, 4, 5, 6, _additionalColumn7); + } +} diff --git a/test/Serilog.Sinks.MSSqlServer.PerformanceTests/Misc/SinkQuickBenchmarks.cs b/test/Serilog.Sinks.MSSqlServer.PerformanceTests/Misc/SinkQuickBenchmarks.cs new file mode 100644 index 00000000..60c843ba --- /dev/null +++ b/test/Serilog.Sinks.MSSqlServer.PerformanceTests/Misc/SinkQuickBenchmarks.cs @@ -0,0 +1,46 @@ +using BenchmarkDotNet.Attributes; + +namespace Serilog.Sinks.MSSqlServer.PerformanceTests.Misc; + +[MemoryDiagnoser] +public class SinkQuickBenchmarks +{ + private const string _connectionString = @"Data Source=(localdb)\MSSQLLocalDB;Database=LogQuickPerfTest;Integrated Security=SSPI;Encrypt=False;"; + private const string _schemaName = "dbo"; + private const string _tableName = "LogEvents"; + private ILogger _log = null!; + + [GlobalSetup] + public void Setup() + { + var options = new ColumnOptions(); + options.Store.Add(StandardColumn.LogEvent); + _log = new LoggerConfiguration() + .WriteTo.MSSqlServer(_connectionString, + sinkOptions: new MSSqlServerSinkOptions + { + TableName = _tableName, + SchemaName = _schemaName, + AutoCreateSqlTable = true, + AutoCreateSqlDatabase = true + }, + appConfiguration: null, + restrictedToMinimumLevel: Serilog.Events.LogEventLevel.Verbose, + formatProvider: null, + columnOptions: options, + columnOptionsSection: null) + .CreateLogger(); + } + + [Benchmark] + public void EmitLogEvent() + { + _log.Information("Hello, {Name}!", "World"); + } + + [Benchmark] + public void IntProperties() + { + _log.Information("Hello, {A} {B} {C}!", 1, 2, 3); + } +} diff --git a/test/Serilog.Sinks.MSSqlServer.PerformanceTests/Program.cs b/test/Serilog.Sinks.MSSqlServer.PerformanceTests/Program.cs new file mode 100644 index 00000000..63ccef94 --- /dev/null +++ b/test/Serilog.Sinks.MSSqlServer.PerformanceTests/Program.cs @@ -0,0 +1,11 @@ +using BenchmarkDotNet.Running; + +namespace Serilog.Sinks.MSSqlServer.PerformanceTests; + +public static class Program +{ + public static void Main(string[] args) + { + BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args); + } +} diff --git a/test/Serilog.Sinks.MSSqlServer.PerformanceTests/Serilog.Sinks.MSSqlServer.PerformanceTests.csproj b/test/Serilog.Sinks.MSSqlServer.PerformanceTests/Serilog.Sinks.MSSqlServer.PerformanceTests.csproj new file mode 100644 index 00000000..b37c5d50 --- /dev/null +++ b/test/Serilog.Sinks.MSSqlServer.PerformanceTests/Serilog.Sinks.MSSqlServer.PerformanceTests.csproj @@ -0,0 +1,21 @@ + + + + net6.0 + true + Serilog.Sinks.MSSqlServer.PerformanceTests + Exe + ../../assets/Serilog.snk + true + AnyCPU + 6.0-recommended + True + + + + + + + + + diff --git a/test/Serilog.Sinks.MSSqlServer.PerformanceTests/Sinks/MSSqlServer/Platform/SqlBulkBatchWriterBenchmarks.cs b/test/Serilog.Sinks.MSSqlServer.PerformanceTests/Sinks/MSSqlServer/Platform/SqlBulkBatchWriterBenchmarks.cs new file mode 100644 index 00000000..c2c7a756 --- /dev/null +++ b/test/Serilog.Sinks.MSSqlServer.PerformanceTests/Sinks/MSSqlServer/Platform/SqlBulkBatchWriterBenchmarks.cs @@ -0,0 +1,76 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.Threading.Tasks; +using BenchmarkDotNet.Attributes; +using Moq; +using Serilog.Events; +using Serilog.Parsing; +using Serilog.Sinks.MSSqlServer.Output; +using Serilog.Sinks.MSSqlServer.Platform; +using Serilog.Sinks.MSSqlServer.Platform.SqlClient; + +namespace Serilog.Sinks.MSSqlServer.PerformanceTests.Platform; + +[MemoryDiagnoser] +[MaxIterationCount(16)] +public class SqlBulkBatchWriterBenchmarks : IDisposable +{ + private const string _tableName = "TestTableName"; + private const string _schemaName = "TestSchemaName"; + private readonly DataTable _dataTable = new(_tableName); + private Mock _sqlConnectionFactoryMock; + private Mock _logEventDataGeneratorMock; + private Mock _sqlConnectionWrapperMock; + private Mock _sqlBulkCopyWrapper; + private List _logEvents; + private SqlBulkBatchWriter _sut; + + [GlobalSetup] + public void Setup() + { + _sqlConnectionFactoryMock = new Mock(); + _logEventDataGeneratorMock = new Mock(); + _sqlConnectionWrapperMock = new Mock(); + _sqlBulkCopyWrapper = new Mock(); + + _sqlConnectionFactoryMock.Setup(f => f.Create()).Returns(_sqlConnectionWrapperMock.Object); + _sqlConnectionWrapperMock.Setup(c => c.CreateSqlBulkCopy(It.IsAny(), It.IsAny())) + .Returns(_sqlBulkCopyWrapper.Object); + + CreateLogEvents(); + + _sut = new SqlBulkBatchWriter(_tableName, _schemaName, false, _sqlConnectionFactoryMock.Object, + _logEventDataGeneratorMock.Object); + } + + [Benchmark] + public async Task WriteBatch() + { + await _sut.WriteBatch(_logEvents, _dataTable); + } + + private static LogEvent CreateLogEvent() + { + return new LogEvent( + new DateTimeOffset(2020, 1, 1, 0, 0, 0, 0, TimeSpan.Zero), + LogEventLevel.Debug, null, new MessageTemplate(new List()), + new List()); + } + + private void CreateLogEvents() + { + _logEvents = new List(); + var eventCount = 500_000; + while (eventCount-- > 0) + { + _logEvents.Add(CreateLogEvent()); + } + } + + public void Dispose() + { + GC.SuppressFinalize(this); + _dataTable.Dispose(); + } +} diff --git a/test/Serilog.Sinks.MSSqlServer.PerformanceTests/Sinks/MSSqlServer/Platform/SqlInsertStatementWriterBenchmarks.cs b/test/Serilog.Sinks.MSSqlServer.PerformanceTests/Sinks/MSSqlServer/Platform/SqlInsertStatementWriterBenchmarks.cs new file mode 100644 index 00000000..2c2f76f4 --- /dev/null +++ b/test/Serilog.Sinks.MSSqlServer.PerformanceTests/Sinks/MSSqlServer/Platform/SqlInsertStatementWriterBenchmarks.cs @@ -0,0 +1,75 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.Threading.Tasks; +using BenchmarkDotNet.Attributes; +using Moq; +using Serilog.Events; +using Serilog.Parsing; +using Serilog.Sinks.MSSqlServer.Output; +using Serilog.Sinks.MSSqlServer.Platform; +using Serilog.Sinks.MSSqlServer.Platform.SqlClient; + +namespace Serilog.Sinks.MSSqlServer.PerformanceTests.Platform; + +[MemoryDiagnoser] +[MaxIterationCount(16)] +public class SqlInsertStatementWriterBenchmarks : IDisposable +{ + private const string _tableName = "TestTableName"; + private const string _schemaName = "TestSchemaName"; + private readonly DataTable _dataTable = new(_tableName); + private Mock _sqlConnectionFactoryMock; + private Mock _logEventDataGeneratorMock; + private Mock _sqlConnectionWrapperMock; + private Mock _sqlCommandWrapperMock; + private List _logEvents; + private SqlInsertStatementWriter _sut; + + [GlobalSetup] + public void Setup() + { + _sqlConnectionFactoryMock = new Mock(); + _logEventDataGeneratorMock = new Mock(); + _sqlConnectionWrapperMock = new Mock(); + _sqlCommandWrapperMock = new Mock(); + + _sqlConnectionFactoryMock.Setup(f => f.Create()).Returns(_sqlConnectionWrapperMock.Object); + _sqlConnectionWrapperMock.Setup(f => f.CreateCommand()).Returns(_sqlCommandWrapperMock.Object); + + CreateLogEvents(); + + _sut = new SqlInsertStatementWriter(_tableName, _schemaName, _sqlConnectionFactoryMock.Object, + _logEventDataGeneratorMock.Object); + } + + [Benchmark] + public async Task WriteBatch() + { + await _sut.WriteBatch(_logEvents, _dataTable); + } + + private static LogEvent CreateLogEvent() + { + return new LogEvent( + new DateTimeOffset(2020, 1, 1, 0, 0, 0, 0, TimeSpan.Zero), + LogEventLevel.Debug, null, new MessageTemplate(new List()), + new List()); + } + + private void CreateLogEvents() + { + _logEvents = new List(); + var eventCount = 200_000; + while (eventCount-- > 0) + { + _logEvents.Add(CreateLogEvent()); + } + } + + public void Dispose() + { + GC.SuppressFinalize(this); + _dataTable.Dispose(); + } +} diff --git a/test/Serilog.Sinks.MSSqlServer.Tests/Serilog.Sinks.MSSqlServer.Tests.csproj b/test/Serilog.Sinks.MSSqlServer.Tests/Serilog.Sinks.MSSqlServer.Tests.csproj index 828477ca..b1e02e28 100644 --- a/test/Serilog.Sinks.MSSqlServer.Tests/Serilog.Sinks.MSSqlServer.Tests.csproj +++ b/test/Serilog.Sinks.MSSqlServer.Tests/Serilog.Sinks.MSSqlServer.Tests.csproj @@ -9,7 +9,6 @@ true Serilog.Sinks.MSSqlServer.Tests true - win true AnyCPU 6.0-recommended diff --git a/test/Serilog.Sinks.MSSqlServer.Tests/Sinks/MSSqlServer/Output/AdditionalColumnDataGeneratorTests.cs b/test/Serilog.Sinks.MSSqlServer.Tests/Sinks/MSSqlServer/Output/AdditionalColumnDataGeneratorTests.cs index 0b08096e..e7143479 100644 --- a/test/Serilog.Sinks.MSSqlServer.Tests/Sinks/MSSqlServer/Output/AdditionalColumnDataGeneratorTests.cs +++ b/test/Serilog.Sinks.MSSqlServer.Tests/Sinks/MSSqlServer/Output/AdditionalColumnDataGeneratorTests.cs @@ -8,7 +8,7 @@ using Serilog.Sinks.MSSqlServer.Tests.TestUtils; using Xunit; -namespace Serilog.Tests.Output +namespace Serilog.Sinks.MSSqlServer.Tests.Output { [Trait(TestCategory.TraitName, TestCategory.Unit)] public class AdditionalColumnDataGeneratorTests