├── .editorconfig ├── .gitattributes ├── .github ├── ISSUE_TEMPLATE.md └── workflows │ ├── perftests.yml │ ├── pr-analysis-codeql.yml │ ├── pr-analysis-devskim.yml │ ├── pr-validation.yml │ └── release.yml ├── .gitignore ├── Build.ps1 ├── CHANGES.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── DEVELOPMENT.md ├── Directory.Build.props ├── Directory.Packages.props ├── LICENSE ├── README.md ├── RunPerfTests.ps1 ├── SECURITY.md ├── assets └── Serilog.snk ├── sample ├── AppConfigDemo │ ├── App.config │ ├── AppConfigDemo.csproj │ └── Program.cs ├── CombinedConfigDemo │ ├── CombinedConfigDemo.csproj │ ├── Program.cs │ └── appsettings.json ├── CustomLogEventFormatterDemo │ ├── CustomLogEventFormatterDemo.csproj │ ├── FlatLogEventFormatter.cs │ └── Program.cs ├── NetStandardDemo │ ├── NetStandardDemoApp │ │ ├── NetStandardDemoApp.csproj │ │ └── Program.cs │ └── NetStandardDemoLib │ │ ├── Initializer.cs │ │ └── NetStandardDemoLib.csproj └── WorkerServiceDemo │ ├── CustomLogEventFormatter.cs │ ├── Program.cs │ ├── Properties │ └── launchSettings.json │ ├── Structured.cs │ ├── Worker.cs │ ├── WorkerServiceDemo.csproj │ ├── appsettings.Development.json │ └── appsettings.json ├── serilog-sinks-mssqlserver.sln ├── src └── Serilog.Sinks.MSSqlServer │ ├── Configuration │ ├── Extensions │ │ ├── Hybrid │ │ │ └── LoggerConfigurationMSSqlServerExtensions.cs │ │ └── Microsoft.Extensions.Configuration │ │ │ └── LoggerConfigurationMSSqlServerExtensions.cs │ ├── Factories │ │ ├── IMSSqlServerAuditSinkFactory.cs │ │ ├── IMSSqlServerSinkFactory.cs │ │ ├── IPeriodicBatchingSinkFactory.cs │ │ ├── MSSqlServerAuditSinkFactory.cs │ │ ├── MSSqlServerSinkFactory.cs │ │ └── PeriodicBatchingSinkFactory.cs │ └── Implementations │ │ ├── Microsoft.Extensions.Configuration │ │ ├── ApplyMicrosoftExtensionsConfiguration.cs │ │ ├── IApplyMicrosoftExtensionsConfiguration.cs │ │ ├── IMicrosoftExtensionsColumnOptionsProvider.cs │ │ ├── IMicrosoftExtensionsConnectionStringProvider.cs │ │ ├── IMicrosoftExtensionsSinkOptionsProvider.cs │ │ ├── MicrosoftExtensionsColumnOptionsProvider.cs │ │ ├── MicrosoftExtensionsConnectionStringProvider.cs │ │ └── MicrosoftExtensionsSinkOptionsProvider.cs │ │ ├── SetProperty.cs │ │ └── System.Configuration │ │ ├── ApplySystemConfiguration.cs │ │ ├── ColumnCollection.cs │ │ ├── ColumnConfig.cs │ │ ├── IApplySystemConfiguration.cs │ │ ├── ISystemConfigurationColumnOptionsProvider.cs │ │ ├── ISystemConfigurationConnectionStringProvider.cs │ │ ├── ISystemConfigurationSinkOptionsProvider.cs │ │ ├── MSSqlServerConfigurationSection.cs │ │ ├── SetPropertyValueOrigin.cs │ │ ├── StandardColumnCollection.cs │ │ ├── StandardColumnConfig.cs │ │ ├── StandardColumnConfigException.cs │ │ ├── StandardColumnConfigId.cs │ │ ├── StandardColumnConfigLevel.cs │ │ ├── StandardColumnConfigLogEvent.cs │ │ ├── StandardColumnConfigMessage.cs │ │ ├── StandardColumnConfigMessageTemplate.cs │ │ ├── StandardColumnConfigProperties.cs │ │ ├── StandardColumnConfigSpanId.cs │ │ ├── StandardColumnConfigTimeStamp.cs │ │ ├── StandardColumnConfigTraceId.cs │ │ ├── SystemConfigurationColumnOptionsProvider.cs │ │ ├── SystemConfigurationConnectionStringProvider.cs │ │ ├── SystemConfigurationSinkOptionsProvider.cs │ │ └── ValueConfigElement.cs │ ├── Extensions │ └── StringExtensions.cs │ ├── GlobalSuppressions.cs │ ├── Images │ └── serilog-sink-nuget.png │ ├── Serilog.Sinks.MSSqlServer.csproj │ └── Sinks │ └── MSSqlServer │ ├── ColumnOptions │ ├── ColumnOptions.cs │ ├── ExceptionColumnOptions.cs │ ├── FinalizeConfigurationForSinkConstructor.cs │ ├── IdColumnOptions.cs │ ├── LevelColumnOptions.cs │ ├── LogEventColumnOptions.cs │ ├── MessageColumnOptions.cs │ ├── MessageTemplateColumnOptions.cs │ ├── PropertiesColumnOptions.cs │ ├── SpanIdColumnOptions.cs │ ├── TimeStampColumnOptions.cs │ └── TraceIdColumnOptions.cs │ ├── Dependencies │ ├── SinkDependencies.cs │ └── SinkDependenciesFactory.cs │ ├── MSSqlServerAuditSink.cs │ ├── MSSqlServerSink.cs │ ├── MSSqlServerSinkOptions.cs │ ├── Options │ └── SinkOptions.cs │ ├── Output │ ├── AdditionalColumnDataGenerator.cs │ ├── ColumnHierarchicalPropertyValueResolver.cs │ ├── ColumnSimplePropertyValueResolver.cs │ ├── IAdditionalColumnDataGenerator.cs │ ├── IColumnHierarchicalPropertyValueResolver.cs │ ├── IColumnSimplePropertyValueResolver.cs │ ├── ILogEventDataGenerator.cs │ ├── IStandardColumnDataGenerator.cs │ ├── IXmlPropertyFormatter.cs │ ├── JsonLogEventFormatter.cs │ ├── LogEventDataGenerator.cs │ ├── StandardColumnDataGenerator.cs │ └── XmlPropertyFormatter.cs │ ├── Platform │ ├── DataTableCreator.cs │ ├── IDataTableCreator.cs │ ├── ISqlBulkBatchWriter.cs │ ├── ISqlCommandExecutor.cs │ ├── ISqlCommandFactory.cs │ ├── ISqlConnectionFactory.cs │ ├── ISqlCreateDatabaseWriter.cs │ ├── ISqlCreateTableWriter.cs │ ├── ISqlLogEventWriter.cs │ ├── ISqlWriter.cs │ ├── SqlBulkBatchWriter.cs │ ├── SqlClient │ │ ├── ISqlBulkCopyWrapper.cs │ │ ├── ISqlCommandWrapper.cs │ │ ├── ISqlConnectionStringBuilderWrapper.cs │ │ ├── ISqlConnectionWrapper.cs │ │ ├── SqlBulkCopyWrapper.cs │ │ ├── SqlCommandWrapper.cs │ │ ├── SqlConnectionStringBuilderWrapper.cs │ │ └── SqlConnectionWrapper.cs │ ├── SqlCommandExecutor.cs │ ├── SqlCommandFactory.cs │ ├── SqlConnectionFactory.cs │ ├── SqlCreateDatabaseWriter.cs │ ├── SqlCreateTableWriter.cs │ ├── SqlDatabaseCreator.cs │ ├── SqlInsertStatementWriter.cs │ └── SqlTableCreator.cs │ ├── SqlColumn.cs │ ├── SqlDataTypes.cs │ └── StandardColumn.cs └── test ├── Serilog.Sinks.MSSqlServer.PerformanceTests ├── Misc │ ├── AuditSinkExtendedBenchmarks.cs │ ├── AuditSinkQuickBenchmarks.cs │ ├── SinkExtendedBenchmarks.cs │ └── SinkQuickBenchmarks.cs ├── Program.cs ├── Serilog.Sinks.MSSqlServer.PerformanceTests.csproj └── Sinks │ └── MSSqlServer │ └── Platform │ ├── SqlBulkBatchWriterBenchmarks.cs │ └── SqlInsertStatementWriterBenchmarks.cs └── Serilog.Sinks.MSSqlServer.Tests ├── App.config ├── Configuration ├── Extensions │ └── Hybrid │ │ ├── ConfigurationExtensionsTests.cs │ │ └── LoggerConfigurationMSSqlServerExtensionsTests.cs ├── Factories │ ├── MSSqlServerAuditSinkFactoryTests.cs │ ├── MSSqlServerSinkFactoryTests.cs │ └── PeriodicBatchingSinkFactoryTests.cs └── Implementations │ ├── Microsoft.Extensions.Configuration │ ├── ApplyMicrosoftExtensionsConfigurationTests.cs │ ├── MicrosoftExtensionsColumnOptionsProviderTests.cs │ ├── MicrosoftExtensionsConnectionStringProviderTests.cs │ └── MicrosoftExtensionsSinkOptionsProviderTests.cs │ └── System.Configuration │ ├── ApplySystemConfigurationTests.cs │ ├── StandardColumnConfigSpanIdTests.cs │ ├── StandardColumnConfigTraceIdTests.cs │ ├── SystemConfigurationColumnOptionsProviderTests.cs │ └── SystemConfigurationSinkOptionsProviderTests.cs ├── Extensions └── StringExtensionsTests.cs ├── GlobalSuppressions.cs ├── Misc ├── AdditionalPropertiesTests.cs ├── CustomStandardColumnNamesTests.cs ├── IndexingFeaturesTests.cs ├── LevelAsEnumTests.cs ├── MiscFeaturesTests.cs ├── OpenTelemetryColumnsTests.cs ├── PropertiesColumnFilteringTests.cs ├── SqlBulkCopyTests.cs ├── SqlTypesTests.cs ├── StructuredSubType.cs ├── StructuredType.cs ├── TimeStampTests.cs ├── TransactionTests.cs └── TriggersOnLogTableTests.cs ├── Serilog.Sinks.MSSqlServer.Tests.csproj ├── Sinks └── MSSqlServer │ ├── ColumnOptions │ ├── ColumnOptionsTests.cs │ ├── SpanIdColumnOptionsTests.cs │ ├── TimeStampColumnOptionsTests.cs │ └── TraceIdColumnOptionsTests.cs │ ├── Dependencies │ └── SinkDependenciesFactoryTests.cs │ ├── MSSqlServerAuditSinkTests.cs │ ├── MSSqlServerSinkOptionsTests.cs │ ├── MSSqlServerSinkTests.cs │ ├── Options │ └── SinkOptionsTests.cs │ ├── Output │ ├── AdditionalColumnDataGeneratorTests.cs │ ├── ColumnHierarchicalPropertyValueResolverTests.cs │ ├── ColumnSimplePropertyValueResolverTests.cs │ ├── JsonLogEventFormatterTests.cs │ ├── LogEventDataGeneratorTests.cs │ ├── StandardColumnDataGeneratorTests.cs │ └── XmlPropertyFormatterTests.cs │ ├── Platform │ ├── DataTableCreatorTests.cs │ ├── SqlBulkBatchWriterTests.cs │ ├── SqlClient │ │ ├── SqlBulkCopyWrapperTests.cs │ │ ├── SqlCommandWrapperTests.cs │ │ ├── SqlConnectionStringBuilderWrapperTests.cs │ │ └── SqlConnectionWrapperTests.cs │ ├── SqlCommandExecutorTests.cs │ ├── SqlConnectionFactoryTests.cs │ ├── SqlCreateDatabaseWriterTests.cs │ ├── SqlCreateTableWriterTests.cs │ ├── SqlInsertStatementWriterTests.cs │ └── TestableSqlCommandExecutor.cs │ └── SqlColumnTests.cs └── TestUtils ├── DapperQueryTemplates.cs ├── DatabaseFixture.cs ├── DatabaseTestsBase.cs ├── Filename.cs ├── PatientSecureFixture.cs ├── TestCategory.cs └── TestLogEventHelper.cs /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | 3 | * text=auto 4 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Bug Report / Support Request Template 2 | -------------------------------------- 3 | If you are opening a feature request, you can ignore this template. Bug reports and requests for assistance usually require the same basic information described below. This will help us more quickly reproduce and investigate the problem you're reporting. (If you are using Serilog.Sinks.MSSqlServerCore, that package is deprecated, please switch to Serilog.Sinks.MSSqlServer before reporting an issue.) 4 | 5 | >> Please clearly describe what the SQL Sink is doing incorrectly: 6 | 7 | >> Please clearly describe the expected behavior: 8 | 9 | >> List the names and versions of all Serilog packages used in the project: 10 | 11 | - Serilog: 12 | - Serilog.Sinks.MSSqlServer: 13 | - (configuration, etc.) 14 | 15 | >> Target framework and operating system: 16 | 17 | [ ] .NET 9 18 | [ ] .NET 8 19 | [ ] .NET Framework 4.8 20 | [ ] .NET Framework 4.7 21 | [ ] .NET Framework 4.6 22 | OS: 23 | 24 | >> Provide a *simple* reproduction of your Serilog configuration code: 25 | 26 | >> Provide a *simple* reproduction of your Serilog configuration file, if any: 27 | 28 | >> Provide a *simple* reproduction of your application code: 29 | -------------------------------------------------------------------------------- /.github/workflows/perftests.yml: -------------------------------------------------------------------------------- 1 | name: Performance Tests 2 | 3 | on: 4 | # Allows you to run this workflow manually from the Actions tab 5 | workflow_dispatch: 6 | 7 | jobs: 8 | build-and-perftest: 9 | runs-on: windows-latest # Build on Windows to ensure .NET Framework targets 10 | steps: 11 | - uses: actions/checkout@v4 12 | 13 | - name: Run build 14 | run: ./Build.ps1 -SkipTests -SkipSamples 15 | shell: pwsh 16 | 17 | - name: Run performance tests 18 | run: ./RunPerfTests.ps1 -Filter ${{ secrets.PERF_TESTS_FILTER }} 19 | shell: pwsh 20 | 21 | - name: Upload perf test results artifact 22 | uses: actions/upload-artifact@v4 23 | with: 24 | name: perftestresults 25 | path: artifacts\perftests 26 | -------------------------------------------------------------------------------- /.github/workflows/pr-analysis-codeql.yml: -------------------------------------------------------------------------------- 1 | name: PR Analysis Code QL 2 | 3 | on: 4 | pull_request: 5 | branches: [ dev, main ] 6 | 7 | # Allows you to run this workflow manually from the Actions tab 8 | workflow_dispatch: 9 | 10 | jobs: 11 | build-and-codeql: 12 | runs-on: windows-latest 13 | permissions: 14 | actions: read 15 | contents: read 16 | security-events: write 17 | 18 | steps: 19 | - name: Checkout repository 20 | uses: actions/checkout@v4 21 | 22 | - name: Initialize CodeQL 23 | uses: github/codeql-action/init@v3 24 | with: 25 | languages: 'csharp' 26 | 27 | - name: Run build 28 | run: ./Build.ps1 -SkipTests 29 | shell: pwsh 30 | 31 | - name: Perform CodeQL Analysis 32 | uses: github/codeql-action/analyze@v3 33 | with: 34 | category: "/language:csharp" 35 | -------------------------------------------------------------------------------- /.github/workflows/pr-analysis-devskim.yml: -------------------------------------------------------------------------------- 1 | name: PR Analysis DevSkim 2 | 3 | on: 4 | pull_request: 5 | branches: [ dev, main ] 6 | 7 | # Allows you to run this workflow manually from the Actions tab 8 | workflow_dispatch: 9 | 10 | jobs: 11 | devskim: 12 | runs-on: ubuntu-latest 13 | permissions: 14 | actions: read 15 | contents: read 16 | security-events: write 17 | 18 | steps: 19 | - name: Checkout code 20 | uses: actions/checkout@v4 21 | 22 | - name: Run DevSkim scanner 23 | uses: microsoft/DevSkim-Action@v1 24 | 25 | - name: Upload DevSkim scan results to GitHub Security tab 26 | uses: github/codeql-action/upload-sarif@v3 27 | with: 28 | sarif_file: devskim-results.sarif 29 | -------------------------------------------------------------------------------- /.github/workflows/pr-validation.yml: -------------------------------------------------------------------------------- 1 | name: PR Validation 2 | 3 | on: 4 | pull_request: 5 | branches: [ dev, main ] 6 | 7 | # Run every biweekly to discover failures due to environment changes 8 | schedule: 9 | - cron: '0 0 1,15 * *' 10 | 11 | # Allows you to run this workflow manually from the Actions tab 12 | workflow_dispatch: 13 | 14 | jobs: 15 | build-and-test: 16 | runs-on: windows-latest # SQL Server LocalDB used in tests requires Windows 17 | steps: 18 | - uses: actions/checkout@v4 19 | 20 | - name: Run build and tests 21 | run: ./Build.ps1 22 | shell: pwsh 23 | 24 | - name: Upload binaries artifact for InferSharp job 25 | uses: actions/upload-artifact@v4 26 | with: 27 | name: bin 28 | path: src\Serilog.Sinks.MSSqlServer\bin\Release\net8.0 29 | 30 | - name: Upload testresults artifact with code coverage file 31 | uses: actions/upload-artifact@v4 32 | with: 33 | name: testresults 34 | path: test\Serilog.Sinks.MSSqlServer.Tests\TestResults 35 | 36 | infersharp: 37 | runs-on: ubuntu-latest # Container action used by Infer# requires Linux 38 | needs: build-and-test 39 | permissions: 40 | actions: read 41 | contents: read 42 | security-events: write 43 | steps: 44 | - name: Download binaries artifact 45 | uses: actions/download-artifact@v4 46 | with: 47 | name: bin 48 | path: bin 49 | 50 | - name: Run Infer# 51 | uses: microsoft/infersharpaction@v1.5 52 | with: 53 | binary-path: bin 54 | 55 | - name: Upload SARIF output to GitHub Security Center 56 | uses: github/codeql-action/upload-sarif@v3 57 | with: 58 | sarif_file: infer-out/report.sarif 59 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | branches: [ dev, main ] 6 | 7 | # Allows you to run this workflow manually from the Actions tab 8 | workflow_dispatch: 9 | 10 | jobs: 11 | build-perftest-and-release: 12 | runs-on: windows-latest # Build on Windows to ensure .NET Framework targets 13 | steps: 14 | - uses: actions/checkout@v4 15 | with: 16 | fetch-depth: 0 17 | 18 | - name: Read version from csproj 19 | if: github.ref == 'refs/heads/main' 20 | run: | 21 | # Extract the version from the .csproj file using PowerShell XML parsing 22 | [xml]$csproj = Get-Content 'src/Serilog.Sinks.MSSqlServer/Serilog.Sinks.MSSqlServer.csproj' 23 | $version = $csproj.Project.PropertyGroup.VersionPrefix 24 | echo "VERSION=$version" >> $env:GITHUB_ENV 25 | 26 | # Check if the tag already exists in git 27 | $tagExists = git tag -l "v$version" 28 | if ($tagExists) { 29 | Write-Host "Tag v$version already exists" 30 | exit 1 31 | } 32 | shell: pwsh 33 | 34 | - name: Run build 35 | run: ./Build.ps1 -SkipTests -SkipSamples 36 | shell: pwsh 37 | 38 | - name: Run performance tests 39 | run: ./RunPerfTests.ps1 -Filter "*QuickBenchmarks*" 40 | shell: pwsh 41 | 42 | - name: Upload perf test results artifact 43 | uses: actions/upload-artifact@v4 44 | with: 45 | name: perftestresults 46 | path: artifacts\perftests 47 | 48 | - name: Get last commit message 49 | id: last_commit 50 | if: success() && github.ref == 'refs/heads/main' 51 | run: | 52 | git log -1 --pretty=%B > last_commit_message.txt 53 | shell: pwsh 54 | 55 | - name: Create Release 56 | if: github.ref == 'refs/heads/main' && success() 57 | env: 58 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 59 | run: | 60 | $baseFileName = "Serilog.Sinks.MSSqlServer.${{ env.VERSION }}" 61 | 62 | $nupkgFile = Get-ChildItem -Path "artifacts/$baseFileName*.nupkg" | Select-Object -First 1 63 | $snupkgFile = Get-ChildItem -Path "artifacts/$baseFileName*.snupkg" | Select-Object -First 1 64 | 65 | if (-not $nupkgFile) { Write-Error "nupkg file not found" ; exit 1 } 66 | if (-not $snupkgFile) { Write-Error "snupkg file not found" ; exit 1 } 67 | 68 | $nupkgFilePath = $nupkgFile.FullName -replace '\\', '/' 69 | $snupkgFilePath = $snupkgFile.FullName -replace '\\', '/' 70 | 71 | Write-Host "Uploading files: $nupkgFilePath, $snupkgFilePath" 72 | 73 | gh release create v${{ env.VERSION }} ` 74 | --title "v${{ env.VERSION }}" ` 75 | --notes "$(Get-Content last_commit_message.txt)" ` 76 | $nupkgFilePath $snupkgFilePath 77 | shell: pwsh 78 | 79 | - name: Publish to nuget.org 80 | run: | 81 | nuget push artifacts\*.nupkg -Source https://api.nuget.org/v3/index.json -ApiKey ${{ secrets.NUGET_API_KEY }} 82 | shell: pwsh 83 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | Please refer to the [Serilog Code of Conduct](https://github.com/serilog/serilog/blob/dev/CODE_OF_CONDUCT.md) which covers all repositories within the Serilog Organization. 4 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Please refer to the [Serilog Contributing Guidelines](https://github.com/serilog/serilog/blob/dev/CONTRIBUTING.md) which also apply to the MSSqlServer Sink. 4 | -------------------------------------------------------------------------------- /DEVELOPMENT.md: -------------------------------------------------------------------------------- 1 | # Creating Releases 2 | 3 | ## Creating a Pre-release (-dev suffix) 4 | 5 | Whenever the `dev` branch is updated (after merging a pull request), the `Release` action is triggered. This action builds a nuget package with a prerelease identifier of the format `-dev-nnnnn` appended to the version number. This package is automatically published on nuget.org. 6 | 7 | ## Creating a latest Release 8 | 9 | ### Normal Update (no major version change) {#normal-update} 10 | 11 | 1. On the `dev` branch, update CHANGES.md and `VersionPrefix` in Serilog.Sinks.MSSqlServer.csproj. 12 | 13 | 1. Create a PR to merge the `dev` branch into `main`. The `Release` action will be triggered. This action builds a nuget package and publishes it on nuget.org. Additionally a release is created in the GitHub repo. The release summary will be taken from the description of the PR, so best thing is to put something similar to the version summary in CHANGES.md in there. 14 | 15 | 1. After the release is done, increase the patch version number in `VersionPrefix` in Serilog.Sinks.MSSqlServer.csproj on the `dev` branch. This ensures that the next dev release will have a higher version number than the latest release. 16 | 17 | ### Major Release (major version change) 18 | 19 | 1. On the `dev` branch, update CHANGES.md and increase the major version in `VersionPrefix` in Serilog.Sinks.MSSqlServer.csproj. Also set `EnablePackageValidation` to false because on an intial release of a new major version you don't have a baseline version yet on nuget.org to compare with. 20 | 21 | 1. Create a PR to merge the `dev` branch into `main`. The `Release` action will be triggered. This works the same as described above under [Normal Update]({#normal-update). 22 | 23 | 1. After the release is done make some changes in Serilog.Sinks.MSSqlServer.csproj on the `dev` branch. Set `EnablePackageValidation` back to true and `PackageValidationBaselineVersion` to the version of the new major release you just created (e.g. 7.0.0). Then also increase the patch version number in `VersionPrefix` (e.g. 7.0.1). 24 | -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | all 4 | high 5 | true 6 | 7 | 8 | -------------------------------------------------------------------------------- /Directory.Packages.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | true 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /RunPerfTests.ps1: -------------------------------------------------------------------------------- 1 | [CmdletBinding()] 2 | param ( 3 | [Parameter(Mandatory = $false)] 4 | [string] 5 | $Filter = "*" 6 | ) 7 | 8 | echo "perf: Performance tests started with Filter = $Filter" 9 | 10 | try 11 | { 12 | Push-Location $PSScriptRoot 13 | 14 | $artifactsPath = "$PSScriptRoot\artifacts\perftests" 15 | 16 | if (Test-Path "$artifactsPath") 17 | { 18 | echo "perf: Cleaning $artifactsPath" 19 | Remove-Item "$artifactsPath" -Force -Recurse 20 | } 21 | 22 | New-Item -Path "$artifactsPath" -ItemType Directory 23 | 24 | $perfTestProjectPath = "$PSScriptRoot/test/Serilog.Sinks.MSSqlServer.PerformanceTests" 25 | try 26 | { 27 | Push-Location "$perfTestProjectPath" 28 | 29 | echo "perf: Running performance test project in $perfTestProjectPath" 30 | & dotnet run -c Release -- -f $Filter 31 | 32 | cp ".\BenchmarkDotNet.Artifacts\results\*.*" "$artifactsPath\" 33 | } 34 | finally 35 | { 36 | Pop-Location 37 | } 38 | 39 | } 40 | finally 41 | { 42 | Pop-Location 43 | } 44 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | We currently do not maintain older major versions of the sink and backport security fixes. Fixes are usually created as a new release based on the latest existing release. 6 | 7 | ## Reporting a Vulnerability 8 | 9 | If you find a security related problem in the library, please create an issue in the GitHub repository and give us as much details and context as you can. 10 | -------------------------------------------------------------------------------- /assets/Serilog.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/serilog-mssql/serilog-sinks-mssqlserver/144ad397ed50e6b26d9c02cdd6f052253301e9fc/assets/Serilog.snk -------------------------------------------------------------------------------- /sample/AppConfigDemo/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 |
5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /sample/AppConfigDemo/AppConfigDemo.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | net462 4 | Exe 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /sample/AppConfigDemo/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using Serilog; 4 | using Serilog.Events; 5 | using Serilog.Sinks.MSSqlServer; 6 | 7 | namespace AppConfigDemo 8 | { 9 | public static class Program 10 | { 11 | private const string _connectionString = "Server=localhost;Database=LogTest;Integrated Security=SSPI;Encrypt=False;"; 12 | private const string _schemaName = "dbo"; 13 | private const string _tableName = "LogEvents"; 14 | 15 | public static void Main() 16 | { 17 | // New MSSqlServerSinkOptions based interface 18 | Log.Logger = new LoggerConfiguration().WriteTo 19 | .MSSqlServer( 20 | connectionString: _connectionString, 21 | sinkOptions: new MSSqlServerSinkOptions 22 | { 23 | TableName = _tableName, 24 | SchemaName = _schemaName, 25 | AutoCreateSqlTable = true 26 | }, 27 | restrictedToMinimumLevel: LogEventLevel.Debug, 28 | formatProvider: null, 29 | columnOptions: null, 30 | logEventFormatter: null) 31 | .CreateLogger(); 32 | 33 | Log.Debug("Getting started"); 34 | 35 | Log.Information("Hello {Name} from thread {ThreadId}", Environment.GetEnvironmentVariable("USERNAME"), 36 | Thread.CurrentThread.ManagedThreadId); 37 | 38 | Log.Warning("No coins remain at position {@Position}", new { Lat = 25, Long = 134 }); 39 | 40 | Log.CloseAndFlush(); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /sample/CombinedConfigDemo/CombinedConfigDemo.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net8.0 6 | 7 | 8 | 9 | 10 | PreserveNewest 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /sample/CombinedConfigDemo/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using Microsoft.Extensions.Configuration; 4 | using Serilog; 5 | using Serilog.Sinks.MSSqlServer; 6 | 7 | namespace CombinedConfigDemo 8 | { 9 | // This sample app reads connection string and column options from appsettings.json 10 | // while schema name, table name and autoCreateSqlTable are supplied programmatically 11 | // as parameters to the MSSqlServer() method. 12 | public static class Program 13 | { 14 | private const string _connectionStringName = "LogDatabase"; 15 | private const string _schemaName = "dbo"; 16 | private const string _tableName = "LogEvents"; 17 | 18 | public static void Main() 19 | { 20 | var configuration = new ConfigurationBuilder() 21 | .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) 22 | .Build(); 23 | var columnOptionsSection = configuration.GetSection("Serilog:ColumnOptions"); 24 | var sinkOptionsSection = configuration.GetSection("Serilog:SinkOptions"); 25 | 26 | // Legacy interface - do not use this anymore 27 | //Log.Logger = new LoggerConfiguration() 28 | // .WriteTo.MSSqlServer( 29 | // connectionString: _connectionStringName, 30 | // tableName: _tableName, 31 | // appConfiguration: configuration, 32 | // autoCreateSqlTable: true, 33 | // columnOptionsSection: columnOptionsSection, 34 | // schemaName: _schemaName) 35 | // .CreateLogger(); 36 | 37 | // New SinkOptions based interface 38 | Log.Logger = new LoggerConfiguration() 39 | .WriteTo.MSSqlServer( 40 | connectionString: _connectionStringName, 41 | sinkOptions: new MSSqlServerSinkOptions 42 | { 43 | TableName = _tableName, 44 | SchemaName = _schemaName, 45 | AutoCreateSqlTable = true 46 | }, 47 | sinkOptionsSection: sinkOptionsSection, 48 | appConfiguration: configuration, 49 | columnOptionsSection: columnOptionsSection) 50 | .CreateLogger(); 51 | 52 | Log.Information("Hello {Name} from thread {ThreadId}", Environment.GetEnvironmentVariable("USERNAME"), Environment.CurrentManagedThreadId); 53 | 54 | Log.Warning("No coins remain at position {@Position}", new { Lat = 25, Long = 134 }); 55 | 56 | Log.CloseAndFlush(); 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /sample/CombinedConfigDemo/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | }, 9 | "ConnectionStrings": { 10 | "LogDatabase": "Server=localhost;Database=LogTest;Integrated Security=SSPI;Encrypt=False;" 11 | }, 12 | "Serilog": { 13 | "SinkOptions": { 14 | "batchPostingLimit": 5, 15 | "batchPeriod": "00:00:15", 16 | "eagerlyEmitFirstEvent": true 17 | }, 18 | "ColumnOptions": { 19 | "addStandardColumns": [ "LogEvent" ], 20 | "removeStandardColumns": [ "MessageTemplate", "Properties" ], 21 | "timeStamp": { 22 | "columnName": "Timestamp", 23 | "convertToUtc": false 24 | } 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /sample/CustomLogEventFormatterDemo/CustomLogEventFormatterDemo.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net8.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /sample/CustomLogEventFormatterDemo/FlatLogEventFormatter.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Linq; 3 | using Serilog.Events; 4 | using Serilog.Formatting; 5 | 6 | namespace CustomLogEventFormatterDemo 7 | { 8 | public class FlatLogEventFormatter : ITextFormatter 9 | { 10 | public void Format(LogEvent logEvent, TextWriter output) 11 | { 12 | logEvent.Properties.ToList().ForEach(e => 13 | { 14 | output.Write($"{e.Key}={e.Value} "); 15 | }); 16 | output.WriteLine(); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /sample/NetStandardDemo/NetStandardDemoApp/NetStandardDemoApp.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net8.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /sample/NetStandardDemo/NetStandardDemoApp/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using NetStandardDemoLib; 4 | using Serilog; 5 | 6 | namespace NetStandardDemoApp 7 | { 8 | public static class Program 9 | { 10 | public static void Main() 11 | { 12 | Log.Logger = Initializer.CreateLoggerConfiguration().CreateLogger(); 13 | 14 | Log.Debug("Getting started"); 15 | 16 | Log.Information("Hello {Name} from thread {ThreadId}", Environment.GetEnvironmentVariable("USERNAME"), Environment.CurrentManagedThreadId); 17 | 18 | Log.Warning("No coins remain at position {@Position}", new { Lat = 25, Long = 134 }); 19 | 20 | Log.CloseAndFlush(); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /sample/NetStandardDemo/NetStandardDemoLib/Initializer.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.ObjectModel; 2 | using System.Data; 3 | using Serilog; 4 | using Serilog.Events; 5 | using Serilog.Sinks.MSSqlServer; 6 | 7 | namespace NetStandardDemoLib 8 | { 9 | public static class Initializer 10 | { 11 | private const string _connectionString = "Server=localhost;Database=LogTest;Integrated Security=SSPI;Encrypt=False;"; 12 | private const string _tableName = "LogEvents"; 13 | 14 | public static LoggerConfiguration CreateLoggerConfiguration() 15 | { 16 | return new LoggerConfiguration() 17 | .Enrich.FromLogContext() 18 | .WriteTo.MSSqlServer( 19 | _connectionString, 20 | new MSSqlServerSinkOptions 21 | { 22 | TableName = _tableName, 23 | AutoCreateSqlTable = true 24 | }, 25 | sinkOptionsSection: null, 26 | appConfiguration: null, 27 | restrictedToMinimumLevel: LevelAlias.Minimum, 28 | formatProvider: null, 29 | columnOptions: BuildColumnOptions(), 30 | columnOptionsSection: null, 31 | logEventFormatter: null); 32 | 33 | } 34 | 35 | private static ColumnOptions BuildColumnOptions() 36 | { 37 | var columnOptions = new ColumnOptions 38 | { 39 | TimeStamp = 40 | { 41 | ColumnName = "TimeStampUTC", 42 | ConvertToUtc = true, 43 | }, 44 | 45 | AdditionalColumns = new Collection 46 | { 47 | new SqlColumn { DataType = SqlDbType.NVarChar, ColumnName = "MachineName" }, 48 | new SqlColumn { DataType = SqlDbType.NVarChar, ColumnName = "ProcessName" }, 49 | new SqlColumn { DataType = SqlDbType.NVarChar, ColumnName = "ThreadId" }, 50 | new SqlColumn { DataType = SqlDbType.NVarChar, ColumnName = "CallerName" }, 51 | new SqlColumn { DataType = SqlDbType.NVarChar, ColumnName = "SourceFile" }, 52 | new SqlColumn { DataType = SqlDbType.NVarChar, ColumnName = "LineNumber" } 53 | } 54 | }; 55 | 56 | columnOptions.Store.Remove(StandardColumn.Properties); 57 | 58 | return columnOptions; 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /sample/NetStandardDemo/NetStandardDemoLib/NetStandardDemoLib.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /sample/WorkerServiceDemo/CustomLogEventFormatter.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Linq; 3 | using Serilog.Events; 4 | using Serilog.Formatting; 5 | 6 | namespace WorkerServiceDemo 7 | { 8 | public class CustomLogEventFormatter : ITextFormatter 9 | { 10 | public static CustomLogEventFormatter Formatter { get; } = new CustomLogEventFormatter(); 11 | 12 | public void Format(LogEvent logEvent, TextWriter output) 13 | { 14 | logEvent.Properties.ToList() 15 | .ForEach(e => output.Write($"{e.Key}={e.Value} ")); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /sample/WorkerServiceDemo/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using Microsoft.Extensions.Hosting; 4 | using Serilog; 5 | 6 | namespace WorkerServiceDemo 7 | { 8 | public static class Program 9 | { 10 | public static void Main(string[] args) 11 | { 12 | //Serilog.Debugging.SelfLog.Enable(Console.Error); 13 | CreateHostBuilder(args).Build().Run(); 14 | } 15 | 16 | public static IHostBuilder CreateHostBuilder(string[] args) => 17 | Host.CreateDefaultBuilder(args) 18 | .ConfigureServices((hostContext, services) => 19 | { 20 | services.AddHostedService(); 21 | }) 22 | .UseSerilog((hostingContext, loggerConfiguration) => 23 | { 24 | loggerConfiguration.ReadFrom.Configuration(hostingContext.Configuration); 25 | }); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /sample/WorkerServiceDemo/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "WorkerServiceDemo": { 4 | "commandName": "Project", 5 | "environmentVariables": { 6 | "DOTNET_ENVIRONMENT": "Development" 7 | } 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /sample/WorkerServiceDemo/Structured.cs: -------------------------------------------------------------------------------- 1 | namespace WorkerServiceDemo 2 | { 3 | internal class Structured 4 | { 5 | public string Name { get; set; } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /sample/WorkerServiceDemo/Worker.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Microsoft.Extensions.Hosting; 5 | using Microsoft.Extensions.Logging; 6 | 7 | namespace WorkerServiceDemo 8 | { 9 | public class Worker : BackgroundService 10 | { 11 | private readonly ILogger _logger; 12 | 13 | public Worker(ILogger logger) 14 | { 15 | _logger = logger; 16 | } 17 | 18 | protected override async Task ExecuteAsync(CancellationToken stoppingToken) 19 | { 20 | _logger.LogInformation("Worker started"); 21 | 22 | // Logging child property Name of structured object structured 23 | // to a separate column according to configuration in AdditionalColumns in appsettings.json 24 | var structured = new Structured 25 | { 26 | Name = "Structured Subproperty Value" 27 | }; 28 | _logger.LogInformation("{@Structured} {@Scalar}", structured, "Scalar Value"); 29 | 30 | 31 | // Logging a property with dots in its name to AdditionalColumn3 32 | // but treat it as unstructured according to configuration in AdditionalColumns in appsettings.json 33 | _logger.LogInformation("Non-structured property with dot-name to AdditionalColumn3 {@NonstructuredProperty.WithNameContainingDots.Name}", 34 | new Random().Next().ToString()); 35 | 36 | while (!stoppingToken.IsCancellationRequested) 37 | { 38 | _logger.LogInformation("Worker running at: {time}. CustomProperty1: {CustomProperty1}", 39 | DateTimeOffset.Now, "Value"); 40 | await Task.Delay(1000, stoppingToken); 41 | } 42 | 43 | _logger.LogInformation("Worker stopping ..."); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /sample/WorkerServiceDemo/WorkerServiceDemo.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net8.0 5 | dotnet-WorkerServiceDemo-A4DFF8A6-AC69-443B-A3B8-34E284CD1C78 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /sample/WorkerServiceDemo/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /sample/WorkerServiceDemo/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | }, 9 | "Serilog": { 10 | "Using": [ "Serilog.Sinks.MSSqlServer" ], 11 | "MinimumLevel": "Debug", 12 | "WriteTo": [ 13 | { 14 | "Name": "MSSqlServer", 15 | "Args": { 16 | "connectionString": "Server=localhost;Database=LogTest;Integrated Security=SSPI;Encrypt=False;", 17 | "sinkOptionsSection": { 18 | "tableName": "LogEvents", 19 | "autoCreateSqlDatabase": true, 20 | "autoCreateSqlTable": true 21 | }, 22 | "restrictedToMinimumLevel": "Information", 23 | "columnOptionsSection": { 24 | "addStandardColumns": [ "LogEvent", "TraceId", "SpanId" ], 25 | "removeStandardColumns": [ "MessageTemplate", "Properties" ], 26 | "timeStamp": { 27 | "columnName": "Timestamp", 28 | "convertToUtc": false 29 | }, 30 | "customColumns": [ 31 | { 32 | "columnName": "AdditionalColumn1", 33 | "propertyName": "CustomProperty1", 34 | "dataType": "12" 35 | }, 36 | { 37 | "columnName": "AdditionalColumn2", 38 | "propertyName": "Structured.Name", 39 | "dataType": "12" 40 | }, 41 | { 42 | "columnName": "AdditionalColumn3", 43 | "propertyName": "NonstructuredProperty.WithNameContainingDots.Name", 44 | "resolveHierarchicalPropertyName": false, 45 | "dataType": "12" 46 | } 47 | ] 48 | }, 49 | "logEventFormatter": "WorkerServiceDemo.CustomLogEventFormatter::Formatter, WorkerServiceDemo" 50 | } 51 | } 52 | ], 53 | "AuditTo": [ 54 | { 55 | "Name": "MSSqlServer", 56 | "Args": { 57 | "connectionString": "Server=localhost;Database=LogTest;Integrated Security=SSPI;Encrypt=False;", 58 | "restrictedToMinimumLevel": "Information", 59 | "logEventFormatter": "WorkerServiceDemo.CustomLogEventFormatter::Formatter, WorkerServiceDemo", 60 | "sinkOptionsSection": { 61 | "tableName": "LogEventsAudit", 62 | "autoCreateSqlDatabase": true, 63 | "autoCreateSqlTable": true 64 | }, 65 | "columnOptionsSection": { 66 | "addStandardColumns": [ "LogEvent", "TraceId", "SpanId" ], 67 | "removeStandardColumns": [ "MessageTemplate", "Properties" ], 68 | "timeStamp": { 69 | "columnName": "Timestamp", 70 | "convertToUtc": false 71 | }, 72 | "customColumns": [ 73 | { 74 | "columnName": "AdditionalColumn1", 75 | "propertyName": "CustomProperty1", 76 | "dataType": "12" 77 | }, 78 | { 79 | "columnName": "AdditionalColumn2", 80 | "propertyName": "Structured.Name", 81 | "dataType": "12" 82 | }, 83 | { 84 | "columnName": "AdditionalColumn3", 85 | "propertyName": "NonstructuredProperty.WithNameContainingDots.Name", 86 | "resolveHierarchicalPropertyName": false, 87 | "dataType": "12" 88 | } 89 | ] 90 | } 91 | } 92 | } 93 | ] 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.MSSqlServer/Configuration/Factories/IMSSqlServerAuditSinkFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Serilog.Core; 3 | using Serilog.Formatting; 4 | 5 | namespace Serilog.Sinks.MSSqlServer.Configuration.Factories 6 | { 7 | internal interface IMSSqlServerAuditSinkFactory 8 | { 9 | ILogEventSink Create( 10 | string connectionString, 11 | MSSqlServerSinkOptions sinkOptions, 12 | IFormatProvider formatProvider, 13 | ColumnOptions columnOptions, 14 | ITextFormatter logEventFormatter); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.MSSqlServer/Configuration/Factories/IMSSqlServerSinkFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Serilog.Formatting; 3 | using Serilog.Core; 4 | 5 | namespace Serilog.Sinks.MSSqlServer.Configuration.Factories 6 | { 7 | internal interface IMSSqlServerSinkFactory 8 | { 9 | IBatchedLogEventSink Create( 10 | string connectionString, 11 | MSSqlServerSinkOptions sinkOptions, 12 | IFormatProvider formatProvider, 13 | ColumnOptions columnOptions, 14 | ITextFormatter logEventFormatter); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.MSSqlServer/Configuration/Factories/IPeriodicBatchingSinkFactory.cs: -------------------------------------------------------------------------------- 1 | using Serilog.Core; 2 | 3 | namespace Serilog.Sinks.MSSqlServer.Configuration.Factories 4 | { 5 | internal interface IPeriodicBatchingSinkFactory 6 | { 7 | ILogEventSink Create(IBatchedLogEventSink sink, MSSqlServerSinkOptions sinkOptions); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.MSSqlServer/Configuration/Factories/MSSqlServerAuditSinkFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Serilog.Core; 3 | using Serilog.Formatting; 4 | 5 | namespace Serilog.Sinks.MSSqlServer.Configuration.Factories 6 | { 7 | internal class MSSqlServerAuditSinkFactory : IMSSqlServerAuditSinkFactory 8 | { 9 | public ILogEventSink Create( 10 | string connectionString, 11 | MSSqlServerSinkOptions sinkOptions, 12 | IFormatProvider formatProvider, 13 | ColumnOptions columnOptions, 14 | ITextFormatter logEventFormatter) => 15 | new MSSqlServerAuditSink( 16 | connectionString, 17 | sinkOptions, 18 | formatProvider, 19 | columnOptions, 20 | logEventFormatter); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.MSSqlServer/Configuration/Factories/MSSqlServerSinkFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Serilog.Formatting; 3 | using Serilog.Core; 4 | 5 | namespace Serilog.Sinks.MSSqlServer.Configuration.Factories 6 | { 7 | internal class MSSqlServerSinkFactory : IMSSqlServerSinkFactory 8 | { 9 | public IBatchedLogEventSink Create( 10 | string connectionString, 11 | MSSqlServerSinkOptions sinkOptions, 12 | IFormatProvider formatProvider, 13 | ColumnOptions columnOptions, 14 | ITextFormatter logEventFormatter) => 15 | new MSSqlServerSink( 16 | connectionString, 17 | sinkOptions, 18 | formatProvider, 19 | columnOptions, 20 | logEventFormatter); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.MSSqlServer/Configuration/Factories/PeriodicBatchingSinkFactory.cs: -------------------------------------------------------------------------------- 1 | using Serilog.Configuration; 2 | using Serilog.Core; 3 | 4 | namespace Serilog.Sinks.MSSqlServer.Configuration.Factories 5 | { 6 | internal class PeriodicBatchingSinkFactory : IPeriodicBatchingSinkFactory 7 | { 8 | public ILogEventSink Create(IBatchedLogEventSink sink, MSSqlServerSinkOptions sinkOptions) 9 | { 10 | var periodicBatchingSinkOptions = new BatchingOptions 11 | { 12 | BatchSizeLimit = sinkOptions.BatchPostingLimit, 13 | BufferingTimeLimit = sinkOptions.BatchPeriod, 14 | EagerlyEmitFirstEvent = sinkOptions.EagerlyEmitFirstEvent 15 | }; 16 | return LoggerSinkConfiguration.CreateSink(lc => lc.Sink(sink, periodicBatchingSinkOptions)); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.MSSqlServer/Configuration/Implementations/Microsoft.Extensions.Configuration/ApplyMicrosoftExtensionsConfiguration.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Configuration; 2 | using Serilog.Sinks.MSSqlServer.Configuration; 3 | 4 | namespace Serilog.Sinks.MSSqlServer 5 | { 6 | internal class ApplyMicrosoftExtensionsConfiguration : IApplyMicrosoftExtensionsConfiguration 7 | { 8 | private readonly IMicrosoftExtensionsConnectionStringProvider _connectionStringProvider; 9 | private readonly IMicrosoftExtensionsColumnOptionsProvider _columnOptionsProvider; 10 | private readonly IMicrosoftExtensionsSinkOptionsProvider _sinkOptionsProvider; 11 | 12 | public ApplyMicrosoftExtensionsConfiguration() : this( 13 | new MicrosoftExtensionsConnectionStringProvider(), 14 | new MicrosoftExtensionsColumnOptionsProvider(), 15 | new MicrosoftExtensionsSinkOptionsProvider()) 16 | { 17 | } 18 | 19 | // Constructor with injectable dependencies for tests 20 | internal ApplyMicrosoftExtensionsConfiguration( 21 | IMicrosoftExtensionsConnectionStringProvider connectionStringProvider, 22 | IMicrosoftExtensionsColumnOptionsProvider columnOptionsProvider, 23 | IMicrosoftExtensionsSinkOptionsProvider sinkOptionsProvider) 24 | { 25 | _connectionStringProvider = connectionStringProvider; 26 | _columnOptionsProvider = columnOptionsProvider; 27 | _sinkOptionsProvider = sinkOptionsProvider; 28 | } 29 | 30 | public string GetConnectionString(string nameOrConnectionString, IConfiguration appConfiguration) => 31 | _connectionStringProvider.GetConnectionString(nameOrConnectionString, appConfiguration); 32 | 33 | public ColumnOptions ConfigureColumnOptions(ColumnOptions columnOptions, IConfigurationSection config) => 34 | _columnOptionsProvider.ConfigureColumnOptions(columnOptions, config); 35 | 36 | public MSSqlServerSinkOptions ConfigureSinkOptions(MSSqlServerSinkOptions sinkOptions, IConfigurationSection config) => 37 | _sinkOptionsProvider.ConfigureSinkOptions(sinkOptions, config); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.MSSqlServer/Configuration/Implementations/Microsoft.Extensions.Configuration/IApplyMicrosoftExtensionsConfiguration.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Configuration; 2 | 3 | namespace Serilog.Sinks.MSSqlServer 4 | { 5 | internal interface IApplyMicrosoftExtensionsConfiguration 6 | { 7 | string GetConnectionString(string nameOrConnectionString, IConfiguration appConfiguration); 8 | ColumnOptions ConfigureColumnOptions(ColumnOptions columnOptions, IConfigurationSection config); 9 | MSSqlServerSinkOptions ConfigureSinkOptions(MSSqlServerSinkOptions sinkOptions, IConfigurationSection config); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.MSSqlServer/Configuration/Implementations/Microsoft.Extensions.Configuration/IMicrosoftExtensionsColumnOptionsProvider.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Configuration; 2 | 3 | namespace Serilog.Sinks.MSSqlServer.Configuration 4 | { 5 | internal interface IMicrosoftExtensionsColumnOptionsProvider 6 | { 7 | ColumnOptions ConfigureColumnOptions(ColumnOptions columnOptions, IConfigurationSection config); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.MSSqlServer/Configuration/Implementations/Microsoft.Extensions.Configuration/IMicrosoftExtensionsConnectionStringProvider.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Configuration; 2 | 3 | namespace Serilog.Sinks.MSSqlServer.Configuration 4 | { 5 | internal interface IMicrosoftExtensionsConnectionStringProvider 6 | { 7 | string GetConnectionString(string nameOrConnectionString, IConfiguration appConfiguration); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.MSSqlServer/Configuration/Implementations/Microsoft.Extensions.Configuration/IMicrosoftExtensionsSinkOptionsProvider.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Configuration; 2 | 3 | namespace Serilog.Sinks.MSSqlServer.Configuration 4 | { 5 | internal interface IMicrosoftExtensionsSinkOptionsProvider 6 | { 7 | MSSqlServerSinkOptions ConfigureSinkOptions(MSSqlServerSinkOptions sinkOptions, IConfigurationSection config); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.MSSqlServer/Configuration/Implementations/Microsoft.Extensions.Configuration/MicrosoftExtensionsConnectionStringProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.Extensions.Configuration; 3 | using Serilog.Debugging; 4 | 5 | namespace Serilog.Sinks.MSSqlServer.Configuration 6 | { 7 | internal class MicrosoftExtensionsConnectionStringProvider : IMicrosoftExtensionsConnectionStringProvider 8 | { 9 | public string GetConnectionString(string nameOrConnectionString, IConfiguration appConfiguration) 10 | { 11 | // If there is an `=`, we assume this is a raw connection string not a named value 12 | // If there are no `=`, attempt to pull the named value from config 13 | if (nameOrConnectionString.IndexOf("=", StringComparison.InvariantCultureIgnoreCase) > -1) return nameOrConnectionString; 14 | var cs = appConfiguration?.GetConnectionString(nameOrConnectionString); 15 | if (string.IsNullOrEmpty(cs)) 16 | { 17 | SelfLog.WriteLine("MSSqlServer sink configured value {0} is not found in ConnectionStrings settings and does not appear to be a raw connection string.", 18 | nameOrConnectionString); 19 | } 20 | return cs; 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.MSSqlServer/Configuration/Implementations/Microsoft.Extensions.Configuration/MicrosoftExtensionsSinkOptionsProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using Microsoft.Extensions.Configuration; 4 | 5 | namespace Serilog.Sinks.MSSqlServer.Configuration 6 | { 7 | internal class MicrosoftExtensionsSinkOptionsProvider : IMicrosoftExtensionsSinkOptionsProvider 8 | { 9 | public MSSqlServerSinkOptions ConfigureSinkOptions(MSSqlServerSinkOptions sinkOptions, IConfigurationSection config) 10 | { 11 | if (config == null) 12 | { 13 | return sinkOptions; 14 | } 15 | 16 | ReadTableOptions(config, sinkOptions); 17 | ReadBatchSettings(config, sinkOptions); 18 | 19 | return sinkOptions; 20 | } 21 | 22 | private static void ReadTableOptions(IConfigurationSection config, MSSqlServerSinkOptions sinkOptions) 23 | { 24 | SetProperty.IfNotNull(config["tableName"], val => sinkOptions.TableName = val); 25 | SetProperty.IfNotNull(config["schemaName"], val => sinkOptions.SchemaName = val); 26 | SetProperty.IfNotNull(config["autoCreateSqlDatabase"], val => sinkOptions.AutoCreateSqlDatabase = val); 27 | SetProperty.IfNotNull(config["autoCreateSqlTable"], val => sinkOptions.AutoCreateSqlTable = val); 28 | SetProperty.IfNotNull(config["enlistInTransaction"], val => sinkOptions.EnlistInTransaction = val); 29 | } 30 | 31 | private static void ReadBatchSettings(IConfigurationSection config, MSSqlServerSinkOptions sinkOptions) 32 | { 33 | SetProperty.IfNotNull(config["batchPostingLimit"], val => sinkOptions.BatchPostingLimit = val); 34 | SetProperty.IfNotNull(config["batchPeriod"], val => sinkOptions.BatchPeriod = TimeSpan.Parse(val, CultureInfo.InvariantCulture)); 35 | SetProperty.IfNotNull(config["eagerlyEmitFirstEvent"], val => sinkOptions.EagerlyEmitFirstEvent = val); 36 | SetProperty.IfNotNull(config["useSqlBulkCopy"], val => sinkOptions.UseSqlBulkCopy = val); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.MSSqlServer/Configuration/Implementations/SetProperty.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | 4 | namespace Serilog.Sinks.MSSqlServer 5 | { 6 | /// 7 | /// Helper for applying only those properties actually specified in external configuration. 8 | /// 9 | public static partial class SetProperty 10 | { 11 | // Usage: 12 | // SetProperty.IfValueNotNull(stringFromConfig, (boolOutputValue) => opts.BoolProperty = boolOutputValue); 13 | 14 | /// 15 | /// Simulates passing a property-setter to an "out" argument. 16 | /// 17 | public delegate void PropertySetter(T value); 18 | 19 | /// 20 | /// This will only set a value (execute the PropertySetter delegate) if the value is non-null. 21 | /// It also converts the provided value to the requested type. This allows configuration to only 22 | /// apply property changes when external configuration has actually provided a value. 23 | /// 24 | public static void IfNotNull(string value, PropertySetter setter) 25 | { 26 | if (value == null || setter == null) return; 27 | try 28 | { 29 | var setting = (T)Convert.ChangeType(value, typeof(T), CultureInfo.InvariantCulture); 30 | setter(setting); 31 | } 32 | // don't change the property if the conversion fails 33 | catch (InvalidCastException) { } 34 | catch (OverflowException) { } 35 | } 36 | 37 | /// 38 | /// This will only set a value (execute the PropertySetter delegate) if the value is non-null and 39 | /// isn't empty or whitespace. This override is used when {T} is a string value that can't be empty. 40 | /// It also converts the provided value to the requested type. This allows configuration to only 41 | /// apply property changes when external configuration has actually provided a value. 42 | /// 43 | public static void IfNotNullOrEmpty(string value, PropertySetter setter) 44 | { 45 | if (string.IsNullOrWhiteSpace(value)) return; 46 | IfNotNull(value, setter); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.MSSqlServer/Configuration/Implementations/System.Configuration/ApplySystemConfiguration.cs: -------------------------------------------------------------------------------- 1 | using System.Configuration; 2 | using Serilog.Configuration; 3 | using Serilog.Sinks.MSSqlServer.Configuration; 4 | 5 | namespace Serilog.Sinks.MSSqlServer 6 | { 7 | internal class ApplySystemConfiguration : IApplySystemConfiguration 8 | { 9 | private readonly ISystemConfigurationConnectionStringProvider _connectionStringProvider; 10 | private readonly ISystemConfigurationColumnOptionsProvider _columnOptionsProvider; 11 | private readonly ISystemConfigurationSinkOptionsProvider _sinkOptionsProvider; 12 | 13 | public ApplySystemConfiguration() : this( 14 | new SystemConfigurationConnectionStringProvider(), 15 | new SystemConfigurationColumnOptionsProvider(), 16 | new SystemConfigurationSinkOptionsProvider()) 17 | { 18 | } 19 | 20 | // Constructor with injectable dependencies for tests 21 | internal ApplySystemConfiguration( 22 | ISystemConfigurationConnectionStringProvider connectionStringProvider, 23 | ISystemConfigurationColumnOptionsProvider columnOptionsProvider, 24 | ISystemConfigurationSinkOptionsProvider sinkOptionsProvider) 25 | { 26 | _connectionStringProvider = connectionStringProvider; 27 | _columnOptionsProvider = columnOptionsProvider; 28 | _sinkOptionsProvider = sinkOptionsProvider; 29 | } 30 | 31 | public MSSqlServerConfigurationSection GetSinkConfigurationSection(string configurationSectionName) 32 | { 33 | return ConfigurationManager.GetSection(configurationSectionName) as MSSqlServerConfigurationSection; 34 | } 35 | 36 | public string GetConnectionString(string nameOrConnectionString) => 37 | _connectionStringProvider.GetConnectionString(nameOrConnectionString); 38 | 39 | public ColumnOptions ConfigureColumnOptions(MSSqlServerConfigurationSection config, ColumnOptions columnOptions) => 40 | _columnOptionsProvider.ConfigureColumnOptions(config, columnOptions); 41 | 42 | public MSSqlServerSinkOptions ConfigureSinkOptions(MSSqlServerConfigurationSection config, MSSqlServerSinkOptions sinkOptions) => 43 | _sinkOptionsProvider.ConfigureSinkOptions(config, sinkOptions); 44 | 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.MSSqlServer/Configuration/Implementations/System.Configuration/ColumnCollection.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Serilog Contributors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | using System.Configuration; 16 | 17 | // Disable XML comment warnings for internal config classes which are required to have public members 18 | #pragma warning disable 1591 19 | 20 | namespace Serilog.Sinks.MSSqlServer 21 | { 22 | public class ColumnCollection : ConfigurationElementCollection 23 | { 24 | protected override ConfigurationElement CreateNewElement() 25 | { 26 | return new ColumnConfig(); 27 | } 28 | 29 | protected override object GetElementKey(ConfigurationElement element) 30 | { 31 | return ((ColumnConfig)element)?.ColumnName; 32 | } 33 | 34 | // For testing 35 | internal void Add(ColumnConfig config) 36 | { 37 | BaseAdd(config); 38 | } 39 | } 40 | } 41 | 42 | #pragma warning restore 1591 43 | 44 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.MSSqlServer/Configuration/Implementations/System.Configuration/IApplySystemConfiguration.cs: -------------------------------------------------------------------------------- 1 | using Serilog.Configuration; 2 | 3 | namespace Serilog.Sinks.MSSqlServer 4 | { 5 | internal interface IApplySystemConfiguration 6 | { 7 | MSSqlServerConfigurationSection GetSinkConfigurationSection(string configurationSectionName); 8 | string GetConnectionString(string nameOrConnectionString); 9 | ColumnOptions ConfigureColumnOptions(MSSqlServerConfigurationSection config, ColumnOptions columnOptions); 10 | MSSqlServerSinkOptions ConfigureSinkOptions(MSSqlServerConfigurationSection config, MSSqlServerSinkOptions sinkOptions); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.MSSqlServer/Configuration/Implementations/System.Configuration/ISystemConfigurationColumnOptionsProvider.cs: -------------------------------------------------------------------------------- 1 | using Serilog.Configuration; 2 | 3 | namespace Serilog.Sinks.MSSqlServer.Configuration 4 | { 5 | internal interface ISystemConfigurationColumnOptionsProvider 6 | { 7 | ColumnOptions ConfigureColumnOptions(MSSqlServerConfigurationSection config, ColumnOptions columnOptions); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.MSSqlServer/Configuration/Implementations/System.Configuration/ISystemConfigurationConnectionStringProvider.cs: -------------------------------------------------------------------------------- 1 | namespace Serilog.Sinks.MSSqlServer.Configuration 2 | { 3 | internal interface ISystemConfigurationConnectionStringProvider 4 | { 5 | string GetConnectionString(string nameOrConnectionString); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.MSSqlServer/Configuration/Implementations/System.Configuration/ISystemConfigurationSinkOptionsProvider.cs: -------------------------------------------------------------------------------- 1 | using Serilog.Configuration; 2 | 3 | namespace Serilog.Sinks.MSSqlServer.Configuration 4 | { 5 | internal interface ISystemConfigurationSinkOptionsProvider 6 | { 7 | MSSqlServerSinkOptions ConfigureSinkOptions(MSSqlServerConfigurationSection config, MSSqlServerSinkOptions sinkOptions); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.MSSqlServer/Configuration/Implementations/System.Configuration/SetPropertyValueOrigin.cs: -------------------------------------------------------------------------------- 1 | using System.Configuration; 2 | 3 | namespace Serilog.Sinks.MSSqlServer 4 | { 5 | public static partial class SetProperty 6 | { 7 | // A null-check isn't possible for XML config strings; they default to an empty string and setting DefaultValue 8 | // to null doesn't work because internally the ConfigurationProperty attribute changes it back to empty string. 9 | 10 | /// 11 | /// Test the underlying property collection's value-origin flag for a non-default string value. Empty strings allowed. 12 | /// 13 | public static void IfProvided(ConfigurationElement element, string propertyName, PropertySetter setter) 14 | { 15 | if (element == null) 16 | { 17 | return; 18 | } 19 | 20 | var property = element.ElementInformation.Properties[propertyName]; 21 | if (property.ValueOrigin == PropertyValueOrigin.Default) return; 22 | IfNotNull((string)property.Value, setter); 23 | } 24 | 25 | /// 26 | /// Test the underlying property collection's value-origin flag for a non-default, non-null, non-empty string value. 27 | /// 28 | public static void IfProvidedNotEmpty(ConfigurationElement element, string propertyName, PropertySetter setter) 29 | { 30 | if (element == null) 31 | { 32 | return; 33 | } 34 | 35 | var property = element.ElementInformation.Properties[propertyName]; 36 | if (property.ValueOrigin == PropertyValueOrigin.Default) return; 37 | IfNotNullOrEmpty((string)property.Value, setter); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.MSSqlServer/Configuration/Implementations/System.Configuration/StandardColumnCollection.cs: -------------------------------------------------------------------------------- 1 | using System.Configuration; 2 | 3 | // Disable XML comment warnings for internal config classes which are required to have public members 4 | #pragma warning disable 1591 5 | 6 | namespace Serilog.Sinks.MSSqlServer 7 | { 8 | public class StandardColumnCollection : ConfigurationElementCollection 9 | { 10 | protected override ConfigurationElement CreateNewElement() 11 | { 12 | return new StandardColumnConfig(); 13 | } 14 | 15 | protected override object GetElementKey(ConfigurationElement element) 16 | { 17 | return ((StandardColumnConfig)element)?.Name; 18 | } 19 | } 20 | } 21 | 22 | #pragma warning restore 1591 23 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.MSSqlServer/Configuration/Implementations/System.Configuration/StandardColumnConfig.cs: -------------------------------------------------------------------------------- 1 | using System.Configuration; 2 | 3 | // Disable XML comment warnings for internal config classes which are required to have public members 4 | #pragma warning disable 1591 5 | 6 | namespace Serilog.Sinks.MSSqlServer 7 | { 8 | public class StandardColumnConfig : ConfigurationElement 9 | { 10 | public StandardColumnConfig() 11 | { } 12 | 13 | [ConfigurationProperty("Name", IsRequired = true, IsKey = true)] 14 | public string Name 15 | { 16 | get { return (string)this[nameof(Name)]; } 17 | set { this[nameof(Name)] = value; } 18 | } 19 | } 20 | } 21 | 22 | #pragma warning restore 1591 23 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.MSSqlServer/Configuration/Implementations/System.Configuration/StandardColumnConfigException.cs: -------------------------------------------------------------------------------- 1 | using System.Configuration; 2 | 3 | // Disable XML comment warnings for internal config classes which are required to have public members 4 | #pragma warning disable 1591 5 | 6 | namespace Serilog.Sinks.MSSqlServer 7 | { 8 | public class StandardColumnConfigException : ColumnConfig 9 | { 10 | public StandardColumnConfigException() : base() 11 | { } 12 | 13 | // override to set IsRequired = false 14 | [ConfigurationProperty("ColumnName", IsRequired = false, IsKey = true)] 15 | public override string ColumnName 16 | { 17 | get => base.ColumnName; 18 | set => base.ColumnName = value; 19 | } 20 | } 21 | } 22 | 23 | #pragma warning restore 1591 24 | 25 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.MSSqlServer/Configuration/Implementations/System.Configuration/StandardColumnConfigId.cs: -------------------------------------------------------------------------------- 1 | using System.Configuration; 2 | 3 | // Disable XML comment warnings for internal config classes which are required to have public members 4 | #pragma warning disable 1591 5 | 6 | namespace Serilog.Sinks.MSSqlServer 7 | { 8 | public class StandardColumnConfigId : ColumnConfig 9 | { 10 | public StandardColumnConfigId() : base() 11 | { } 12 | 13 | // override to set IsRequired = false 14 | [ConfigurationProperty("ColumnName", IsRequired = false, IsKey = true)] 15 | public override string ColumnName 16 | { 17 | get => base.ColumnName; 18 | set => base.ColumnName = value; 19 | } 20 | } 21 | } 22 | 23 | #pragma warning restore 1591 24 | 25 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.MSSqlServer/Configuration/Implementations/System.Configuration/StandardColumnConfigLevel.cs: -------------------------------------------------------------------------------- 1 | using System.Configuration; 2 | 3 | // Disable XML comment warnings for internal config classes which are required to have public members 4 | #pragma warning disable 1591 5 | 6 | namespace Serilog.Sinks.MSSqlServer 7 | { 8 | public class StandardColumnConfigLevel : ColumnConfig 9 | { 10 | public StandardColumnConfigLevel() : base() 11 | { } 12 | 13 | // override to set IsRequired = false 14 | [ConfigurationProperty("ColumnName", IsRequired = false, IsKey = true)] 15 | public override string ColumnName 16 | { 17 | get => base.ColumnName; 18 | set => base.ColumnName = value; 19 | } 20 | 21 | [ConfigurationProperty("StoreAsEnum")] 22 | public string StoreAsEnum 23 | { 24 | get => (string)base[nameof(StoreAsEnum)]; 25 | set 26 | { 27 | base[nameof(StoreAsEnum)] = value; 28 | } 29 | } 30 | 31 | } 32 | } 33 | 34 | #pragma warning restore 1591 35 | 36 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.MSSqlServer/Configuration/Implementations/System.Configuration/StandardColumnConfigLogEvent.cs: -------------------------------------------------------------------------------- 1 | using System.Configuration; 2 | 3 | // Disable XML comment warnings for internal config classes which are required to have public members 4 | #pragma warning disable 1591 5 | 6 | namespace Serilog.Sinks.MSSqlServer 7 | { 8 | public class StandardColumnConfigLogEvent : ColumnConfig 9 | { 10 | public StandardColumnConfigLogEvent() : base() 11 | { } 12 | 13 | // override to set IsRequired = false 14 | [ConfigurationProperty("ColumnName", IsRequired = false, IsKey = true)] 15 | public override string ColumnName 16 | { 17 | get => base.ColumnName; 18 | set => base.ColumnName = value; 19 | } 20 | 21 | [ConfigurationProperty("ExcludeAdditionalProperties")] 22 | public string ExcludeAdditionalProperties 23 | { 24 | get => (string)base[nameof(ExcludeAdditionalProperties)]; 25 | set 26 | { 27 | base[nameof(ExcludeAdditionalProperties)] = value; 28 | } 29 | } 30 | 31 | [ConfigurationProperty("ExcludeStandardColumns")] 32 | public string ExcludeStandardColumns 33 | { 34 | get => (string)base[nameof(ExcludeStandardColumns)]; 35 | set 36 | { 37 | base[nameof(ExcludeStandardColumns)] = value; 38 | } 39 | } 40 | } 41 | } 42 | 43 | #pragma warning restore 1591 44 | 45 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.MSSqlServer/Configuration/Implementations/System.Configuration/StandardColumnConfigMessage.cs: -------------------------------------------------------------------------------- 1 | using System.Configuration; 2 | 3 | // Disable XML comment warnings for internal config classes which are required to have public members 4 | #pragma warning disable 1591 5 | 6 | namespace Serilog.Sinks.MSSqlServer 7 | { 8 | public class StandardColumnConfigMessage : ColumnConfig 9 | { 10 | public StandardColumnConfigMessage() : base() 11 | { } 12 | 13 | // override to set IsRequired = false 14 | [ConfigurationProperty("ColumnName", IsRequired = false, IsKey = true)] 15 | public override string ColumnName 16 | { 17 | get => base.ColumnName; 18 | set => base.ColumnName = value; 19 | } 20 | } 21 | } 22 | 23 | #pragma warning restore 1591 24 | 25 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.MSSqlServer/Configuration/Implementations/System.Configuration/StandardColumnConfigMessageTemplate.cs: -------------------------------------------------------------------------------- 1 | using System.Configuration; 2 | 3 | // Disable XML comment warnings for internal config classes which are required to have public members 4 | #pragma warning disable 1591 5 | 6 | namespace Serilog.Sinks.MSSqlServer 7 | { 8 | public class StandardColumnConfigMessageTemplate : ColumnConfig 9 | { 10 | public StandardColumnConfigMessageTemplate() : base() 11 | { } 12 | 13 | // override to set IsRequired = false 14 | [ConfigurationProperty("ColumnName", IsRequired = false, IsKey = true)] 15 | public override string ColumnName 16 | { 17 | get => base.ColumnName; 18 | set => base.ColumnName = value; 19 | } 20 | } 21 | } 22 | 23 | #pragma warning restore 1591 24 | 25 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.MSSqlServer/Configuration/Implementations/System.Configuration/StandardColumnConfigSpanId.cs: -------------------------------------------------------------------------------- 1 | using System.Configuration; 2 | 3 | // Disable XML comment warnings for internal config classes which are required to have public members 4 | #pragma warning disable 1591 5 | 6 | namespace Serilog.Sinks.MSSqlServer 7 | { 8 | public class StandardColumnConfigSpanId : ColumnConfig 9 | { 10 | public StandardColumnConfigSpanId() : base() 11 | { } 12 | 13 | // override to set IsRequired = false 14 | [ConfigurationProperty("ColumnName", IsRequired = false, IsKey = true)] 15 | public override string ColumnName 16 | { 17 | get => base.ColumnName; 18 | set => base.ColumnName = value; 19 | } 20 | } 21 | } 22 | 23 | #pragma warning restore 1591 24 | 25 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.MSSqlServer/Configuration/Implementations/System.Configuration/StandardColumnConfigTimeStamp.cs: -------------------------------------------------------------------------------- 1 | using System.Configuration; 2 | 3 | // Disable XML comment warnings for internal config classes which are required to have public members 4 | #pragma warning disable 1591 5 | 6 | namespace Serilog.Sinks.MSSqlServer 7 | { 8 | public class StandardColumnConfigTimeStamp : ColumnConfig 9 | { 10 | public StandardColumnConfigTimeStamp() : base() 11 | { } 12 | 13 | // override to set IsRequired = false 14 | [ConfigurationProperty("ColumnName", IsRequired = false, IsKey = true)] 15 | public override string ColumnName 16 | { 17 | get => base.ColumnName; 18 | set => base.ColumnName = value; 19 | } 20 | 21 | [ConfigurationProperty("ConvertToUtc")] 22 | public string ConvertToUtc 23 | { 24 | get => (string)base[nameof(ConvertToUtc)]; 25 | set 26 | { 27 | base[nameof(ConvertToUtc)] = value; 28 | } 29 | } 30 | 31 | } 32 | } 33 | 34 | #pragma warning restore 1591 35 | 36 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.MSSqlServer/Configuration/Implementations/System.Configuration/StandardColumnConfigTraceId.cs: -------------------------------------------------------------------------------- 1 | using System.Configuration; 2 | 3 | // Disable XML comment warnings for internal config classes which are required to have public members 4 | #pragma warning disable 1591 5 | 6 | namespace Serilog.Sinks.MSSqlServer 7 | { 8 | public class StandardColumnConfigTraceId : ColumnConfig 9 | { 10 | public StandardColumnConfigTraceId() : base() 11 | { } 12 | 13 | // override to set IsRequired = false 14 | [ConfigurationProperty("ColumnName", IsRequired = false, IsKey = true)] 15 | public override string ColumnName 16 | { 17 | get => base.ColumnName; 18 | set => base.ColumnName = value; 19 | } 20 | } 21 | } 22 | 23 | #pragma warning restore 1591 24 | 25 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.MSSqlServer/Configuration/Implementations/System.Configuration/SystemConfigurationConnectionStringProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Configuration; 3 | using Serilog.Debugging; 4 | 5 | namespace Serilog.Sinks.MSSqlServer.Configuration 6 | { 7 | internal class SystemConfigurationConnectionStringProvider : ISystemConfigurationConnectionStringProvider 8 | { 9 | public string GetConnectionString(string nameOrConnectionString) 10 | { 11 | // If there is an `=`, we assume this is a raw connection string not a named value 12 | // If there are no `=`, attempt to pull the named value from config 13 | if (nameOrConnectionString.IndexOf("=", StringComparison.InvariantCultureIgnoreCase) < 0) 14 | { 15 | var cs = ConfigurationManager.ConnectionStrings[nameOrConnectionString]; 16 | if (cs != null) 17 | { 18 | return cs.ConnectionString; 19 | } 20 | else 21 | { 22 | SelfLog.WriteLine("MSSqlServer sink configured value {0} is not found in ConnectionStrings settings and does not appear to be a raw connection string.", nameOrConnectionString); 23 | } 24 | } 25 | 26 | return nameOrConnectionString; 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.MSSqlServer/Configuration/Implementations/System.Configuration/SystemConfigurationSinkOptionsProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using Serilog.Configuration; 4 | 5 | namespace Serilog.Sinks.MSSqlServer.Configuration 6 | { 7 | internal class SystemConfigurationSinkOptionsProvider : ISystemConfigurationSinkOptionsProvider 8 | { 9 | public MSSqlServerSinkOptions ConfigureSinkOptions(MSSqlServerConfigurationSection config, MSSqlServerSinkOptions sinkOptions) 10 | { 11 | ReadTableOptions(config, sinkOptions); 12 | ReadBatchSettings(config, sinkOptions); 13 | 14 | return sinkOptions; 15 | } 16 | 17 | private static void ReadTableOptions(MSSqlServerConfigurationSection config, MSSqlServerSinkOptions sinkOptions) 18 | { 19 | SetProperty.IfProvided(config.TableName, nameof(config.TableName.Value), value => sinkOptions.TableName = value); 20 | SetProperty.IfProvided(config.SchemaName, nameof(config.SchemaName.Value), value => sinkOptions.SchemaName = value); 21 | SetProperty.IfProvided(config.AutoCreateSqlDatabase, nameof(config.AutoCreateSqlDatabase.Value), 22 | value => sinkOptions.AutoCreateSqlDatabase = value); 23 | SetProperty.IfProvided(config.AutoCreateSqlTable, nameof(config.AutoCreateSqlTable.Value), 24 | value => sinkOptions.AutoCreateSqlTable = value); 25 | SetProperty.IfProvided(config.EnlistInTransaction, nameof(config.EnlistInTransaction.Value), 26 | value => sinkOptions.EnlistInTransaction = value); 27 | } 28 | 29 | private static void ReadBatchSettings(MSSqlServerConfigurationSection config, MSSqlServerSinkOptions sinkOptions) 30 | { 31 | SetProperty.IfProvided(config.BatchPostingLimit, nameof(config.BatchPostingLimit.Value), value => sinkOptions.BatchPostingLimit = value); 32 | SetProperty.IfProvided(config.BatchPeriod, nameof(config.BatchPeriod.Value), value => sinkOptions.BatchPeriod = TimeSpan.Parse(value, CultureInfo.InvariantCulture)); 33 | SetProperty.IfProvided(config.EagerlyEmitFirstEvent, nameof(config.EagerlyEmitFirstEvent.Value), 34 | value => sinkOptions.EagerlyEmitFirstEvent = value); 35 | SetProperty.IfProvided(config.UseSqlBulkCopy, nameof(config.UseSqlBulkCopy.Value), 36 | value => sinkOptions.UseSqlBulkCopy = value); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.MSSqlServer/Configuration/Implementations/System.Configuration/ValueConfigElement.cs: -------------------------------------------------------------------------------- 1 | using System.Configuration; 2 | 3 | namespace Serilog.Sinks.MSSqlServer 4 | { 5 | /// 6 | /// Class for reading System.Configuration (app.config, web.config) elements of the form <PropertyName Value="Some value" />' 7 | /// 8 | public class ValueConfigElement : ConfigurationElement 9 | { 10 | /// 11 | /// Intiazes a new instance of ValueConfigElement 12 | /// 13 | public ValueConfigElement() 14 | { 15 | } 16 | 17 | /// 18 | /// Intiazes a new instance of ValueConfigElement with a value 19 | /// 20 | public ValueConfigElement(string value) 21 | { 22 | Value = value; 23 | } 24 | 25 | /// 26 | /// The value property 27 | /// 28 | [ConfigurationProperty(nameof(Value), IsRequired = true)] 29 | public string Value 30 | { 31 | get { return (string)this[nameof(Value)]; } 32 | set { this[nameof(Value)] = value; } 33 | } 34 | } 35 | } 36 | 37 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.MSSqlServer/Extensions/StringExtensions.cs: -------------------------------------------------------------------------------- 1 | using static System.FormattableString; 2 | 3 | namespace Serilog.Sinks.MSSqlServer.Extensions 4 | { 5 | internal static class StringExtensions 6 | { 7 | public static string TruncateOutput(this string value, int dataLength) => 8 | dataLength < 0 9 | ? value // No need to truncate if length set to maximum 10 | : value.Truncate(dataLength, "..."); 11 | 12 | public static string Truncate(this string value, int maxLength, string suffix) 13 | { 14 | if (value == null) return null; 15 | else if (value.Length <= maxLength) return value; 16 | 17 | var suffixLength = suffix?.Length ?? 0; 18 | if (maxLength <= suffixLength) return string.Empty; 19 | 20 | var correctedMaxLength = maxLength - suffixLength; 21 | return Invariant($"{value.Substring(0, correctedMaxLength)}{suffix}"); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.MSSqlServer/GlobalSuppressions.cs: -------------------------------------------------------------------------------- 1 | // This file is used by Code Analysis to maintain SuppressMessage 2 | // attributes that are applied to this project. 3 | // Project-level suppressions either have no target or are given 4 | // a specific target and scoped to a namespace, type, member, etc. 5 | 6 | using System.Diagnostics.CodeAnalysis; 7 | 8 | [assembly: SuppressMessage("Globalization", "CA1303:Do not pass literals as localized parameters", Justification = "Supplying string literals and not using resources is accepted within this project.", Scope = "namespaceanddescendants", Target = "~N:Serilog.Sinks.MSSqlServer")] 9 | [assembly: SuppressMessage("Security", "CA2100:Review SQL queries for security vulnerabilities", Justification = "Too hard to change. Accepted for now.", Scope = "namespaceanddescendants", Target = "~N:Serilog.Sinks.MSSqlServer")] 10 | [assembly: SuppressMessage("Design", "CA1034:Nested types should not be visible", Justification = "Cannot be changed on public classes for backward compatibility reasons.", Scope = "namespaceanddescendants", Target = "~N:Serilog.Sinks.MSSqlServer")] 11 | [assembly: SuppressMessage("Usage", "CA2227:Collection properties should be read only", Justification = "Cannot be changed on public classes for backward compatibility reasons.", Scope = "type", Target = "~T:Serilog.Sinks.MSSqlServer.ColumnOptions")] 12 | [assembly: SuppressMessage("Design", "CA1010:Collections should implement generic interface", Justification = "Cannot be changed on public classes for backward compatibility reasons.", Scope = "namespaceanddescendants", Target = "~N:Serilog.Sinks.MSSqlServer")] 13 | [assembly: SuppressMessage("Performance", "CA1822: Mark members as static", Justification = "Using instance members over static for better testablilty.", Scope = "namespaceanddescendants", Target = "~N:Serilog.Sinks.MSSqlServer")] 14 | [assembly: SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "Too hard to change. Accepted for now.", Scope = "member", Target = "~M:Serilog.Sinks.MSSqlServer.SetProperty.IfNotNull``1(System.String,Serilog.Sinks.MSSqlServer.SetProperty.PropertySetter{``0})")] 15 | [assembly: SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "Too hard to change. Accepted for now.", Scope = "member", Target = "~M:Serilog.Sinks.MSSqlServer.Platform.SqlCommandExecutor.Execute()")] 16 | [assembly: SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "Too hard to change. Accepted for now.", Scope = "member", Target = "~M:Serilog.Sinks.MSSqlServer.Output.StandardColumnDataGenerator.ConvertPropertiesToXmlStructure(System.Collections.Generic.IEnumerable{System.Collections.Generic.KeyValuePair{System.String,Serilog.Events.LogEventPropertyValue}})~System.String")] 17 | [assembly: SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "Too hard to change. Accepted for now.", Scope = "member", Target = "~M:Serilog.Sinks.MSSqlServer.Output.AdditionalColumnDataGenerator.TryChangeType(System.Object,System.Type,System.Object@)~System.Boolean")] 18 | [assembly: SuppressMessage("Naming", "CA1711:Identifiers should not have incorrect suffix", Justification = "Cannot be changed on public classes for backward compatibility reasons.", Scope = "type", Target = "~T:Serilog.Sinks.MSSqlServer.StandardColumnConfigException")] 19 | [assembly: SuppressMessage("Naming", "CA1725:Parameter names should match base declaration", Justification = "Cannot be changed on public classes for backward compatibility reasons.", Scope = "member", Target = "~M:Serilog.Sinks.MSSqlServer.MSSqlServerSink.EmitBatchAsync(System.Collections.Generic.IEnumerable{Serilog.Events.LogEvent})~System.Threading.Tasks.Task")] 20 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.MSSqlServer/Images/serilog-sink-nuget.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/serilog-mssql/serilog-sinks-mssqlserver/144ad397ed50e6b26d9c02cdd6f052253301e9fc/src/Serilog.Sinks.MSSqlServer/Images/serilog-sink-nuget.png -------------------------------------------------------------------------------- /src/Serilog.Sinks.MSSqlServer/Sinks/MSSqlServer/ColumnOptions/ExceptionColumnOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data; 3 | 4 | namespace Serilog.Sinks.MSSqlServer 5 | { 6 | public partial class ColumnOptions // Standard Column options are inner classes for backwards-compatibility. 7 | { 8 | /// 9 | /// Options for the Exception column. 10 | /// 11 | public class ExceptionColumnOptions : SqlColumn 12 | { 13 | /// 14 | /// Constructor. 15 | /// 16 | public ExceptionColumnOptions() : base() 17 | { 18 | StandardColumnIdentifier = StandardColumn.Exception; 19 | DataType = SqlDbType.NVarChar; 20 | } 21 | 22 | /// 23 | /// The Exception column defaults to NVarChar and must be of a character-storage data type. 24 | /// 25 | public new SqlDbType DataType 26 | { 27 | get => base.DataType; 28 | set 29 | { 30 | if (!SqlDataTypes.VariableCharacterColumnTypes.Contains(value)) 31 | throw new ArgumentException("The Standard Column \"Exception\" must be NVarChar or VarChar."); 32 | base.DataType = value; 33 | } 34 | } 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.MSSqlServer/Sinks/MSSqlServer/ColumnOptions/IdColumnOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data; 3 | using Serilog.Debugging; 4 | 5 | namespace Serilog.Sinks.MSSqlServer 6 | { 7 | public partial class ColumnOptions // Standard Column options are inner classes for backwards-compatibility. 8 | { 9 | /// 10 | /// Options for the Id column. 11 | /// 12 | public class IdColumnOptions : SqlColumn 13 | { 14 | /// 15 | /// Constructor. 16 | /// 17 | public IdColumnOptions() : base() 18 | { 19 | StandardColumnIdentifier = StandardColumn.Id; 20 | DataType = SqlDbType.Int; 21 | base.AllowNull = false; 22 | } 23 | 24 | /// 25 | /// The Id column must be either Int (default) or BigInt. 26 | /// 27 | public new SqlDbType DataType 28 | { 29 | get => base.DataType; 30 | set 31 | { 32 | if (value != SqlDbType.Int && value != SqlDbType.BigInt) 33 | throw new ArgumentException("The Standard Column \"Id\" must be of data type Int or BigInt."); 34 | base.DataType = value; 35 | } 36 | } 37 | 38 | /// 39 | /// The Id column must never allow null values (it is an auto-incremnting identity value and normally the primary key). 40 | /// 41 | public new bool AllowNull // shadow base class with "new" to prevent accidentally setting this to true 42 | { 43 | get => false; 44 | set 45 | { 46 | if (value) 47 | throw new ArgumentException("The Standard Column \"Id\" must always be NOT NULL."); 48 | } 49 | } 50 | 51 | /// 52 | /// Overrides the SqlColumn base method to also set Id-specific properties. 53 | /// 54 | internal override DataColumn AsDataColumn() 55 | { 56 | var dataColumn = base.AsDataColumn(); 57 | dataColumn.AutoIncrement = true; 58 | dataColumn.Unique = true; 59 | dataColumn.AllowDBNull = false; 60 | return dataColumn; 61 | } 62 | 63 | /// 64 | /// Set the DataType property to BigInt. 65 | /// 66 | [Obsolete("Set the DataType property to BigInt. This will be removed in a future release.", error: false)] 67 | public bool BigInt 68 | { 69 | get => (base.DataType == SqlDbType.BigInt); 70 | set 71 | { 72 | base.DataType = value ? SqlDbType.BigInt : SqlDbType.Int; 73 | SelfLog.WriteLine("Deprecated: The Standard Column \"Id.BigInt\" property will be removed in a future release. Please set the \"DataType\" property."); 74 | } 75 | } 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.MSSqlServer/Sinks/MSSqlServer/ColumnOptions/LevelColumnOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data; 3 | 4 | namespace Serilog.Sinks.MSSqlServer 5 | { 6 | public partial class ColumnOptions // Standard Column options are inner classes for backwards-compatibility. 7 | { 8 | /// 9 | /// Options for the Level column. 10 | /// 11 | public class LevelColumnOptions : SqlColumn 12 | { 13 | /// 14 | /// Constructor. 15 | /// 16 | public LevelColumnOptions() : base() 17 | { 18 | StandardColumnIdentifier = StandardColumn.Level; 19 | DataType = SqlDbType.NVarChar; 20 | } 21 | 22 | /// 23 | /// The Level column must be either NVarChar (the default) or TinyInt (which stores the underlying Level enum value). 24 | /// The recommended DataLength for NVarChar is 16 characters. 25 | /// 26 | public new SqlDbType DataType 27 | { 28 | get => base.DataType; 29 | set 30 | { 31 | if (!SqlDataTypes.VariableCharacterColumnTypes.Contains(value) && value != SqlDbType.TinyInt) 32 | throw new ArgumentException("The Standard Column \"Level\" must be of data type NVarChar, VarChar or TinyInt."); 33 | base.DataType = value; 34 | } 35 | } 36 | 37 | 38 | /// 39 | /// If true will store Level as an enum in a tinyint column as opposed to a string. 40 | /// 41 | public bool StoreAsEnum 42 | { 43 | get => (base.DataType == SqlDbType.TinyInt); 44 | set 45 | { 46 | base.DataType = value ? SqlDbType.TinyInt : SqlDbType.NVarChar; 47 | } 48 | } 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.MSSqlServer/Sinks/MSSqlServer/ColumnOptions/LogEventColumnOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data; 3 | 4 | namespace Serilog.Sinks.MSSqlServer 5 | { 6 | public partial class ColumnOptions // Standard Column options are inner classes for backwards-compatibility. 7 | { 8 | /// 9 | /// Options for the LogEvent column. 10 | /// 11 | public class LogEventColumnOptions : SqlColumn 12 | { 13 | /// 14 | /// Constructor. 15 | /// 16 | public LogEventColumnOptions() : base() 17 | { 18 | StandardColumnIdentifier = StandardColumn.LogEvent; 19 | DataType = SqlDbType.NVarChar; 20 | } 21 | 22 | /// 23 | /// The LogEvent column defaults to NVarChar and must be of a character-storage data type. 24 | /// 25 | public new SqlDbType DataType 26 | { 27 | get => base.DataType; 28 | set 29 | { 30 | if (!SqlDataTypes.VariableCharacterColumnTypes.Contains(value)) 31 | throw new ArgumentException("The Standard Column \"LogEvent\" must be NVarChar or VarChar."); 32 | base.DataType = value; 33 | } 34 | } 35 | 36 | /// 37 | /// Exclude properties from the LogEvent column if they are being saved to additional columns. 38 | /// Defaults to false for backwards-compatibility, but true is the recommended setting. 39 | /// 40 | public bool ExcludeAdditionalProperties { get; set; } 41 | 42 | /// 43 | /// Whether to include Standard Columns in the LogEvent column (for backwards compatibility). 44 | /// Defaults to false for backwards-compatibility, but true is the recommended setting. 45 | /// 46 | public bool ExcludeStandardColumns { get; set; } 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.MSSqlServer/Sinks/MSSqlServer/ColumnOptions/MessageColumnOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data; 3 | 4 | namespace Serilog.Sinks.MSSqlServer 5 | { 6 | public partial class ColumnOptions // Standard Column options are inner classes for backwards-compatibility. 7 | { 8 | /// 9 | /// Options for the message column 10 | /// 11 | public class MessageColumnOptions : SqlColumn 12 | { 13 | /// 14 | /// Constructor. 15 | /// 16 | public MessageColumnOptions() : base() 17 | { 18 | StandardColumnIdentifier = StandardColumn.Message; 19 | DataType = SqlDbType.NVarChar; 20 | } 21 | 22 | /// 23 | /// The Message column defaults to NVarChar and must be of a character-storage data type. 24 | /// 25 | public new SqlDbType DataType 26 | { 27 | get => base.DataType; 28 | set 29 | { 30 | if (!SqlDataTypes.VariableCharacterColumnTypes.Contains(value)) 31 | throw new ArgumentException("The Standard Column \"Message\" must be NVarChar or VarChar."); 32 | base.DataType = value; 33 | } 34 | } 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.MSSqlServer/Sinks/MSSqlServer/ColumnOptions/MessageTemplateColumnOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data; 3 | 4 | namespace Serilog.Sinks.MSSqlServer 5 | { 6 | public partial class ColumnOptions // Standard Column options are inner classes for backwards-compatibility. 7 | { 8 | /// 9 | /// Options for the MessageTemplate column. 10 | /// 11 | public class MessageTemplateColumnOptions : SqlColumn 12 | { 13 | /// 14 | /// Constructor. 15 | /// 16 | public MessageTemplateColumnOptions() : base() 17 | { 18 | StandardColumnIdentifier = StandardColumn.MessageTemplate; 19 | DataType = SqlDbType.NVarChar; 20 | } 21 | 22 | /// 23 | /// The MessageTemplate column defaults to NVarChar and must be of a character-storage data type. 24 | /// 25 | public new SqlDbType DataType 26 | { 27 | get => base.DataType; 28 | set 29 | { 30 | if (!SqlDataTypes.VariableCharacterColumnTypes.Contains(value)) 31 | throw new ArgumentException("The Standard Column \"MessageTemplate\" must be NVarChar or VarChar."); 32 | base.DataType = value; 33 | } 34 | } 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.MSSqlServer/Sinks/MSSqlServer/ColumnOptions/SpanIdColumnOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data; 3 | 4 | namespace Serilog.Sinks.MSSqlServer 5 | { 6 | public partial class ColumnOptions // Standard Column options are inner classes for backwards-compatibility. 7 | { 8 | /// 9 | /// Options for the SpanId column. 10 | /// 11 | public class SpanIdColumnOptions : SqlColumn 12 | { 13 | /// 14 | /// Constructor. 15 | /// 16 | public SpanIdColumnOptions() : base() 17 | { 18 | StandardColumnIdentifier = StandardColumn.SpanId; 19 | DataType = SqlDbType.NVarChar; 20 | } 21 | 22 | /// 23 | /// The SpanId column defaults to NVarChar and must be of a character-storage data type. 24 | /// 25 | public new SqlDbType DataType 26 | { 27 | get => base.DataType; 28 | set 29 | { 30 | if (!SqlDataTypes.VariableCharacterColumnTypes.Contains(value)) 31 | throw new ArgumentException("The Standard Column \"SpanId\" must be NVarChar or VarChar."); 32 | base.DataType = value; 33 | } 34 | } 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.MSSqlServer/Sinks/MSSqlServer/ColumnOptions/TimeStampColumnOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data; 3 | 4 | namespace Serilog.Sinks.MSSqlServer 5 | { 6 | public partial class ColumnOptions // Standard Column options are inner classes for backwards-compatibility. 7 | { 8 | /// 9 | /// Options for the TimeStamp column. 10 | /// 11 | public class TimeStampColumnOptions : SqlColumn 12 | { 13 | /// 14 | /// Constructor. 15 | /// 16 | public TimeStampColumnOptions() : base() 17 | { 18 | StandardColumnIdentifier = StandardColumn.TimeStamp; 19 | DataType = SqlDbType.DateTime; 20 | } 21 | 22 | /// 23 | /// The TimeStamp column only supports the DateTime, DateTime2 and DateTimeOffset data types. 24 | /// 25 | public new SqlDbType DataType 26 | { 27 | get => base.DataType; 28 | set 29 | { 30 | if (value != SqlDbType.DateTime && value != SqlDbType.DateTimeOffset && value != SqlDbType.DateTime2) 31 | throw new ArgumentException("The Standard Column \"TimeStamp\" only supports the DateTime, DateTime2 and DateTimeOffset formats."); 32 | base.DataType = value; 33 | } 34 | } 35 | 36 | /// 37 | /// If true, the logging source's local time is converted to Coordinated Universal Time. 38 | /// By definition, UTC does not include any timezone or timezone offset information. 39 | /// 40 | public bool ConvertToUtc { get; set; } 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.MSSqlServer/Sinks/MSSqlServer/ColumnOptions/TraceIdColumnOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data; 3 | 4 | namespace Serilog.Sinks.MSSqlServer 5 | { 6 | public partial class ColumnOptions // Standard Column options are inner classes for backwards-compatibility. 7 | { 8 | /// 9 | /// Options for the TraceId column. 10 | /// 11 | public class TraceIdColumnOptions : SqlColumn 12 | { 13 | /// 14 | /// Constructor. 15 | /// 16 | public TraceIdColumnOptions() : base() 17 | { 18 | StandardColumnIdentifier = StandardColumn.TraceId; 19 | DataType = SqlDbType.NVarChar; 20 | } 21 | 22 | /// 23 | /// The TraceId column defaults to NVarChar and must be of a character-storage data type. 24 | /// 25 | public new SqlDbType DataType 26 | { 27 | get => base.DataType; 28 | set 29 | { 30 | if (!SqlDataTypes.VariableCharacterColumnTypes.Contains(value)) 31 | throw new ArgumentException("The Standard Column \"TraceId\" must be NVarChar or VarChar."); 32 | base.DataType = value; 33 | } 34 | } 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.MSSqlServer/Sinks/MSSqlServer/Dependencies/SinkDependencies.cs: -------------------------------------------------------------------------------- 1 | using Serilog.Sinks.MSSqlServer.Platform; 2 | 3 | namespace Serilog.Sinks.MSSqlServer.Dependencies 4 | { 5 | internal class SinkDependencies 6 | { 7 | public ISqlCommandExecutor SqlDatabaseCreator { get; set; } 8 | public ISqlCommandExecutor SqlTableCreator { get; set; } 9 | public ISqlBulkBatchWriter SqlBulkBatchWriter { get; set; } 10 | public ISqlLogEventWriter SqlLogEventWriter { get; set; } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.MSSqlServer/Sinks/MSSqlServer/Dependencies/SinkDependenciesFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Serilog.Formatting; 3 | using Serilog.Sinks.MSSqlServer.Output; 4 | using Serilog.Sinks.MSSqlServer.Platform; 5 | using Serilog.Sinks.MSSqlServer.Platform.SqlClient; 6 | 7 | namespace Serilog.Sinks.MSSqlServer.Dependencies 8 | { 9 | internal static class SinkDependenciesFactory 10 | { 11 | internal static SinkDependencies Create( 12 | string connectionString, 13 | MSSqlServerSinkOptions sinkOptions, 14 | IFormatProvider formatProvider, 15 | ColumnOptions columnOptions, 16 | ITextFormatter logEventFormatter) 17 | { 18 | columnOptions = columnOptions ?? new ColumnOptions(); 19 | columnOptions.FinalizeConfigurationForSinkConstructor(); 20 | 21 | // Add 'Enlist=false', so that ambient transactions (TransactionScope) will not affect/rollback logging 22 | // unless sink option EnlistInTransaction is set to true. 23 | var sqlConnectionStringBuilderWrapper = new SqlConnectionStringBuilderWrapper( 24 | connectionString, sinkOptions.EnlistInTransaction); 25 | var sqlConnectionFactory = new SqlConnectionFactory(sqlConnectionStringBuilderWrapper, sinkOptions.ConnectionConfiguration); 26 | var sqlCommandFactory = new SqlCommandFactory(); 27 | var dataTableCreator = new DataTableCreator(sinkOptions.TableName, columnOptions); 28 | var sqlCreateTableWriter = new SqlCreateTableWriter(sinkOptions.SchemaName, 29 | sinkOptions.TableName, columnOptions, dataTableCreator); 30 | 31 | var sqlConnectionStringBuilderWrapperNoDb = new SqlConnectionStringBuilderWrapper( 32 | connectionString, sinkOptions.EnlistInTransaction) 33 | { 34 | InitialCatalog = "" 35 | }; 36 | var sqlConnectionFactoryNoDb = 37 | new SqlConnectionFactory(sqlConnectionStringBuilderWrapperNoDb, sinkOptions.ConnectionConfiguration); 38 | var sqlCreateDatabaseWriter = new SqlCreateDatabaseWriter(sqlConnectionStringBuilderWrapper.InitialCatalog); 39 | 40 | var logEventDataGenerator = 41 | new LogEventDataGenerator(columnOptions, 42 | new StandardColumnDataGenerator(columnOptions, formatProvider, 43 | new XmlPropertyFormatter(), 44 | logEventFormatter), 45 | new AdditionalColumnDataGenerator( 46 | new ColumnSimplePropertyValueResolver(), 47 | new ColumnHierarchicalPropertyValueResolver())); 48 | 49 | var sinkDependencies = new SinkDependencies 50 | { 51 | SqlDatabaseCreator = new SqlDatabaseCreator( 52 | sqlCreateDatabaseWriter, sqlConnectionFactoryNoDb, sqlCommandFactory), 53 | SqlTableCreator = new SqlTableCreator( 54 | sqlCreateTableWriter, sqlConnectionFactory, sqlCommandFactory), 55 | SqlBulkBatchWriter = new SqlBulkBatchWriter( 56 | sinkOptions.TableName, sinkOptions.SchemaName, columnOptions.DisableTriggers, 57 | dataTableCreator, sqlConnectionFactory, logEventDataGenerator), 58 | SqlLogEventWriter = new SqlInsertStatementWriter( 59 | sinkOptions.TableName, sinkOptions.SchemaName, 60 | sqlConnectionFactory, sqlCommandFactory, logEventDataGenerator) 61 | }; 62 | 63 | return sinkDependencies; 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.MSSqlServer/Sinks/MSSqlServer/Options/SinkOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Serilog.Sinks.MSSqlServer.Sinks.MSSqlServer.Options 4 | { 5 | /// 6 | /// Provides MSSqlServerSink with configurable options. 7 | /// 8 | [Obsolete("Replace SinkOptions with MSSqlServerSinkOptions. SinkOptions will be removed in a future release.", error: false)] 9 | public class SinkOptions : MSSqlServerSinkOptions 10 | { 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.MSSqlServer/Sinks/MSSqlServer/Output/ColumnHierarchicalPropertyValueResolver.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Serilog.Events; 5 | 6 | namespace Serilog.Sinks.MSSqlServer.Output 7 | { 8 | internal class ColumnHierarchicalPropertyValueResolver : IColumnHierarchicalPropertyValueResolver 9 | { 10 | public KeyValuePair GetPropertyValueForColumn(SqlColumn additionalColumn, IReadOnlyDictionary properties) 11 | { 12 | var propertyNameHierarchy = additionalColumn.PropertyNameHierarchy; 13 | KeyValuePair? nullableProperty = properties.FirstOrDefault(p => p.Key == propertyNameHierarchy[0]); 14 | if (nullableProperty == null) 15 | { 16 | // Top level property not found, return default 17 | return default; 18 | } 19 | 20 | var property = nullableProperty.Value; 21 | if (property.Value is StructureValue structureValue) 22 | { 23 | // Continue on sub property level 24 | var propertyNameHierarchyRemainder = new ArraySegment(propertyNameHierarchy.ToArray(), 1, additionalColumn.PropertyNameHierarchy.Count - 1); 25 | return GetSubPropertyValueForColumnRecursive(propertyNameHierarchyRemainder, structureValue.Properties); 26 | } 27 | else 28 | { 29 | // Sub property not found, return default 30 | return default; 31 | } 32 | } 33 | 34 | private KeyValuePair GetSubPropertyValueForColumnRecursive(IReadOnlyList propertyNameHierarchy, IReadOnlyList properties) 35 | { 36 | var property = properties.FirstOrDefault(p => p.Name == propertyNameHierarchy[0]); 37 | if (property == null) 38 | { 39 | // Current sub property not found, return default 40 | return default; 41 | } 42 | 43 | if (propertyNameHierarchy.Count == 1) 44 | { 45 | // Current sub property found and no further levels in property name of column 46 | // Return final property value 47 | return new KeyValuePair(property.Name, property.Value); 48 | } 49 | else 50 | { 51 | if (property.Value is StructureValue structureValue) 52 | { 53 | // Continue on next sub property level 54 | var propertyNameHierarchyRemainder = new ArraySegment(propertyNameHierarchy.ToArray(), 1, propertyNameHierarchy.Count - 1); 55 | return GetSubPropertyValueForColumnRecursive(propertyNameHierarchyRemainder, structureValue.Properties); 56 | } 57 | else 58 | { 59 | // Next sub property not found, return default 60 | return default; 61 | } 62 | } 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.MSSqlServer/Sinks/MSSqlServer/Output/ColumnSimplePropertyValueResolver.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using Serilog.Events; 4 | 5 | namespace Serilog.Sinks.MSSqlServer.Output 6 | { 7 | internal class ColumnSimplePropertyValueResolver : IColumnSimplePropertyValueResolver 8 | { 9 | public KeyValuePair GetPropertyValueForColumn(SqlColumn additionalColumn, IReadOnlyDictionary properties) 10 | { 11 | return properties.FirstOrDefault(p => p.Key == additionalColumn.PropertyName); 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.MSSqlServer/Sinks/MSSqlServer/Output/IAdditionalColumnDataGenerator.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Serilog.Events; 3 | 4 | namespace Serilog.Sinks.MSSqlServer.Output 5 | { 6 | internal interface IAdditionalColumnDataGenerator 7 | { 8 | KeyValuePair GetAdditionalColumnNameAndValue(SqlColumn additionalColumn, IReadOnlyDictionary properties); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.MSSqlServer/Sinks/MSSqlServer/Output/IColumnHierarchicalPropertyValueResolver.cs: -------------------------------------------------------------------------------- 1 | using Serilog.Events; 2 | using System.Collections.Generic; 3 | 4 | namespace Serilog.Sinks.MSSqlServer.Output 5 | { 6 | internal interface IColumnHierarchicalPropertyValueResolver 7 | { 8 | KeyValuePair GetPropertyValueForColumn(SqlColumn additionalColumn, IReadOnlyDictionary properties); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.MSSqlServer/Sinks/MSSqlServer/Output/IColumnSimplePropertyValueResolver.cs: -------------------------------------------------------------------------------- 1 | using Serilog.Events; 2 | using System.Collections.Generic; 3 | 4 | namespace Serilog.Sinks.MSSqlServer.Output 5 | { 6 | internal interface IColumnSimplePropertyValueResolver 7 | { 8 | KeyValuePair GetPropertyValueForColumn(SqlColumn additionalColumn, IReadOnlyDictionary properties); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.MSSqlServer/Sinks/MSSqlServer/Output/ILogEventDataGenerator.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Serilog.Events; 3 | 4 | namespace Serilog.Sinks.MSSqlServer.Output 5 | { 6 | internal interface ILogEventDataGenerator 7 | { 8 | IEnumerable> GetColumnsAndValues(LogEvent logEvent); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.MSSqlServer/Sinks/MSSqlServer/Output/IStandardColumnDataGenerator.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Serilog.Events; 3 | 4 | namespace Serilog.Sinks.MSSqlServer.Output 5 | { 6 | internal interface IStandardColumnDataGenerator 7 | { 8 | KeyValuePair GetStandardColumnNameAndValue(StandardColumn column, LogEvent logEvent); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.MSSqlServer/Sinks/MSSqlServer/Output/IXmlPropertyFormatter.cs: -------------------------------------------------------------------------------- 1 | using Serilog.Events; 2 | 3 | namespace Serilog.Sinks.MSSqlServer.Output 4 | { 5 | internal interface IXmlPropertyFormatter 6 | { 7 | string GetValidElementName(string name); 8 | string Simplify(LogEventPropertyValue value, ColumnOptions.PropertiesColumnOptions options); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.MSSqlServer/Sinks/MSSqlServer/Output/LogEventDataGenerator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Serilog.Events; 5 | 6 | namespace Serilog.Sinks.MSSqlServer.Output 7 | { 8 | internal class LogEventDataGenerator : ILogEventDataGenerator 9 | { 10 | private readonly ColumnOptions _columnOptions; 11 | private readonly IStandardColumnDataGenerator _standardColumnDataGenerator; 12 | private readonly IAdditionalColumnDataGenerator _additionalColumnDataGenerator; 13 | 14 | public LogEventDataGenerator( 15 | ColumnOptions columnOptions, 16 | IStandardColumnDataGenerator standardColumnDataGenerator, 17 | IAdditionalColumnDataGenerator additionalColumnDataGenerator) 18 | { 19 | _columnOptions = columnOptions ?? throw new ArgumentNullException(nameof(columnOptions)); 20 | _standardColumnDataGenerator = standardColumnDataGenerator ?? throw new ArgumentNullException(nameof(standardColumnDataGenerator)); 21 | _additionalColumnDataGenerator = additionalColumnDataGenerator ?? throw new ArgumentNullException(nameof(additionalColumnDataGenerator)); 22 | } 23 | 24 | public IEnumerable> GetColumnsAndValues(LogEvent logEvent) 25 | { 26 | // skip Id (auto-incrementing identity) 27 | foreach (var column in _columnOptions.Store.Where(c => c != StandardColumn.Id)) 28 | { 29 | yield return _standardColumnDataGenerator.GetStandardColumnNameAndValue(column, logEvent); 30 | } 31 | 32 | if (_columnOptions.AdditionalColumns != null) 33 | { 34 | foreach (var additionalColumn in _columnOptions.AdditionalColumns) 35 | { 36 | yield return _additionalColumnDataGenerator.GetAdditionalColumnNameAndValue(additionalColumn, logEvent.Properties); 37 | } 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.MSSqlServer/Sinks/MSSqlServer/Platform/DataTableCreator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data; 3 | 4 | namespace Serilog.Sinks.MSSqlServer.Platform 5 | { 6 | internal class DataTableCreator : IDataTableCreator 7 | { 8 | private readonly string _tableName; 9 | private readonly ColumnOptions _columnOptions; 10 | 11 | public DataTableCreator(string tableName, ColumnOptions columnOptions) 12 | { 13 | _tableName = tableName ?? throw new ArgumentNullException(nameof(tableName)); 14 | _columnOptions = columnOptions ?? throw new ArgumentNullException(nameof(columnOptions)); 15 | } 16 | 17 | public DataTable CreateDataTable() 18 | { 19 | var eventsTable = new DataTable(_tableName); 20 | 21 | foreach (var standardColumn in _columnOptions.Store) 22 | { 23 | var standardOpts = _columnOptions.GetStandardColumnOptions(standardColumn); 24 | var dataColumn = standardOpts.AsDataColumn(); 25 | eventsTable.Columns.Add(dataColumn); 26 | if (standardOpts == _columnOptions.PrimaryKey) 27 | eventsTable.PrimaryKey = new DataColumn[] { dataColumn }; 28 | } 29 | 30 | if (_columnOptions.AdditionalColumns != null) 31 | { 32 | foreach (var addCol in _columnOptions.AdditionalColumns) 33 | { 34 | var dataColumn = addCol.AsDataColumn(); 35 | eventsTable.Columns.Add(dataColumn); 36 | if (addCol == _columnOptions.PrimaryKey) 37 | eventsTable.PrimaryKey = new DataColumn[] { dataColumn }; 38 | } 39 | } 40 | 41 | return eventsTable; 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.MSSqlServer/Sinks/MSSqlServer/Platform/IDataTableCreator.cs: -------------------------------------------------------------------------------- 1 | using System.Data; 2 | 3 | namespace Serilog.Sinks.MSSqlServer.Platform 4 | { 5 | internal interface IDataTableCreator 6 | { 7 | DataTable CreateDataTable(); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.MSSqlServer/Sinks/MSSqlServer/Platform/ISqlBulkBatchWriter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading.Tasks; 4 | using Serilog.Events; 5 | 6 | namespace Serilog.Sinks.MSSqlServer.Platform 7 | { 8 | internal interface ISqlBulkBatchWriter : IDisposable 9 | { 10 | Task WriteBatch(IEnumerable events); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.MSSqlServer/Sinks/MSSqlServer/Platform/ISqlCommandExecutor.cs: -------------------------------------------------------------------------------- 1 | namespace Serilog.Sinks.MSSqlServer.Platform 2 | { 3 | internal interface ISqlCommandExecutor 4 | { 5 | void Execute(); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.MSSqlServer/Sinks/MSSqlServer/Platform/ISqlCommandFactory.cs: -------------------------------------------------------------------------------- 1 | using Serilog.Sinks.MSSqlServer.Platform.SqlClient; 2 | 3 | namespace Serilog.Sinks.MSSqlServer.Platform 4 | { 5 | internal interface ISqlCommandFactory 6 | { 7 | ISqlCommandWrapper CreateCommand(string cmdText, ISqlConnectionWrapper sqlConnection); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.MSSqlServer/Sinks/MSSqlServer/Platform/ISqlConnectionFactory.cs: -------------------------------------------------------------------------------- 1 | using Serilog.Sinks.MSSqlServer.Platform.SqlClient; 2 | 3 | namespace Serilog.Sinks.MSSqlServer.Platform 4 | { 5 | internal interface ISqlConnectionFactory 6 | { 7 | ISqlConnectionWrapper Create(); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.MSSqlServer/Sinks/MSSqlServer/Platform/ISqlCreateDatabaseWriter.cs: -------------------------------------------------------------------------------- 1 | namespace Serilog.Sinks.MSSqlServer.Platform 2 | { 3 | internal interface ISqlCreateDatabaseWriter : ISqlWriter 4 | { 5 | string DatabaseName { get; } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.MSSqlServer/Sinks/MSSqlServer/Platform/ISqlCreateTableWriter.cs: -------------------------------------------------------------------------------- 1 | namespace Serilog.Sinks.MSSqlServer.Platform 2 | { 3 | internal interface ISqlCreateTableWriter : ISqlWriter 4 | { 5 | string TableName { get; } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.MSSqlServer/Sinks/MSSqlServer/Platform/ISqlLogEventWriter.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading.Tasks; 3 | using Serilog.Events; 4 | 5 | namespace Serilog.Sinks.MSSqlServer.Platform 6 | { 7 | internal interface ISqlLogEventWriter 8 | { 9 | void WriteEvent(LogEvent logEvent); 10 | 11 | Task WriteEvents(IEnumerable events); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.MSSqlServer/Sinks/MSSqlServer/Platform/ISqlWriter.cs: -------------------------------------------------------------------------------- 1 | namespace Serilog.Sinks.MSSqlServer.Platform 2 | { 3 | internal interface ISqlWriter 4 | { 5 | string GetSql(); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.MSSqlServer/Sinks/MSSqlServer/Platform/SqlClient/ISqlBulkCopyWrapper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data; 3 | using System.Threading.Tasks; 4 | 5 | namespace Serilog.Sinks.MSSqlServer.Platform.SqlClient 6 | { 7 | internal interface ISqlBulkCopyWrapper : IDisposable 8 | { 9 | void AddSqlBulkCopyColumnMapping(string sourceColumn, string destinationColumn); 10 | Task WriteToServerAsync(DataTable table); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.MSSqlServer/Sinks/MSSqlServer/Platform/SqlClient/ISqlCommandWrapper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | 4 | namespace Serilog.Sinks.MSSqlServer.Platform.SqlClient 5 | { 6 | internal interface ISqlCommandWrapper : IDisposable 7 | { 8 | void AddParameter(string parameterName, object value); 9 | int ExecuteNonQuery(); 10 | Task ExecuteNonQueryAsync(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.MSSqlServer/Sinks/MSSqlServer/Platform/SqlClient/ISqlConnectionStringBuilderWrapper.cs: -------------------------------------------------------------------------------- 1 | namespace Serilog.Sinks.MSSqlServer.Platform.SqlClient 2 | { 3 | internal interface ISqlConnectionStringBuilderWrapper 4 | { 5 | string ConnectionString { get; } 6 | string InitialCatalog { get; } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.MSSqlServer/Sinks/MSSqlServer/Platform/SqlClient/ISqlConnectionWrapper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Microsoft.Data.SqlClient; 4 | 5 | namespace Serilog.Sinks.MSSqlServer.Platform.SqlClient 6 | { 7 | internal interface ISqlConnectionWrapper : IDisposable 8 | { 9 | SqlConnection SqlConnection { get; } 10 | 11 | void Open(); 12 | Task OpenAsync(); 13 | ISqlBulkCopyWrapper CreateSqlBulkCopy(bool disableTriggers, string destinationTableName); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.MSSqlServer/Sinks/MSSqlServer/Platform/SqlClient/SqlBulkCopyWrapper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data; 3 | using System.Threading.Tasks; 4 | using Microsoft.Data.SqlClient; 5 | 6 | namespace Serilog.Sinks.MSSqlServer.Platform.SqlClient 7 | { 8 | internal class SqlBulkCopyWrapper : ISqlBulkCopyWrapper 9 | { 10 | private readonly SqlBulkCopy _sqlBulkCopy; 11 | private bool _disposedValue; 12 | 13 | public SqlBulkCopyWrapper(SqlBulkCopy sqlBulkCopy) 14 | { 15 | _sqlBulkCopy = sqlBulkCopy ?? throw new ArgumentNullException(nameof(sqlBulkCopy)); 16 | } 17 | 18 | public void AddSqlBulkCopyColumnMapping(string sourceColumn, string destinationColumn) 19 | { 20 | var mapping = new SqlBulkCopyColumnMapping(sourceColumn, destinationColumn); 21 | _sqlBulkCopy.ColumnMappings.Add(mapping); 22 | } 23 | 24 | public Task WriteToServerAsync(DataTable table) => 25 | _sqlBulkCopy.WriteToServerAsync(table); 26 | 27 | protected virtual void Dispose(bool disposing) 28 | { 29 | if (!_disposedValue) 30 | { 31 | ((IDisposable)_sqlBulkCopy).Dispose(); 32 | _disposedValue = true; 33 | } 34 | } 35 | 36 | public void Dispose() 37 | { 38 | Dispose(disposing: true); 39 | GC.SuppressFinalize(this); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.MSSqlServer/Sinks/MSSqlServer/Platform/SqlClient/SqlCommandWrapper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data; 3 | using System.Threading.Tasks; 4 | using Microsoft.Data.SqlClient; 5 | 6 | namespace Serilog.Sinks.MSSqlServer.Platform.SqlClient 7 | { 8 | internal class SqlCommandWrapper : ISqlCommandWrapper 9 | { 10 | private readonly SqlCommand _sqlCommand; 11 | private bool _disposedValue; 12 | 13 | public SqlCommandWrapper(SqlCommand sqlCommand) 14 | { 15 | _sqlCommand = sqlCommand ?? throw new ArgumentNullException(nameof(sqlCommand)); 16 | } 17 | 18 | public void AddParameter(string parameterName, object value) 19 | { 20 | var parameter = new SqlParameter(parameterName, value ?? DBNull.Value); 21 | 22 | // The default is SqlDbType.DateTime, which will truncate the DateTime value if the actual 23 | // type in the database table is datetime2. So we explicitly set it to DateTime2, which will 24 | // work both if the field in the table is datetime and datetime2, which is also consistent with 25 | // the behavior of the non-audit sink. 26 | if (value is DateTime) 27 | parameter.SqlDbType = SqlDbType.DateTime2; 28 | 29 | _sqlCommand.Parameters.Add(parameter); 30 | } 31 | 32 | public int ExecuteNonQuery() => 33 | _sqlCommand.ExecuteNonQuery(); 34 | 35 | public Task ExecuteNonQueryAsync() => 36 | _sqlCommand.ExecuteNonQueryAsync(); 37 | 38 | protected virtual void Dispose(bool disposing) 39 | { 40 | if (!_disposedValue) 41 | { 42 | _sqlCommand.Dispose(); 43 | _disposedValue = true; 44 | } 45 | } 46 | 47 | public void Dispose() 48 | { 49 | Dispose(disposing: true); 50 | GC.SuppressFinalize(this); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.MSSqlServer/Sinks/MSSqlServer/Platform/SqlClient/SqlConnectionStringBuilderWrapper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.Data.SqlClient; 3 | 4 | namespace Serilog.Sinks.MSSqlServer.Platform.SqlClient 5 | { 6 | internal class SqlConnectionStringBuilderWrapper : ISqlConnectionStringBuilderWrapper 7 | { 8 | private readonly SqlConnectionStringBuilder _sqlConnectionStringBuilder; 9 | 10 | public SqlConnectionStringBuilderWrapper(string connectionString, bool enlist) 11 | { 12 | if (string.IsNullOrWhiteSpace(connectionString)) 13 | { 14 | throw new ArgumentNullException(nameof(connectionString)); 15 | } 16 | 17 | _sqlConnectionStringBuilder = new SqlConnectionStringBuilder(connectionString) 18 | { 19 | Enlist = enlist 20 | }; 21 | } 22 | 23 | public string ConnectionString => _sqlConnectionStringBuilder.ConnectionString; 24 | 25 | public string InitialCatalog 26 | { 27 | get => _sqlConnectionStringBuilder.InitialCatalog; 28 | set => _sqlConnectionStringBuilder.InitialCatalog = value; 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.MSSqlServer/Sinks/MSSqlServer/Platform/SqlClient/SqlConnectionWrapper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Microsoft.Data.SqlClient; 4 | 5 | namespace Serilog.Sinks.MSSqlServer.Platform.SqlClient 6 | { 7 | internal class SqlConnectionWrapper : ISqlConnectionWrapper 8 | { 9 | private readonly SqlConnection _sqlConnection; 10 | private bool _disposedValue; 11 | 12 | public SqlConnectionWrapper(string connectionString, Action connectionConfiguration = null) 13 | { 14 | _sqlConnection = new SqlConnection(connectionString); 15 | if (connectionConfiguration != null) 16 | { 17 | connectionConfiguration(_sqlConnection); 18 | } 19 | } 20 | 21 | public SqlConnection SqlConnection => _sqlConnection; 22 | 23 | public void Open() 24 | { 25 | _sqlConnection.Open(); 26 | } 27 | 28 | public async Task OpenAsync() 29 | { 30 | await _sqlConnection.OpenAsync().ConfigureAwait(false); 31 | } 32 | 33 | public ISqlBulkCopyWrapper CreateSqlBulkCopy(bool disableTriggers, string destinationTableName) 34 | { 35 | var sqlBulkCopy = disableTriggers 36 | ? new SqlBulkCopy(_sqlConnection) 37 | : new SqlBulkCopy(_sqlConnection, SqlBulkCopyOptions.CheckConstraints | SqlBulkCopyOptions.FireTriggers, null); 38 | sqlBulkCopy.DestinationTableName = destinationTableName; 39 | 40 | return new SqlBulkCopyWrapper(sqlBulkCopy); 41 | } 42 | 43 | protected virtual void Dispose(bool disposing) 44 | { 45 | if (!_disposedValue) 46 | { 47 | _sqlConnection.Dispose(); 48 | _disposedValue = true; 49 | } 50 | } 51 | 52 | public void Dispose() 53 | { 54 | Dispose(disposing: true); 55 | GC.SuppressFinalize(this); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.MSSqlServer/Sinks/MSSqlServer/Platform/SqlCommandExecutor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Serilog.Sinks.MSSqlServer.Platform 4 | { 5 | internal abstract class SqlCommandExecutor : ISqlCommandExecutor 6 | { 7 | private readonly ISqlWriter _sqlWriter; 8 | private readonly ISqlConnectionFactory _sqlConnectionFactory; 9 | private readonly ISqlCommandFactory _sqlCommandFactory; 10 | 11 | public SqlCommandExecutor( 12 | ISqlWriter sqlWriter, 13 | ISqlConnectionFactory sqlConnectionFactory, 14 | ISqlCommandFactory sqlCommandFactory) 15 | { 16 | _sqlWriter = sqlWriter ?? throw new ArgumentNullException(nameof(sqlWriter)); 17 | _sqlConnectionFactory = sqlConnectionFactory ?? throw new ArgumentNullException(nameof(sqlConnectionFactory)); 18 | _sqlCommandFactory = sqlCommandFactory ?? throw new ArgumentNullException(nameof(sqlCommandFactory)); 19 | } 20 | 21 | public void Execute() 22 | { 23 | try 24 | { 25 | using (var conn = _sqlConnectionFactory.Create()) 26 | { 27 | var sql = _sqlWriter.GetSql(); 28 | using (var cmd = _sqlCommandFactory.CreateCommand(sql, conn)) 29 | { 30 | conn.Open(); 31 | cmd.ExecuteNonQuery(); 32 | } 33 | } 34 | } 35 | catch (Exception ex) 36 | { 37 | HandleException(ex); 38 | throw; 39 | } 40 | } 41 | 42 | protected abstract void HandleException(Exception ex); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.MSSqlServer/Sinks/MSSqlServer/Platform/SqlCommandFactory.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Data.SqlClient; 2 | using Serilog.Sinks.MSSqlServer.Platform.SqlClient; 3 | 4 | namespace Serilog.Sinks.MSSqlServer.Platform 5 | { 6 | internal class SqlCommandFactory : ISqlCommandFactory 7 | { 8 | public ISqlCommandWrapper CreateCommand(string cmdText, ISqlConnectionWrapper sqlConnection) 9 | { 10 | var sqlCommand = new SqlCommand(cmdText, sqlConnection.SqlConnection); 11 | return new SqlCommandWrapper(sqlCommand); 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.MSSqlServer/Sinks/MSSqlServer/Platform/SqlConnectionFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.Data.SqlClient; 3 | using Serilog.Sinks.MSSqlServer.Platform.SqlClient; 4 | 5 | namespace Serilog.Sinks.MSSqlServer.Platform 6 | { 7 | internal class SqlConnectionFactory : ISqlConnectionFactory 8 | { 9 | private readonly string _connectionString; 10 | private readonly ISqlConnectionStringBuilderWrapper _sqlConnectionStringBuilderWrapper; 11 | private readonly Action _connectionConfiguration; 12 | 13 | public SqlConnectionFactory(ISqlConnectionStringBuilderWrapper sqlConnectionStringBuilderWrapper, 14 | Action connectionConfiguration = null) 15 | { 16 | _sqlConnectionStringBuilderWrapper = sqlConnectionStringBuilderWrapper 17 | ?? throw new ArgumentNullException(nameof(sqlConnectionStringBuilderWrapper)); 18 | 19 | _connectionString = _sqlConnectionStringBuilderWrapper.ConnectionString; 20 | _connectionConfiguration = connectionConfiguration; 21 | } 22 | 23 | public ISqlConnectionWrapper Create() 24 | { 25 | return new SqlConnectionWrapper(_connectionString, _connectionConfiguration); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.MSSqlServer/Sinks/MSSqlServer/Platform/SqlCreateDatabaseWriter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | using static System.FormattableString; 4 | 5 | namespace Serilog.Sinks.MSSqlServer.Platform 6 | { 7 | internal class SqlCreateDatabaseWriter : ISqlCreateDatabaseWriter 8 | { 9 | private readonly string _databaseName; 10 | 11 | public SqlCreateDatabaseWriter(string databaseName) 12 | { 13 | _databaseName = databaseName ?? throw new ArgumentNullException(nameof(databaseName)); 14 | } 15 | 16 | public string DatabaseName => _databaseName; 17 | 18 | public string GetSql() 19 | { 20 | var sql = new StringBuilder(); 21 | 22 | sql.AppendLine(Invariant($"IF NOT EXISTS (SELECT name FROM sys.databases WHERE name = '{_databaseName}')")); 23 | sql.AppendLine("BEGIN"); 24 | sql.AppendLine(Invariant($"CREATE DATABASE [{_databaseName}]")); 25 | sql.AppendLine("END"); 26 | 27 | return sql.ToString(); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.MSSqlServer/Sinks/MSSqlServer/Platform/SqlDatabaseCreator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Serilog.Debugging; 3 | 4 | namespace Serilog.Sinks.MSSqlServer.Platform 5 | { 6 | internal class SqlDatabaseCreator : SqlCommandExecutor 7 | { 8 | private readonly string _databaseName; 9 | 10 | public SqlDatabaseCreator( 11 | ISqlCreateDatabaseWriter sqlCreateDatabaseWriter, 12 | ISqlConnectionFactory sqlConnectionFactory, 13 | ISqlCommandFactory sqlCommandFactory) : 14 | base(sqlCreateDatabaseWriter, sqlConnectionFactory, sqlCommandFactory) 15 | { 16 | if (sqlCreateDatabaseWriter == null) throw new ArgumentNullException(nameof(sqlCreateDatabaseWriter)); 17 | _databaseName = sqlCreateDatabaseWriter.DatabaseName; 18 | } 19 | 20 | protected override void HandleException(Exception ex) 21 | { 22 | SelfLog.WriteLine("Unable to create database {0} due to following error: {1}", 23 | _databaseName, ex); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.MSSqlServer/Sinks/MSSqlServer/Platform/SqlTableCreator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Serilog.Debugging; 3 | 4 | namespace Serilog.Sinks.MSSqlServer.Platform 5 | { 6 | internal class SqlTableCreator : SqlCommandExecutor 7 | { 8 | private readonly string _tableName; 9 | 10 | public SqlTableCreator( 11 | ISqlCreateTableWriter sqlCreateTableWriter, 12 | ISqlConnectionFactory sqlConnectionFactory, 13 | ISqlCommandFactory sqlCommandFactory) : base(sqlCreateTableWriter, sqlConnectionFactory, sqlCommandFactory) 14 | { 15 | if (sqlCreateTableWriter == null) throw new ArgumentNullException(nameof(sqlCreateTableWriter)); 16 | _tableName = sqlCreateTableWriter.TableName; 17 | } 18 | 19 | protected override void HandleException(Exception ex) 20 | { 21 | SelfLog.WriteLine("Unable to create database table {0} due to following error: {1}", 22 | _tableName, ex); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.MSSqlServer/Sinks/MSSqlServer/StandardColumn.cs: -------------------------------------------------------------------------------- 1 | namespace Serilog.Sinks.MSSqlServer 2 | { 3 | /// 4 | /// List of columns that are available to be written to the database, excluding Id and additional columns. 5 | /// 6 | public enum StandardColumn 7 | { 8 | /// 9 | /// The optional primary key 10 | /// 11 | Id, 12 | 13 | /// 14 | /// The message rendered with the template given the properties associated with the event. 15 | /// 16 | Message, 17 | 18 | /// 19 | /// The message template describing the event. 20 | /// 21 | MessageTemplate, 22 | 23 | /// 24 | /// The level of the event. 25 | /// 26 | Level, 27 | 28 | /// 29 | /// The OpenTelemetry trace id of the event. 30 | /// 31 | TraceId, 32 | 33 | /// 34 | /// The OpenTelemetry span id of the event. 35 | /// 36 | SpanId, 37 | 38 | /// 39 | /// The time at which the event occurred. 40 | /// 41 | TimeStamp, 42 | 43 | /// 44 | /// An exception associated with the event, or null. 45 | /// 46 | Exception, 47 | 48 | /// 49 | /// Properties associated with the event, including those presented in . 50 | /// 51 | Properties, 52 | 53 | /// 54 | /// A log event. 55 | /// 56 | LogEvent 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /test/Serilog.Sinks.MSSqlServer.PerformanceTests/Misc/AuditSinkExtendedBenchmarks.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Data; 4 | using BenchmarkDotNet.Attributes; 5 | 6 | namespace Serilog.Sinks.MSSqlServer.PerformanceTests.Misc; 7 | 8 | [MemoryDiagnoser] 9 | public class AuditSinkExtendedBenchmarks 10 | { 11 | private const string _connectionString = @"Data Source=(localdb)\MSSQLLocalDB;Database=LogAuditExtPerfTest;Integrated Security=SSPI;Encrypt=False;"; 12 | private const string _schemaName = "dbo"; 13 | private const string _tableName = "LogEvents"; 14 | private ILogger _log = null!; 15 | private DateTimeOffset _additionalColumn7; 16 | 17 | 18 | [Params("String One", "String Two")] 19 | public string AdditionalColumn1 { get; set; } 20 | 21 | [Params(1, 2)] 22 | public int AdditionalColumn2 { get; set; } 23 | 24 | 25 | [GlobalSetup] 26 | public void Setup() 27 | { 28 | var options = new ColumnOptions 29 | { 30 | AdditionalColumns = new List 31 | { 32 | new() { DataType = SqlDbType.NVarChar, ColumnName = "AdditionalColumn1", DataLength = 40 }, 33 | new() { DataType = SqlDbType.Int, ColumnName = "AdditionalColumn2" }, 34 | new() { DataType = SqlDbType.Int, ColumnName = "AdditionalColumn3" }, 35 | new() { DataType = SqlDbType.Int, ColumnName = "AdditionalColumn4" }, 36 | new() { DataType = SqlDbType.Int, ColumnName = "AdditionalColumn5" }, 37 | new() { DataType = SqlDbType.Int, ColumnName = "AdditionalColumn6" }, 38 | new() { DataType = SqlDbType.DateTimeOffset, ColumnName = "AdditionalColumn7" } 39 | } 40 | }; 41 | options.Store.Add(StandardColumn.LogEvent); 42 | _log = new LoggerConfiguration() 43 | .AuditTo.MSSqlServer(_connectionString, 44 | sinkOptions: new MSSqlServerSinkOptions 45 | { 46 | TableName = _tableName, 47 | SchemaName = _schemaName, 48 | AutoCreateSqlTable = true, 49 | AutoCreateSqlDatabase = true 50 | }, 51 | appConfiguration: null, 52 | restrictedToMinimumLevel: Serilog.Events.LogEventLevel.Verbose, 53 | formatProvider: null, 54 | columnOptions: options, 55 | columnOptionsSection: null) 56 | .CreateLogger(); 57 | 58 | _additionalColumn7 = new DateTimeOffset(2024, 01, 01, 00, 00, 00, TimeSpan.FromHours(1)); 59 | } 60 | 61 | [Benchmark] 62 | public void EmitComplexLogEvent() 63 | { 64 | _log.Information("Hello, {AdditionalColumn1} {AdditionalColumn2} {AdditionalColumn3} {AdditionalColumn4} {AdditionalColumn5} {AdditionalColumn6} {AdditionalColumn7}!", 65 | AdditionalColumn1, AdditionalColumn2, 3, 4, 5, 6, _additionalColumn7); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /test/Serilog.Sinks.MSSqlServer.PerformanceTests/Misc/AuditSinkQuickBenchmarks.cs: -------------------------------------------------------------------------------- 1 | using BenchmarkDotNet.Attributes; 2 | 3 | namespace Serilog.Sinks.MSSqlServer.PerformanceTests.Misc; 4 | 5 | [MemoryDiagnoser] 6 | public class AuditSinkQuickBenchmarks 7 | { 8 | private const string _connectionString = @"Data Source=(localdb)\MSSQLLocalDB;Database=LogAuditQuickPerfTest;Integrated Security=SSPI;Encrypt=False;"; 9 | private const string _schemaName = "dbo"; 10 | private const string _tableName = "LogEvents"; 11 | private ILogger _log = null!; 12 | 13 | [GlobalSetup] 14 | public void Setup() 15 | { 16 | var options = new ColumnOptions(); 17 | options.Store.Add(StandardColumn.LogEvent); 18 | _log = new LoggerConfiguration() 19 | .AuditTo.MSSqlServer(_connectionString, 20 | sinkOptions: new MSSqlServerSinkOptions 21 | { 22 | TableName = _tableName, 23 | SchemaName = _schemaName, 24 | AutoCreateSqlTable = true, 25 | AutoCreateSqlDatabase = true 26 | }, 27 | appConfiguration: null, 28 | restrictedToMinimumLevel: Serilog.Events.LogEventLevel.Verbose, 29 | formatProvider: null, 30 | columnOptions: options, 31 | columnOptionsSection: null) 32 | .CreateLogger(); 33 | } 34 | 35 | [Benchmark] 36 | public void EmitLogEvent() 37 | { 38 | _log.Information("Hello, {Name}!", "World"); 39 | } 40 | 41 | [Benchmark] 42 | public void IntProperties() 43 | { 44 | _log.Information("Hello, {A} {B} {C}!", 1, 2, 3); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /test/Serilog.Sinks.MSSqlServer.PerformanceTests/Misc/SinkExtendedBenchmarks.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Data; 4 | using BenchmarkDotNet.Attributes; 5 | 6 | namespace Serilog.Sinks.MSSqlServer.PerformanceTests.Misc; 7 | 8 | [MemoryDiagnoser] 9 | public class SinkExtendedBenchmarks 10 | { 11 | private const string _connectionString = @"Data Source=(localdb)\MSSQLLocalDB;Database=LogExtPerfTest;Integrated Security=SSPI;Encrypt=False;"; 12 | private const string _schemaName = "dbo"; 13 | private const string _tableName = "LogEvents"; 14 | private ILogger _log = null!; 15 | private DateTimeOffset _additionalColumn7; 16 | 17 | 18 | [Params("String One", "String Two")] 19 | public string AdditionalColumn1 { get; set; } 20 | 21 | [Params(1, 2)] 22 | public int AdditionalColumn2 { get; set; } 23 | 24 | 25 | [GlobalSetup] 26 | public void Setup() 27 | { 28 | var options = new ColumnOptions 29 | { 30 | AdditionalColumns = new List 31 | { 32 | new() { DataType = SqlDbType.NVarChar, ColumnName = "AdditionalColumn1", DataLength = 40 }, 33 | new() { DataType = SqlDbType.Int, ColumnName = "AdditionalColumn2" }, 34 | new() { DataType = SqlDbType.Int, ColumnName = "AdditionalColumn3" }, 35 | new() { DataType = SqlDbType.Int, ColumnName = "AdditionalColumn4" }, 36 | new() { DataType = SqlDbType.Int, ColumnName = "AdditionalColumn5" }, 37 | new() { DataType = SqlDbType.Int, ColumnName = "AdditionalColumn6" }, 38 | new() { DataType = SqlDbType.DateTimeOffset, ColumnName = "AdditionalColumn7" } 39 | } 40 | }; 41 | options.Store.Add(StandardColumn.LogEvent); 42 | _log = new LoggerConfiguration() 43 | .WriteTo.MSSqlServer(_connectionString, 44 | sinkOptions: new MSSqlServerSinkOptions 45 | { 46 | TableName = _tableName, 47 | SchemaName = _schemaName, 48 | AutoCreateSqlTable = true, 49 | AutoCreateSqlDatabase = true 50 | }, 51 | appConfiguration: null, 52 | restrictedToMinimumLevel: Serilog.Events.LogEventLevel.Verbose, 53 | formatProvider: null, 54 | columnOptions: options, 55 | columnOptionsSection: null) 56 | .CreateLogger(); 57 | 58 | _additionalColumn7 = new DateTimeOffset(2024, 01, 01, 00, 00, 00, TimeSpan.FromHours(1)); 59 | } 60 | 61 | [Benchmark] 62 | public void EmitComplexLogEvent() 63 | { 64 | _log.Information("Hello, {AdditionalColumn1} {AdditionalColumn2} {AdditionalColumn3} {AdditionalColumn4} {AdditionalColumn5} {AdditionalColumn6} {AdditionalColumn7}!", 65 | AdditionalColumn1, AdditionalColumn2,3, 4, 5, 6, _additionalColumn7); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /test/Serilog.Sinks.MSSqlServer.PerformanceTests/Misc/SinkQuickBenchmarks.cs: -------------------------------------------------------------------------------- 1 | using BenchmarkDotNet.Attributes; 2 | 3 | namespace Serilog.Sinks.MSSqlServer.PerformanceTests.Misc; 4 | 5 | [MemoryDiagnoser] 6 | public class SinkQuickBenchmarks 7 | { 8 | private const string _connectionString = @"Data Source=(localdb)\MSSQLLocalDB;Database=LogQuickPerfTest;Integrated Security=SSPI;Encrypt=False;"; 9 | private const string _schemaName = "dbo"; 10 | private const string _tableName = "LogEvents"; 11 | private ILogger _log = null!; 12 | 13 | [GlobalSetup] 14 | public void Setup() 15 | { 16 | var options = new ColumnOptions(); 17 | options.Store.Add(StandardColumn.LogEvent); 18 | _log = new LoggerConfiguration() 19 | .WriteTo.MSSqlServer(_connectionString, 20 | sinkOptions: new MSSqlServerSinkOptions 21 | { 22 | TableName = _tableName, 23 | SchemaName = _schemaName, 24 | AutoCreateSqlTable = true, 25 | AutoCreateSqlDatabase = true 26 | }, 27 | appConfiguration: null, 28 | restrictedToMinimumLevel: Serilog.Events.LogEventLevel.Verbose, 29 | formatProvider: null, 30 | columnOptions: options, 31 | columnOptionsSection: null) 32 | .CreateLogger(); 33 | } 34 | 35 | [Benchmark] 36 | public void EmitLogEvent() 37 | { 38 | _log.Information("Hello, {Name}!", "World"); 39 | } 40 | 41 | [Benchmark] 42 | public void IntProperties() 43 | { 44 | _log.Information("Hello, {A} {B} {C}!", 1, 2, 3); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /test/Serilog.Sinks.MSSqlServer.PerformanceTests/Program.cs: -------------------------------------------------------------------------------- 1 | using BenchmarkDotNet.Running; 2 | 3 | namespace Serilog.Sinks.MSSqlServer.PerformanceTests; 4 | 5 | public static class Program 6 | { 7 | public static void Main(string[] args) 8 | { 9 | BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /test/Serilog.Sinks.MSSqlServer.PerformanceTests/Serilog.Sinks.MSSqlServer.PerformanceTests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net8.0 5 | true 6 | Serilog.Sinks.MSSqlServer.PerformanceTests 7 | Exe 8 | ../../assets/Serilog.snk 9 | true 10 | AnyCPU 11 | 6.0-recommended 12 | True 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /test/Serilog.Sinks.MSSqlServer.PerformanceTests/Sinks/MSSqlServer/Platform/SqlBulkBatchWriterBenchmarks.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Data; 4 | using System.Threading.Tasks; 5 | using BenchmarkDotNet.Attributes; 6 | using Moq; 7 | using Serilog.Events; 8 | using Serilog.Parsing; 9 | using Serilog.Sinks.MSSqlServer.Output; 10 | using Serilog.Sinks.MSSqlServer.Platform; 11 | using Serilog.Sinks.MSSqlServer.Platform.SqlClient; 12 | 13 | namespace Serilog.Sinks.MSSqlServer.PerformanceTests.Platform; 14 | 15 | [MemoryDiagnoser] 16 | [MaxIterationCount(16)] 17 | public class SqlBulkBatchWriterBenchmarks : IDisposable 18 | { 19 | private const string _tableName = "TestTableName"; 20 | private const string _schemaName = "TestSchemaName"; 21 | private readonly DataTable _dataTable = new(_tableName); 22 | private Mock _dataTableCreatorMock; 23 | private Mock _sqlConnectionFactoryMock; 24 | private Mock _logEventDataGeneratorMock; 25 | private Mock _sqlConnectionWrapperMock; 26 | private Mock _sqlBulkCopyWrapper; 27 | private List _logEvents; 28 | private SqlBulkBatchWriter _sut; 29 | 30 | [GlobalSetup] 31 | public void Setup() 32 | { 33 | _dataTableCreatorMock = new Mock(); 34 | _sqlConnectionFactoryMock = new Mock(); 35 | _logEventDataGeneratorMock = new Mock(); 36 | _sqlConnectionWrapperMock = new Mock(); 37 | _sqlBulkCopyWrapper = new Mock(); 38 | 39 | _dataTableCreatorMock.Setup(d => d.CreateDataTable()).Returns(_dataTable); 40 | 41 | _sqlConnectionFactoryMock.Setup(f => f.Create()).Returns(_sqlConnectionWrapperMock.Object); 42 | _sqlConnectionWrapperMock.Setup(c => c.CreateSqlBulkCopy(It.IsAny(), It.IsAny())) 43 | .Returns(_sqlBulkCopyWrapper.Object); 44 | 45 | CreateLogEvents(); 46 | 47 | _sut = new SqlBulkBatchWriter(_tableName, _schemaName, false, 48 | _dataTableCreatorMock.Object, _sqlConnectionFactoryMock.Object, _logEventDataGeneratorMock.Object); 49 | } 50 | 51 | [Benchmark] 52 | public async Task WriteBatch() 53 | { 54 | await _sut.WriteBatch(_logEvents); 55 | } 56 | 57 | private static LogEvent CreateLogEvent() 58 | { 59 | return new LogEvent( 60 | new DateTimeOffset(2020, 1, 1, 0, 0, 0, 0, TimeSpan.Zero), 61 | LogEventLevel.Debug, null, new MessageTemplate(new List()), 62 | new List()); 63 | } 64 | 65 | private void CreateLogEvents() 66 | { 67 | _logEvents = new List(); 68 | var eventCount = 500_000; 69 | while (eventCount-- > 0) 70 | { 71 | _logEvents.Add(CreateLogEvent()); 72 | } 73 | } 74 | 75 | public void Dispose() 76 | { 77 | GC.SuppressFinalize(this); 78 | _sut.Dispose(); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /test/Serilog.Sinks.MSSqlServer.PerformanceTests/Sinks/MSSqlServer/Platform/SqlInsertStatementWriterBenchmarks.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading.Tasks; 4 | using BenchmarkDotNet.Attributes; 5 | using Moq; 6 | using Serilog.Events; 7 | using Serilog.Parsing; 8 | using Serilog.Sinks.MSSqlServer.Output; 9 | using Serilog.Sinks.MSSqlServer.Platform; 10 | using Serilog.Sinks.MSSqlServer.Platform.SqlClient; 11 | 12 | namespace Serilog.Sinks.MSSqlServer.PerformanceTests.Platform; 13 | 14 | [MemoryDiagnoser] 15 | [MaxIterationCount(16)] 16 | public class SqlInsertStatementWriterBenchmarks 17 | { 18 | private const string _tableName = "TestTableName"; 19 | private const string _schemaName = "TestSchemaName"; 20 | private Mock _sqlConnectionFactoryMock; 21 | private Mock _sqlCommandFactoryMock; 22 | private Mock _logEventDataGeneratorMock; 23 | private Mock _sqlConnectionWrapperMock; 24 | private Mock _sqlCommandWrapperMock; 25 | private List _logEvents; 26 | private SqlInsertStatementWriter _sut; 27 | 28 | [GlobalSetup] 29 | public void Setup() 30 | { 31 | _sqlConnectionFactoryMock = new Mock(); 32 | _sqlCommandFactoryMock = new Mock(); 33 | _logEventDataGeneratorMock = new Mock(); 34 | _sqlConnectionWrapperMock = new Mock(); 35 | _sqlCommandWrapperMock = new Mock(); 36 | 37 | _sqlConnectionFactoryMock.Setup(f => f.Create()).Returns(_sqlConnectionWrapperMock.Object); 38 | _sqlCommandFactoryMock.Setup(f => f.CreateCommand(It.IsAny(), It.IsAny())) 39 | .Returns(_sqlCommandWrapperMock.Object); 40 | 41 | CreateLogEvents(); 42 | 43 | _sut = new SqlInsertStatementWriter(_tableName, _schemaName, 44 | _sqlConnectionFactoryMock.Object, _sqlCommandFactoryMock.Object, _logEventDataGeneratorMock.Object); 45 | } 46 | 47 | [Benchmark] 48 | public async Task WriteEvents() 49 | { 50 | await _sut.WriteEvents(_logEvents); 51 | } 52 | 53 | private static LogEvent CreateLogEvent() 54 | { 55 | return new LogEvent( 56 | new DateTimeOffset(2020, 1, 1, 0, 0, 0, 0, TimeSpan.Zero), 57 | LogEventLevel.Debug, null, new MessageTemplate(new List()), 58 | new List()); 59 | } 60 | 61 | private void CreateLogEvents() 62 | { 63 | _logEvents = new List(); 64 | var eventCount = 200_000; 65 | while (eventCount-- > 0) 66 | { 67 | _logEvents.Add(CreateLogEvent()); 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /test/Serilog.Sinks.MSSqlServer.Tests/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 |
5 |
6 |
7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /test/Serilog.Sinks.MSSqlServer.Tests/Configuration/Factories/MSSqlServerAuditSinkFactoryTests.cs: -------------------------------------------------------------------------------- 1 | using Serilog.Sinks.MSSqlServer.Configuration.Factories; 2 | using Serilog.Sinks.MSSqlServer.Tests.TestUtils; 3 | using Xunit; 4 | 5 | namespace Serilog.Sinks.MSSqlServer.Tests.Configuration.Factories 6 | { 7 | [Trait(TestCategory.TraitName, TestCategory.Unit)] 8 | public class MSSqlServerAuditSinkFactoryTests 9 | { 10 | [Fact] 11 | public void MSSqlServerAuditSinkFactoryCreateReturnsInstance() 12 | { 13 | // Arrange 14 | var sinkOptions = new MSSqlServerSinkOptions { TableName = "TestTableName" }; 15 | var sut = new MSSqlServerAuditSinkFactory(); 16 | 17 | // Act 18 | var result = sut.Create(DatabaseFixture.LogEventsConnectionString, sinkOptions, null, new MSSqlServer.ColumnOptions(), null); 19 | 20 | // Assert 21 | Assert.IsType(result); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /test/Serilog.Sinks.MSSqlServer.Tests/Configuration/Factories/MSSqlServerSinkFactoryTests.cs: -------------------------------------------------------------------------------- 1 | using Serilog.Sinks.MSSqlServer.Configuration.Factories; 2 | using Serilog.Sinks.MSSqlServer.Tests.TestUtils; 3 | using Xunit; 4 | 5 | namespace Serilog.Sinks.MSSqlServer.Tests.Configuration.Factories 6 | { 7 | [Trait(TestCategory.TraitName, TestCategory.Unit)] 8 | public class MSSqlServerSinkFactoryTests 9 | { 10 | [Fact] 11 | public void MSSqlServerSinkFactoryCreateReturnsInstance() 12 | { 13 | // Arrange 14 | var sinkOptions = new MSSqlServerSinkOptions { TableName = "TestTableName" }; 15 | var sut = new MSSqlServerSinkFactory(); 16 | 17 | // Act 18 | var result = sut.Create(DatabaseFixture.LogEventsConnectionString, sinkOptions, null, new MSSqlServer.ColumnOptions(), null); 19 | 20 | // Assert 21 | Assert.IsType(result); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /test/Serilog.Sinks.MSSqlServer.Tests/Configuration/Factories/PeriodicBatchingSinkFactoryTests.cs: -------------------------------------------------------------------------------- 1 | using Moq; 2 | using Serilog.Sinks.MSSqlServer.Configuration.Factories; 3 | using Serilog.Sinks.MSSqlServer.Tests.TestUtils; 4 | using Serilog.Core; 5 | using Xunit; 6 | 7 | namespace Serilog.Sinks.MSSqlServer.Tests.Configuration.Factories 8 | { 9 | // BatchingSink is not public 10 | // temporarily removing this test 11 | 12 | //[Trait(TestCategory.TraitName, TestCategory.Unit)] 13 | //public class PeriodicBatchingSinkFactoryTests 14 | //{ 15 | // [Fact] 16 | // public void PeriodicBatchingSinkFactoryCreateReturnsInstance() 17 | // { 18 | // // Arrange 19 | // var sinkMock = new Mock(); 20 | // var sut = new PeriodicBatchingSinkFactory(); 21 | 22 | // // Act 23 | // var result = sut.Create(sinkMock.Object, new MSSqlServerSinkOptions()); 24 | 25 | // // Assert 26 | // Assert.IsType(result); 27 | // } 28 | //} 29 | } 30 | -------------------------------------------------------------------------------- /test/Serilog.Sinks.MSSqlServer.Tests/Configuration/Implementations/Microsoft.Extensions.Configuration/MicrosoftExtensionsConnectionStringProviderTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Configuration; 2 | using Moq; 3 | using Serilog.Sinks.MSSqlServer.Configuration; 4 | using Serilog.Sinks.MSSqlServer.Tests.TestUtils; 5 | using Xunit; 6 | 7 | namespace Serilog.Sinks.MSSqlServer.Tests.Configuration.Implementations.Microsoft.Extensions.Configuration 8 | { 9 | [Trait(TestCategory.TraitName, TestCategory.Unit)] 10 | public class MicrosoftExtensionsConnectionStringProviderTests 11 | { 12 | [Fact] 13 | public void GetConnectionStringCalledWithConnectionStringReturnsSameValue() 14 | { 15 | // Arrange 16 | const string connectionString = "Server=localhost;Database=LogTest;Integrated Security=SSPI;Encrypt=False;"; 17 | var configurationMock = new Mock(); 18 | var sut = new MicrosoftExtensionsConnectionStringProvider(); 19 | 20 | // Act 21 | var result = sut.GetConnectionString(connectionString, configurationMock.Object); 22 | 23 | // Assert 24 | Assert.Equal(connectionString, result); 25 | } 26 | 27 | [Fact] 28 | public void GetConnectionStringCalledWithNameItGetsConnectionStringFromConfig() 29 | { 30 | // Arrange 31 | const string connectionStringName = "LogDatabase"; 32 | const string connectionString = "Server=localhost;Database=LogTest;Integrated Security=SSPI;Encrypt=False;"; 33 | var configurationMock = new Mock(); 34 | var configSectionMock = new Mock(); 35 | configurationMock.Setup(c => c.GetSection(It.IsAny())).Returns(configSectionMock.Object); 36 | configSectionMock.Setup(c => c[It.IsAny()]).Returns(connectionString); 37 | var sut = new MicrosoftExtensionsConnectionStringProvider(); 38 | 39 | // Act 40 | var result = sut.GetConnectionString(connectionStringName, configurationMock.Object); 41 | 42 | // Assert 43 | configurationMock.Verify(c => c.GetSection("ConnectionStrings"), Times.Once); 44 | configSectionMock.Verify(c => c[connectionStringName], Times.Once); 45 | Assert.Equal(connectionString, result); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /test/Serilog.Sinks.MSSqlServer.Tests/Configuration/Implementations/System.Configuration/ApplySystemConfigurationTests.cs: -------------------------------------------------------------------------------- 1 | using Moq; 2 | using Serilog.Configuration; 3 | using Serilog.Sinks.MSSqlServer.Configuration; 4 | using Serilog.Sinks.MSSqlServer.Tests.TestUtils; 5 | using Xunit; 6 | 7 | namespace Serilog.Sinks.MSSqlServer.Tests.Configuration.Implementations.System.Configuration 8 | { 9 | [Trait(TestCategory.TraitName, TestCategory.Unit)] 10 | public class ApplySystemConfigurationTests 11 | { 12 | [Fact] 13 | public void GetConfigurationStringCallsAttachedConfigurationStringProvider() 14 | { 15 | // Arrange 16 | const string connectionStringName = "TestConnectionStringName"; 17 | const string expectedResult = "TestConnectionString"; 18 | var connectionStringProviderMock = new Mock(); 19 | connectionStringProviderMock.Setup(p => p.GetConnectionString(It.IsAny())).Returns(expectedResult); 20 | var sut = new ApplySystemConfiguration(connectionStringProviderMock.Object, null, null); 21 | 22 | // Act 23 | var result = sut.GetConnectionString(connectionStringName); 24 | 25 | // Assert 26 | connectionStringProviderMock.Verify(p => p.GetConnectionString(connectionStringName), Times.Once); 27 | Assert.Equal(expectedResult, result); 28 | } 29 | 30 | [Fact] 31 | public void ConfigureColumnOptionsCallsAttachedColumnOptionsProvider() 32 | { 33 | // Arrange 34 | var inputConfigSection = new MSSqlServerConfigurationSection(); 35 | var inputColumnOptions = new Serilog.Sinks.MSSqlServer.ColumnOptions(); 36 | var expectedResult = new Serilog.Sinks.MSSqlServer.ColumnOptions(); 37 | var columnOptionsProviderMock = new Mock(); 38 | columnOptionsProviderMock.Setup(p => p.ConfigureColumnOptions(It.IsAny(), It.IsAny())) 39 | .Returns(expectedResult); 40 | var sut = new ApplySystemConfiguration(null, columnOptionsProviderMock.Object, null); 41 | 42 | // Act 43 | var result = sut.ConfigureColumnOptions(inputConfigSection, inputColumnOptions); 44 | 45 | // Assert 46 | columnOptionsProviderMock.Verify(p => p.ConfigureColumnOptions(inputConfigSection, inputColumnOptions), Times.Once); 47 | Assert.Same(expectedResult, result); 48 | } 49 | 50 | [Fact] 51 | public void ConfigureSinkOptionsCallsAttachedSinkOptionsProvider() 52 | { 53 | // Arrange 54 | var inputConfigSection = new MSSqlServerConfigurationSection(); 55 | var inputSinkOptions = new MSSqlServerSinkOptions(); 56 | var expectedResult = new MSSqlServerSinkOptions(); 57 | var sinkOptionsProviderMock = new Mock(); 58 | sinkOptionsProviderMock.Setup(p => p.ConfigureSinkOptions(It.IsAny(), It.IsAny())) 59 | .Returns(expectedResult); 60 | var sut = new ApplySystemConfiguration(null, null, sinkOptionsProviderMock.Object); 61 | 62 | // Act 63 | var result = sut.ConfigureSinkOptions(inputConfigSection, inputSinkOptions); 64 | 65 | // Assert 66 | sinkOptionsProviderMock.Verify(p => p.ConfigureSinkOptions(inputConfigSection, inputSinkOptions), Times.Once); 67 | Assert.Same(expectedResult, result); 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /test/Serilog.Sinks.MSSqlServer.Tests/Configuration/Implementations/System.Configuration/StandardColumnConfigSpanIdTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Configuration; 3 | using Serilog.Sinks.MSSqlServer.Tests.TestUtils; 4 | using Xunit; 5 | 6 | namespace Serilog.Sinks.MSSqlServer.Tests.Configuration.Implementations.System.Configuration 7 | { 8 | [Trait(TestCategory.TraitName, TestCategory.Unit)] 9 | public class StandardColumnConfigSpanIdTests 10 | { 11 | [Fact] 12 | public void ClassSetsColumnNameRequiredAttributeToFalse() 13 | { 14 | var sut = typeof(StandardColumnConfigSpanId); 15 | var columNameProperty = sut.GetProperty("ColumnName"); 16 | var configurationPropertyAttribute = (ConfigurationPropertyAttribute) Attribute.GetCustomAttribute(columNameProperty, typeof(ConfigurationPropertyAttribute)); 17 | 18 | Assert.Equal("ColumnName", configurationPropertyAttribute.Name); 19 | Assert.False(configurationPropertyAttribute.IsRequired); 20 | } 21 | 22 | [Fact] 23 | public void ClassSetsColumnNameIsKeyAttributeToTrue() 24 | { 25 | var sut = typeof(StandardColumnConfigSpanId); 26 | var columNameProperty = sut.GetProperty("ColumnName"); 27 | var configurationPropertyAttribute = (ConfigurationPropertyAttribute)Attribute.GetCustomAttribute(columNameProperty, typeof(ConfigurationPropertyAttribute)); 28 | 29 | Assert.Equal("ColumnName", configurationPropertyAttribute.Name); 30 | Assert.True(configurationPropertyAttribute.IsKey); 31 | } 32 | 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /test/Serilog.Sinks.MSSqlServer.Tests/Configuration/Implementations/System.Configuration/StandardColumnConfigTraceIdTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Configuration; 3 | using Serilog.Sinks.MSSqlServer.Tests.TestUtils; 4 | using Xunit; 5 | 6 | namespace Serilog.Sinks.MSSqlServer.Tests.Configuration.Implementations.System.Configuration 7 | { 8 | [Trait(TestCategory.TraitName, TestCategory.Unit)] 9 | public class StandardColumnConfigTraceIdTests 10 | { 11 | [Fact] 12 | public void ClassSetsColumnNameRequiredAttributeToFalse() 13 | { 14 | // Arrange + act 15 | var sut = typeof(StandardColumnConfigTraceId); 16 | var columNameProperty = sut.GetProperty("ColumnName"); 17 | var configurationPropertyAttribute = (ConfigurationPropertyAttribute) Attribute.GetCustomAttribute(columNameProperty, typeof(ConfigurationPropertyAttribute)); 18 | 19 | // Assert 20 | Assert.Equal("ColumnName", configurationPropertyAttribute.Name); 21 | Assert.False(configurationPropertyAttribute.IsRequired); 22 | } 23 | 24 | [Fact] 25 | public void ClassSetsColumnNameIsKeyAttributeToTrue() 26 | { 27 | // Arrange + act 28 | var sut = typeof(StandardColumnConfigTraceId); 29 | var columNameProperty = sut.GetProperty("ColumnName"); 30 | var configurationPropertyAttribute = (ConfigurationPropertyAttribute)Attribute.GetCustomAttribute(columNameProperty, typeof(ConfigurationPropertyAttribute)); 31 | 32 | // Assert 33 | Assert.Equal("ColumnName", configurationPropertyAttribute.Name); 34 | Assert.True(configurationPropertyAttribute.IsKey); 35 | } 36 | 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /test/Serilog.Sinks.MSSqlServer.Tests/Extensions/StringExtensionsTests.cs: -------------------------------------------------------------------------------- 1 | using Serilog.Sinks.MSSqlServer.Extensions; 2 | using Serilog.Sinks.MSSqlServer.Tests.TestUtils; 3 | using Xunit; 4 | 5 | namespace Serilog.Sinks.MSSqlServer.Tests.Extensions 6 | { 7 | [Trait(TestCategory.TraitName, TestCategory.Unit)] 8 | public class StringExtensionsTests 9 | { 10 | [Fact] 11 | public void ReturnNullWhenInputStringIsNull() 12 | { 13 | // Arrange 14 | string inputMessage = null; 15 | 16 | // Act 17 | var nonTruncatedMessage = inputMessage.Truncate(5, "..."); 18 | 19 | // Assert 20 | Assert.Null(nonTruncatedMessage); 21 | } 22 | 23 | [Fact] 24 | public void ReturnEmptyWhenInputStringIsEmpty() 25 | { 26 | // Arrange 27 | var inputMessage = ""; 28 | 29 | // Act 30 | var nonTruncatedMessage = inputMessage.Truncate(5, "..."); 31 | 32 | // Assert 33 | Assert.Equal(inputMessage, nonTruncatedMessage); 34 | } 35 | 36 | [Theory] 37 | [InlineData(0)] 38 | [InlineData(-1)] 39 | [InlineData(-5)] 40 | public void ReturnEmptyStringWhenRequestedMaxValueIsZeroOrSmaller(int maxValue) 41 | { 42 | // Arrange 43 | var inputMessage = "A simple test message"; 44 | 45 | // Act 46 | var nonTruncatedMessage = inputMessage.Truncate(maxValue, "..."); 47 | 48 | // Assert 49 | Assert.Equal("", nonTruncatedMessage); 50 | } 51 | 52 | [Fact] 53 | public void ReturnTruncatedStringWithSuffix() 54 | { 55 | // Arrange 56 | var inputMessage = "A simple test message"; 57 | 58 | // Act 59 | var truncatedMessage = inputMessage.Truncate(15, "..."); 60 | 61 | // Assert 62 | Assert.Equal("A simple tes...", truncatedMessage); 63 | } 64 | 65 | [Theory] 66 | [InlineData("Abc")] 67 | [InlineData("Ab")] 68 | [InlineData("X")] 69 | [Trait("Bugfix", "#505")] 70 | public void ReturnNonTruncatedShortStringWhenMaxLengthIsLessOrEqualToSuffixLength(string inputMessage) 71 | { 72 | // Act 73 | var nonTruncatedMessage = inputMessage.Truncate(3, "..."); 74 | 75 | // Assert 76 | Assert.Equal(inputMessage, nonTruncatedMessage); 77 | } 78 | 79 | [Fact] 80 | public void ReturnTruncatedStringWithEmptySuffix() 81 | { 82 | // Arrange 83 | var inputMessage = "A simple test message"; 84 | 85 | // Act 86 | var truncatedMessage = inputMessage.Truncate(15, ""); 87 | 88 | // Assert 89 | Assert.Equal("A simple test m", truncatedMessage); 90 | } 91 | 92 | [Fact] 93 | public void ReturnTruncatedStringWithNullSuffix() 94 | { 95 | // Arrange 96 | var inputMessage = "A simple test message"; 97 | 98 | // Act 99 | var truncatedMessage = inputMessage.Truncate(15, null); 100 | 101 | // Assert 102 | Assert.Equal("A simple test m", truncatedMessage); 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /test/Serilog.Sinks.MSSqlServer.Tests/GlobalSuppressions.cs: -------------------------------------------------------------------------------- 1 | // This file is used by Code Analysis to maintain SuppressMessage 2 | // attributes that are applied to this project. 3 | // Project-level suppressions either have no target or are given 4 | // a specific target and scoped to a namespace, type, member, etc. 5 | 6 | using System.Diagnostics.CodeAnalysis; 7 | 8 | [assembly: SuppressMessage("Globalization", "CA1303:Do not pass literals as localized parameters", Justification = "Supplying string literals and not using resources is accepted within this project.", Scope = "namespaceanddescendants", Target = "Serilog.Sinks.MSSqlServer.Tests")] 9 | [assembly: SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores", Justification = "Member names must match SQL server master DB objects.", Scope = "type", Target = "~T:Serilog.Sinks.MSSqlServer.Tests.TestUtils.sp_pkey")] 10 | [assembly: SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores", Justification = "Member names must match SQL server master DB objects.", Scope = "type", Target = "~T:Serilog.Sinks.MSSqlServer.Tests.TestUtils.SysIndex_CCI")] 11 | -------------------------------------------------------------------------------- /test/Serilog.Sinks.MSSqlServer.Tests/Misc/OpenTelemetryColumnsTests.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using System.Globalization; 3 | using Serilog.Sinks.MSSqlServer.Tests.TestUtils; 4 | using Xunit; 5 | using Xunit.Abstractions; 6 | 7 | namespace Serilog.Sinks.MSSqlServer.Tests.Misc 8 | { 9 | [Trait(TestCategory.TraitName, TestCategory.Integration)] 10 | public class OpenTelemetryColumnsTests : DatabaseTestsBase 11 | { 12 | public OpenTelemetryColumnsTests(ITestOutputHelper output) : base(output) 13 | { 14 | } 15 | 16 | [Fact] 17 | public void OpenTelemetryActivityTraceIdAndSpanIdAreStoredInColumns() 18 | { 19 | // Arrange 20 | var expectedTraceId = string.Empty; 21 | var expectedSpanId = string.Empty; 22 | var columnOptions = new MSSqlServer.ColumnOptions(); 23 | columnOptions.Store.Add(StandardColumn.TraceId); 24 | columnOptions.Store.Add(StandardColumn.SpanId); 25 | 26 | Log.Logger = new LoggerConfiguration() 27 | .WriteTo.MSSqlServer 28 | ( 29 | connectionString: DatabaseFixture.LogEventsConnectionString, 30 | new MSSqlServerSinkOptions 31 | { 32 | TableName = DatabaseFixture.LogTableName, 33 | AutoCreateSqlTable = true 34 | }, 35 | columnOptions: columnOptions, 36 | formatProvider: CultureInfo.InvariantCulture 37 | ) 38 | .CreateLogger(); 39 | 40 | // Act 41 | using (var testActivity = new Activity("OpenTelemetryColumnsTests")) 42 | { 43 | testActivity.SetIdFormat(ActivityIdFormat.W3C); 44 | testActivity.Start(); 45 | expectedTraceId = testActivity.TraceId.ToString(); 46 | expectedSpanId = testActivity.SpanId.ToString(); 47 | 48 | 49 | Log.Logger.Information("Logging message"); 50 | Log.CloseAndFlush(); 51 | } 52 | 53 | // Assert 54 | VerifyStringColumnWritten("TraceId", expectedTraceId); 55 | VerifyStringColumnWritten("SpanId", expectedSpanId); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /test/Serilog.Sinks.MSSqlServer.Tests/Misc/PropertiesColumnFilteringTests.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | using FluentAssertions; 3 | using Serilog.Sinks.MSSqlServer.Tests.TestUtils; 4 | using Xunit; 5 | using Xunit.Abstractions; 6 | 7 | namespace Serilog.Sinks.MSSqlServer.Tests.Misc 8 | { 9 | [Trait(TestCategory.TraitName, TestCategory.Integration)] 10 | public class PropertiesColumnFilteringTests : DatabaseTestsBase 11 | { 12 | public PropertiesColumnFilteringTests(ITestOutputHelper output) : base(output) 13 | { 14 | } 15 | 16 | [Fact] 17 | public void FilteredProperties() 18 | { 19 | // Arrange 20 | var columnOptions = new MSSqlServer.ColumnOptions(); 21 | columnOptions.Properties.PropertiesFilter = (propName) => propName == "A"; 22 | 23 | Log.Logger = new LoggerConfiguration() 24 | .WriteTo.MSSqlServer 25 | ( 26 | connectionString: DatabaseFixture.LogEventsConnectionString, 27 | new MSSqlServerSinkOptions 28 | { 29 | TableName = DatabaseFixture.LogTableName, 30 | AutoCreateSqlTable = true, 31 | }, 32 | columnOptions: columnOptions, 33 | formatProvider: CultureInfo.InvariantCulture 34 | ) 35 | .CreateLogger(); 36 | 37 | // Act 38 | Log.Logger 39 | .ForContext("A", "AValue") 40 | .ForContext("B", "BValue") 41 | .Information("Logging message"); 42 | 43 | Log.CloseAndFlush(); 44 | 45 | // Assert 46 | VerifyCustomQuery($"SELECT Properties from {DatabaseFixture.LogTableName}", 47 | e => e.Should().Contain(l => l.Properties.Contains("AValue"))); 48 | VerifyCustomQuery($"SELECT Properties from {DatabaseFixture.LogTableName}", 49 | e => e.Should().NotContain(l => l.Properties.Contains("BValue"))); 50 | } 51 | 52 | [Fact] 53 | public void FilteredPropertiesWhenAuditing() 54 | { 55 | // Arrange 56 | var columnOptions = new MSSqlServer.ColumnOptions(); 57 | columnOptions.Properties.PropertiesFilter = (propName) => propName == "A"; 58 | 59 | Log.Logger = new LoggerConfiguration() 60 | .AuditTo.MSSqlServer 61 | ( 62 | connectionString: DatabaseFixture.LogEventsConnectionString, 63 | new MSSqlServerSinkOptions 64 | { 65 | TableName = DatabaseFixture.LogTableName, 66 | AutoCreateSqlTable = true 67 | }, 68 | columnOptions: columnOptions, 69 | formatProvider: CultureInfo.InvariantCulture 70 | ) 71 | .CreateLogger(); 72 | 73 | // Act 74 | Log.Logger 75 | .ForContext("A", "AValue") 76 | .ForContext("B", "BValue") 77 | .Information("Logging message"); 78 | 79 | Log.CloseAndFlush(); 80 | 81 | // Assert 82 | VerifyCustomQuery($"SELECT Properties from {DatabaseFixture.LogTableName}", 83 | e => e.Should().Contain(l => l.Properties.Contains("AValue"))); 84 | VerifyCustomQuery($"SELECT Properties from {DatabaseFixture.LogTableName}", 85 | e => e.Should().NotContain(l => l.Properties.Contains("BValue"))); 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /test/Serilog.Sinks.MSSqlServer.Tests/Misc/SqlBulkCopyTests.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | using Serilog.Sinks.MSSqlServer.Tests.TestUtils; 3 | using Xunit; 4 | using Xunit.Abstractions; 5 | 6 | namespace Serilog.Sinks.MSSqlServer.Tests.Misc 7 | { 8 | [Trait(TestCategory.TraitName, TestCategory.Integration)] 9 | public class SqlBulkCopyTests : DatabaseTestsBase 10 | { 11 | public SqlBulkCopyTests(ITestOutputHelper output) : base(output) 12 | { 13 | } 14 | 15 | [Fact] 16 | public void UseSqlBulkCopySetToTrue() 17 | { 18 | // Arrange 19 | Log.Logger = new LoggerConfiguration() 20 | .WriteTo.MSSqlServer 21 | ( 22 | connectionString: DatabaseFixture.LogEventsConnectionString, 23 | new MSSqlServerSinkOptions 24 | { 25 | TableName = DatabaseFixture.LogTableName, 26 | AutoCreateSqlTable = true, 27 | UseSqlBulkCopy = true 28 | }, 29 | formatProvider: CultureInfo.InvariantCulture 30 | ) 31 | .CreateLogger(); 32 | 33 | // Act 34 | Log.Logger.Information("Logging message 1"); 35 | Log.Logger.Information("Logging message 2"); 36 | Log.CloseAndFlush(); 37 | 38 | // Assert 39 | VerifyLogMessageWasWritten("Logging message 1"); 40 | VerifyLogMessageWasWritten("Logging message 2"); 41 | } 42 | 43 | [Fact] 44 | public void UseSqlBulkCopySetToFalse() 45 | { 46 | // Arrange 47 | Log.Logger = new LoggerConfiguration() 48 | .WriteTo.MSSqlServer 49 | ( 50 | connectionString: DatabaseFixture.LogEventsConnectionString, 51 | new MSSqlServerSinkOptions 52 | { 53 | TableName = DatabaseFixture.LogTableName, 54 | AutoCreateSqlTable = true, 55 | UseSqlBulkCopy = false 56 | }, 57 | formatProvider: CultureInfo.InvariantCulture 58 | ) 59 | .CreateLogger(); 60 | 61 | // Act 62 | Log.Logger.Information("Logging message 1"); 63 | Log.Logger.Information("Logging message 2"); 64 | Log.CloseAndFlush(); 65 | 66 | // Assert 67 | VerifyLogMessageWasWritten("Logging message 1"); 68 | VerifyLogMessageWasWritten("Logging message 2"); 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /test/Serilog.Sinks.MSSqlServer.Tests/Misc/StructuredSubType.cs: -------------------------------------------------------------------------------- 1 | namespace Serilog.Sinks.MSSqlServer.Tests.Misc 2 | { 3 | internal class StructuredSubType 4 | { 5 | public int SubSubProperty1 { get; set; } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /test/Serilog.Sinks.MSSqlServer.Tests/Misc/StructuredType.cs: -------------------------------------------------------------------------------- 1 | namespace Serilog.Sinks.MSSqlServer.Tests.Misc 2 | { 3 | internal class StructuredType 4 | { 5 | public string SubProperty1 { get; set; } 6 | public StructuredSubType SubProperty2 { get; set; } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /test/Serilog.Sinks.MSSqlServer.Tests/Misc/TransactionTests.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | using System.Transactions; 3 | using FluentAssertions; 4 | using Serilog.Sinks.MSSqlServer.Tests.TestUtils; 5 | using Xunit; 6 | using Xunit.Abstractions; 7 | 8 | namespace Serilog.Sinks.MSSqlServer.Tests.Misc 9 | { 10 | [Trait(TestCategory.TraitName, TestCategory.Integration)] 11 | public class TransactionTests : DatabaseTestsBase 12 | { 13 | public TransactionTests(ITestOutputHelper output) : base(output) 14 | { 15 | } 16 | 17 | [Fact] 18 | public void LogsAreNotAffectedByTransactionsByDefault() 19 | { 20 | // Arrange 21 | Log.Logger = new LoggerConfiguration() 22 | .WriteTo.MSSqlServer 23 | ( 24 | connectionString: DatabaseFixture.LogEventsConnectionString, 25 | new MSSqlServerSinkOptions 26 | { 27 | TableName = DatabaseFixture.LogTableName, 28 | AutoCreateSqlTable = true 29 | }, 30 | formatProvider: CultureInfo.InvariantCulture 31 | ) 32 | .CreateLogger(); 33 | 34 | using (var scope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled)) 35 | { 36 | // Act 37 | Log.Logger.Information("Logging message"); 38 | 39 | // Flush message so it is written on foreground thread instead of timer 40 | // So we can test if it is affected by transaction 41 | Log.CloseAndFlush(); 42 | } 43 | 44 | // Assert after rollback, the message should still be persisted 45 | VerifyCustomQuery($"SELECT Id from {DatabaseFixture.LogTableName}", 46 | e => e.Should().HaveCount(1)); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /test/Serilog.Sinks.MSSqlServer.Tests/Serilog.Sinks.MSSqlServer.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net462;net472;net8.0 5 | true 6 | Serilog.Sinks.MSSqlServer.Tests 7 | ../../assets/Serilog.snk 8 | true 9 | true 10 | Serilog.Sinks.MSSqlServer.Tests 11 | true 12 | true 13 | AnyCPU 14 | 6.0-recommended 15 | True 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | runtime; build; native; contentfiles; analyzers; buildtransitive 50 | all 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | Always 60 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /test/Serilog.Sinks.MSSqlServer.Tests/Sinks/MSSqlServer/ColumnOptions/ColumnOptionsTests.cs: -------------------------------------------------------------------------------- 1 | using Serilog.Sinks.MSSqlServer.Tests.TestUtils; 2 | using Xunit; 3 | 4 | namespace Serilog.Sinks.MSSqlServer.Tests.ColumnOptions 5 | { 6 | [Trait(TestCategory.TraitName, TestCategory.Unit)] 7 | public class ColumnOptionsTests 8 | { 9 | [Fact] 10 | public void GetStandardColumnOptionsReturnsTraceIdOptions() 11 | { 12 | // Arrange 13 | var sut = new MSSqlServer.ColumnOptions(); 14 | 15 | // Act 16 | var result = sut.GetStandardColumnOptions(StandardColumn.TraceId); 17 | 18 | // Assert 19 | Assert.Same(sut.TraceId, result); 20 | } 21 | 22 | [Fact] 23 | public void GetStandardColumnOptionsReturnsSpanIdOptions() 24 | { 25 | // Arrange 26 | var sut = new MSSqlServer.ColumnOptions(); 27 | 28 | // Act 29 | var result = sut.GetStandardColumnOptions(StandardColumn.SpanId); 30 | 31 | // Assert 32 | Assert.Same(sut.SpanId, result); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /test/Serilog.Sinks.MSSqlServer.Tests/Sinks/MSSqlServer/ColumnOptions/SpanIdColumnOptionsTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data; 3 | using Serilog.Sinks.MSSqlServer.Tests.TestUtils; 4 | using Xunit; 5 | 6 | namespace Serilog.Sinks.MSSqlServer.Tests.ColumnOptions 7 | { 8 | [Trait(TestCategory.TraitName, TestCategory.Unit)] 9 | public class SpanIdColumnOptionsTests 10 | { 11 | [Fact] 12 | public void CanSetDataTypeNVarChar() 13 | { 14 | // Arrange 15 | var options = new MSSqlServer.ColumnOptions(); 16 | 17 | // Act - should not throw 18 | options.SpanId.DataType = SqlDbType.NVarChar; 19 | } 20 | 21 | [Fact] 22 | public void CanSetDataTypeVarChar() 23 | { 24 | // Arrange 25 | var options = new MSSqlServer.ColumnOptions(); 26 | 27 | // Act - should not throw 28 | options.SpanId.DataType = SqlDbType.VarChar; 29 | } 30 | 31 | [Fact] 32 | public void CannotSetDataTypeBigInt() 33 | { 34 | // Arrange 35 | var options = new MSSqlServer.ColumnOptions(); 36 | 37 | // Act and assert - should throw 38 | Assert.Throws(() => options.SpanId.DataType = SqlDbType.BigInt); 39 | } 40 | 41 | [Fact] 42 | public void CannotSetDataTypeNChar() 43 | { 44 | // Arrange 45 | var options = new MSSqlServer.ColumnOptions(); 46 | 47 | // Act and assert - should throw 48 | Assert.Throws(() => options.SpanId.DataType = SqlDbType.NChar); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /test/Serilog.Sinks.MSSqlServer.Tests/Sinks/MSSqlServer/ColumnOptions/TimeStampColumnOptionsTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data; 3 | using Serilog.Sinks.MSSqlServer.Tests.TestUtils; 4 | using Xunit; 5 | 6 | namespace Serilog.Sinks.MSSqlServer.Tests.ColumnOptions 7 | { 8 | [Trait(TestCategory.TraitName, TestCategory.Unit)] 9 | public class TimeStampColumnOptionsTests 10 | { 11 | [Trait("Bugfix", "#187")] 12 | [Fact] 13 | public void CanSetDataTypeDateTime() 14 | { 15 | // Arrange 16 | var options = new Serilog.Sinks.MSSqlServer.ColumnOptions(); 17 | 18 | // Act - should not throw 19 | options.TimeStamp.DataType = SqlDbType.DateTime; 20 | } 21 | 22 | [Trait("Bugfix", "#187")] 23 | [Fact] 24 | public void CanSetDataTypeDateTimeOffset() 25 | { 26 | // Arrange 27 | var options = new Serilog.Sinks.MSSqlServer.ColumnOptions(); 28 | 29 | // Act - should not throw 30 | options.TimeStamp.DataType = SqlDbType.DateTimeOffset; 31 | } 32 | 33 | [Trait("Bugfix", "#187")] 34 | [Fact] 35 | public void CannotSetDataTypeNVarChar() 36 | { 37 | // Arrange 38 | var options = new Serilog.Sinks.MSSqlServer.ColumnOptions(); 39 | 40 | // Act and assert - should throw 41 | Assert.Throws(() => options.TimeStamp.DataType = SqlDbType.NVarChar); 42 | } 43 | 44 | [Trait("Feature", "#300")] 45 | [Fact] 46 | public void CanSetDataTypeDateTime2() 47 | { 48 | // Arrange 49 | var options = new Serilog.Sinks.MSSqlServer.ColumnOptions(); 50 | 51 | // Act - should not throw 52 | options.TimeStamp.DataType = SqlDbType.DateTime2; 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /test/Serilog.Sinks.MSSqlServer.Tests/Sinks/MSSqlServer/ColumnOptions/TraceIdColumnOptionsTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data; 3 | using Serilog.Sinks.MSSqlServer.Tests.TestUtils; 4 | using Xunit; 5 | 6 | namespace Serilog.Sinks.MSSqlServer.Tests.ColumnOptions 7 | { 8 | [Trait(TestCategory.TraitName, TestCategory.Unit)] 9 | public class TraceIdColumnOptionsTests 10 | { 11 | [Fact] 12 | public void CanSetDataTypeNVarChar() 13 | { 14 | // Arrange 15 | var options = new MSSqlServer.ColumnOptions(); 16 | 17 | // Act - should not throw 18 | options.TraceId.DataType = SqlDbType.NVarChar; 19 | } 20 | 21 | [Fact] 22 | public void CanSetDataTypeVarChar() 23 | { 24 | // Arrange 25 | var options = new MSSqlServer.ColumnOptions(); 26 | 27 | // Act - should not throw 28 | options.TraceId.DataType = SqlDbType.VarChar; 29 | } 30 | 31 | [Fact] 32 | public void CannotSetDataTypeBigInt() 33 | { 34 | // Arrange 35 | var options = new MSSqlServer.ColumnOptions(); 36 | 37 | // Act and assert - should throw 38 | Assert.Throws(() => options.TraceId.DataType = SqlDbType.BigInt); 39 | } 40 | 41 | [Fact] 42 | public void CannotSetDataTypeNChar() 43 | { 44 | // Arrange 45 | var options = new MSSqlServer.ColumnOptions(); 46 | 47 | // Act and assert - should throw 48 | Assert.Throws(() => options.TraceId.DataType = SqlDbType.NChar); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /test/Serilog.Sinks.MSSqlServer.Tests/Sinks/MSSqlServer/Dependencies/SinkDependenciesFactoryTests.cs: -------------------------------------------------------------------------------- 1 | using Moq; 2 | using System; 3 | using Serilog.Sinks.MSSqlServer.Dependencies; 4 | using Serilog.Sinks.MSSqlServer.Platform; 5 | using Serilog.Sinks.MSSqlServer.Tests.TestUtils; 6 | using Xunit; 7 | using Microsoft.Data.SqlClient; 8 | 9 | namespace Serilog.Sinks.MSSqlServer.Tests.Dependencies 10 | { 11 | [Trait(TestCategory.TraitName, TestCategory.Unit)] 12 | public class SinkDependenciesFactoryTests 13 | { 14 | private const string _connectionString = "Server=localhost;Database=LogTest;Integrated Security=SSPI;Encrypt=False;"; 15 | private readonly MSSqlServerSinkOptions _sinkOptions; 16 | private readonly MSSqlServer.ColumnOptions _columnOptions; 17 | 18 | public SinkDependenciesFactoryTests() 19 | { 20 | _sinkOptions = new MSSqlServerSinkOptions { TableName = "LogEvents" }; 21 | _columnOptions = new MSSqlServer.ColumnOptions(); 22 | } 23 | 24 | [Fact] 25 | public void CreatesSinkDependenciesWithSqlDatabaseCreator() 26 | { 27 | // Act 28 | var result = SinkDependenciesFactory.Create(_connectionString, _sinkOptions, null, _columnOptions, null); 29 | 30 | // Assert 31 | Assert.NotNull(result.SqlDatabaseCreator); 32 | Assert.IsType(result.SqlDatabaseCreator); 33 | } 34 | 35 | [Fact] 36 | public void CreatesSinkDependenciesWithSqlTableCreator() 37 | { 38 | // Act 39 | var result = SinkDependenciesFactory.Create(_connectionString, _sinkOptions, null, _columnOptions, null); 40 | 41 | // Assert 42 | Assert.NotNull(result.SqlTableCreator); 43 | Assert.IsType(result.SqlTableCreator); 44 | } 45 | 46 | [Fact] 47 | public void CreatesSinkDependenciesWithSqlBulkBatchWriter() 48 | { 49 | // Act 50 | var result = SinkDependenciesFactory.Create(_connectionString, _sinkOptions, null, _columnOptions, null); 51 | 52 | // Assert 53 | Assert.NotNull(result.SqlBulkBatchWriter); 54 | Assert.IsType(result.SqlBulkBatchWriter); 55 | } 56 | 57 | [Fact] 58 | public void CreatesSinkDependenciesWithSqlLogEventWriter() 59 | { 60 | // Act 61 | var result = SinkDependenciesFactory.Create(_connectionString, _sinkOptions, null, _columnOptions, null); 62 | 63 | // Assert 64 | Assert.NotNull(result.SqlLogEventWriter); 65 | Assert.IsType(result.SqlLogEventWriter); 66 | } 67 | 68 | [Fact] 69 | public void DefaultsColumnOptionsIfNull() 70 | { 71 | // Act (should not throw) 72 | SinkDependenciesFactory.Create(_connectionString, _sinkOptions, null, null, null); 73 | } 74 | 75 | [Fact] 76 | public void CreatesSinkDependenciesWithSqlConnectionConfiguration() 77 | { 78 | // Arrange 79 | var mockConfigurationAction = new Mock>(); 80 | var sinkOptions = new MSSqlServerSinkOptions { TableName = "LogEvents", ConnectionConfiguration = mockConfigurationAction.Object }; 81 | 82 | // Act 83 | var result = SinkDependenciesFactory.Create(_connectionString, sinkOptions, null, _columnOptions, null); 84 | 85 | // Assert 86 | Assert.NotNull(result.SqlDatabaseCreator); 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /test/Serilog.Sinks.MSSqlServer.Tests/Sinks/MSSqlServer/MSSqlServerSinkOptionsTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Serilog.Sinks.MSSqlServer.Tests.TestUtils; 3 | using Xunit; 4 | 5 | namespace Serilog.Sinks.MSSqlServer.Tests 6 | { 7 | [Trait(TestCategory.TraitName, TestCategory.Unit)] 8 | public class MSSqlServerSinkOptionsTests 9 | { 10 | [Fact] 11 | public void InitializesDefaultedPropertiesWithDefaultsWhenCalledWithoutParameters() 12 | { 13 | // Act 14 | var sut = new MSSqlServerSinkOptions(); 15 | 16 | // Assert 17 | Assert.Equal(MSSqlServerSink.DefaultSchemaName, sut.SchemaName); 18 | Assert.Equal(MSSqlServerSink.DefaultBatchPostingLimit, sut.BatchPostingLimit); 19 | Assert.Equal(MSSqlServerSink.DefaultPeriod, sut.BatchPeriod); 20 | } 21 | 22 | [Fact] 23 | public void InitializesDefaultedPropertiesWithDefaultsWhenCalledWithParameters() 24 | { 25 | // Act 26 | var sut = new MSSqlServerSinkOptions("TestTableName", null, null, true, null); 27 | 28 | // Assert 29 | Assert.Equal(MSSqlServerSink.DefaultSchemaName, sut.SchemaName); 30 | Assert.Equal(MSSqlServerSink.DefaultBatchPostingLimit, sut.BatchPostingLimit); 31 | Assert.Equal(MSSqlServerSink.DefaultPeriod, sut.BatchPeriod); 32 | } 33 | 34 | [Fact] 35 | public void InitializesPropertiesWithParameterValues() 36 | { 37 | // Arrange 38 | const string tableName = "TestTableName"; 39 | const int batchPostingLimit = 23; 40 | const string schemaName = "TestSchemaName"; 41 | var batchPeriod = new TimeSpan(0, 3, 23); 42 | 43 | // Act 44 | var sut = new MSSqlServerSinkOptions(tableName, batchPostingLimit, batchPeriod, true, schemaName); 45 | 46 | // Assert 47 | Assert.Equal(tableName, sut.TableName); 48 | Assert.Equal(batchPostingLimit, sut.BatchPostingLimit); 49 | Assert.Equal(batchPeriod, sut.BatchPeriod); 50 | Assert.True(sut.AutoCreateSqlTable); 51 | Assert.Equal(schemaName, sut.SchemaName); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /test/Serilog.Sinks.MSSqlServer.Tests/Sinks/MSSqlServer/Options/SinkOptionsTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Serilog.Sinks.MSSqlServer.Tests.TestUtils; 3 | using Xunit; 4 | 5 | namespace Serilog.Sinks.MSSqlServer.Tests.Options 6 | { 7 | [Obsolete("Backwards compatibility tests for old SinkOptions class", error: false)] 8 | [Trait(TestCategory.TraitName, TestCategory.Unit)] 9 | public class SinkOptionsTests 10 | { 11 | [Fact] 12 | public void InitializesDefaultedPropertiesWithDefaultsWhenCalledWithoutParameters() 13 | { 14 | // Act 15 | var sut = new Serilog.Sinks.MSSqlServer.Sinks.MSSqlServer.Options.SinkOptions(); 16 | 17 | // Assert 18 | Assert.Equal(MSSqlServerSink.DefaultSchemaName, sut.SchemaName); 19 | Assert.Equal(MSSqlServerSink.DefaultBatchPostingLimit, sut.BatchPostingLimit); 20 | Assert.Equal(MSSqlServerSink.DefaultPeriod, sut.BatchPeriod); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /test/Serilog.Sinks.MSSqlServer.Tests/Sinks/MSSqlServer/Output/ColumnSimplePropertyValueResolverTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Data; 3 | using Serilog.Events; 4 | using Serilog.Sinks.MSSqlServer.Output; 5 | using Serilog.Sinks.MSSqlServer.Tests.TestUtils; 6 | using Xunit; 7 | 8 | namespace Serilog.Sinks.MSSqlServer.Tests.Output 9 | { 10 | [Trait(TestCategory.TraitName, TestCategory.Unit)] 11 | public class ColumnSimplePropertyValueResolverTests 12 | { 13 | private readonly Dictionary _properties; 14 | private readonly ColumnSimplePropertyValueResolver _sut; 15 | 16 | public ColumnSimplePropertyValueResolverTests() 17 | { 18 | _properties = new Dictionary(); 19 | _sut = new ColumnSimplePropertyValueResolver(); 20 | } 21 | 22 | [Fact] 23 | public void GetPropertyValueForColumnDefaultIfPropertyNotFound() 24 | { 25 | // Arrange 26 | _properties.Add("Property1", new ScalarValue("Value1")); 27 | _properties.Add("Property2", new ScalarValue("Value2")); 28 | _properties.Add("Property3", new ScalarValue("Value3")); 29 | 30 | // Act 31 | var result = _sut.GetPropertyValueForColumn(new SqlColumn("NotFoundProperty", SqlDbType.NVarChar), _properties); 32 | 33 | // Assert 34 | Assert.Equal(default, result); 35 | } 36 | 37 | [Fact] 38 | public void GetPropertyValueForColumnReturnsPropertyValue() 39 | { 40 | // Arrange 41 | const string property2Key = "Property2"; 42 | _properties.Add("Property1", new ScalarValue("Value1")); 43 | _properties.Add(property2Key, new ScalarValue("Value2")); 44 | _properties.Add("Property3", new ScalarValue("Value3")); 45 | 46 | // Act 47 | var result = _sut.GetPropertyValueForColumn(new SqlColumn(property2Key, SqlDbType.NVarChar), _properties); 48 | 49 | // Assert 50 | Assert.Equal(property2Key, result.Key); 51 | Assert.Equal(_properties[property2Key], result.Value); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /test/Serilog.Sinks.MSSqlServer.Tests/Sinks/MSSqlServer/Platform/SqlClient/SqlBulkCopyWrapperTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.Data.SqlClient; 3 | using Xunit; 4 | using Serilog.Sinks.MSSqlServer.Platform.SqlClient; 5 | using Serilog.Sinks.MSSqlServer.Tests.TestUtils; 6 | 7 | namespace Serilog.Sinks.MSSqlServer.Tests.Platform.SqlClient 8 | { 9 | [Trait(TestCategory.TraitName, TestCategory.Unit)] 10 | public class SqlBulkCopyWrapperTests 11 | { 12 | [Fact] 13 | public void InitializeThrowsIfSqlBulkCopyIsNull() 14 | { 15 | Assert.Throws(() => new SqlBulkCopyWrapper(null)); 16 | } 17 | 18 | [Fact] 19 | public void AddSqlBulkCopyColumnMappingDoesNotThrow() 20 | { 21 | // Arrange 22 | using (var connection = new SqlConnection()) 23 | { 24 | using (var sqlBulkCopy = new SqlBulkCopy(connection)) 25 | { 26 | using (var sut = new SqlBulkCopyWrapper(sqlBulkCopy)) 27 | { 28 | // Act (should not throw) 29 | sut.AddSqlBulkCopyColumnMapping("Column", "Column"); 30 | } 31 | } 32 | } 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /test/Serilog.Sinks.MSSqlServer.Tests/Sinks/MSSqlServer/Platform/SqlClient/SqlCommandWrapperTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.Data.SqlClient; 3 | using Xunit; 4 | using Serilog.Sinks.MSSqlServer.Tests.TestUtils; 5 | using Serilog.Sinks.MSSqlServer.Platform.SqlClient; 6 | 7 | namespace Serilog.Sinks.MSSqlServer.Tests.Platform.SqlClient 8 | { 9 | [Trait(TestCategory.TraitName, TestCategory.Unit)] 10 | public class SqlCommandWrapperTests 11 | { 12 | [Fact] 13 | public void InitializeThrowsIfSqlCommandIsNull() 14 | { 15 | // Arrange + act 16 | Assert.Throws(() => new SqlCommandWrapper(null)); 17 | } 18 | 19 | [Fact] 20 | public void AddParameterDoesNotThrow() 21 | { 22 | // Arrange 23 | using (var sqlConnection = new SqlConnection()) 24 | { 25 | using (var sqlCommand = new SqlCommand("SELECT * FROM Table WHERE Id = @Parameter", sqlConnection)) 26 | { 27 | using (var sut = new SqlCommandWrapper(sqlCommand)) 28 | { 29 | // Act (should not throw) 30 | sut.AddParameter("Parameter", "Value"); 31 | } 32 | } 33 | } 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /test/Serilog.Sinks.MSSqlServer.Tests/Sinks/MSSqlServer/Platform/SqlClient/SqlConnectionStringBuilderWrapperTests.cs: -------------------------------------------------------------------------------- 1 | using Xunit; 2 | using Serilog.Sinks.MSSqlServer.Platform.SqlClient; 3 | using Serilog.Sinks.MSSqlServer.Tests.TestUtils; 4 | 5 | namespace Serilog.Sinks.MSSqlServer.Tests.Platform.SqlClient 6 | { 7 | [Trait(TestCategory.TraitName, TestCategory.Unit)] 8 | public class SqlConnectionStringBuilderWrapperTests 9 | { 10 | [Fact] 11 | public void ChangeEnlistFalseToTrueIfEnlistPropertyIsSetToTrue() 12 | { 13 | // Arrange + act 14 | var sut = new SqlConnectionStringBuilderWrapper(DatabaseFixture.LogEventsConnectionString + ";Enlist=False", true); 15 | 16 | // Assert 17 | Assert.Equal(DatabaseFixture.LogEventsConnectionString + ";Enlist=True", sut.ConnectionString); 18 | } 19 | 20 | [Fact] 21 | public void ChangeEnlistTrueToFalseIfEnlistPropertyIsSetToFalse() 22 | { 23 | // Arrange 24 | var sut = new SqlConnectionStringBuilderWrapper(DatabaseFixture.LogEventsConnectionString + ";Enlist=True", false); 25 | 26 | // Assert 27 | Assert.Equal(DatabaseFixture.LogEventsConnectionString + ";Enlist=False", sut.ConnectionString); 28 | } 29 | 30 | [Fact] 31 | public void AddsEnlistFalseIfEnlistPropertySetToFalse() 32 | { 33 | // Arrange 34 | var sut = new SqlConnectionStringBuilderWrapper(DatabaseFixture.LogEventsConnectionString, false); 35 | 36 | // Assert 37 | Assert.Equal(DatabaseFixture.LogEventsConnectionString + ";Enlist=False", sut.ConnectionString); 38 | } 39 | 40 | [Fact] 41 | public void AddsEnlistTrueIfEnlistPropertySetToTrue() 42 | { 43 | // Arrange 44 | var sut = new SqlConnectionStringBuilderWrapper(DatabaseFixture.LogEventsConnectionString, true); 45 | 46 | // Assert 47 | Assert.Equal(DatabaseFixture.LogEventsConnectionString + ";Enlist=True", sut.ConnectionString); 48 | } 49 | 50 | [Fact] 51 | public void DoesNotDuplicateEnlistIfEnlistFalseIsPresentAndEnlistPropertySetToFalse() 52 | { 53 | // Arrange 54 | var sut = new SqlConnectionStringBuilderWrapper("Enlist = false ; " + DatabaseFixture.LogEventsConnectionString, false); 55 | 56 | // Assert 57 | Assert.Equal(DatabaseFixture.LogEventsConnectionString + ";Enlist=False", sut.ConnectionString); 58 | } 59 | 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /test/Serilog.Sinks.MSSqlServer.Tests/Sinks/MSSqlServer/Platform/SqlClient/SqlConnectionWrapperTests.cs: -------------------------------------------------------------------------------- 1 | using Xunit; 2 | using Serilog.Sinks.MSSqlServer.Tests.TestUtils; 3 | using Serilog.Sinks.MSSqlServer.Platform.SqlClient; 4 | 5 | namespace Serilog.Sinks.MSSqlServer.Tests.Platform.SqlClient 6 | { 7 | [Trait(TestCategory.TraitName, TestCategory.Unit)] 8 | public class SqlConnectionWrapperTests 9 | { 10 | [Fact] 11 | public void CreateSqlBulkCopyReturnsSqlBulkCopyWrapper() 12 | { 13 | // Arrange 14 | using (var sut = new SqlConnectionWrapper(DatabaseFixture.LogEventsConnectionString)) 15 | { 16 | // Act 17 | var result = sut.CreateSqlBulkCopy(false, "DestinationTableName"); 18 | 19 | // Assert 20 | Assert.NotNull(result); 21 | } 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /test/Serilog.Sinks.MSSqlServer.Tests/Sinks/MSSqlServer/Platform/SqlConnectionFactoryTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.Data.SqlClient; 3 | using Moq; 4 | using Serilog.Sinks.MSSqlServer.Platform; 5 | using Serilog.Sinks.MSSqlServer.Platform.SqlClient; 6 | using Serilog.Sinks.MSSqlServer.Tests.TestUtils; 7 | using Xunit; 8 | 9 | namespace Serilog.Sinks.MSSqlServer.Tests.Platform 10 | { 11 | [Trait(TestCategory.TraitName, TestCategory.Unit)] 12 | public class SqlConnectionFactoryTests 13 | { 14 | private readonly Mock _sqlConnectionStringBuilderWrapperMock; 15 | 16 | public SqlConnectionFactoryTests() 17 | { 18 | _sqlConnectionStringBuilderWrapperMock = new Mock(); 19 | _sqlConnectionStringBuilderWrapperMock.SetupAllProperties(); 20 | } 21 | 22 | [Fact] 23 | public void IntializeThrowsIfSqlConnectionStringBuilderWrapperIsNull() 24 | { 25 | Assert.Throws(() => new SqlConnectionFactory(null)); 26 | } 27 | 28 | [Fact] 29 | public void CreateConnectionReturnsConnectionWrapper() 30 | { 31 | // Arrange 32 | var sut = new SqlConnectionFactory(_sqlConnectionStringBuilderWrapperMock.Object); 33 | 34 | // Act 35 | using (var connection = sut.Create()) 36 | { 37 | // Assert 38 | Assert.NotNull(connection); 39 | Assert.IsAssignableFrom(connection); 40 | } 41 | } 42 | 43 | 44 | [Fact] 45 | public void CreateConnectionCallsCustomConfigurationAction() 46 | { 47 | // Arrange 48 | var mockAction = new Mock>(); 49 | var sut = new SqlConnectionFactory(_sqlConnectionStringBuilderWrapperMock.Object, mockAction.Object); 50 | 51 | // Act 52 | using (var connection = sut.Create()) 53 | { 54 | // Assert 55 | Assert.NotNull(connection); 56 | Assert.IsAssignableFrom(connection); 57 | mockAction.Verify(m => m.Invoke(connection.SqlConnection), Times.Once); 58 | } 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /test/Serilog.Sinks.MSSqlServer.Tests/Sinks/MSSqlServer/Platform/SqlCreateDatabaseWriterTests.cs: -------------------------------------------------------------------------------- 1 | using Serilog.Sinks.MSSqlServer.Platform; 2 | using Serilog.Sinks.MSSqlServer.Tests.TestUtils; 3 | using Xunit; 4 | 5 | namespace Serilog.Sinks.MSSqlServer.Tests.Platform 6 | { 7 | [Trait(TestCategory.TraitName, TestCategory.Unit)] 8 | public class SqlCreateDatabaseWriterTests 9 | { 10 | [Fact] 11 | public void GetSqlWritesCorrectCommand() 12 | { 13 | // Arrange 14 | const string databaseName = "LogDatabase"; 15 | const string expectedResult = "IF NOT EXISTS (SELECT name FROM sys.databases WHERE name = 'LogDatabase')\r\nBEGIN\r\nCREATE DATABASE [LogDatabase]\r\nEND\r\n"; 16 | var sut = new SqlCreateDatabaseWriter(databaseName); 17 | 18 | // Act 19 | var result = sut.GetSql(); 20 | 21 | // Assert 22 | Assert.Equal(expectedResult, result); 23 | } 24 | 25 | [Fact] 26 | public void GetSqlWritesCorrectCommandForDatabaseNameWithSpaces() 27 | { 28 | // Arrange 29 | const string databaseName = "Log Data Base"; 30 | const string expectedResult = "IF NOT EXISTS (SELECT name FROM sys.databases WHERE name = 'Log Data Base')\r\nBEGIN\r\nCREATE DATABASE [Log Data Base]\r\nEND\r\n"; 31 | var sut = new SqlCreateDatabaseWriter(databaseName); 32 | 33 | // Act 34 | var result = sut.GetSql(); 35 | 36 | // Assert 37 | Assert.Equal(expectedResult, result); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /test/Serilog.Sinks.MSSqlServer.Tests/Sinks/MSSqlServer/Platform/TestableSqlCommandExecutor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Serilog.Debugging; 3 | 4 | namespace Serilog.Sinks.MSSqlServer.Platform 5 | { 6 | internal class TestableSqlCommandExecutor : SqlCommandExecutor 7 | { 8 | public TestableSqlCommandExecutor( 9 | ISqlWriter sqlWriter, 10 | ISqlConnectionFactory sqlConnectionFactory, 11 | ISqlCommandFactory sqlCommandFactory) : base(sqlWriter, sqlConnectionFactory, sqlCommandFactory) 12 | { 13 | } 14 | 15 | public Action HandleExceptionCallback { get; set; } 16 | 17 | protected override void HandleException(Exception ex) 18 | { 19 | HandleExceptionCallback?.Invoke(ex); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /test/Serilog.Sinks.MSSqlServer.Tests/TestUtils/DapperQueryTemplates.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Serilog.Events; 3 | 4 | namespace Serilog.Sinks.MSSqlServer.Tests.TestUtils 5 | { 6 | public class CustomStandardLogColumns 7 | { 8 | public int CustomId { get; set; } 9 | public string CustomMessage { get; set; } 10 | public string CustomMessageTemplate { get; set; } 11 | public string CustomLevel { get; set; } 12 | public DateTime CustomTimeStamp { get; set; } 13 | public string CustomException { get; set; } 14 | public string CustomProperties { get; set; } 15 | } 16 | 17 | public class DefaultStandardLogColumns 18 | { 19 | public string Message { get; set; } 20 | public LogEventLevel Level { get; set; } 21 | } 22 | 23 | public class InfoSchema 24 | { 25 | public string ColumnName { get; set; } 26 | public string SchemaName { get; set; } 27 | public string DataType { get; set; } 28 | public string DataLength { get; set; } 29 | public string AllowNull { get; set; } 30 | } 31 | 32 | public class sp_pkey 33 | { 34 | public string COLUMN_NAME { get; set; } 35 | public string PK_NAME { get; set; } 36 | } 37 | 38 | public class SysIndex_CCI 39 | { 40 | public string name { get; set; } 41 | } 42 | 43 | public class IdentityQuery 44 | { 45 | public int IsIdentity { get; set; } 46 | } 47 | 48 | public class LogEventColumn 49 | { 50 | public string LogEvent { get; set; } 51 | } 52 | 53 | public class SysObjectQuery 54 | { 55 | public int IndexType { get; set; } 56 | } 57 | 58 | public class PropertiesColumns 59 | { 60 | public string Properties { get; set; } 61 | } 62 | 63 | public class EnumLevelStandardLogColumns 64 | { 65 | public string Message { get; set; } 66 | public byte Level { get; set; } 67 | } 68 | 69 | public class StringLevelStandardLogColumns 70 | { 71 | public string Message { get; set; } 72 | public string Level { get; set; } 73 | } 74 | 75 | public class TestTimeStampDateTimeOffsetEntry 76 | { 77 | public DateTimeOffset TimeStamp { get; set; } 78 | } 79 | 80 | public class TestTimeStampDateTimeEntry 81 | { 82 | public DateTime TimeStamp { get; set; } 83 | } 84 | 85 | public class TestTriggerEntry 86 | { 87 | public Guid Id { get; set; } 88 | public string Data { get; set; } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /test/Serilog.Sinks.MSSqlServer.Tests/TestUtils/DatabaseFixture.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Linq; 4 | using Dapper; 5 | using Microsoft.Data.SqlClient; 6 | using static System.FormattableString; 7 | 8 | namespace Serilog.Sinks.MSSqlServer.Tests.TestUtils 9 | { 10 | public sealed class DatabaseFixture : IDisposable 11 | { 12 | 13 | private const string _masterConnectionString = @"Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=Master;Integrated Security=True;Connect Timeout=120"; 14 | private const string _createLogEventsDatabase = @" 15 | EXEC ('CREATE DATABASE [{0}] ON PRIMARY 16 | (NAME = [{0}], 17 | FILENAME =''{1}'', 18 | SIZE = 25MB, 19 | MAXSIZE = 50MB, 20 | FILEGROWTH = 5MB )')"; 21 | 22 | private static readonly string _databaseFileNameQuery = Invariant($@"SELECT CONVERT(VARCHAR(255), SERVERPROPERTY('instancedefaultdatapath')) + '{Database}.mdf' AS Name"); 23 | private static readonly string _dropLogEventsDatabase = Invariant($@" 24 | ALTER DATABASE [{Database}] 25 | SET SINGLE_USER 26 | WITH ROLLBACK IMMEDIATE 27 | DROP DATABASE [{Database}] 28 | "); 29 | 30 | public static string Database => "LogTest"; 31 | public static string LogTableName => "LogEvents"; 32 | public static string LogEventsConnectionString => Invariant($@"Data Source=(localdb)\MSSQLLocalDB;Initial Catalog={Database};Integrated Security=True"); 33 | 34 | public DatabaseFixture() 35 | { 36 | CreateDatabase(); 37 | } 38 | 39 | public void Dispose() 40 | { 41 | DeleteDatabase(); 42 | } 43 | 44 | public static void DropTable(string tableName = null) 45 | { 46 | using (var conn = new SqlConnection(LogEventsConnectionString)) 47 | { 48 | var actualTableName = string.IsNullOrEmpty(tableName) ? LogTableName : tableName; 49 | conn.Execute(Invariant($"IF OBJECT_ID('{actualTableName}', 'U') IS NOT NULL DROP TABLE {actualTableName};")); 50 | } 51 | } 52 | 53 | private static void DeleteDatabase() 54 | { 55 | using (var conn = new SqlConnection(_masterConnectionString)) 56 | { 57 | conn.Open(); 58 | var databases = conn.Query("select name from sys.databases"); 59 | 60 | if (databases.Any(d => d.name == Database)) conn.Execute(_dropLogEventsDatabase); 61 | } 62 | } 63 | 64 | private static void CreateDatabase() 65 | { 66 | DeleteDatabase(); 67 | 68 | using (var conn = new SqlConnection(_masterConnectionString)) 69 | { 70 | conn.Open(); 71 | // ReSharper disable once PossibleNullReferenceException 72 | var filename = conn.Query(_databaseFileNameQuery).FirstOrDefault().Name; 73 | var createDatabase = string.Format(CultureInfo.InvariantCulture, _createLogEventsDatabase, Database, filename); 74 | 75 | conn.Execute(createDatabase); 76 | } 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /test/Serilog.Sinks.MSSqlServer.Tests/TestUtils/Filename.cs: -------------------------------------------------------------------------------- 1 | namespace Serilog.Sinks.MSSqlServer.Tests.TestUtils 2 | { 3 | public class FileName 4 | { 5 | public string Name { get; set; } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /test/Serilog.Sinks.MSSqlServer.Tests/TestUtils/PatientSecureFixture.cs: -------------------------------------------------------------------------------- 1 | using Xunit; 2 | 3 | namespace Serilog.Sinks.MSSqlServer.Tests.TestUtils 4 | { 5 | [CollectionDefinition("DatabaseTests", DisableParallelization = true)] 6 | public class PatientSecureFixture : ICollectionFixture { } 7 | } 8 | -------------------------------------------------------------------------------- /test/Serilog.Sinks.MSSqlServer.Tests/TestUtils/TestCategory.cs: -------------------------------------------------------------------------------- 1 | namespace Serilog.Sinks.MSSqlServer.Tests.TestUtils 2 | { 3 | public static class TestCategory 4 | { 5 | public const string TraitName = "Category"; 6 | 7 | public const string Integration = nameof(Integration); 8 | public const string Unit = nameof(Unit); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /test/Serilog.Sinks.MSSqlServer.Tests/TestUtils/TestLogEventHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Serilog.Events; 4 | using Serilog.Parsing; 5 | 6 | namespace Serilog.Sinks.MSSqlServer.Tests.TestUtils 7 | { 8 | internal static class TestLogEventHelper 9 | { 10 | public static LogEvent CreateLogEvent() 11 | { 12 | return new LogEvent( 13 | new DateTimeOffset(2020, 1, 1, 0, 0, 0, 0, TimeSpan.Zero), 14 | LogEventLevel.Debug, null, new MessageTemplate(new List()), 15 | new List()); 16 | } 17 | } 18 | } 19 | --------------------------------------------------------------------------------