├── .editorconfig ├── .gitattributes ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── Build.ps1 ├── Directory.Build.props ├── Directory.Version.props ├── LICENSE ├── README.md ├── assets ├── Serilog.snk └── serilog-extension-nuget.png ├── global.json ├── samples ├── Sample │ ├── Program.cs │ └── Sample.csproj ├── SampleWithExternalScope │ ├── Program.cs │ └── SampleWithExternalScope.csproj └── SampleWithMelProviders │ ├── Program.cs │ └── SampleWithMelProviders.csproj ├── serilog-extensions-logging.sln ├── serilog-extensions-logging.sln.DotSettings ├── src └── Serilog.Extensions.Logging │ ├── Extensions │ ├── Logging │ │ ├── CachingMessageTemplateParser.cs │ │ ├── EventIdPropertyCache.cs │ │ ├── LevelConvert.cs │ │ ├── LoggerProviderCollection.cs │ │ ├── LoggerProviderCollectionSink.cs │ │ ├── SerilogLogValues.cs │ │ ├── SerilogLogger.cs │ │ ├── SerilogLoggerFactory.cs │ │ ├── SerilogLoggerProvider.cs │ │ └── SerilogLoggerScope.cs │ └── StringExtensions.cs │ ├── LoggerSinkConfigurationExtensions.cs │ ├── Properties │ └── AssemblyInfo.cs │ ├── Serilog.Extensions.Logging.csproj │ ├── SerilogLoggerFactoryExtensions.cs │ └── SerilogLoggingBuilderExtensions.cs └── test ├── Serilog.Extensions.Logging.Benchmarks ├── EventIdCapturingBenchmark.cs ├── LogEventBenchmark.cs ├── Serilog.Extensions.Logging.Benchmarks.csproj └── Support │ └── CapturingSink.cs └── Serilog.Extensions.Logging.Tests ├── ApiApprovalTests.cs ├── DisposeTests.cs ├── EventIdPropertyCacheTests.cs ├── LoggerProviderCollectionSinkTests.cs ├── Serilog.Extensions.Logging.Tests.csproj ├── Serilog.Extensions.Logging.approved.txt ├── SerilogLogValuesTests.cs ├── SerilogLoggerScopeTests.cs ├── SerilogLoggerTests.cs ├── SerilogLoggingBuilderExtensionsTests.cs └── Support ├── CollectingSink.cs ├── DisposeTrackingLogger.cs ├── ExtensionsProvider.cs └── LogEventPropertyFactory.cs /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | trim_trailing_whitespace = true 5 | insert_final_newline = true 6 | indent_style = space 7 | indent_size = 4 8 | charset = utf-8 9 | end_of_line = lf 10 | 11 | [*.{csproj,json,config,yml,props}] 12 | indent_size = 2 13 | 14 | [*.sh] 15 | end_of_line = lf 16 | 17 | [*.{cmd, bat}] 18 | end_of_line = crlf 19 | 20 | # C# formatting settings - Namespace options 21 | csharp_style_namespace_declarations = file_scoped:suggestion 22 | 23 | csharp_style_prefer_switch_expression = true:suggestion -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | 3 | * text=auto 4 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | # If this file is renamed, the incrementing run attempt number will be reset. 2 | 3 | name: CI 4 | 5 | on: 6 | push: 7 | branches: [ "dev", "main" ] 8 | pull_request: 9 | branches: [ "dev", "main" ] 10 | 11 | env: 12 | CI_BUILD_NUMBER_BASE: ${{ github.run_number }} 13 | CI_TARGET_BRANCH: ${{ github.head_ref || github.ref_name }} 14 | 15 | jobs: 16 | build: 17 | 18 | # The build must run on Windows so that .NET Framework targets can be built and tested. 19 | runs-on: windows-latest 20 | 21 | permissions: 22 | contents: write 23 | 24 | steps: 25 | - uses: actions/checkout@v4 26 | - name: Setup 27 | uses: actions/setup-dotnet@v4 28 | with: 29 | dotnet-version: 9.0.x 30 | - name: Compute build number 31 | shell: bash 32 | run: | 33 | echo "CI_BUILD_NUMBER=$(($CI_BUILD_NUMBER_BASE+2300))" >> $GITHUB_ENV 34 | - name: Build and Publish 35 | env: 36 | DOTNET_CLI_TELEMETRY_OPTOUT: true 37 | NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }} 38 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 39 | shell: pwsh 40 | run: | 41 | ./Build.ps1 42 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | build/ 21 | bld/ 22 | [Bb]in/ 23 | [Oo]bj/ 24 | 25 | # Visual Studo 2015 cache/options directory 26 | .vs/ 27 | 28 | # MSTest test Results 29 | [Tt]est[Rr]esult*/ 30 | [Bb]uild[Ll]og.* 31 | 32 | # NUNIT 33 | *.VisualState.xml 34 | TestResult.xml 35 | 36 | # Build Results of an ATL Project 37 | [Dd]ebugPS/ 38 | [Rr]eleasePS/ 39 | dlldata.c 40 | 41 | *_i.c 42 | *_p.c 43 | *_i.h 44 | *.ilk 45 | *.meta 46 | *.obj 47 | *.pch 48 | *.pdb 49 | *.pgc 50 | *.pgd 51 | *.rsp 52 | *.sbr 53 | *.tlb 54 | *.tli 55 | *.tlh 56 | *.tmp 57 | *.tmp_proj 58 | *.log 59 | *.vspscc 60 | *.vssscc 61 | .builds 62 | *.pidb 63 | *.svclog 64 | *.scc 65 | 66 | # Chutzpah Test files 67 | _Chutzpah* 68 | 69 | # Visual C++ cache files 70 | ipch/ 71 | *.aps 72 | *.ncb 73 | *.opensdf 74 | *.sdf 75 | *.cachefile 76 | 77 | # Visual Studio profiler 78 | *.psess 79 | *.vsp 80 | *.vspx 81 | 82 | # TFS 2012 Local Workspace 83 | $tf/ 84 | 85 | # Guidance Automation Toolkit 86 | *.gpState 87 | 88 | # ReSharper is a .NET coding add-in 89 | _ReSharper*/ 90 | *.[Rr]e[Ss]harper 91 | *.DotSettings.user 92 | 93 | # JustCode is a .NET coding addin-in 94 | .JustCode 95 | 96 | # TeamCity is a build add-in 97 | _TeamCity* 98 | 99 | # DotCover is a Code Coverage Tool 100 | *.dotCover 101 | 102 | # NCrunch 103 | _NCrunch_* 104 | .*crunch*.local.xml 105 | 106 | # MightyMoose 107 | *.mm.* 108 | AutoTest.Net/ 109 | 110 | # Web workbench (sass) 111 | .sass-cache/ 112 | 113 | # Installshield output folder 114 | [Ee]xpress/ 115 | 116 | # DocProject is a documentation generator add-in 117 | DocProject/buildhelp/ 118 | DocProject/Help/*.HxT 119 | DocProject/Help/*.HxC 120 | DocProject/Help/*.hhc 121 | DocProject/Help/*.hhk 122 | DocProject/Help/*.hhp 123 | DocProject/Help/Html2 124 | DocProject/Help/html 125 | 126 | # Click-Once directory 127 | publish/ 128 | 129 | # Publish Web Output 130 | *.[Pp]ublish.xml 131 | *.azurePubxml 132 | # TODO: Comment the next line if you want to checkin your web deploy settings 133 | # but database connection strings (with potential passwords) will be unencrypted 134 | *.pubxml 135 | *.publishproj 136 | 137 | # NuGet Packages 138 | *.nupkg 139 | # The packages folder can be ignored because of Package Restore 140 | **/packages/* 141 | # except build/, which is used as an MSBuild target. 142 | !**/packages/build/ 143 | # Uncomment if necessary however generally it will be regenerated when needed 144 | #!**/packages/repositories.config 145 | 146 | # Windows Azure Build Output 147 | csx/ 148 | *.build.csdef 149 | 150 | # Windows Store app package directory 151 | AppPackages/ 152 | 153 | # Others 154 | *.[Cc]ache 155 | ClientBin/ 156 | [Ss]tyle[Cc]op.* 157 | ~$* 158 | *~ 159 | *.dbmdl 160 | *.dbproj.schemaview 161 | *.pfx 162 | *.publishsettings 163 | node_modules/ 164 | bower_components/ 165 | 166 | # RIA/Silverlight projects 167 | Generated_Code/ 168 | 169 | # Backup & report files from converting an old project file 170 | # to a newer Visual Studio version. Backup files are not needed, 171 | # because we have git ;-) 172 | _UpgradeReport_Files/ 173 | Backup*/ 174 | UpgradeLog*.XML 175 | UpgradeLog*.htm 176 | 177 | # SQL Server files 178 | *.mdf 179 | *.ldf 180 | 181 | # Business Intelligence projects 182 | *.rdl.data 183 | *.bim.layout 184 | *.bim_*.settings 185 | 186 | # Microsoft Fakes 187 | FakesAssemblies/ 188 | 189 | # Node.js Tools for Visual Studio 190 | .ntvs_analysis.dat 191 | 192 | # Visual Studio 6 build log 193 | *.plg 194 | 195 | # Visual Studio 6 workspace options file 196 | *.opt 197 | *.orig 198 | project.lock.json 199 | 200 | # JetBrains Rider 201 | .idea 202 | 203 | .vscode 204 | 205 | .DS_Store 206 | 207 | -------------------------------------------------------------------------------- /Build.ps1: -------------------------------------------------------------------------------- 1 | Write-Output "build: Tool versions follow" 2 | 3 | dotnet --version 4 | dotnet --list-sdks 5 | 6 | Write-Output "build: Build started" 7 | 8 | Push-Location $PSScriptRoot 9 | try { 10 | if(Test-Path .\artifacts) { 11 | Write-Output "build: Cleaning ./artifacts" 12 | Remove-Item ./artifacts -Force -Recurse 13 | } 14 | 15 | & dotnet restore --no-cache 16 | 17 | $dbp = [Xml] (Get-Content .\Directory.Version.props) 18 | $versionPrefix = $dbp.Project.PropertyGroup.VersionPrefix 19 | 20 | Write-Output "build: Package version prefix is $versionPrefix" 21 | 22 | $branch = @{ $true = $env:CI_TARGET_BRANCH; $false = $(git symbolic-ref --short -q HEAD) }[$NULL -ne $env:CI_TARGET_BRANCH]; 23 | $revision = @{ $true = "{0:00000}" -f [convert]::ToInt32("0" + $env:CI_BUILD_NUMBER, 10); $false = "local" }[$NULL -ne $env:CI_BUILD_NUMBER]; 24 | $suffix = @{ $true = ""; $false = "$($branch.Substring(0, [math]::Min(10,$branch.Length)) -replace '([^a-zA-Z0-9\-]*)', '')-$revision"}[$branch -eq "main" -and $revision -ne "local"] 25 | $commitHash = $(git rev-parse --short HEAD) 26 | $buildSuffix = @{ $true = "$($suffix)-$($commitHash)"; $false = "$($branch)-$($commitHash)" }[$suffix -ne ""] 27 | 28 | Write-Output "build: Package version suffix is $suffix" 29 | Write-Output "build: Build version suffix is $buildSuffix" 30 | 31 | & dotnet build -c Release --version-suffix=$buildSuffix /p:ContinuousIntegrationBuild=true 32 | if($LASTEXITCODE -ne 0) { throw "Build failed" } 33 | 34 | foreach ($src in Get-ChildItem src/*) { 35 | Push-Location $src 36 | 37 | Write-Output "build: Packaging project in $src" 38 | 39 | if ($suffix) { 40 | & dotnet pack -c Release --no-build --no-restore -o ../../artifacts --version-suffix=$suffix 41 | } else { 42 | & dotnet pack -c Release --no-build --no-restore -o ../../artifacts 43 | } 44 | if($LASTEXITCODE -ne 0) { throw "Packaging failed" } 45 | 46 | Pop-Location 47 | } 48 | 49 | foreach ($test in Get-ChildItem test/*.Tests) { 50 | Push-Location $test 51 | 52 | Write-Output "build: Testing project in $test" 53 | 54 | & dotnet test -c Release --no-build --no-restore 55 | if($LASTEXITCODE -ne 0) { throw "Testing failed" } 56 | 57 | Pop-Location 58 | } 59 | 60 | if ($env:NUGET_API_KEY) { 61 | # GitHub Actions will only supply this to branch builds and not PRs. We publish 62 | # builds from any branch this action targets (i.e. main and dev). 63 | 64 | Write-Output "build: Publishing NuGet packages" 65 | 66 | foreach ($nupkg in Get-ChildItem artifacts/*.nupkg) { 67 | & dotnet nuget push -k $env:NUGET_API_KEY -s https://api.nuget.org/v3/index.json "$nupkg" 68 | if($LASTEXITCODE -ne 0) { throw "Publishing failed" } 69 | } 70 | 71 | if (!($suffix)) { 72 | Write-Output "build: Creating release for version $versionPrefix" 73 | 74 | iex "gh release create v$versionPrefix --title v$versionPrefix --generate-notes $(get-item ./artifacts/*.nupkg) $(get-item ./artifacts/*.snupkg)" 75 | } 76 | } 77 | } finally { 78 | Pop-Location 79 | } 80 | -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | latest 7 | True 8 | 9 | true 10 | $(MSBuildThisFileDirectory)assets/Serilog.snk 11 | false 12 | enable 13 | enable 14 | true 15 | true 16 | true 17 | true 18 | snupkg 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Directory.Version.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9.0.3 5 | 6 | 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Serilog.Extensions.Logging [![Build status](https://github.com/serilog/serilog-extensions-logging/actions/workflows/ci.yml/badge.svg?branch=dev)](https://github.com/serilog/serilog-extensions-logging/actions) [![NuGet Version](http://img.shields.io/nuget/v/Serilog.Extensions.Logging.svg?style=flat)](https://www.nuget.org/packages/Serilog.Extensions.Logging/) 2 | 3 | A Serilog provider for [Microsoft.Extensions.Logging](https://www.nuget.org/packages/Microsoft.Extensions.Logging), the logging subsystem used by ASP.NET Core. 4 | 5 | ### ASP.NET Core Instructions 6 | 7 | **ASP.NET Core** applications should prefer [Serilog.AspNetCore](https://github.com/serilog/serilog-aspnetcore#instructions) and `AddSerilog()` instead. 8 | 9 | ### Non-web .NET Core Instructions 10 | 11 | **Non-web .NET Core** applications should prefer [Serilog.Extensions.Hosting](https://github.com/serilog/serilog-extensions-hosting#instructions) and `AddSerilog()` instead. 12 | 13 | ### .NET Core 1.0, 1.1 and Default Provider Integration 14 | 15 | The package implements `AddSerilog()` on `ILoggingBuilder` and `ILoggerFactory` to enable the Serilog provider under the default _Microsoft.Extensions.Logging_ implementation. 16 | 17 | **First**, install the _Serilog.Extensions.Logging_ [NuGet package](https://www.nuget.org/packages/Serilog.Extensions.Logging) into your web or console app. You will need a way to view the log messages - _Serilog.Sinks.Console_ writes these to the console. 18 | 19 | ```sh 20 | dotnet add package Serilog.Extensions.Logging 21 | dotnet add package Serilog.Sinks.Console 22 | ``` 23 | 24 | **Next**, in your application's `Startup` method, configure Serilog first: 25 | 26 | ```csharp 27 | using Serilog; 28 | 29 | public class Startup 30 | { 31 | public Startup(IHostingEnvironment env) 32 | { 33 | Log.Logger = new LoggerConfiguration() 34 | .Enrich.FromLogContext() 35 | .WriteTo.Console() 36 | .CreateLogger(); 37 | 38 | // Other startup code 39 | ``` 40 | 41 | **Finally, for .NET Core 2.0+**, in your `Startup` class's `Configure()` method, remove the existing logger configuration entries and 42 | call `AddSerilog()` on the provided `loggingBuilder`. 43 | 44 | ```csharp 45 | public void ConfigureServices(IServiceCollection services) 46 | { 47 | services.AddLogging(loggingBuilder => 48 | loggingBuilder.AddSerilog(dispose: true)); 49 | 50 | // Other services ... 51 | } 52 | ``` 53 | 54 | **For .NET Core 1.0 or 1.1**, in your `Startup` class's `Configure()` method, remove the existing logger configuration entries and call `AddSerilog()` on the provided `loggerFactory`. 55 | 56 | ``` 57 | public void Configure(IApplicationBuilder app, 58 | IHostingEnvironment env, 59 | ILoggerFactory loggerfactory, 60 | IApplicationLifetime appLifetime) 61 | { 62 | loggerfactory.AddSerilog(); 63 | 64 | // Ensure any buffered events are sent at shutdown 65 | appLifetime.ApplicationStopped.Register(Log.CloseAndFlush); 66 | ``` 67 | 68 | That's it! With the level bumped up a little you should see log output like: 69 | 70 | ``` 71 | [22:14:44.646 DBG] RouteCollection.RouteAsync 72 | Routes: 73 | Microsoft.AspNet.Mvc.Routing.AttributeRoute 74 | {controller=Home}/{action=Index}/{id?} 75 | Handled? True 76 | [22:14:44.647 DBG] RouterMiddleware.Invoke 77 | Handled? True 78 | [22:14:45.706 DBG] /lib/jquery/jquery.js not modified 79 | [22:14:45.706 DBG] /css/site.css not modified 80 | [22:14:45.741 DBG] Handled. Status code: 304 File: /css/site.css 81 | ``` 82 | 83 | ### Including the log category in text-format sink output 84 | All _Microsoft.Extensions.Logging.ILogger_ implementations are created with a specified [_log category_](https://learn.microsoft.com/en-us/dotnet/core/extensions/logging?tabs=command-line#log-category) string, which is then attached as structured data to each log message created by that `ILogger` instance. Typically, the log category is the fully-qualified name of the class generating the log messages. This convention is implemented by the [`ILogger`](https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.logging.ilogger-1) interface, which is commonly used as an injected dependency in frameworks that use _Microsoft.Extensions.Logging_. 85 | 86 | _Serilog.Extensions.Logging_ captures the `ILogger`'s log category, but it's not included in the default output templates for text-based sinks, such as [Console](https://github.com/serilog/serilog-sinks-console), [File](https://github.com/serilog/serilog-sinks-file) and [Debug](https://github.com/serilog/serilog-sinks-debug). 87 | 88 | To include the log category in the final written messages, add the `{SourceContext}` named hole to a customised `outputTemplate` parameter value when configuring the relevant sink(s). For example: 89 | ```csharp 90 | .WriteTo.Console( 91 | outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3}] {SourceContext}: {Message:lj}{NewLine}{Exception}") 92 | .WriteTo.File("log.txt", 93 | outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {SourceContext}: {Message:lj}{NewLine}{Exception}") 94 | ``` 95 | 96 | ### Notes on Log Scopes 97 | 98 | _Microsoft.Extensions.Logging_ provides the `BeginScope` API, which can be used to add arbitrary properties to log events within a certain region of code. The API comes in two forms: 99 | 100 | 1. The method: `IDisposable BeginScope(TState state)` 101 | 2. The extension method: `IDisposable BeginScope(this ILogger logger, string messageFormat, params object[] args)` 102 | 103 | Using the extension method will add a `Scope` property to your log events. This is most useful for adding simple "scope strings" to your events, as in the following code: 104 | 105 | ```csharp 106 | using (_logger.BeginScope("Transaction")) 107 | { 108 | _logger.LogInformation("Beginning..."); 109 | _logger.LogInformation("Completed in {DurationMs}ms...", 30); 110 | } 111 | // Example JSON output: 112 | // {"@t":"2020-10-29T19:05:56.4126822Z","@m":"Beginning...","@i":"f6a328e9","SourceContext":"SomeNamespace.SomeService","Scope":["Transaction"]} 113 | // {"@t":"2020-10-29T19:05:56.4176816Z","@m":"Completed in 30ms...","@i":"51812baa","DurationMs":30,"SourceContext":"SomeNamespace.SomeService","Scope":["Transaction"]} 114 | ``` 115 | 116 | If you simply want to add a "bag" of additional properties to your log events, however, this extension method approach can be overly verbose. For example, to add `TransactionId` and `ResponseJson` properties to your log events, you would have to do something like the following: 117 | 118 | ```csharp 119 | // WRONG! Prefer the dictionary or value tuple approach below instead 120 | using (_logger.BeginScope("TransactionId: {TransactionId}, ResponseJson: {ResponseJson}", 12345, jsonString)) 121 | { 122 | _logger.LogInformation("Completed in {DurationMs}ms...", 30); 123 | } 124 | // Example JSON output: 125 | // { 126 | // "@t":"2020-10-29T19:05:56.4176816Z", 127 | // "@m":"Completed in 30ms...", 128 | // "@i":"51812baa", 129 | // "DurationMs":30, 130 | // "SourceContext":"SomeNamespace.SomeService", 131 | // "TransactionId": 12345, 132 | // "ResponseJson": "{ \"Key1\": \"Value1\", \"Key2\": \"Value2\" }", 133 | // "Scope":["TransactionId: 12345, ResponseJson: { \"Key1\": \"Value1\", \"Key2\": \"Value2\" }"] 134 | // } 135 | ``` 136 | 137 | Not only does this add the unnecessary `Scope` property to your event, but it also duplicates serialized values between `Scope` and the intended properties, as you can see here with `ResponseJson`. If this were "real" JSON like an API response, then a potentially very large block of text would be duplicated within your log event! 138 | Moreover, the template string within `BeginScope` is rather arbitrary when all you want to do is add a bag of properties, and you start mixing enriching concerns with formatting concerns. 139 | 140 | A far better alternative is to use the `BeginScope(TState state)` method. If you provide any `IEnumerable>` to this method, then Serilog will output the key/value pairs as structured properties _without_ the `Scope` property, as in this example: 141 | 142 | ```csharp 143 | var scopeProps = new Dictionary 144 | { 145 | { "TransactionId", 12345 }, 146 | { "ResponseJson", jsonString }, 147 | }; 148 | using (_logger.BeginScope(scopeProps) 149 | { 150 | _logger.LogInformation("Transaction completed in {DurationMs}ms...", 30); 151 | } 152 | // Example JSON output: 153 | // { 154 | // "@t":"2020-10-29T19:05:56.4176816Z", 155 | // "@m":"Completed in 30ms...", 156 | // "@i":"51812baa", 157 | // "DurationMs":30, 158 | // "SourceContext":"SomeNamespace.SomeService", 159 | // "TransactionId": 12345, 160 | // "ResponseJson": "{ \"Key1\": \"Value1\", \"Key2\": \"Value2\" }" 161 | // } 162 | ``` 163 | 164 | Alternatively provide a `ValueTuple` to this method, where `Item1` is the property name and `Item2` is the property value. 165 | Note that `T2` _must_ be `object?` if your target platform is net462 or netstandard2.0. 166 | 167 | ```csharp 168 | using (_logger.BeginScope(("TransactionId", 12345)) 169 | { 170 | _logger.LogInformation("Transaction completed in {DurationMs}ms...", 30); 171 | } 172 | // Example JSON output: 173 | // { 174 | // "@t":"2020-10-29T19:05:56.4176816Z", 175 | // "@m":"Completed in 30ms...", 176 | // "@i":"51812baa", 177 | // "DurationMs":30, 178 | // "SourceContext":"SomeNamespace.SomeService", 179 | // "TransactionId": 12345 180 | // } 181 | ``` 182 | 183 | ### Versioning 184 | 185 | This package tracks the versioning and target framework support of its [_Microsoft.Extensions.Logging_](https://nuget.org/packages/Microsoft.Extensions.Logging) dependency. 186 | 187 | ### Credits 188 | 189 | This package evolved from an earlier package _Microsoft.Framework.Logging.Serilog_ [provided by the ASP.NET team](https://github.com/aspnet/Logging/pull/182). 190 | -------------------------------------------------------------------------------- /assets/Serilog.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/serilog/serilog-extensions-logging/d8c75ece18b37efb5d0302784aeff1d8b2dada5d/assets/Serilog.snk -------------------------------------------------------------------------------- /assets/serilog-extension-nuget.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/serilog/serilog-extensions-logging/d8c75ece18b37efb5d0302784aeff1d8b2dada5d/assets/serilog-extension-nuget.png -------------------------------------------------------------------------------- /global.json: -------------------------------------------------------------------------------- 1 | { 2 | "sdk": { 3 | "version": "9.0.100", 4 | "allowPrerelease": false, 5 | "rollForward": "latestFeature" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /samples/Sample/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using Microsoft.Extensions.Logging; 3 | using Serilog; 4 | using Serilog.Extensions.Logging; 5 | 6 | Log.Logger = new LoggerConfiguration() 7 | .WriteTo.Console() 8 | .CreateLogger(); 9 | 10 | var services = new ServiceCollection(); 11 | 12 | services.AddLogging(); 13 | services.AddSingleton(new SerilogLoggerFactory()); 14 | 15 | using var serviceProvider = services.BuildServiceProvider(); 16 | var logger = serviceProvider.GetRequiredService>(); 17 | 18 | var startTime = DateTimeOffset.UtcNow; 19 | logger.LogInformation(1, "Started at {StartTime} and 0x{Hello:X} is hex of 42", startTime, 42); 20 | 21 | try 22 | { 23 | throw new Exception("Boom!"); 24 | } 25 | catch (Exception ex) 26 | { 27 | logger.LogCritical(ex, "Unexpected critical error starting application"); 28 | logger.Log(LogLevel.Critical, 0, "Unexpected critical error", ex, null!); 29 | // This write should not log anything 30 | logger.Log(LogLevel.Critical, 0, null!, null, null!); 31 | logger.LogError(ex, "Unexpected error"); 32 | logger.LogWarning(ex, "Unexpected warning"); 33 | } 34 | 35 | using (logger.BeginScope("Main")) 36 | { 37 | logger.LogInformation("Waiting for user input"); 38 | var key = Console.Read(); 39 | logger.LogInformation("User pressed {@KeyInfo}", new { Key = key, KeyChar = (char)key }); 40 | } 41 | 42 | var endTime = DateTimeOffset.UtcNow; 43 | logger.LogInformation(2, "Stopping at {StopTime}", endTime); 44 | 45 | logger.LogInformation("Stopping"); 46 | 47 | logger.LogInformation("{Result,-10:l}{StartTime,15:l}{EndTime,15:l}{Duration,15:l}", "RESULT", "START TIME", "END TIME", "DURATION(ms)"); 48 | logger.LogInformation("{Result,-10:l}{StartTime,15:l}{EndTime,15:l}{Duration,15:l}", "------", "----- ----", "--- ----", "------------"); 49 | logger.LogInformation("{Result,-10:l}{StartTime,15:mm:s tt}{EndTime,15:mm:s tt}{Duration,15}", "SUCCESS", startTime, endTime, (endTime - startTime).TotalMilliseconds); 50 | 51 | -------------------------------------------------------------------------------- /samples/Sample/Sample.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net9.0 5 | Exe 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /samples/SampleWithExternalScope/Program.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using Microsoft.Extensions.Logging; 4 | using Serilog; 5 | using Serilog.Formatting.Json; 6 | 7 | // Configure a JsonFormatter to log out scope to the console 8 | Log.Logger = new LoggerConfiguration() 9 | .MinimumLevel.Debug() 10 | .WriteTo.Console(new JsonFormatter()) 11 | .CreateLogger(); 12 | 13 | // Setup Serilog with M.E.L, and configure the appropriate ActivityTrackingOptions 14 | var services = new ServiceCollection(); 15 | 16 | services.AddLogging(l => l 17 | .AddSerilog() 18 | .Configure(options => 19 | { 20 | options.ActivityTrackingOptions = 21 | ActivityTrackingOptions.SpanId 22 | | ActivityTrackingOptions.TraceId 23 | | ActivityTrackingOptions.ParentId 24 | | ActivityTrackingOptions.TraceState 25 | | ActivityTrackingOptions.TraceFlags 26 | | ActivityTrackingOptions.Tags 27 | | ActivityTrackingOptions.Baggage; 28 | })); 29 | 30 | // Add an ActivityListener (required, otherwise Activities don't actually get created if nothing is listening to them) 31 | ActivitySource.AddActivityListener(new ActivityListener 32 | { 33 | ShouldListenTo = _ => true, 34 | Sample = (ref ActivityCreationOptions _) => ActivitySamplingResult.AllDataAndRecorded 35 | }); 36 | 37 | // Run our test 38 | var activitySource = new ActivitySource("SomeActivitySource"); 39 | 40 | using var serviceProvider = services.BuildServiceProvider(); 41 | var logger = serviceProvider.GetRequiredService>(); 42 | 43 | using var activity = activitySource.StartActivity(); 44 | 45 | activity?.SetTag("tag.domain.id", 1234); 46 | activity?.SetBaggage("baggage.environment", "uat"); 47 | 48 | using var scope = logger.BeginScope(new 49 | { 50 | User = "Hugh Mann", 51 | Time = DateTimeOffset.UtcNow 52 | }); 53 | 54 | logger.LogInformation("Hello world!"); 55 | 56 | -------------------------------------------------------------------------------- /samples/SampleWithExternalScope/SampleWithExternalScope.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net9.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /samples/SampleWithMelProviders/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using Microsoft.Extensions.Logging; 3 | using Serilog; 4 | using Serilog.Extensions.Logging; 5 | 6 | // Creating a `LoggerProviderCollection` lets Serilog optionally write 7 | // events through other dynamically-added MEL ILoggerProviders. 8 | var providers = new LoggerProviderCollection(); 9 | 10 | // The sample sets up Serilog's console sink here: 11 | Log.Logger = new LoggerConfiguration() 12 | .MinimumLevel.Debug() 13 | .WriteTo.Console() 14 | .WriteTo.Providers(providers) 15 | .CreateLogger(); 16 | 17 | var services = new ServiceCollection(); 18 | 19 | services.AddSingleton(providers); 20 | services.AddSingleton(sc => 21 | { 22 | var providerCollection = sc.GetService(); 23 | var factory = new SerilogLoggerFactory(null, true, providerCollection); 24 | 25 | foreach (var provider in sc.GetServices()) 26 | factory.AddProvider(provider); 27 | 28 | return factory; 29 | }); 30 | 31 | // ..and MEL's console provider here: 32 | services.AddLogging(l => l.AddConsole()); 33 | 34 | using var serviceProvider = services.BuildServiceProvider(); 35 | var logger = serviceProvider.GetRequiredService>(); 36 | 37 | var startTime = DateTimeOffset.UtcNow; 38 | logger.LogInformation(1, "Started at {StartTime} and 0x{Hello:X} is hex of 42", startTime, 42); 39 | 40 | try 41 | { 42 | throw new Exception("Boom!"); 43 | } 44 | catch (Exception ex) 45 | { 46 | logger.LogCritical(ex, "Unexpected critical error starting application"); 47 | logger.Log(LogLevel.Critical, 0, "Unexpected critical error", ex, null!); 48 | // This write should not log anything 49 | logger.Log(LogLevel.Critical, 0, null!, null, null!); 50 | logger.LogError(ex, "Unexpected error"); 51 | logger.LogWarning(ex, "Unexpected warning"); 52 | } 53 | 54 | using (logger.BeginScope("Main")) 55 | { 56 | logger.LogInformation("Waiting for user input"); 57 | var key = Console.Read(); 58 | logger.LogInformation("User pressed {@KeyInfo}", new { Key = key, KeyChar = (char)key }); 59 | } 60 | 61 | var endTime = DateTimeOffset.UtcNow; 62 | logger.LogInformation(2, "Stopping at {StopTime}", endTime); 63 | 64 | logger.LogInformation("Stopping"); 65 | 66 | logger.LogInformation("{Result,-10:l}{StartTime,15:l}{EndTime,15:l}{Duration,15:l}", "RESULT", "START TIME", "END TIME", "DURATION(ms)"); 67 | logger.LogInformation("{Result,-10:l}{StartTime,15:l}{EndTime,15:l}{Duration,15:l}", "------", "----- ----", "--- ----", "------------"); 68 | logger.LogInformation("{Result,-10:l}{StartTime,15:mm:s tt}{EndTime,15:mm:s tt}{Duration,15}", "SUCCESS", startTime, endTime, (endTime - startTime).TotalMilliseconds); 69 | 70 | -------------------------------------------------------------------------------- /samples/SampleWithMelProviders/SampleWithMelProviders.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net9.0 5 | Exe 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /serilog-extensions-logging.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.5.33424.131 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{A1893BD1-333D-4DFE-A0F0-DDBB2FE526E0}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Serilog.Extensions.Logging", "src\Serilog.Extensions.Logging\Serilog.Extensions.Logging.csproj", "{903CD13A-D54B-4CEC-A55F-E22AE3D93B3B}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Serilog.Extensions.Logging.Tests", "test\Serilog.Extensions.Logging.Tests\Serilog.Extensions.Logging.Tests.csproj", "{37EADF84-5E41-4224-A194-1E3299DCD0B8}" 11 | EndProject 12 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{E30F638E-BBBE-4AD1-93CE-48CC69CFEFE1}" 13 | EndProject 14 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{F2407211-6043-439C-8E06-3641634332E7}" 15 | EndProject 16 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sample", "samples\Sample\Sample.csproj", "{65357FBC-9BC4-466D-B621-1C3A19BC2A78}" 17 | EndProject 18 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "assets", "assets", "{9C21B9DF-AEDD-4AA6-BEA4-912DEF3E5B8E}" 19 | ProjectSection(SolutionItems) = preProject 20 | .editorconfig = .editorconfig 21 | Build.ps1 = Build.ps1 22 | Directory.Build.props = Directory.Build.props 23 | README.md = README.md 24 | assets\Serilog.snk = assets\Serilog.snk 25 | global.json = global.json 26 | Directory.Version.props = Directory.Version.props 27 | EndProjectSection 28 | EndProject 29 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Serilog.Extensions.Logging.Benchmarks", "test\Serilog.Extensions.Logging.Benchmarks\Serilog.Extensions.Logging.Benchmarks.csproj", "{6D5986FF-EECD-4E75-8BC6-A5F78AB549B2}" 30 | EndProject 31 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SampleWithExternalScope", "samples\SampleWithExternalScope\SampleWithExternalScope.csproj", "{653092A8-CBAD-40AA-A4CE-F8B19D6492C2}" 32 | EndProject 33 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SampleWithMelProviders", "samples\SampleWithMelProviders\SampleWithMelProviders.csproj", "{B1454759-126F-4F33-84EE-C8E19541DF79}" 34 | EndProject 35 | Global 36 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 37 | Debug|Any CPU = Debug|Any CPU 38 | Release|Any CPU = Release|Any CPU 39 | EndGlobalSection 40 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 41 | {903CD13A-D54B-4CEC-A55F-E22AE3D93B3B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 42 | {903CD13A-D54B-4CEC-A55F-E22AE3D93B3B}.Debug|Any CPU.Build.0 = Debug|Any CPU 43 | {903CD13A-D54B-4CEC-A55F-E22AE3D93B3B}.Release|Any CPU.ActiveCfg = Release|Any CPU 44 | {903CD13A-D54B-4CEC-A55F-E22AE3D93B3B}.Release|Any CPU.Build.0 = Release|Any CPU 45 | {37EADF84-5E41-4224-A194-1E3299DCD0B8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 46 | {37EADF84-5E41-4224-A194-1E3299DCD0B8}.Debug|Any CPU.Build.0 = Debug|Any CPU 47 | {37EADF84-5E41-4224-A194-1E3299DCD0B8}.Release|Any CPU.ActiveCfg = Release|Any CPU 48 | {37EADF84-5E41-4224-A194-1E3299DCD0B8}.Release|Any CPU.Build.0 = Release|Any CPU 49 | {65357FBC-9BC4-466D-B621-1C3A19BC2A78}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 50 | {65357FBC-9BC4-466D-B621-1C3A19BC2A78}.Debug|Any CPU.Build.0 = Debug|Any CPU 51 | {65357FBC-9BC4-466D-B621-1C3A19BC2A78}.Release|Any CPU.ActiveCfg = Release|Any CPU 52 | {65357FBC-9BC4-466D-B621-1C3A19BC2A78}.Release|Any CPU.Build.0 = Release|Any CPU 53 | {6D5986FF-EECD-4E75-8BC6-A5F78AB549B2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 54 | {6D5986FF-EECD-4E75-8BC6-A5F78AB549B2}.Debug|Any CPU.Build.0 = Debug|Any CPU 55 | {6D5986FF-EECD-4E75-8BC6-A5F78AB549B2}.Release|Any CPU.ActiveCfg = Release|Any CPU 56 | {6D5986FF-EECD-4E75-8BC6-A5F78AB549B2}.Release|Any CPU.Build.0 = Release|Any CPU 57 | {653092A8-CBAD-40AA-A4CE-F8B19D6492C2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 58 | {653092A8-CBAD-40AA-A4CE-F8B19D6492C2}.Debug|Any CPU.Build.0 = Debug|Any CPU 59 | {653092A8-CBAD-40AA-A4CE-F8B19D6492C2}.Release|Any CPU.ActiveCfg = Release|Any CPU 60 | {653092A8-CBAD-40AA-A4CE-F8B19D6492C2}.Release|Any CPU.Build.0 = Release|Any CPU 61 | {B1454759-126F-4F33-84EE-C8E19541DF79}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 62 | {B1454759-126F-4F33-84EE-C8E19541DF79}.Debug|Any CPU.Build.0 = Debug|Any CPU 63 | {B1454759-126F-4F33-84EE-C8E19541DF79}.Release|Any CPU.ActiveCfg = Release|Any CPU 64 | {B1454759-126F-4F33-84EE-C8E19541DF79}.Release|Any CPU.Build.0 = Release|Any CPU 65 | EndGlobalSection 66 | GlobalSection(SolutionProperties) = preSolution 67 | HideSolutionNode = FALSE 68 | EndGlobalSection 69 | GlobalSection(NestedProjects) = preSolution 70 | {903CD13A-D54B-4CEC-A55F-E22AE3D93B3B} = {A1893BD1-333D-4DFE-A0F0-DDBB2FE526E0} 71 | {37EADF84-5E41-4224-A194-1E3299DCD0B8} = {E30F638E-BBBE-4AD1-93CE-48CC69CFEFE1} 72 | {65357FBC-9BC4-466D-B621-1C3A19BC2A78} = {F2407211-6043-439C-8E06-3641634332E7} 73 | {6D5986FF-EECD-4E75-8BC6-A5F78AB549B2} = {E30F638E-BBBE-4AD1-93CE-48CC69CFEFE1} 74 | {653092A8-CBAD-40AA-A4CE-F8B19D6492C2} = {F2407211-6043-439C-8E06-3641634332E7} 75 | {B1454759-126F-4F33-84EE-C8E19541DF79} = {F2407211-6043-439C-8E06-3641634332E7} 76 | EndGlobalSection 77 | GlobalSection(ExtensibilityGlobals) = postSolution 78 | SolutionGuid = {811E61C5-3871-4633-AFAE-B35B619C8A10} 79 | EndGlobalSection 80 | EndGlobal 81 | -------------------------------------------------------------------------------- /serilog-extensions-logging.sln.DotSettings: -------------------------------------------------------------------------------- 1 |  2 | True 3 | True -------------------------------------------------------------------------------- /src/Serilog.Extensions.Logging/Extensions/Logging/CachingMessageTemplateParser.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 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 Serilog.Events; 16 | using Serilog.Parsing; 17 | using System.Collections; 18 | 19 | namespace Serilog.Extensions.Logging; 20 | 21 | sealed class CachingMessageTemplateParser 22 | { 23 | readonly MessageTemplateParser _innerParser = new(); 24 | 25 | readonly object _templatesLock = new(); 26 | readonly Hashtable _templates = new(); 27 | 28 | const int MaxCacheItems = 1000; 29 | const int MaxCachedTemplateLength = 1024; 30 | 31 | public MessageTemplate Parse(string messageTemplate) 32 | { 33 | if (messageTemplate == null) throw new ArgumentNullException(nameof(messageTemplate)); 34 | 35 | if (messageTemplate.Length > MaxCachedTemplateLength) 36 | return _innerParser.Parse(messageTemplate); 37 | 38 | // ReSharper disable once InconsistentlySynchronizedField 39 | // ignored warning because this is by design 40 | var result = (MessageTemplate?)_templates[messageTemplate]; 41 | if (result != null) 42 | return result; 43 | 44 | result = _innerParser.Parse(messageTemplate); 45 | 46 | lock (_templatesLock) 47 | { 48 | // Exceeding MaxCacheItems is *not* the sunny day scenario; all we're doing here is preventing out-of-memory 49 | // conditions when the library is used incorrectly. Correct use (templates, rather than 50 | // direct message strings) should barely, if ever, overflow this cache. 51 | 52 | // Changing workloads through the lifecycle of an app instance mean we can gain some ground by 53 | // potentially dropping templates generated only in startup, or only during specific infrequent 54 | // activities. 55 | 56 | if (_templates.Count == MaxCacheItems) 57 | _templates.Clear(); 58 | 59 | _templates[messageTemplate] = result; 60 | } 61 | 62 | return result; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/Serilog.Extensions.Logging/Extensions/Logging/EventIdPropertyCache.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 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 | namespace Serilog.Extensions.Logging; 16 | 17 | using System.Collections.Concurrent; 18 | using Microsoft.Extensions.Logging; 19 | using Events; 20 | 21 | sealed class EventIdPropertyCache 22 | { 23 | readonly int _maxCachedProperties; 24 | readonly ConcurrentDictionary _propertyCache = new(); 25 | 26 | int _count; 27 | 28 | public EventIdPropertyCache(int maxCachedProperties = 1024) 29 | { 30 | _maxCachedProperties = maxCachedProperties; 31 | } 32 | 33 | public LogEventPropertyValue GetOrCreatePropertyValue(in EventId eventId) 34 | { 35 | var eventKey = new EventKey(eventId); 36 | 37 | LogEventPropertyValue? propertyValue; 38 | 39 | if (_count >= _maxCachedProperties) 40 | { 41 | if (!_propertyCache.TryGetValue(eventKey, out propertyValue)) 42 | { 43 | propertyValue = CreatePropertyValue(in eventKey); 44 | } 45 | } 46 | else 47 | { 48 | if (!_propertyCache.TryGetValue(eventKey, out propertyValue)) 49 | { 50 | // GetOrAdd is moved to a separate method to prevent closure allocation 51 | propertyValue = GetOrAddCore(in eventKey); 52 | } 53 | } 54 | 55 | return propertyValue; 56 | } 57 | 58 | static LogEventPropertyValue CreatePropertyValue(in EventKey eventKey) 59 | { 60 | var properties = new List(2); 61 | 62 | if (eventKey.Id != 0) 63 | { 64 | properties.Add(new LogEventProperty("Id", new ScalarValue(eventKey.Id))); 65 | } 66 | 67 | if (eventKey.Name != null) 68 | { 69 | properties.Add(new LogEventProperty("Name", new ScalarValue(eventKey.Name))); 70 | } 71 | 72 | return new StructureValue(properties); 73 | } 74 | 75 | LogEventPropertyValue GetOrAddCore(in EventKey eventKey) => 76 | _propertyCache.GetOrAdd( 77 | eventKey, 78 | key => 79 | { 80 | Interlocked.Increment(ref _count); 81 | 82 | return CreatePropertyValue(in key); 83 | }); 84 | 85 | readonly record struct EventKey 86 | { 87 | public EventKey(EventId eventId) 88 | { 89 | Id = eventId.Id; 90 | Name = eventId.Name; 91 | } 92 | 93 | public int Id { get; } 94 | 95 | public string? Name { get; } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/Serilog.Extensions.Logging/Extensions/Logging/LevelConvert.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2019 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 Microsoft.Extensions.Logging; 16 | using Serilog.Events; 17 | 18 | // ReSharper disable RedundantCaseLabel 19 | 20 | namespace Serilog.Extensions.Logging; 21 | 22 | /// 23 | /// Converts between Serilog and Microsoft.Extensions.Logging level enum values. 24 | /// 25 | public static class LevelConvert 26 | { 27 | /// 28 | /// Convert to the equivalent Serilog . 29 | /// 30 | /// A Microsoft.Extensions.Logging . 31 | /// The Serilog equivalent of . 32 | /// The value has no Serilog equivalent. It is mapped to 33 | /// as the closest approximation, but this has entirely 34 | /// different semantics. 35 | public static LogEventLevel ToSerilogLevel(LogLevel logLevel) 36 | { 37 | return logLevel switch 38 | { 39 | LogLevel.None => LevelAlias.Off, 40 | LogLevel.Critical => LogEventLevel.Fatal, 41 | LogLevel.Error => LogEventLevel.Error, 42 | LogLevel.Warning => LogEventLevel.Warning, 43 | LogLevel.Information => LogEventLevel.Information, 44 | LogLevel.Debug => LogEventLevel.Debug, 45 | _ => LogEventLevel.Verbose, 46 | }; 47 | } 48 | 49 | /// 50 | /// Convert to the equivalent Microsoft.Extensions.Logging . 51 | /// 52 | /// A Serilog . 53 | /// The Microsoft.Extensions.Logging equivalent of . 54 | public static LogLevel ToExtensionsLevel(LogEventLevel logEventLevel) 55 | { 56 | return logEventLevel switch 57 | { 58 | LogEventLevel.Fatal => LogLevel.Critical, 59 | LogEventLevel.Error => LogLevel.Error, 60 | LogEventLevel.Warning => LogLevel.Warning, 61 | LogEventLevel.Information => LogLevel.Information, 62 | LogEventLevel.Debug => LogLevel.Debug, 63 | _ => LogLevel.Trace, 64 | }; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/Serilog.Extensions.Logging/Extensions/Logging/LoggerProviderCollection.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2019 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 | 16 | using Microsoft.Extensions.Logging; 17 | 18 | namespace Serilog.Extensions.Logging; 19 | 20 | /// 21 | /// A dynamically-modifiable collection of s. 22 | /// 23 | public sealed class LoggerProviderCollection : IDisposable 24 | { 25 | volatile ILoggerProvider[] _providers = []; 26 | 27 | /// 28 | /// Add to the collection. 29 | /// 30 | /// A logger provider. 31 | public void AddProvider(ILoggerProvider provider) 32 | { 33 | if (provider == null) throw new ArgumentNullException(nameof(provider)); 34 | 35 | ILoggerProvider[] existing, added; 36 | 37 | do 38 | { 39 | existing = _providers; 40 | added = [..existing, provider]; 41 | } 42 | #pragma warning disable 420 // ref to a volatile field 43 | while (Interlocked.CompareExchange(ref _providers, added, existing) != existing); 44 | #pragma warning restore 420 45 | } 46 | 47 | /// 48 | /// Get the currently-active providers. 49 | /// 50 | /// 51 | /// If the collection has been disposed, we'll leave the individual 52 | /// providers with the job of throwing . 53 | /// 54 | public IEnumerable Providers => _providers; 55 | 56 | /// 57 | public void Dispose() 58 | { 59 | foreach (var provider in _providers) 60 | provider.Dispose(); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/Serilog.Extensions.Logging/Extensions/Logging/LoggerProviderCollectionSink.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2019 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 Microsoft.Extensions.Logging; 16 | using Serilog.Core; 17 | using Serilog.Events; 18 | 19 | namespace Serilog.Extensions.Logging; 20 | 21 | sealed class LoggerProviderCollectionSink : ILogEventSink, IDisposable 22 | { 23 | readonly LoggerProviderCollection _providers; 24 | 25 | public LoggerProviderCollectionSink(LoggerProviderCollection providers) 26 | { 27 | _providers = providers ?? throw new ArgumentNullException(nameof(providers)); 28 | } 29 | 30 | public void Emit(LogEvent logEvent) 31 | { 32 | string categoryName = "None"; 33 | EventId eventId = default; 34 | 35 | if (logEvent.Properties.TryGetValue("SourceContext", out var sourceContextProperty) && 36 | sourceContextProperty is ScalarValue sourceContextValue && 37 | sourceContextValue.Value is string sourceContext) 38 | { 39 | categoryName = sourceContext; 40 | } 41 | if (logEvent.Properties.TryGetValue("EventId", out var eventIdPropertyValue) && eventIdPropertyValue is StructureValue structuredEventId) 42 | { 43 | string? name = null; 44 | var id = 0; 45 | foreach (var item in structuredEventId.Properties) 46 | { 47 | if (item.Name == "Id" && item.Value is ScalarValue sv && sv.Value is int i) id = i; 48 | if (item.Name == "Name" && item.Value is ScalarValue sv2 && sv2.Value is string s) name = s; 49 | } 50 | 51 | eventId = new EventId(id, name); 52 | } 53 | 54 | var level = LevelConvert.ToExtensionsLevel(logEvent.Level); 55 | var slv = new SerilogLogValues(logEvent.MessageTemplate, logEvent.Properties); 56 | 57 | foreach (var provider in _providers.Providers) 58 | { 59 | var logger = provider.CreateLogger(categoryName); 60 | 61 | logger.Log( 62 | level, 63 | eventId, 64 | slv, 65 | logEvent.Exception, 66 | (s, e) => s.ToString()); 67 | } 68 | } 69 | 70 | public void Dispose() 71 | { 72 | _providers.Dispose(); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLogValues.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2019 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 Serilog.Events; 16 | using System.Collections; 17 | 18 | namespace Serilog.Extensions.Logging; 19 | 20 | readonly struct SerilogLogValues : IReadOnlyList> 21 | { 22 | // Note, this struct is only used in a very limited context internally, so we ignore 23 | // the possibility of fields being null via the default struct initialization. 24 | 25 | readonly MessageTemplate _messageTemplate; 26 | readonly IReadOnlyDictionary _properties; 27 | readonly KeyValuePair[] _values; 28 | 29 | public SerilogLogValues(MessageTemplate messageTemplate, IReadOnlyDictionary properties) 30 | { 31 | _messageTemplate = messageTemplate ?? throw new ArgumentNullException(nameof(messageTemplate)); 32 | 33 | // The dictionary is needed for rendering through the message template 34 | _properties = properties ?? throw new ArgumentNullException(nameof(properties)); 35 | 36 | // The array is needed because the IReadOnlyList interface expects indexed access 37 | _values = new KeyValuePair[_properties.Count + 1]; 38 | var i = 0; 39 | foreach (var p in properties) 40 | { 41 | _values[i] = new KeyValuePair(p.Key, (p.Value is ScalarValue sv) ? sv.Value : p.Value); 42 | ++i; 43 | } 44 | _values[i] = new KeyValuePair("{OriginalFormat}", _messageTemplate.Text); 45 | } 46 | 47 | public KeyValuePair this[int index] => _values[index]; 48 | 49 | public int Count => _properties.Count + 1; 50 | 51 | public IEnumerator> GetEnumerator() => ((IEnumerable>)_values).GetEnumerator(); 52 | 53 | public override string ToString() => _messageTemplate.Render(_properties); 54 | 55 | IEnumerator IEnumerable.GetEnumerator() => _values.GetEnumerator(); 56 | } 57 | -------------------------------------------------------------------------------- /src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLogger.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using Microsoft.Extensions.Logging; 5 | using Serilog.Core; 6 | using Serilog.Events; 7 | using FrameworkLogger = Microsoft.Extensions.Logging.ILogger; 8 | using System.Reflection; 9 | using Serilog.Debugging; 10 | using System.Collections.Concurrent; 11 | using System.Diagnostics; 12 | 13 | namespace Serilog.Extensions.Logging; 14 | 15 | sealed class SerilogLogger : FrameworkLogger 16 | { 17 | internal static readonly ConcurrentDictionary DestructureDictionary = new(); 18 | internal static readonly ConcurrentDictionary StringifyDictionary = new(); 19 | 20 | internal static string GetKeyWithoutFirstSymbol(ConcurrentDictionary source, string key) 21 | { 22 | if (source.TryGetValue(key, out var value)) 23 | return value; 24 | if (source.Count < 1000) 25 | return source.GetOrAdd(key, k => k.Substring(1)); 26 | return key.Substring(1); 27 | } 28 | 29 | readonly SerilogLoggerProvider _provider; 30 | readonly ILogger _logger; 31 | readonly EventIdPropertyCache _eventIdPropertyCache = new(); 32 | 33 | static readonly CachingMessageTemplateParser MessageTemplateParser = new(); 34 | 35 | public SerilogLogger( 36 | SerilogLoggerProvider provider, 37 | ILogger? logger = null, 38 | string? name = null) 39 | { 40 | _provider = provider ?? throw new ArgumentNullException(nameof(provider)); 41 | 42 | // If a logger was passed, the provider has already added itself as an enricher 43 | _logger = logger ?? Serilog.Log.Logger.ForContext([provider]); 44 | 45 | if (name != null) 46 | { 47 | _logger = _logger.ForContext(Constants.SourceContextPropertyName, name); 48 | } 49 | } 50 | 51 | public bool IsEnabled(LogLevel logLevel) 52 | { 53 | return logLevel != LogLevel.None && _logger.IsEnabled(LevelConvert.ToSerilogLevel(logLevel)); 54 | } 55 | 56 | public IDisposable BeginScope(TState state) where TState : notnull 57 | { 58 | return _provider.BeginScope(state); 59 | } 60 | 61 | public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter) 62 | { 63 | if (logLevel == LogLevel.None) 64 | { 65 | return; 66 | } 67 | var level = LevelConvert.ToSerilogLevel(logLevel); 68 | if (!_logger.IsEnabled(level)) 69 | { 70 | return; 71 | } 72 | 73 | LogEvent? evt = null; 74 | try 75 | { 76 | evt = PrepareWrite(level, eventId, state, exception, formatter); 77 | } 78 | catch (Exception ex) 79 | { 80 | SelfLog.WriteLine($"Failed to write event through {nameof(SerilogLogger)}: {ex}"); 81 | } 82 | 83 | // Do not swallow exceptions from here because Serilog takes care of them in case of WriteTo and throws them back to the caller in case of AuditTo. 84 | if (evt != null) 85 | _logger.Write(evt); 86 | } 87 | 88 | LogEvent PrepareWrite(LogEventLevel level, EventId eventId, TState state, Exception? exception, Func formatter) 89 | { 90 | string? messageTemplate = null; 91 | 92 | var properties = new Dictionary(); 93 | 94 | if (state is IEnumerable> structure) 95 | { 96 | foreach (var property in structure) 97 | { 98 | if (property is { Key: SerilogLoggerProvider.OriginalFormatPropertyName, Value: string value }) 99 | { 100 | messageTemplate = value; 101 | } 102 | else if (property.Key.StartsWith('@')) 103 | { 104 | if (_logger.BindProperty(GetKeyWithoutFirstSymbol(DestructureDictionary, property.Key), property.Value, true, out var destructured)) 105 | properties[destructured.Name] = destructured.Value; 106 | } 107 | else if (property.Key.StartsWith('$')) 108 | { 109 | if (_logger.BindProperty(GetKeyWithoutFirstSymbol(StringifyDictionary, property.Key), property.Value?.ToString(), true, out var stringified)) 110 | properties[stringified.Name] = stringified.Value; 111 | } 112 | else 113 | { 114 | // Simple micro-optimization for the most common and reliably scalar values; could go further here. 115 | if (property.Value is null or string or int or long && LogEventProperty.IsValidName(property.Key)) 116 | properties[property.Key] = new ScalarValue(property.Value); 117 | else if (_logger.BindProperty(property.Key, property.Value, false, out var bound)) 118 | properties[bound.Name] = bound.Value; 119 | } 120 | } 121 | 122 | var stateType = state.GetType(); 123 | var stateTypeInfo = stateType.GetTypeInfo(); 124 | // Imperfect, but at least eliminates `1 names 125 | if (messageTemplate == null && !stateTypeInfo.IsGenericType) 126 | { 127 | messageTemplate = "{" + stateType.Name + ":l}"; 128 | if (_logger.BindProperty(stateType.Name, AsLoggableValue(state, formatter), false, out var stateTypeProperty)) 129 | properties[stateTypeProperty.Name] = stateTypeProperty.Value; 130 | } 131 | } 132 | 133 | if (messageTemplate == null) 134 | { 135 | string? propertyName = null; 136 | if (state != null) 137 | { 138 | propertyName = "State"; 139 | messageTemplate = "{State:l}"; 140 | } 141 | // `formatter` was originally accepted as nullable, so despite the new annotation, this check should still 142 | // be made. 143 | else if (formatter != null!) 144 | { 145 | propertyName = "Message"; 146 | messageTemplate = "{Message:l}"; 147 | } 148 | 149 | if (propertyName != null) 150 | { 151 | if (_logger.BindProperty(propertyName, AsLoggableValue(state, formatter!), false, out var property)) 152 | properties[property.Name] = property.Value; 153 | } 154 | } 155 | 156 | // The overridden `!=` operator on this type ignores `Name`. 157 | if (eventId.Id != 0 || eventId.Name != null) 158 | properties["EventId"] = _eventIdPropertyCache.GetOrCreatePropertyValue(in eventId); 159 | 160 | var (traceId, spanId) = Activity.Current is { } activity ? 161 | (activity.TraceId, activity.SpanId) : 162 | (default(ActivityTraceId), default(ActivitySpanId)); 163 | 164 | var parsedTemplate = messageTemplate != null ? MessageTemplateParser.Parse(messageTemplate) : MessageTemplate.Empty; 165 | return LogEvent.UnstableAssembleFromParts(DateTimeOffset.Now, level, exception, parsedTemplate, properties, traceId, spanId); 166 | } 167 | 168 | static object? AsLoggableValue(TState state, Func? formatter) 169 | { 170 | object? stateObj = null; 171 | if (formatter != null) 172 | stateObj = formatter(state, null); 173 | return stateObj ?? state; 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLoggerFactory.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2019 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 Microsoft.Extensions.Logging; 16 | using Serilog.Debugging; 17 | 18 | namespace Serilog.Extensions.Logging; 19 | 20 | /// 21 | /// A complete Serilog-backed implementation of the .NET Core logging infrastructure. 22 | /// 23 | public sealed class SerilogLoggerFactory : ILoggerFactory 24 | { 25 | readonly LoggerProviderCollection? _providerCollection; 26 | readonly SerilogLoggerProvider _provider; 27 | 28 | /// 29 | /// Initializes a new instance of the class. 30 | /// 31 | /// The Serilog logger; if not supplied, the static will be used. 32 | /// When true, dispose when the framework disposes the provider. If the 33 | /// logger is not specified but is true, the method will be 34 | /// called on the static class instead. 35 | /// A , for use with WriteTo.Providers(). 36 | public SerilogLoggerFactory(ILogger? logger = null, bool dispose = false, LoggerProviderCollection? providerCollection = null) 37 | { 38 | _provider = new SerilogLoggerProvider(logger, dispose); 39 | _providerCollection = providerCollection; 40 | } 41 | 42 | /// 43 | /// Disposes the provider. 44 | /// 45 | public void Dispose() 46 | { 47 | _provider.Dispose(); 48 | } 49 | 50 | /// 51 | /// Creates a new instance. 52 | /// 53 | /// The category name for messages produced by the logger. 54 | /// 55 | /// The . 56 | /// 57 | public Microsoft.Extensions.Logging.ILogger CreateLogger(string categoryName) 58 | { 59 | return _provider.CreateLogger(categoryName); 60 | } 61 | 62 | /// 63 | /// Adds an to the logging system. 64 | /// 65 | /// The . 66 | public void AddProvider(ILoggerProvider provider) 67 | { 68 | if (provider == null) throw new ArgumentNullException(nameof(provider)); 69 | if (_providerCollection != null) 70 | _providerCollection.AddProvider(provider); 71 | else 72 | SelfLog.WriteLine("Ignoring added logger provider {0}", provider); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLoggerProvider.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using Microsoft.Extensions.Logging; 5 | using Serilog.Core; 6 | using Serilog.Events; 7 | using FrameworkLogger = Microsoft.Extensions.Logging.ILogger; 8 | using Serilog.Context; 9 | 10 | namespace Serilog.Extensions.Logging; 11 | 12 | /// 13 | /// An that pipes events through Serilog. 14 | /// 15 | [ProviderAlias("Serilog")] 16 | public sealed class SerilogLoggerProvider : ILoggerProvider, ILogEventEnricher, ISupportExternalScope 17 | #if FEATURE_ASYNCDISPOSABLE 18 | , IAsyncDisposable 19 | #endif 20 | { 21 | internal const string OriginalFormatPropertyName = "{OriginalFormat}"; 22 | internal const string ScopePropertyName = "Scope"; 23 | 24 | // May be null; if it is, Log.Logger will be lazily used 25 | readonly ILogger? _logger; 26 | readonly Action? _dispose; 27 | readonly ThreadLocal _scopeCollector = new(() => new ScopeCollector()); 28 | #if FEATURE_ASYNCDISPOSABLE 29 | readonly Func? _disposeAsync; 30 | #endif 31 | IExternalScopeProvider? _externalScopeProvider; 32 | 33 | /// 34 | /// Construct a . 35 | /// 36 | /// A Serilog logger to pipe events through; if null, the static class will be used. 37 | /// If true, the provided logger or static log class will be disposed/closed when the provider is disposed. 38 | public SerilogLoggerProvider(ILogger? logger = null, bool dispose = false) 39 | { 40 | if (logger != null) 41 | _logger = logger.ForContext([this]); 42 | 43 | if (dispose) 44 | { 45 | if (logger != null) 46 | { 47 | _dispose = () => (logger as IDisposable)?.Dispose(); 48 | #if FEATURE_ASYNCDISPOSABLE 49 | _disposeAsync = async () => 50 | { 51 | if (logger is IAsyncDisposable asyncDisposable) 52 | { 53 | await asyncDisposable.DisposeAsync(); 54 | } 55 | else 56 | { 57 | (logger as IDisposable)?.Dispose(); 58 | } 59 | }; 60 | #endif 61 | } 62 | else 63 | { 64 | _dispose = Log.CloseAndFlush; 65 | #if FEATURE_ASYNCDISPOSABLE 66 | _disposeAsync = Log.CloseAndFlushAsync; 67 | #endif 68 | } 69 | } 70 | } 71 | 72 | /// 73 | public FrameworkLogger CreateLogger(string name) 74 | { 75 | return new SerilogLogger(this, _logger, name); 76 | } 77 | 78 | /// 79 | public IDisposable BeginScope(T state) 80 | { 81 | if (CurrentScope != null) 82 | return new SerilogLoggerScope(this, state); 83 | 84 | // The outermost scope pushes and pops the Serilog `LogContext` - once 85 | // this enricher is on the stack, the `CurrentScope` property takes care 86 | // of the rest of the `BeginScope()` stack. 87 | var popSerilogContext = LogContext.Push(this); 88 | return new SerilogLoggerScope(this, state, popSerilogContext); 89 | } 90 | 91 | /// 92 | public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory) 93 | { 94 | var scopeCollector = _scopeCollector.Value!; 95 | 96 | for (var scope = CurrentScope; scope != null; scope = scope.Parent) 97 | { 98 | scope.EnrichAndCreateScopeItem(logEvent, propertyFactory, out var scopeItem); 99 | 100 | if (scopeItem != null) 101 | { 102 | scopeCollector.AddItem(scopeItem); 103 | } 104 | } 105 | 106 | scopeCollector.ReverseItems(); 107 | 108 | _externalScopeProvider?.ForEachScope(static (state, parameters) => 109 | { 110 | SerilogLoggerScope.EnrichWithStateAndCreateScopeItem( 111 | parameters.LogEvent, 112 | parameters.PropertyFactory, 113 | state, 114 | update: true, 115 | out var scopeItem); 116 | 117 | if (scopeItem != null) 118 | { 119 | parameters.ScopeCollector.AddItem(scopeItem); 120 | } 121 | }, (ScopeCollector: scopeCollector, PropertyFactory: propertyFactory, LogEvent: logEvent)); 122 | 123 | if (scopeCollector.Complete() is { } items) 124 | { 125 | logEvent.AddPropertyIfAbsent(new LogEventProperty(ScopePropertyName, new SequenceValue(items))); 126 | } 127 | } 128 | 129 | /// 130 | public void SetScopeProvider(IExternalScopeProvider scopeProvider) 131 | { 132 | _externalScopeProvider = scopeProvider; 133 | } 134 | 135 | readonly AsyncLocal _value = new(); 136 | 137 | internal SerilogLoggerScope? CurrentScope 138 | { 139 | get => _value.Value; 140 | set => _value.Value = value; 141 | } 142 | 143 | /// 144 | public void Dispose() 145 | { 146 | _dispose?.Invoke(); 147 | } 148 | 149 | #if FEATURE_ASYNCDISPOSABLE 150 | /// 151 | public ValueTask DisposeAsync() 152 | { 153 | return _disposeAsync?.Invoke() ?? default; 154 | } 155 | #endif 156 | 157 | /// 158 | /// A wrapper around a list to allow lazy initialization when iterating through scopes. 159 | /// 160 | sealed class ScopeCollector 161 | { 162 | List? _scopeItems; 163 | 164 | public void AddItem(LogEventPropertyValue scopeItem) 165 | { 166 | _scopeItems ??= []; 167 | _scopeItems.Add(scopeItem); 168 | } 169 | 170 | public void ReverseItems() => _scopeItems?.Reverse(); 171 | 172 | public List? Complete() 173 | { 174 | var scopeItems = _scopeItems; 175 | _scopeItems = null; 176 | return scopeItems; 177 | } 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLoggerScope.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using Serilog.Core; 5 | using Serilog.Events; 6 | 7 | namespace Serilog.Extensions.Logging; 8 | 9 | sealed class SerilogLoggerScope : IDisposable 10 | { 11 | const string NoName = "None"; 12 | 13 | readonly SerilogLoggerProvider _provider; 14 | readonly object? _state; 15 | readonly IDisposable? _chainedDisposable; 16 | 17 | // An optimization only, no problem if there are data races on this. 18 | bool _disposed; 19 | 20 | public SerilogLoggerScope(SerilogLoggerProvider provider, object? state, IDisposable? chainedDisposable = null) 21 | { 22 | _provider = provider; 23 | _state = state; 24 | 25 | Parent = _provider.CurrentScope; 26 | _provider.CurrentScope = this; 27 | _chainedDisposable = chainedDisposable; 28 | } 29 | 30 | public SerilogLoggerScope? Parent { get; } 31 | 32 | public void Dispose() 33 | { 34 | if (!_disposed) 35 | { 36 | _disposed = true; 37 | 38 | // In case one of the parent scopes has been disposed out-of-order, don't 39 | // just blindly reinstate our own parent. 40 | for (var scan = _provider.CurrentScope; scan != null; scan = scan.Parent) 41 | { 42 | if (ReferenceEquals(scan, this)) 43 | _provider.CurrentScope = Parent; 44 | } 45 | 46 | _chainedDisposable?.Dispose(); 47 | } 48 | } 49 | 50 | public void EnrichAndCreateScopeItem(LogEvent logEvent, ILogEventPropertyFactory propertyFactory, out LogEventPropertyValue? scopeItem) => EnrichWithStateAndCreateScopeItem(logEvent, propertyFactory, _state, update: false, out scopeItem); 51 | 52 | public static void EnrichWithStateAndCreateScopeItem(LogEvent logEvent, ILogEventPropertyFactory propertyFactory, object? state, bool update, out LogEventPropertyValue? scopeItem) 53 | { 54 | if (state == null) 55 | { 56 | scopeItem = null; 57 | return; 58 | } 59 | 60 | // Eliminates boxing of Dictionary.Enumerator for the most common use case 61 | if (state is Dictionary dictionary) 62 | { 63 | // Separate handling of this case eliminates boxing of Dictionary.Enumerator. 64 | scopeItem = null; 65 | foreach (var stateProperty in dictionary) 66 | { 67 | AddProperty(logEvent, propertyFactory, stateProperty.Key, stateProperty.Value, update); 68 | } 69 | } 70 | else if (state is IEnumerable> stateProperties) 71 | { 72 | scopeItem = null; 73 | foreach (var stateProperty in stateProperties) 74 | { 75 | if (stateProperty is { Key: SerilogLoggerProvider.OriginalFormatPropertyName, Value: string }) 76 | { 77 | // `_state` is most likely `FormattedLogValues` (a MEL internal type). 78 | scopeItem = new ScalarValue(state.ToString()); 79 | } 80 | else 81 | { 82 | AddProperty(logEvent, propertyFactory, stateProperty.Key, stateProperty.Value, update); 83 | } 84 | } 85 | } 86 | #if FEATURE_ITUPLE 87 | else if (state is System.Runtime.CompilerServices.ITuple tuple && tuple.Length == 2 && tuple[0] is string s) 88 | { 89 | scopeItem = null; // Unless it's `FormattedLogValues`, these are treated as property bags rather than scope items. 90 | AddProperty(logEvent, propertyFactory, s, tuple[1], update); 91 | } 92 | #else 93 | else if (state is ValueTuple tuple) 94 | { 95 | scopeItem = null; 96 | AddProperty(logEvent, propertyFactory, tuple.Item1, tuple.Item2, update); 97 | } 98 | #endif 99 | else 100 | { 101 | scopeItem = propertyFactory.CreateProperty(NoName, state).Value; 102 | } 103 | } 104 | 105 | static void AddProperty(LogEvent logEvent, ILogEventPropertyFactory propertyFactory, string key, object? value, bool update) 106 | { 107 | var destructureObjects = false; 108 | 109 | if (key.StartsWith('@')) 110 | { 111 | key = SerilogLogger.GetKeyWithoutFirstSymbol(SerilogLogger.DestructureDictionary, key); 112 | destructureObjects = true; 113 | } 114 | else if (key.StartsWith('$')) 115 | { 116 | key = SerilogLogger.GetKeyWithoutFirstSymbol(SerilogLogger.StringifyDictionary, key); 117 | value = value?.ToString(); 118 | } 119 | 120 | var property = propertyFactory.CreateProperty(key, value, destructureObjects); 121 | if (update) 122 | { 123 | logEvent.AddOrUpdateProperty(property); 124 | } 125 | else 126 | { 127 | logEvent.AddPropertyIfAbsent(property); 128 | } 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/Serilog.Extensions.Logging/Extensions/StringExtensions.cs: -------------------------------------------------------------------------------- 1 | #if !NET6_0_OR_GREATER && !NETSTANDARD2_1_OR_GREATER 2 | using System.Runtime.CompilerServices; 3 | 4 | namespace Serilog.Extensions; 5 | 6 | static class StringExtensions 7 | { 8 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 9 | public static bool StartsWith(this string str, char value) 10 | { 11 | return str.Length > 0 && str[0] == value; 12 | } 13 | } 14 | #endif 15 | -------------------------------------------------------------------------------- /src/Serilog.Extensions.Logging/LoggerSinkConfigurationExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2019 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 Serilog.Configuration; 16 | using Serilog.Core; 17 | using Serilog.Events; 18 | using Serilog.Extensions.Logging; 19 | 20 | namespace Serilog; 21 | 22 | /// 23 | /// Extensions for . 24 | /// 25 | public static class LoggerSinkConfigurationExtensions 26 | { 27 | /// 28 | /// Write Serilog events to the providers in . 29 | /// 30 | /// The `WriteTo` object. 31 | /// A to write events to. 32 | /// The minimum level for 33 | /// events passed through the sink. Ignored when is specified. 34 | /// A switch allowing the pass-through minimum level 35 | /// to be changed at runtime. 36 | /// A to allow method chaining. 37 | public static LoggerConfiguration Providers( 38 | this LoggerSinkConfiguration configuration, 39 | LoggerProviderCollection providers, 40 | LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum, 41 | LoggingLevelSwitch? levelSwitch = null) 42 | { 43 | if (configuration == null) throw new ArgumentNullException(nameof(configuration)); 44 | if (providers == null) throw new ArgumentNullException(nameof(providers)); 45 | return configuration.Sink(new LoggerProviderCollectionSink(providers), restrictedToMinimumLevel, levelSwitch); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Serilog.Extensions.Logging/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | global using System; 2 | global using System.Collections.Generic; 3 | global using System.Linq; 4 | global using System.Threading; 5 | 6 | using System.Reflection; 7 | using System.Runtime.CompilerServices; 8 | 9 | [assembly: InternalsVisibleTo("Serilog.Extensions.Logging.Tests, PublicKey=" + 10 | "0024000004800000940000000602000000240000525341310004000001000100fb8d13fd344a1c" + 11 | "6fe0fe83ef33c1080bf30690765bc6eb0df26ebfdf8f21670c64265b30db09f73a0dea5b3db4c9" + 12 | "d18dbf6d5a25af5ce9016f281014d79dc3b4201ac646c451830fc7e61a2dfd633d34c39f87b818" + 13 | "94191652df5ac63cc40c77f3542f702bda692e6e8a9158353df189007a49da0f3cfd55eb250066" + 14 | "b19485ec")] 15 | -------------------------------------------------------------------------------- /src/Serilog.Extensions.Logging/Serilog.Extensions.Logging.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Low-level Serilog provider for Microsoft.Extensions.Logging 5 | Microsoft;Serilog Contributors 6 | 8 | net462;netstandard2.0;netstandard2.1;net8.0;net9.0 9 | true 10 | serilog;Microsoft.Extensions.Logging 11 | serilog-extension-nuget.png 12 | https://github.com/serilog/serilog-extensions-logging 13 | Apache-2.0 14 | Serilog 15 | README.md 16 | 17 | NU5118 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | $(DefineConstants);FEATURE_ITUPLE 31 | 32 | 33 | 34 | $(DefineConstants);FEATURE_ITUPLE;FEATURE_ASYNCDISPOSABLE 35 | 36 | 37 | 38 | $(DefineConstants);FEATURE_ITUPLE;FEATURE_ASYNCDISPOSABLE 39 | 40 | 41 | -------------------------------------------------------------------------------- /src/Serilog.Extensions.Logging/SerilogLoggerFactoryExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using Microsoft.Extensions.Logging; 5 | using Serilog.Extensions.Logging; 6 | 7 | namespace Serilog; 8 | 9 | /// 10 | /// Extends with Serilog configuration methods. 11 | /// 12 | public static class SerilogLoggerFactoryExtensions 13 | { 14 | /// 15 | /// Add Serilog to the logging pipeline. 16 | /// 17 | /// The logger factory to configure. 18 | /// The Serilog logger; if not supplied, the static will be used. 19 | /// When true, dispose when the framework disposes the provider. If the 20 | /// logger is not specified but is true, the method will be 21 | /// called on the static class instead. 22 | /// Reference to the supplied . 23 | public static ILoggerFactory AddSerilog( 24 | this ILoggerFactory factory, 25 | ILogger? logger = null, 26 | bool dispose = false) 27 | { 28 | if (factory == null) throw new ArgumentNullException(nameof(factory)); 29 | 30 | factory.AddProvider(new SerilogLoggerProvider(logger, dispose)); 31 | 32 | return factory; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Serilog.Extensions.Logging/SerilogLoggingBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2017 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 Microsoft.Extensions.DependencyInjection; 16 | using Microsoft.Extensions.Logging; 17 | using Serilog.Extensions.Logging; 18 | 19 | namespace Serilog; 20 | 21 | /// 22 | /// Extends with Serilog configuration methods. 23 | /// 24 | public static class SerilogLoggingBuilderExtensions 25 | { 26 | /// 27 | /// Add Serilog to the logging pipeline. 28 | /// 29 | /// The to add logging provider to. 30 | /// The Serilog logger; if not supplied, the static will be used. 31 | /// When true, dispose when the framework disposes the provider. If the 32 | /// logger is not specified but is true, the method will be 33 | /// called on the static class instead. 34 | /// Reference to the supplied . 35 | public static ILoggingBuilder AddSerilog(this ILoggingBuilder builder, ILogger? logger = null, bool dispose = false) 36 | { 37 | if (builder == null) throw new ArgumentNullException(nameof(builder)); 38 | 39 | if (dispose) 40 | { 41 | builder.Services.AddSingleton(_ => new SerilogLoggerProvider(logger, true)); 42 | } 43 | else 44 | { 45 | builder.AddProvider(new SerilogLoggerProvider(logger)); 46 | } 47 | 48 | builder.AddFilter(null, LogLevel.Trace); 49 | 50 | return builder; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /test/Serilog.Extensions.Logging.Benchmarks/EventIdCapturingBenchmark.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2019 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 BenchmarkDotNet.Attributes; 16 | using BenchmarkDotNet.Running; 17 | using Microsoft.Extensions.Logging; 18 | using IMelLogger = Microsoft.Extensions.Logging.ILogger; 19 | using Serilog.Events; 20 | using Serilog.Extensions.Logging.Benchmarks.Support; 21 | using Xunit; 22 | 23 | namespace Serilog.Extensions.Logging.Benchmarks; 24 | 25 | [MemoryDiagnoser] 26 | public class EventIdCapturingBenchmark 27 | { 28 | readonly IMelLogger _melLogger; 29 | readonly ILogger _serilogContextualLogger; 30 | readonly CapturingSink _sink; 31 | const int LowId = 10, HighId = 101; 32 | const string Template = "This is an event"; 33 | 34 | public EventIdCapturingBenchmark() 35 | { 36 | _sink = new CapturingSink(); 37 | var underlyingLogger = new LoggerConfiguration().WriteTo.Sink(_sink).CreateLogger(); 38 | _serilogContextualLogger = underlyingLogger.ForContext(); 39 | _melLogger = new SerilogLoggerProvider(underlyingLogger).CreateLogger(GetType().FullName!); 40 | } 41 | 42 | static void VerifyEventId(LogEvent? evt, int? expectedId) 43 | { 44 | if (evt == null) throw new ArgumentNullException(nameof(evt)); 45 | if (expectedId == null) 46 | { 47 | Assert.False(evt.Properties.TryGetValue("EventId", out _)); 48 | } 49 | else 50 | { 51 | Assert.True(evt.Properties.TryGetValue("EventId", out var eventIdValue)); 52 | var structure = Assert.IsType(eventIdValue); 53 | var idValue = Assert.Single(structure.Properties, p => p.Name == "Id")?.Value; 54 | var scalar = Assert.IsType(idValue); 55 | Assert.Equal(expectedId.Value, scalar.Value); 56 | } 57 | } 58 | 59 | [Fact] 60 | public void Verify() 61 | { 62 | VerifyEventId(Native(), null); 63 | VerifyEventId(NoId(), null); 64 | VerifyEventId(LowNumbered(), LowId); 65 | VerifyEventId(HighNumbered(), HighId); 66 | } 67 | 68 | [Fact] 69 | public void Benchmark() 70 | { 71 | BenchmarkRunner.Run(); 72 | } 73 | 74 | [Benchmark(Baseline = true)] 75 | public LogEvent? Native() 76 | { 77 | _serilogContextualLogger.Information(Template); 78 | return _sink.Collect(); 79 | } 80 | 81 | [Benchmark] 82 | public LogEvent? NoId() 83 | { 84 | _melLogger.LogInformation(Template); 85 | return _sink.Collect(); 86 | } 87 | 88 | [Benchmark] 89 | public LogEvent? LowNumbered() 90 | { 91 | _melLogger.LogInformation(LowId, Template); 92 | return _sink.Collect(); 93 | } 94 | 95 | [Benchmark] 96 | public LogEvent? HighNumbered() 97 | { 98 | _melLogger.LogInformation(HighId, Template); 99 | return _sink.Collect(); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /test/Serilog.Extensions.Logging.Benchmarks/LogEventBenchmark.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2019 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 BenchmarkDotNet.Attributes; 16 | using BenchmarkDotNet.Running; 17 | using Microsoft.Extensions.Logging; 18 | using Xunit; 19 | using IMelLogger = Microsoft.Extensions.Logging.ILogger; 20 | 21 | #pragma warning disable xUnit1013 // Public method should be marked as test 22 | 23 | namespace Serilog.Extensions.Logging.Benchmarks; 24 | 25 | [MemoryDiagnoser] 26 | public class LogEventBenchmark 27 | { 28 | class Person 29 | { 30 | public string? Name { get; set; } 31 | public int Age { get; set; } 32 | public override string ToString() => "Fixed text"; 33 | } 34 | 35 | readonly IMelLogger _melLogger; 36 | readonly Person _bob, _alice; 37 | readonly ILogger _underlyingLogger; 38 | readonly EventId _eventId = new EventId(1, "Test"); 39 | 40 | public LogEventBenchmark() 41 | { 42 | _underlyingLogger = new LoggerConfiguration().CreateLogger(); 43 | _melLogger = new SerilogLoggerProvider(_underlyingLogger).CreateLogger(GetType().FullName!); 44 | _bob = new Person { Name = "Bob", Age = 42 }; 45 | _alice = new Person { Name = "Alice", Age = 42 }; 46 | } 47 | 48 | [Fact] 49 | public void Benchmark() 50 | { 51 | BenchmarkRunner.Run(); 52 | } 53 | 54 | [Benchmark(Baseline = true)] 55 | public void SerilogOnly() 56 | { 57 | _underlyingLogger.Information("Hello!"); 58 | } 59 | 60 | [Benchmark] 61 | public void SimpleEvent() 62 | { 63 | _melLogger.LogInformation("Hello!"); 64 | } 65 | 66 | [Benchmark] 67 | public void Template() 68 | { 69 | _melLogger.LogInformation("Hello, {Property1}!", 42); 70 | } 71 | 72 | [Benchmark] 73 | public void StringScope() 74 | { 75 | using var scope = _melLogger.BeginScope("Scope1"); 76 | _melLogger.LogInformation("Hello!"); 77 | } 78 | 79 | [Benchmark] 80 | public void TemplateScope() 81 | { 82 | using var scope = _melLogger.BeginScope("Scope1 {Property1}", 42); 83 | _melLogger.LogInformation("Hello!"); 84 | } 85 | 86 | [Benchmark] 87 | public void TupleScope() 88 | { 89 | using var scope = _melLogger.BeginScope(("Property1", 42)); 90 | _melLogger.LogInformation("Hello!"); 91 | } 92 | 93 | [Benchmark] 94 | public void DictionaryScope() 95 | { 96 | // Note that allocations here include the dictionary and boxed int. 97 | using var scope = _melLogger.BeginScope(new Dictionary { ["Property1"] = 42 }); 98 | _melLogger.LogInformation("Hello!"); 99 | } 100 | 101 | [Benchmark] 102 | public void Capturing() 103 | { 104 | _melLogger.LogInformation("Hi {@User} from {$Me}", _bob, _alice); 105 | } 106 | 107 | [Benchmark] 108 | public void CapturingScope() 109 | { 110 | using var scope = _melLogger.BeginScope("Hi {@User} from {$Me}", _bob, _alice); 111 | _melLogger.LogInformation("Hi"); 112 | } 113 | 114 | [Benchmark] 115 | public void LogInformationScoped() 116 | { 117 | using (var scope = _melLogger.BeginScope("Hi {@User} from {$Me}", _bob, _alice)) 118 | _melLogger.LogInformation("Hi"); 119 | } 120 | 121 | [Benchmark] 122 | public void LogInformation_WithEventId() 123 | { 124 | this._melLogger.Log( 125 | LogLevel.Information, 126 | _eventId, 127 | "Hi {@User} from {$Me}", 128 | _bob, 129 | _alice); 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /test/Serilog.Extensions.Logging.Benchmarks/Serilog.Extensions.Logging.Benchmarks.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0;net9.0 5 | false 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /test/Serilog.Extensions.Logging.Benchmarks/Support/CapturingSink.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2019 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 Serilog.Core; 16 | using Serilog.Events; 17 | 18 | namespace Serilog.Extensions.Logging.Benchmarks.Support; 19 | 20 | class CapturingSink : ILogEventSink 21 | { 22 | LogEvent? _emitted; 23 | 24 | public void Emit(LogEvent logEvent) 25 | { 26 | _emitted = logEvent; 27 | } 28 | 29 | public LogEvent? Collect() 30 | { 31 | var collected = _emitted; 32 | _emitted = null; 33 | return collected; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /test/Serilog.Extensions.Logging.Tests/ApiApprovalTests.cs: -------------------------------------------------------------------------------- 1 | #if NET7_0 2 | 3 | using PublicApiGenerator; 4 | using Shouldly; 5 | using Xunit; 6 | 7 | namespace Serilog.Extensions.Logging.Tests; 8 | 9 | public class ApiApprovalTests 10 | { 11 | [Fact] 12 | public void PublicApi_Should_Not_Change_Unintentionally() 13 | { 14 | var assembly = typeof(LoggerSinkConfigurationExtensions).Assembly; 15 | var publicApi = assembly.GeneratePublicApi( 16 | new() 17 | { 18 | IncludeAssemblyAttributes = false, 19 | ExcludeAttributes = new[] { "System.Diagnostics.DebuggerDisplayAttribute" }, 20 | }); 21 | 22 | publicApi.ShouldMatchApproved(options => options.WithFilenameGenerator((_, _, fileType, fileExtension) => $"{assembly.GetName().Name!}.{fileType}.{fileExtension}")); 23 | } 24 | } 25 | 26 | #endif 27 | -------------------------------------------------------------------------------- /test/Serilog.Extensions.Logging.Tests/DisposeTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using Microsoft.Extensions.Logging; 3 | using Serilog.Core; 4 | using Serilog.Events; 5 | using Xunit; 6 | 7 | namespace Serilog.Extensions.Logging.Tests; 8 | 9 | public class DisposeTests 10 | { 11 | private readonly DisposableSink _sink; 12 | private readonly Logger _serilogLogger; 13 | 14 | public DisposeTests() 15 | { 16 | _sink = new DisposableSink(); 17 | _serilogLogger = new LoggerConfiguration() 18 | .WriteTo.Sink(_sink) 19 | .CreateLogger(); 20 | } 21 | 22 | [Fact] 23 | public void DisposesProviderWhenDisposeIsTrue() 24 | { 25 | var services = new ServiceCollection() 26 | .AddLogging(builder => builder.AddSerilog(logger: _serilogLogger, dispose: true)) 27 | .BuildServiceProvider(); 28 | 29 | // Get a logger so that we ensure SerilogLoggerProvider is created 30 | var logger = services.GetRequiredService>(); 31 | logger.LogInformation("Hello, world!"); 32 | 33 | services.Dispose(); 34 | Assert.True(_sink.DisposeCalled); 35 | Assert.False(_sink.DisposeAsyncCalled); 36 | } 37 | 38 | #if NET8_0_OR_GREATER 39 | [Fact] 40 | public async Task DisposesProviderAsyncWhenDisposeIsTrue() 41 | { 42 | var services = new ServiceCollection() 43 | .AddLogging(builder => builder.AddSerilog(logger: _serilogLogger, dispose: true)) 44 | .BuildServiceProvider(); 45 | 46 | // Get a logger so that we ensure SerilogLoggerProvider is created 47 | var logger = services.GetRequiredService>(); 48 | logger.LogInformation("Hello, world!"); 49 | 50 | await services.DisposeAsync(); 51 | Assert.False(_sink.DisposeCalled); 52 | Assert.True(_sink.DisposeAsyncCalled); 53 | } 54 | #endif 55 | 56 | private sealed class DisposableSink : ILogEventSink, IDisposable, IAsyncDisposable 57 | { 58 | public bool DisposeAsyncCalled { get; private set; } 59 | public bool DisposeCalled { get; private set; } 60 | 61 | public void Dispose() => DisposeCalled = true; 62 | public ValueTask DisposeAsync() 63 | { 64 | DisposeAsyncCalled = true; 65 | return default; 66 | } 67 | 68 | public void Emit(LogEvent logEvent) 69 | { 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /test/Serilog.Extensions.Logging.Tests/EventIdPropertyCacheTests.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 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 Microsoft.Extensions.Logging; 16 | using Serilog.Events; 17 | using Xunit; 18 | 19 | namespace Serilog.Extensions.Logging.Tests; 20 | 21 | public class EventIdPropertyCacheTests 22 | { 23 | [Fact] 24 | public void CreatesPropertyValueWithCorrectIdAndName() 25 | { 26 | // Arrange 27 | const int id = 101; 28 | const string name = "TestEvent"; 29 | var eventId = new EventId(id, name); 30 | 31 | var cache = new EventIdPropertyCache(); 32 | 33 | // Act 34 | var eventPropertyValue = cache.GetOrCreatePropertyValue(eventId); 35 | 36 | // Assert 37 | var value = Assert.IsType(eventPropertyValue); 38 | 39 | Assert.Equal(2, value.Properties.Count); 40 | 41 | var idValue = value.Properties.Single(property => property.Name == "Id").Value; 42 | var nameValue = value.Properties.Single(property => property.Name == "Name").Value; 43 | 44 | var scalarId = Assert.IsType(idValue); 45 | var scalarName = Assert.IsType(nameValue); 46 | 47 | Assert.Equal(id, scalarId.Value); 48 | Assert.Equal(name, scalarName.Value); 49 | } 50 | 51 | [Fact] 52 | public void EventsWithDSameKeysHaveSameReferences() 53 | { 54 | // Arrange 55 | var cache = new EventIdPropertyCache(); 56 | 57 | // Act 58 | var propertyValue1 = cache.GetOrCreatePropertyValue(new EventId(1, "Name1")); 59 | var propertyValue2 = cache.GetOrCreatePropertyValue(new EventId(1, "Name1")); 60 | 61 | // Assert 62 | Assert.Same(propertyValue1, propertyValue2); 63 | } 64 | 65 | [Theory] 66 | [InlineData(1, "SomeName", 1, "AnotherName")] 67 | [InlineData(1, "SomeName", 2, "SomeName")] 68 | [InlineData(1, "SomeName", 2, "AnotherName")] 69 | public void EventsWithDifferentKeysHaveDifferentReferences(int firstId, string firstName, int secondId, string secondName) 70 | { 71 | // Arrange 72 | var cache = new EventIdPropertyCache(); 73 | 74 | // Act 75 | var propertyValue1 = cache.GetOrCreatePropertyValue(new EventId(firstId, firstName)); 76 | var propertyValue2 = cache.GetOrCreatePropertyValue(new EventId(secondId, secondName)); 77 | 78 | // Assert 79 | Assert.NotSame(propertyValue1, propertyValue2); 80 | } 81 | 82 | 83 | [Fact] 84 | public void WhenLimitIsNotOverSameEventsHaveSameReferences() 85 | { 86 | // Arrange 87 | var eventId = new EventId(101, "test"); 88 | var cache = new EventIdPropertyCache(); 89 | 90 | // Act 91 | var propertyValue1 = cache.GetOrCreatePropertyValue(eventId); 92 | var propertyValue2 = cache.GetOrCreatePropertyValue(eventId); 93 | 94 | // Assert 95 | Assert.Same(propertyValue1, propertyValue2); 96 | } 97 | 98 | [Fact] 99 | public void WhenLimitIsOverSameEventsHaveDifferentReferences() 100 | { 101 | // Arrange 102 | var cache = new EventIdPropertyCache(maxCachedProperties: 1); 103 | cache.GetOrCreatePropertyValue(new EventId(1, "InitialEvent")); 104 | 105 | var eventId = new EventId(101, "DifferentEvent"); 106 | 107 | // Act 108 | var propertyValue1 = cache.GetOrCreatePropertyValue(eventId); 109 | var propertyValue2 = cache.GetOrCreatePropertyValue(eventId); 110 | 111 | // Assert 112 | Assert.NotSame(propertyValue1, propertyValue2); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /test/Serilog.Extensions.Logging.Tests/LoggerProviderCollectionSinkTests.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using Microsoft.Extensions.Logging; 5 | using Serilog.Extensions.Logging.Tests.Support; 6 | using Xunit; 7 | 8 | namespace Serilog.Extensions.Logging.Tests; 9 | 10 | public class LoggerProviderCollectionSinkTests 11 | { 12 | const string Name = "test"; 13 | const string TestMessage = "This is a test"; 14 | 15 | static Tuple SetUp(LogLevel logLevel) 16 | { 17 | var providers = new LoggerProviderCollection(); 18 | var provider = new ExtensionsProvider(logLevel); 19 | providers.AddProvider(provider); 20 | var serilogLogger = new LoggerConfiguration() 21 | .WriteTo.Providers(providers) 22 | .MinimumLevel.Is(LevelConvert.ToSerilogLevel(logLevel)) 23 | .CreateLogger(); 24 | 25 | var logger = (SerilogLogger)new SerilogLoggerProvider(serilogLogger).CreateLogger(Name); 26 | 27 | return new Tuple(logger, provider); 28 | } 29 | 30 | [Fact] 31 | public void LogsCorrectLevel() 32 | { 33 | var (logger, sink) = SetUp(LogLevel.Trace); 34 | 35 | logger.Log(LogLevel.Trace, 0, TestMessage, null!, null!); 36 | logger.Log(LogLevel.Debug, 0, TestMessage, null!, null!); 37 | logger.Log(LogLevel.Information, 0, TestMessage, null!, null!); 38 | logger.Log(LogLevel.Warning, 0, TestMessage, null!, null!); 39 | logger.Log(LogLevel.Error, 0, TestMessage, null!, null!); 40 | logger.Log(LogLevel.Critical, 0, TestMessage, null!, null!); 41 | 42 | Assert.Equal(6, sink.Writes.Count); 43 | Assert.Equal(LogLevel.Trace, sink.Writes[0].logLevel); 44 | Assert.Equal(LogLevel.Debug, sink.Writes[1].logLevel); 45 | Assert.Equal(LogLevel.Information, sink.Writes[2].logLevel); 46 | Assert.Equal(LogLevel.Warning, sink.Writes[3].logLevel); 47 | Assert.Equal(LogLevel.Error, sink.Writes[4].logLevel); 48 | Assert.Equal(LogLevel.Critical, sink.Writes[5].logLevel); 49 | } 50 | 51 | [Fact] 52 | public void LogsCorrectEventId() 53 | { 54 | var (logger, sink) = SetUp(LogLevel.Trace); 55 | 56 | logger.Log(LogLevel.Trace, new EventId(1, nameof(LogLevel.Trace)), TestMessage, null!, null!); 57 | logger.Log(LogLevel.Debug, new EventId(2, nameof(LogLevel.Debug)), TestMessage, null!, null!); 58 | logger.Log(LogLevel.Information, new EventId(3, nameof(LogLevel.Information)), TestMessage, null!, null!); 59 | logger.Log(LogLevel.Warning, new EventId(4, nameof(LogLevel.Warning)), TestMessage, null!, null!); 60 | logger.Log(LogLevel.Error, new EventId(5, nameof(LogLevel.Error)), TestMessage, null!, null!); 61 | logger.Log(LogLevel.Critical, new EventId(6, nameof(LogLevel.Critical)), TestMessage, null!, null!); 62 | 63 | Assert.Equal(6, sink.Writes.Count); 64 | 65 | Assert.Equal(1, sink.Writes[0].eventId.Id); 66 | Assert.Equal(nameof(LogLevel.Trace), sink.Writes[0].eventId.Name); 67 | 68 | Assert.Equal(2, sink.Writes[1].eventId.Id); 69 | Assert.Equal(nameof(LogLevel.Debug), sink.Writes[1].eventId.Name); 70 | 71 | Assert.Equal(3, sink.Writes[2].eventId.Id); 72 | Assert.Equal(nameof(LogLevel.Information), sink.Writes[2].eventId.Name); 73 | 74 | Assert.Equal(4, sink.Writes[3].eventId.Id); 75 | Assert.Equal(nameof(LogLevel.Warning), sink.Writes[3].eventId.Name); 76 | 77 | Assert.Equal(5, sink.Writes[4].eventId.Id); 78 | Assert.Equal(nameof(LogLevel.Error), sink.Writes[4].eventId.Name); 79 | 80 | Assert.Equal(6, sink.Writes[5].eventId.Id); 81 | Assert.Equal(nameof(LogLevel.Critical), sink.Writes[5].eventId.Name); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /test/Serilog.Extensions.Logging.Tests/Serilog.Extensions.Logging.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0;net9.0;net48 5 | false 6 | 7 | 8 | 9 | $(DefineConstants);FORCE_W3C_ACTIVITY_ID 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /test/Serilog.Extensions.Logging.Tests/Serilog.Extensions.Logging.approved.txt: -------------------------------------------------------------------------------- 1 | namespace Serilog.Extensions.Logging 2 | { 3 | public static class LevelConvert 4 | { 5 | public static Microsoft.Extensions.Logging.LogLevel ToExtensionsLevel(Serilog.Events.LogEventLevel logEventLevel) { } 6 | public static Serilog.Events.LogEventLevel ToSerilogLevel(Microsoft.Extensions.Logging.LogLevel logLevel) { } 7 | } 8 | public class LoggerProviderCollection : System.IDisposable 9 | { 10 | public LoggerProviderCollection() { } 11 | public System.Collections.Generic.IEnumerable Providers { get; } 12 | public void AddProvider(Microsoft.Extensions.Logging.ILoggerProvider provider) { } 13 | public void Dispose() { } 14 | } 15 | public class SerilogLoggerFactory : Microsoft.Extensions.Logging.ILoggerFactory, System.IDisposable 16 | { 17 | public SerilogLoggerFactory(Serilog.ILogger? logger = null, bool dispose = false, Serilog.Extensions.Logging.LoggerProviderCollection? providerCollection = null) { } 18 | public void AddProvider(Microsoft.Extensions.Logging.ILoggerProvider provider) { } 19 | public Microsoft.Extensions.Logging.ILogger CreateLogger(string categoryName) { } 20 | public void Dispose() { } 21 | } 22 | [Microsoft.Extensions.Logging.ProviderAlias("Serilog")] 23 | public class SerilogLoggerProvider : Microsoft.Extensions.Logging.ILoggerProvider, Serilog.Core.ILogEventEnricher, System.IDisposable 24 | { 25 | public SerilogLoggerProvider(Serilog.ILogger? logger = null, bool dispose = false) { } 26 | public System.IDisposable BeginScope(T state) { } 27 | public Microsoft.Extensions.Logging.ILogger CreateLogger(string name) { } 28 | public void Dispose() { } 29 | public void Enrich(Serilog.Events.LogEvent logEvent, Serilog.Core.ILogEventPropertyFactory propertyFactory) { } 30 | } 31 | } 32 | namespace Serilog 33 | { 34 | public static class LoggerSinkConfigurationExtensions 35 | { 36 | public static Serilog.LoggerConfiguration Providers(this Serilog.Configuration.LoggerSinkConfiguration configuration, Serilog.Extensions.Logging.LoggerProviderCollection providers, Serilog.Events.LogEventLevel restrictedToMinimumLevel = 0, Serilog.Core.LoggingLevelSwitch? levelSwitch = null) { } 37 | } 38 | public static class SerilogLoggerFactoryExtensions 39 | { 40 | public static Microsoft.Extensions.Logging.ILoggerFactory AddSerilog(this Microsoft.Extensions.Logging.ILoggerFactory factory, Serilog.ILogger? logger = null, bool dispose = false) { } 41 | } 42 | public static class SerilogLoggingBuilderExtensions 43 | { 44 | public static Microsoft.Extensions.Logging.ILoggingBuilder AddSerilog(this Microsoft.Extensions.Logging.ILoggingBuilder builder, Serilog.ILogger? logger = null, bool dispose = false) { } 45 | } 46 | } -------------------------------------------------------------------------------- /test/Serilog.Extensions.Logging.Tests/SerilogLogValuesTests.cs: -------------------------------------------------------------------------------- 1 | using Serilog.Events; 2 | using Serilog.Parsing; 3 | using Xunit; 4 | 5 | namespace Serilog.Extensions.Logging.Tests; 6 | 7 | public class SerilogLogValuesTests 8 | { 9 | [Fact] 10 | public void OriginalFormatIsExposed() 11 | { 12 | const string format = "Hello, {Name}!"; 13 | var mt = new MessageTemplateParser().Parse(format); 14 | var lv = new SerilogLogValues(mt, new Dictionary()); 15 | var kvp = lv.Single(); 16 | Assert.Equal("{OriginalFormat}", kvp.Key); 17 | Assert.Equal(format, kvp.Value); 18 | } 19 | 20 | [Fact] 21 | public void ScalarPropertiesAreSimplified() 22 | { 23 | const string name = "Scalar"; 24 | var scalar = 15; 25 | var lv = new SerilogLogValues(MessageTemplate.Empty, new Dictionary { [name] = new ScalarValue(scalar) }); 26 | var kvp = lv.Single(p => p.Key == name); 27 | var sv = Assert.IsType(kvp.Value); 28 | Assert.Equal(scalar, sv); 29 | } 30 | 31 | [Fact] 32 | public void NonscalarPropertiesAreWrapped() 33 | { 34 | const string name = "Sequence"; 35 | var seq = new SequenceValue(Enumerable.Empty()); 36 | var lv = new SerilogLogValues(MessageTemplate.Empty, new Dictionary { [name] = seq }); 37 | var kvp = lv.Single(p => p.Key == name); 38 | var sv = Assert.IsType(kvp.Value); 39 | Assert.Equal(seq, sv); 40 | } 41 | 42 | [Fact] 43 | public void MessageTemplatesAreRendered() 44 | { 45 | const string format = "Hello, {Name}!"; 46 | var mt = new MessageTemplateParser().Parse(format); 47 | var lv = new SerilogLogValues(mt, new Dictionary { ["Name"] = new ScalarValue("World") }); 48 | Assert.Equal("Hello, \"World\"!", lv.ToString()); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /test/Serilog.Extensions.Logging.Tests/SerilogLoggerScopeTests.cs: -------------------------------------------------------------------------------- 1 | // Copyright © Serilog Contributors 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using Serilog.Events; 5 | using Serilog.Extensions.Logging.Tests.Support; 6 | 7 | using Xunit; 8 | 9 | namespace Serilog.Extensions.Logging.Tests; 10 | public class SerilogLoggerScopeTests 11 | { 12 | static (SerilogLoggerProvider, LogEventPropertyFactory, LogEvent) SetUp() 13 | { 14 | var loggerProvider = new SerilogLoggerProvider(); 15 | 16 | var logEventPropertyFactory = new LogEventPropertyFactory(); 17 | 18 | var dateTimeOffset = new DateTimeOffset(2000, 1, 1, 0, 0, 0, TimeSpan.Zero); 19 | var messageTemplate = new MessageTemplate(Enumerable.Empty()); 20 | var properties = Enumerable.Empty(); 21 | var logEvent = new LogEvent(dateTimeOffset, LogEventLevel.Information, null, messageTemplate, properties); 22 | 23 | return (loggerProvider, logEventPropertyFactory, logEvent); 24 | } 25 | 26 | [Fact] 27 | public void EnrichWithDictionaryStringObject() 28 | { 29 | const string propertyName = "Foo"; 30 | const string expectedValue = "Bar"; 31 | 32 | var(loggerProvider, logEventPropertyFactory, logEvent) = SetUp(); 33 | 34 | 35 | var state = new Dictionary() { { propertyName, expectedValue } }; 36 | 37 | var loggerScope = new SerilogLoggerScope(loggerProvider, state); 38 | 39 | loggerScope.EnrichAndCreateScopeItem(logEvent, logEventPropertyFactory, out LogEventPropertyValue? scopeItem); 40 | 41 | Assert.Contains(propertyName, logEvent.Properties); 42 | 43 | var scalarValue = logEvent.Properties[propertyName] as ScalarValue; 44 | Assert.NotNull(scalarValue); 45 | 46 | var actualValue = scalarValue.Value as string; 47 | Assert.NotNull(actualValue); 48 | Assert.Equal(expectedValue, actualValue); 49 | } 50 | 51 | [Fact] 52 | public void EnrichWithIEnumerableKeyValuePairStringObject() 53 | { 54 | const string propertyName = "Foo"; 55 | const string expectedValue = "Bar"; 56 | 57 | var (loggerProvider, logEventPropertyFactory, logEvent) = SetUp(); 58 | 59 | 60 | var state = new KeyValuePair[] { new KeyValuePair(propertyName, expectedValue) }; 61 | 62 | var loggerScope = new SerilogLoggerScope(loggerProvider, state); 63 | 64 | loggerScope.EnrichAndCreateScopeItem(logEvent, logEventPropertyFactory, out LogEventPropertyValue? scopeItem); 65 | 66 | Assert.Contains(propertyName, logEvent.Properties); 67 | 68 | var scalarValue = logEvent.Properties[propertyName] as ScalarValue; 69 | Assert.NotNull(scalarValue); 70 | 71 | var actualValue = scalarValue.Value as string; 72 | Assert.NotNull(actualValue); 73 | Assert.Equal(expectedValue, actualValue); 74 | } 75 | 76 | [Fact] 77 | public void EnrichWithTupleStringObject() 78 | { 79 | const string propertyName = "Foo"; 80 | const string expectedValue = "Bar"; 81 | 82 | var (loggerProvider, logEventPropertyFactory, logEvent) = SetUp(); 83 | 84 | #if NET48 85 | var state = (propertyName, (object)expectedValue); 86 | #else 87 | var state = (propertyName, expectedValue); 88 | #endif 89 | 90 | var loggerScope = new SerilogLoggerScope(loggerProvider, state); 91 | 92 | loggerScope.EnrichAndCreateScopeItem(logEvent, logEventPropertyFactory, out LogEventPropertyValue? scopeItem); 93 | 94 | Assert.Contains(propertyName, logEvent.Properties); 95 | 96 | var scalarValue = logEvent.Properties[propertyName] as ScalarValue; 97 | Assert.NotNull(scalarValue); 98 | 99 | var actualValue = scalarValue.Value as string; 100 | Assert.NotNull(actualValue); 101 | Assert.Equal(expectedValue, actualValue); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /test/Serilog.Extensions.Logging.Tests/SerilogLoggerTests.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System.Collections; 5 | using System.Diagnostics; 6 | using Microsoft.Extensions.DependencyInjection; 7 | using Serilog.Events; 8 | using Microsoft.Extensions.Logging; 9 | using Serilog.Debugging; 10 | using Serilog.Extensions.Logging.Tests.Support; 11 | using Xunit; 12 | using Serilog.Core; 13 | // ReSharper disable AccessToDisposedClosure 14 | 15 | namespace Serilog.Extensions.Logging.Tests; 16 | 17 | public class SerilogLoggerTest 18 | { 19 | const string Name = "test"; 20 | const string TestMessage = "This is a test"; 21 | 22 | static Tuple SetUp(LogLevel logLevel, IExternalScopeProvider? externalScopeProvider = null) 23 | { 24 | var sink = new CollectingSink(); 25 | 26 | var serilogLogger = new LoggerConfiguration() 27 | .WriteTo.Sink(sink) 28 | .MinimumLevel.Is(LevelConvert.ToSerilogLevel(logLevel)) 29 | .CreateLogger(); 30 | 31 | var provider = new SerilogLoggerProvider(serilogLogger); 32 | var logger = (SerilogLogger)provider.CreateLogger(Name); 33 | 34 | if (externalScopeProvider is not null) 35 | { 36 | provider.SetScopeProvider(externalScopeProvider); 37 | } 38 | 39 | return new Tuple(logger, sink); 40 | } 41 | 42 | [Fact] 43 | public void LogsWhenNullFilterGiven() 44 | { 45 | var (logger, sink) = SetUp(LogLevel.Trace); 46 | 47 | logger.Log(LogLevel.Information, 0, TestMessage, null!, null!); 48 | 49 | Assert.Single(sink.Writes); 50 | } 51 | 52 | [Fact] 53 | public void LogsCorrectLevel() 54 | { 55 | var (logger, sink) = SetUp(LogLevel.Trace); 56 | 57 | logger.Log(LogLevel.Trace, 0, TestMessage, null!, null!); 58 | logger.Log(LogLevel.Debug, 0, TestMessage, null!, null!); 59 | logger.Log(LogLevel.Information, 0, TestMessage, null!, null!); 60 | logger.Log(LogLevel.Warning, 0, TestMessage, null!, null!); 61 | logger.Log(LogLevel.Error, 0, TestMessage, null!, null!); 62 | logger.Log(LogLevel.Critical, 0, TestMessage, null!, null!); 63 | logger.Log(LogLevel.None, 0, TestMessage, null!, null!); 64 | 65 | Assert.Equal(6, sink.Writes.Count); 66 | Assert.Equal(LogEventLevel.Verbose, sink.Writes[0].Level); 67 | Assert.Equal(LogEventLevel.Debug, sink.Writes[1].Level); 68 | Assert.Equal(LogEventLevel.Information, sink.Writes[2].Level); 69 | Assert.Equal(LogEventLevel.Warning, sink.Writes[3].Level); 70 | Assert.Equal(LogEventLevel.Error, sink.Writes[4].Level); 71 | Assert.Equal(LogEventLevel.Fatal, sink.Writes[5].Level); 72 | } 73 | 74 | 75 | [Theory] 76 | [InlineData(LogLevel.Trace, true)] 77 | [InlineData(LogLevel.Debug, true)] 78 | [InlineData(LogLevel.Information, true)] 79 | [InlineData(LogLevel.Warning, true)] 80 | [InlineData(LogLevel.Error, true)] 81 | [InlineData(LogLevel.Critical, true)] 82 | [InlineData(LogLevel.None, false)] 83 | public void IsEnabledCorrect(LogLevel logLevel, bool isEnabled) 84 | { 85 | var (logger, _) = SetUp(LogLevel.Trace); 86 | 87 | Assert.Equal(isEnabled, logger.IsEnabled(logLevel)); 88 | } 89 | 90 | [Theory] 91 | [InlineData(LogLevel.Trace, LogLevel.Trace, 1)] 92 | [InlineData(LogLevel.Trace, LogLevel.Debug, 1)] 93 | [InlineData(LogLevel.Trace, LogLevel.Information, 1)] 94 | [InlineData(LogLevel.Trace, LogLevel.Warning, 1)] 95 | [InlineData(LogLevel.Trace, LogLevel.Error, 1)] 96 | [InlineData(LogLevel.Trace, LogLevel.Critical, 1)] 97 | [InlineData(LogLevel.Trace, LogLevel.None, 0)] 98 | [InlineData(LogLevel.Debug, LogLevel.Trace, 0)] 99 | [InlineData(LogLevel.Debug, LogLevel.Debug, 1)] 100 | [InlineData(LogLevel.Debug, LogLevel.Information, 1)] 101 | [InlineData(LogLevel.Debug, LogLevel.Warning, 1)] 102 | [InlineData(LogLevel.Debug, LogLevel.Error, 1)] 103 | [InlineData(LogLevel.Debug, LogLevel.Critical, 1)] 104 | [InlineData(LogLevel.Debug, LogLevel.None, 0)] 105 | [InlineData(LogLevel.Information, LogLevel.Trace, 0)] 106 | [InlineData(LogLevel.Information, LogLevel.Debug, 0)] 107 | [InlineData(LogLevel.Information, LogLevel.Information, 1)] 108 | [InlineData(LogLevel.Information, LogLevel.Warning, 1)] 109 | [InlineData(LogLevel.Information, LogLevel.Error, 1)] 110 | [InlineData(LogLevel.Information, LogLevel.Critical, 1)] 111 | [InlineData(LogLevel.Information, LogLevel.None, 0)] 112 | [InlineData(LogLevel.Warning, LogLevel.Trace, 0)] 113 | [InlineData(LogLevel.Warning, LogLevel.Debug, 0)] 114 | [InlineData(LogLevel.Warning, LogLevel.Information, 0)] 115 | [InlineData(LogLevel.Warning, LogLevel.Warning, 1)] 116 | [InlineData(LogLevel.Warning, LogLevel.Error, 1)] 117 | [InlineData(LogLevel.Warning, LogLevel.Critical, 1)] 118 | [InlineData(LogLevel.Warning, LogLevel.None, 0)] 119 | [InlineData(LogLevel.Error, LogLevel.Trace, 0)] 120 | [InlineData(LogLevel.Error, LogLevel.Debug, 0)] 121 | [InlineData(LogLevel.Error, LogLevel.Information, 0)] 122 | [InlineData(LogLevel.Error, LogLevel.Warning, 0)] 123 | [InlineData(LogLevel.Error, LogLevel.Error, 1)] 124 | [InlineData(LogLevel.Error, LogLevel.Critical, 1)] 125 | [InlineData(LogLevel.Error, LogLevel.None, 0)] 126 | [InlineData(LogLevel.Critical, LogLevel.Trace, 0)] 127 | [InlineData(LogLevel.Critical, LogLevel.Debug, 0)] 128 | [InlineData(LogLevel.Critical, LogLevel.Information, 0)] 129 | [InlineData(LogLevel.Critical, LogLevel.Warning, 0)] 130 | [InlineData(LogLevel.Critical, LogLevel.Error, 0)] 131 | [InlineData(LogLevel.Critical, LogLevel.Critical, 1)] 132 | [InlineData(LogLevel.Critical, LogLevel.None, 0)] 133 | public void LogsWhenEnabled(LogLevel minLevel, LogLevel logLevel, int expected) 134 | { 135 | var (logger, sink) = SetUp(minLevel); 136 | 137 | logger.Log(logLevel, 0, TestMessage, null!, null!); 138 | 139 | Assert.Equal(expected, sink.Writes.Count); 140 | } 141 | 142 | [Fact] 143 | public void LogsCorrectMessage() 144 | { 145 | var (logger, sink) = SetUp(LogLevel.Trace); 146 | 147 | logger.Log(LogLevel.Information, 0, null!, null!, null!); 148 | logger.Log(LogLevel.Information, 0, TestMessage, null!, null!); 149 | logger.Log(LogLevel.Information, 0, null!, null!, (_, _) => TestMessage); 150 | 151 | Assert.Equal(3, sink.Writes.Count); 152 | 153 | Assert.Single(sink.Writes[0].Properties); 154 | Assert.Empty(sink.Writes[0].RenderMessage()); 155 | 156 | Assert.Equal(2, sink.Writes[1].Properties.Count); 157 | Assert.True(sink.Writes[1].Properties.ContainsKey("State")); 158 | Assert.Equal(TestMessage, sink.Writes[1].RenderMessage()); 159 | 160 | Assert.Equal(2, sink.Writes[2].Properties.Count); 161 | Assert.True(sink.Writes[2].Properties.ContainsKey("Message")); 162 | Assert.Equal(TestMessage, sink.Writes[2].RenderMessage()); 163 | } 164 | 165 | [Fact] 166 | public void CarriesException() 167 | { 168 | var (logger, sink) = SetUp(LogLevel.Trace); 169 | 170 | var exception = new Exception(); 171 | 172 | logger.Log(LogLevel.Information, 0, "Test", exception, null!); 173 | 174 | Assert.Single(sink.Writes); 175 | Assert.Same(exception, sink.Writes[0].Exception); 176 | } 177 | 178 | [Fact] 179 | public void SingleScopeProperty() 180 | { 181 | var (logger, sink) = SetUp(LogLevel.Trace); 182 | 183 | using (logger.BeginScope(new FoodScope("pizza"))) 184 | { 185 | logger.Log(LogLevel.Information, 0, TestMessage, null!, null!); 186 | } 187 | 188 | Assert.Single(sink.Writes); 189 | Assert.True(sink.Writes[0].Properties.ContainsKey("Name")); 190 | Assert.Equal("\"pizza\"", sink.Writes[0].Properties["Name"].ToString()); 191 | } 192 | 193 | [Fact] 194 | public void StringifyScopeProperty() 195 | { 196 | var (logger, sink) = SetUp(LogLevel.Trace); 197 | 198 | using (logger.BeginScope("{$values}", new [] { 1, 2, 3, 4 })) 199 | { 200 | logger.Log(LogLevel.Information, 0, TestMessage, null!, null!); 201 | } 202 | 203 | Assert.Single(sink.Writes); 204 | Assert.True(sink.Writes[0].Properties.ContainsKey("values")); 205 | Assert.Equal("\"System.Int32[]\"", sink.Writes[0].Properties["values"].ToString()); 206 | } 207 | 208 | [Fact] 209 | public void NestedScopeSameProperty() 210 | { 211 | var (logger, sink) = SetUp(LogLevel.Trace); 212 | 213 | using (logger.BeginScope(new FoodScope("avocado"))) 214 | { 215 | using (logger.BeginScope(new FoodScope("bacon"))) 216 | { 217 | logger.Log(LogLevel.Information, 0, TestMessage, null!, null!); 218 | } 219 | } 220 | 221 | // Should retain the property of the most specific scope 222 | Assert.Single(sink.Writes); 223 | Assert.True(sink.Writes[0].Properties.ContainsKey("Name")); 224 | Assert.Equal("\"bacon\"", sink.Writes[0].Properties["Name"].ToString()); 225 | } 226 | 227 | [Fact] 228 | public void NestedScopesDifferentProperties() 229 | { 230 | var (logger, sink) = SetUp(LogLevel.Trace); 231 | 232 | using (logger.BeginScope(new FoodScope("spaghetti"))) 233 | { 234 | using (logger.BeginScope(new LuckyScope(7))) 235 | { 236 | logger.Log(LogLevel.Information, 0, TestMessage, null!, null!); 237 | } 238 | } 239 | 240 | Assert.Single(sink.Writes); 241 | Assert.True(sink.Writes[0].Properties.ContainsKey("Name")); 242 | Assert.Equal("\"spaghetti\"", sink.Writes[0].Properties["Name"].ToString()); 243 | Assert.True(sink.Writes[0].Properties.ContainsKey("LuckyNumber")); 244 | Assert.Equal("7", sink.Writes[0].Properties["LuckyNumber"].ToString()); 245 | } 246 | 247 | [Fact] 248 | public void CarriesMessageTemplateProperties() 249 | { 250 | var selfLog = new StringWriter(); 251 | SelfLog.Enable(selfLog); 252 | 253 | var (logger, sink) = SetUp(LogLevel.Trace); 254 | 255 | logger.LogInformation("Hello, {Recipient}", "World"); 256 | 257 | Assert.True(sink.Writes[0].Properties.ContainsKey("Recipient")); 258 | Assert.Equal("\"World\"", sink.Writes[0].Properties["Recipient"].ToString()); 259 | Assert.Equal("Hello, {Recipient}", sink.Writes[0].MessageTemplate.Text); 260 | 261 | SelfLog.Disable(); 262 | Assert.Empty(selfLog.ToString()); 263 | } 264 | 265 | [Fact] 266 | public void CarriesMessageTemplatePropertiesWhenStringificationIsUsed() 267 | { 268 | var selfLog = new StringWriter(); 269 | SelfLog.Enable(selfLog); 270 | var (logger, sink) = SetUp(LogLevel.Trace); 271 | var array = new[] { 1, 2, 3, 4 }; 272 | 273 | logger.LogInformation("{$array}", array); 274 | 275 | Assert.True(sink.Writes[0].Properties.ContainsKey("array")); 276 | Assert.Equal("\"System.Int32[]\"", sink.Writes[0].Properties["array"].ToString()); 277 | Assert.Equal("{$array}", sink.Writes[0].MessageTemplate.Text); 278 | 279 | SelfLog.Disable(); 280 | Assert.Empty(selfLog.ToString()); 281 | } 282 | 283 | [Fact] 284 | public void CarriesEventIdIfNonzero() 285 | { 286 | var (logger, sink) = SetUp(LogLevel.Trace); 287 | 288 | const int expected = 42; 289 | 290 | logger.Log(LogLevel.Information, expected, "Test", null!, null!); 291 | 292 | Assert.Single(sink.Writes); 293 | 294 | var eventId = (StructureValue) sink.Writes[0].Properties["EventId"]; 295 | var id = (ScalarValue) eventId.Properties.Single(p => p.Name == "Id").Value; 296 | Assert.Equal(42, id.Value); 297 | } 298 | 299 | [Fact] 300 | public void WhenDisposeIsFalseProvidedLoggerIsNotDisposed() 301 | { 302 | var logger = new DisposeTrackingLogger(); 303 | // ReSharper disable once RedundantArgumentDefaultValue 304 | var provider = new SerilogLoggerProvider(logger, false); 305 | provider.Dispose(); 306 | Assert.False(logger.IsDisposed); 307 | } 308 | 309 | [Fact] 310 | public void WhenDisposeIsTrueProvidedLoggerIsDisposed() 311 | { 312 | var logger = new DisposeTrackingLogger(); 313 | var provider = new SerilogLoggerProvider(logger, true); 314 | provider.Dispose(); 315 | Assert.True(logger.IsDisposed); 316 | } 317 | 318 | [Fact] 319 | public void BeginScopeDestructuresObjectsWhenCapturingOperatorIsUsedInMessageTemplate() 320 | { 321 | var (logger, sink) = SetUp(LogLevel.Trace); 322 | 323 | using (logger.BeginScope("{@Person}", new Person { FirstName = "John", LastName = "Smith" })) 324 | { 325 | logger.Log(LogLevel.Information, 0, TestMessage, null!, null!); 326 | } 327 | 328 | Assert.Single(sink.Writes); 329 | Assert.True(sink.Writes[0].Properties.ContainsKey("Person")); 330 | 331 | var person = (StructureValue)sink.Writes[0].Properties["Person"]; 332 | var firstName = (ScalarValue)person.Properties.Single(p => p.Name == "FirstName").Value; 333 | var lastName = (ScalarValue)person.Properties.Single(p => p.Name == "LastName").Value; 334 | Assert.Equal("John", firstName.Value); 335 | Assert.Equal("Smith", lastName.Value); 336 | } 337 | 338 | [Fact] 339 | public void BeginScopeDestructuresObjectsWhenCapturingOperatorIsUsedInDictionary() 340 | { 341 | var (logger, sink) = SetUp(LogLevel.Trace); 342 | 343 | using (logger.BeginScope(new Dictionary {{ "@Person", new Person { FirstName = "John", LastName = "Smith" }}})) 344 | { 345 | logger.Log(LogLevel.Information, 0, TestMessage, null!, null!); 346 | } 347 | 348 | Assert.Single(sink.Writes); 349 | Assert.True(sink.Writes[0].Properties.ContainsKey("Person")); 350 | 351 | var person = (StructureValue)sink.Writes[0].Properties["Person"]; 352 | var firstName = (ScalarValue)person.Properties.Single(p => p.Name == "FirstName").Value; 353 | var lastName = (ScalarValue)person.Properties.Single(p => p.Name == "LastName").Value; 354 | Assert.Equal("John", firstName.Value); 355 | Assert.Equal("Smith", lastName.Value); 356 | } 357 | 358 | [Fact] 359 | public void BeginScopeDoesNotModifyKeyWhenCapturingOperatorIsNotUsedInMessageTemplate() 360 | { 361 | var (logger, sink) = SetUp(LogLevel.Trace); 362 | 363 | using (logger.BeginScope("{FirstName}", "John")) 364 | { 365 | logger.Log(LogLevel.Information, 0, TestMessage, null!, null!); 366 | } 367 | 368 | Assert.Single(sink.Writes); 369 | Assert.True(sink.Writes[0].Properties.ContainsKey("FirstName")); 370 | } 371 | 372 | [Fact] 373 | public void BeginScopeDoesNotModifyKeyWhenCapturingOperatorIsNotUsedInDictionary() 374 | { 375 | var (logger, sink) = SetUp(LogLevel.Trace); 376 | 377 | using (logger.BeginScope(new Dictionary { { "FirstName", "John"}})) 378 | { 379 | logger.Log(LogLevel.Information, 0, TestMessage, null!, null!); 380 | } 381 | 382 | Assert.Single(sink.Writes); 383 | Assert.True(sink.Writes[0].Properties.ContainsKey("FirstName")); 384 | } 385 | 386 | [Fact] 387 | public void NamedScopesAreCaptured() 388 | { 389 | var (logger, sink) = SetUp(LogLevel.Trace); 390 | 391 | using (logger.BeginScope("Outer")) 392 | using (logger.BeginScope("Inner")) 393 | { 394 | logger.Log(LogLevel.Information, 0, TestMessage, null!, null!); 395 | } 396 | 397 | Assert.Single(sink.Writes); 398 | 399 | Assert.True(sink.Writes[0].Properties.TryGetValue(SerilogLoggerProvider.ScopePropertyName, out var scopeValue)); 400 | var sequence = Assert.IsType(scopeValue); 401 | var items = sequence.Elements.Select(e => Assert.IsType(e).Value).Cast().ToArray(); 402 | Assert.Equal(2, items.Length); 403 | Assert.Equal("Outer", items[0]); 404 | Assert.Equal("Inner", items[1]); 405 | } 406 | 407 | [Fact] 408 | public void ExternalScopesAreCaptured() 409 | { 410 | var externalScopeProvider = new FakeExternalScopeProvider(); 411 | var (logger, sink) = SetUp(LogLevel.Trace, externalScopeProvider); 412 | 413 | externalScopeProvider.Push(new Dictionary() 414 | { 415 | { "FirstKey", 1 }, 416 | { "SecondKey", 2 } 417 | }); 418 | 419 | var scopeObject = new { ObjectKey = "Some value" }; 420 | externalScopeProvider.Push(scopeObject); 421 | 422 | logger.Log(LogLevel.Information, 0, TestMessage, null!, null!); 423 | 424 | Assert.Single(sink.Writes); 425 | Assert.True(sink.Writes[0].Properties.TryGetValue(SerilogLoggerProvider.ScopePropertyName, out var scopeValue)); 426 | var sequence = Assert.IsType(scopeValue); 427 | 428 | var objectScope = (ScalarValue) sequence.Elements.Single(e => e is ScalarValue); 429 | Assert.Equal(scopeObject.ToString(), (string?)objectScope.Value); 430 | 431 | var dictionaryScope = (DictionaryValue) sequence.Elements.Single(e => e is DictionaryValue); 432 | Assert.Equal(1, ((ScalarValue)dictionaryScope.Elements.Single(pair => pair.Key.Value!.Equals("FirstKey")).Value).Value); 433 | Assert.Equal(2, ((ScalarValue)dictionaryScope.Elements.Single(pair => pair.Key.Value!.Equals("SecondKey")).Value).Value); 434 | } 435 | 436 | class FoodScope : IEnumerable> 437 | { 438 | readonly string _name; 439 | 440 | public FoodScope(string name) 441 | { 442 | _name = name; 443 | } 444 | 445 | public IEnumerator> GetEnumerator() 446 | { 447 | yield return new KeyValuePair("Name", _name); 448 | } 449 | 450 | IEnumerator IEnumerable.GetEnumerator() 451 | { 452 | return GetEnumerator(); 453 | } 454 | } 455 | 456 | class LuckyScope : IEnumerable> 457 | { 458 | readonly int _luckyNumber; 459 | 460 | public LuckyScope(int luckyNumber) 461 | { 462 | _luckyNumber = luckyNumber; 463 | } 464 | 465 | public IEnumerator> GetEnumerator() 466 | { 467 | yield return new KeyValuePair("LuckyNumber", _luckyNumber); 468 | } 469 | 470 | IEnumerator IEnumerable.GetEnumerator() 471 | { 472 | return GetEnumerator(); 473 | } 474 | } 475 | 476 | class Person 477 | { 478 | // ReSharper disable once UnusedAutoPropertyAccessor.Local 479 | public string? FirstName { get; set; } 480 | 481 | // ReSharper disable once UnusedAutoPropertyAccessor.Local 482 | public string? LastName { get; set; } 483 | } 484 | 485 | class FakeExternalScopeProvider : IExternalScopeProvider 486 | { 487 | private readonly List _scopes = new List(); 488 | 489 | public void ForEachScope(Action callback, TState state) 490 | { 491 | foreach (var scope in _scopes) 492 | { 493 | if (scope.IsDisposed) continue; 494 | callback(scope.Value, state); 495 | } 496 | } 497 | 498 | public IDisposable Push(object? state) 499 | { 500 | var scope = new Scope(state); 501 | _scopes.Add(scope); 502 | return scope; 503 | } 504 | 505 | class Scope : IDisposable 506 | { 507 | public bool IsDisposed { get; set; } 508 | public object? Value { get; set; } 509 | 510 | public Scope(object? value) 511 | { 512 | Value = value; 513 | } 514 | 515 | public void Dispose() 516 | { 517 | IsDisposed = true; 518 | } 519 | } 520 | } 521 | 522 | [Fact] 523 | public void MismatchedMessageTemplateParameterCountIsHandled() 524 | { 525 | var (logger, sink) = SetUp(LogLevel.Trace); 526 | 527 | #pragma warning disable CA2017 528 | // ReSharper disable once StructuredMessageTemplateProblem 529 | logger.LogInformation("Some test message with {Two} {Properties}", "OneProperty"); 530 | #pragma warning restore CA2017 531 | 532 | Assert.Empty(sink.Writes); 533 | } 534 | 535 | [Fact] 536 | public void ExceptionFromAuditSinkIsUnhandled() 537 | { 538 | var serilogLogger = new LoggerConfiguration() 539 | .AuditTo.Sink(new UnimplementedSink()) 540 | .CreateLogger(); 541 | 542 | var provider = new SerilogLoggerProvider(serilogLogger); 543 | var logger = provider.CreateLogger(Name); 544 | 545 | var ex = Assert.Throws(() => logger.LogInformation("Normal text")); 546 | Assert.IsType(ex.InnerException); 547 | Assert.Equal("Oops", ex.InnerException.Message); 548 | } 549 | 550 | class UnimplementedSink : ILogEventSink 551 | { 552 | public void Emit(LogEvent logEvent) 553 | { 554 | throw new NotImplementedException("Oops"); 555 | } 556 | } 557 | 558 | [Fact] 559 | public void TraceAndSpanIdsAreCaptured() 560 | { 561 | #if FORCE_W3C_ACTIVITY_ID 562 | Activity.DefaultIdFormat = ActivityIdFormat.W3C; 563 | Activity.ForceDefaultIdFormat = true; 564 | #endif 565 | 566 | using var listener = new ActivityListener(); 567 | listener.ShouldListenTo = _ => true; 568 | listener.Sample = (ref ActivityCreationOptions _) => ActivitySamplingResult.AllData; 569 | 570 | ActivitySource.AddActivityListener(listener); 571 | 572 | var source = new ActivitySource("test.activity", "1.0.0"); 573 | using var activity = source.StartActivity(); 574 | Assert.NotNull(Activity.Current); 575 | 576 | var (logger, sink) = SetUp(LogLevel.Trace); 577 | logger.LogInformation("Hello trace and span!"); 578 | 579 | var evt = Assert.Single(sink.Writes); 580 | 581 | Assert.Equal(Activity.Current.TraceId, evt.TraceId); 582 | Assert.Equal(Activity.Current.SpanId, evt.SpanId); 583 | } 584 | 585 | [Fact] 586 | public void LoggingScopeReplacesPropertyInNestedScope() 587 | { 588 | var sink = new CollectingSink(); 589 | using var logger = new LoggerConfiguration().WriteTo.Sink(sink).CreateLogger(); 590 | 591 | var services = new ServiceCollection(); 592 | services.AddLogging(l => l.AddSerilog(logger)); 593 | 594 | using var serviceProvider = services.BuildServiceProvider(); 595 | var msLogger = serviceProvider.GetRequiredService>(); 596 | 597 | using (msLogger.BeginScope(new Dictionary { { "EXECUTION_TAGS", "[TAG1]" } })) 598 | { 599 | msLogger.LogInformation("Message1"); 600 | using (msLogger.BeginScope(new Dictionary { { "EXECUTION_TAGS", "[TAG2]" } })) 601 | { 602 | msLogger.LogInformation("Message2"); 603 | } 604 | } 605 | 606 | var logEvent = sink.Writes.FirstOrDefault(e => e.MessageTemplate.Text == "Message1"); 607 | Assert.NotNull(logEvent); 608 | AssertHasScalarProperty(logEvent, "EXECUTION_TAGS", "[TAG1]"); 609 | 610 | logEvent = sink.Writes.FirstOrDefault(e => e.MessageTemplate.Text == "Message2"); 611 | Assert.NotNull(logEvent); 612 | AssertHasScalarProperty(logEvent, "EXECUTION_TAGS", "[TAG2]"); 613 | } 614 | 615 | static void AssertHasScalarProperty(LogEvent logEvent, string name, object? expectedValue) 616 | { 617 | Assert.True(logEvent.Properties.TryGetValue(name, out var result)); 618 | var scalar = Assert.IsType(result); 619 | Assert.Equal(expectedValue, scalar.Value); 620 | } 621 | 622 | [Fact] 623 | public void ConflictingEventIdTemplatePropertyIsIgnored() 624 | { 625 | var (logger, sink) = SetUp(LogLevel.Trace); 626 | 627 | var loggedEventId = 17; 628 | logger.LogInformation(loggedEventId, "{EventId}", 42); 629 | 630 | var write = Assert.Single(sink.Writes); 631 | var recordedEventIdProperty = Assert.IsType(write.Properties["EventId"]); 632 | var recordedEventIdStructure = Assert.Single(recordedEventIdProperty.Properties, p => p.Name == "Id"); 633 | var recordedEventIdPropertyValue = Assert.IsType(recordedEventIdStructure.Value); 634 | var recordedEventId = Assert.IsType(recordedEventIdPropertyValue.Value); 635 | Assert.Equal(loggedEventId, recordedEventId); 636 | } 637 | } 638 | -------------------------------------------------------------------------------- /test/Serilog.Extensions.Logging.Tests/SerilogLoggingBuilderExtensionsTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using Microsoft.Extensions.Logging; 3 | using Serilog.Extensions.Logging.Tests.Support; 4 | using Xunit; 5 | 6 | namespace Serilog.Extensions.Logging.Tests; 7 | 8 | public class SerilogLoggingBuilderExtensionsTests 9 | { 10 | [Fact] 11 | public void AddSerilogMustRegisterAnILoggerProvider() 12 | { 13 | var services = new ServiceCollection() 14 | .AddLogging(builder => { builder.AddSerilog(); }) 15 | .BuildServiceProvider(); 16 | 17 | var loggerProviders = services.GetServices(); 18 | Assert.Contains(loggerProviders, provider => provider is SerilogLoggerProvider); 19 | } 20 | 21 | [Fact] 22 | public void AddSerilogMustRegisterAnILoggerProviderThatForwardsLogsToStaticSerilogLogger() 23 | { 24 | var sink = new CollectingSink(); 25 | Log.Logger = new LoggerConfiguration() 26 | .WriteTo.Sink(sink) 27 | .CreateLogger(); 28 | 29 | var services = new ServiceCollection() 30 | .AddLogging(builder => { builder.AddSerilog(); }) 31 | .BuildServiceProvider(); 32 | 33 | var logger = services.GetRequiredService>(); 34 | logger.LogInformation("Hello, world!"); 35 | 36 | Assert.Single(sink.Writes); 37 | } 38 | 39 | [Fact] 40 | public void AddSerilogMustRegisterAnILoggerProviderThatForwardsLogsToProvidedLogger() 41 | { 42 | var sink = new CollectingSink(); 43 | var serilogLogger = new LoggerConfiguration() 44 | .WriteTo.Sink(sink) 45 | .CreateLogger(); 46 | 47 | var services = new ServiceCollection() 48 | .AddLogging(builder => { builder.AddSerilog(logger: serilogLogger); }) 49 | .BuildServiceProvider(); 50 | 51 | var logger = services.GetRequiredService>(); 52 | logger.LogInformation("Hello, world!"); 53 | 54 | Assert.Single(sink.Writes); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /test/Serilog.Extensions.Logging.Tests/Support/CollectingSink.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using Serilog.Core; 5 | using Serilog.Events; 6 | 7 | namespace Serilog.Extensions.Logging.Tests.Support; 8 | 9 | public class CollectingSink : ILogEventSink 10 | { 11 | public List Writes { get; set; } = new(); 12 | 13 | public void Emit(LogEvent logEvent) 14 | { 15 | Writes.Add(logEvent); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /test/Serilog.Extensions.Logging.Tests/Support/DisposeTrackingLogger.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | using Serilog.Core; 3 | using Serilog.Events; 4 | 5 | namespace Serilog.Extensions.Logging.Tests.Support; 6 | 7 | sealed class DisposeTrackingLogger : ILogger, IDisposable 8 | { 9 | public bool IsDisposed { get; private set; } 10 | 11 | public ILogger ForContext(ILogEventEnricher enricher) 12 | { 13 | return new LoggerConfiguration().CreateLogger(); 14 | } 15 | 16 | public ILogger ForContext(IEnumerable enrichers) 17 | { 18 | return new LoggerConfiguration().CreateLogger(); 19 | } 20 | 21 | public ILogger ForContext(string propertyName, object? value, bool destructureObjects = false) 22 | { 23 | return new LoggerConfiguration().CreateLogger(); 24 | } 25 | 26 | public ILogger ForContext() 27 | { 28 | return new LoggerConfiguration().CreateLogger(); 29 | } 30 | 31 | public ILogger ForContext(Type source) 32 | { 33 | return new LoggerConfiguration().CreateLogger(); 34 | } 35 | 36 | public void Write(LogEvent logEvent) 37 | { 38 | } 39 | 40 | public void Write(LogEventLevel level, string messageTemplate) 41 | { 42 | } 43 | 44 | public void Write(LogEventLevel level, string messageTemplate, T propertyValue) 45 | { 46 | } 47 | 48 | public void Write(LogEventLevel level, string messageTemplate, T0 propertyValue0, T1 propertyValue1) 49 | { 50 | } 51 | 52 | public void Write(LogEventLevel level, string messageTemplate, T0 propertyValue0, T1 propertyValue1, 53 | T2 propertyValue2) 54 | { 55 | } 56 | 57 | public void Write(LogEventLevel level, string messageTemplate, params object?[]? propertyValues) 58 | { 59 | } 60 | 61 | public void Write(LogEventLevel level, Exception? exception, string messageTemplate) 62 | { 63 | } 64 | 65 | public void Write(LogEventLevel level, Exception? exception, string messageTemplate, T propertyValue) 66 | { 67 | } 68 | 69 | public void Write(LogEventLevel level, Exception? exception, string messageTemplate, T0 propertyValue0, 70 | T1 propertyValue1) 71 | { 72 | } 73 | 74 | public void Write(LogEventLevel level, Exception? exception, string messageTemplate, T0 propertyValue0, 75 | T1 propertyValue1, T2 propertyValue2) 76 | { 77 | } 78 | 79 | public void Write(LogEventLevel level, Exception? exception, string messageTemplate, params object?[]? propertyValues) 80 | { 81 | } 82 | 83 | public bool IsEnabled(LogEventLevel level) 84 | { 85 | return false; 86 | } 87 | 88 | public void Verbose(string messageTemplate) 89 | { 90 | } 91 | 92 | public void Verbose(string messageTemplate, T propertyValue) 93 | { 94 | } 95 | 96 | public void Verbose(string messageTemplate, T0 propertyValue0, T1 propertyValue1) 97 | { 98 | } 99 | 100 | public void Verbose(string messageTemplate, T0 propertyValue0, T1 propertyValue1, T2 propertyValue2) 101 | { 102 | } 103 | 104 | public void Verbose(string messageTemplate, params object?[]? propertyValues) 105 | { 106 | } 107 | 108 | public void Verbose(Exception? exception, string messageTemplate) 109 | { 110 | } 111 | 112 | public void Verbose(Exception? exception, string messageTemplate, T propertyValue) 113 | { 114 | } 115 | 116 | public void Verbose(Exception? exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1) 117 | { 118 | } 119 | 120 | public void Verbose(Exception? exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1, 121 | T2 propertyValue2) 122 | { 123 | } 124 | 125 | public void Verbose(Exception? exception, string messageTemplate, params object?[]? propertyValues) 126 | { 127 | } 128 | 129 | public void Debug(string messageTemplate) 130 | { 131 | } 132 | 133 | public void Debug(string messageTemplate, T propertyValue) 134 | { 135 | } 136 | 137 | public void Debug(string messageTemplate, T0 propertyValue0, T1 propertyValue1) 138 | { 139 | } 140 | 141 | public void Debug(string messageTemplate, T0 propertyValue0, T1 propertyValue1, T2 propertyValue2) 142 | { 143 | } 144 | 145 | public void Debug(string messageTemplate, params object?[]? propertyValues) 146 | { 147 | } 148 | 149 | public void Debug(Exception? exception, string messageTemplate) 150 | { 151 | } 152 | 153 | public void Debug(Exception? exception, string messageTemplate, T propertyValue) 154 | { 155 | } 156 | 157 | public void Debug(Exception? exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1) 158 | { 159 | } 160 | 161 | public void Debug(Exception? exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1, 162 | T2 propertyValue2) 163 | { 164 | } 165 | 166 | public void Debug(Exception? exception, string messageTemplate, params object?[]? propertyValues) 167 | { 168 | } 169 | 170 | public void Information(string messageTemplate) 171 | { 172 | } 173 | 174 | public void Information(string messageTemplate, T propertyValue) 175 | { 176 | } 177 | 178 | public void Information(string messageTemplate, T0 propertyValue0, T1 propertyValue1) 179 | { 180 | } 181 | 182 | public void Information(string messageTemplate, T0 propertyValue0, T1 propertyValue1, T2 propertyValue2) 183 | { 184 | } 185 | 186 | public void Information(string messageTemplate, params object?[]? propertyValues) 187 | { 188 | } 189 | 190 | public void Information(Exception? exception, string messageTemplate) 191 | { 192 | } 193 | 194 | public void Information(Exception? exception, string messageTemplate, T propertyValue) 195 | { 196 | } 197 | 198 | public void Information(Exception? exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1) 199 | { 200 | } 201 | 202 | public void Information(Exception? exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1, 203 | T2 propertyValue2) 204 | { 205 | } 206 | 207 | public void Information(Exception? exception, string messageTemplate, params object?[]? propertyValues) 208 | { 209 | } 210 | 211 | public void Warning(string messageTemplate) 212 | { 213 | } 214 | 215 | public void Warning(string messageTemplate, T propertyValue) 216 | { 217 | } 218 | 219 | public void Warning(string messageTemplate, T0 propertyValue0, T1 propertyValue1) 220 | { 221 | } 222 | 223 | public void Warning(string messageTemplate, T0 propertyValue0, T1 propertyValue1, T2 propertyValue2) 224 | { 225 | } 226 | 227 | public void Warning(string messageTemplate, params object?[]? propertyValues) 228 | { 229 | } 230 | 231 | public void Warning(Exception? exception, string messageTemplate) 232 | { 233 | } 234 | 235 | public void Warning(Exception? exception, string messageTemplate, T propertyValue) 236 | { 237 | } 238 | 239 | public void Warning(Exception? exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1) 240 | { 241 | } 242 | 243 | public void Warning(Exception? exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1, 244 | T2 propertyValue2) 245 | { 246 | } 247 | 248 | public void Warning(Exception? exception, string messageTemplate, params object?[]? propertyValues) 249 | { 250 | } 251 | 252 | public void Error(string messageTemplate) 253 | { 254 | } 255 | 256 | public void Error(string messageTemplate, T propertyValue) 257 | { 258 | } 259 | 260 | public void Error(string messageTemplate, T0 propertyValue0, T1 propertyValue1) 261 | { 262 | } 263 | 264 | public void Error(string messageTemplate, T0 propertyValue0, T1 propertyValue1, T2 propertyValue2) 265 | { 266 | } 267 | 268 | public void Error(string messageTemplate, params object?[]? propertyValues) 269 | { 270 | } 271 | 272 | public void Error(Exception? exception, string messageTemplate) 273 | { 274 | } 275 | 276 | public void Error(Exception? exception, string messageTemplate, T propertyValue) 277 | { 278 | } 279 | 280 | public void Error(Exception? exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1) 281 | { 282 | } 283 | 284 | public void Error(Exception? exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1, 285 | T2 propertyValue2) 286 | { 287 | } 288 | 289 | public void Error(Exception? exception, string messageTemplate, params object?[]? propertyValues) 290 | { 291 | } 292 | 293 | public void Fatal(string messageTemplate) 294 | { 295 | } 296 | 297 | public void Fatal(string messageTemplate, T propertyValue) 298 | { 299 | } 300 | 301 | public void Fatal(string messageTemplate, T0 propertyValue0, T1 propertyValue1) 302 | { 303 | } 304 | 305 | public void Fatal(string messageTemplate, T0 propertyValue0, T1 propertyValue1, T2 propertyValue2) 306 | { 307 | } 308 | 309 | public void Fatal(string messageTemplate, params object?[]? propertyValues) 310 | { 311 | } 312 | 313 | public void Fatal(Exception? exception, string messageTemplate) 314 | { 315 | } 316 | 317 | public void Fatal(Exception? exception, string messageTemplate, T propertyValue) 318 | { 319 | } 320 | 321 | public void Fatal(Exception? exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1) 322 | { 323 | } 324 | 325 | public void Fatal(Exception? exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1, 326 | T2 propertyValue2) 327 | { 328 | } 329 | 330 | public void Fatal(Exception? exception, string messageTemplate, params object?[]? propertyValues) 331 | { 332 | } 333 | 334 | public bool BindMessageTemplate(string messageTemplate, object?[]? propertyValues, [NotNullWhen(true)] out MessageTemplate? parsedTemplate, 335 | [NotNullWhen(true)] out IEnumerable? boundProperties) 336 | { 337 | parsedTemplate = null; 338 | boundProperties = null; 339 | return false; 340 | } 341 | 342 | public bool BindProperty(string? propertyName, object? value, bool destructureObjects, [NotNullWhen(true)] out LogEventProperty? property) 343 | { 344 | property = null; 345 | return false; 346 | } 347 | 348 | public void Dispose() 349 | { 350 | IsDisposed = true; 351 | } 352 | } 353 | -------------------------------------------------------------------------------- /test/Serilog.Extensions.Logging.Tests/Support/ExtensionsProvider.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using Microsoft.Extensions.Logging; 5 | 6 | namespace Serilog.Extensions.Logging.Tests.Support; 7 | 8 | sealed class ExtensionsProvider : ILoggerProvider, Microsoft.Extensions.Logging.ILogger 9 | { 10 | readonly LogLevel _enabledLevel; 11 | 12 | public List<(LogLevel logLevel, EventId eventId, object? state, Exception? exception, string message)> Writes { get; } = new(); 13 | 14 | public ExtensionsProvider(LogLevel enabledLevel) 15 | { 16 | _enabledLevel = enabledLevel; 17 | } 18 | 19 | public Microsoft.Extensions.Logging.ILogger CreateLogger(string categoryName) 20 | { 21 | return this; 22 | } 23 | 24 | public IDisposable BeginScope(TState state) where TState: notnull 25 | { 26 | return this; 27 | } 28 | 29 | public bool IsEnabled(LogLevel logLevel) 30 | { 31 | return _enabledLevel <= logLevel; 32 | } 33 | 34 | public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter) 35 | { 36 | Writes.Add((logLevel, eventId, state, exception, formatter(state, exception))); 37 | } 38 | 39 | public void Dispose() 40 | { 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /test/Serilog.Extensions.Logging.Tests/Support/LogEventPropertyFactory.cs: -------------------------------------------------------------------------------- 1 | // Copyright © Serilog Contributors 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using Serilog.Core; 5 | using Serilog.Events; 6 | 7 | namespace Serilog.Extensions.Logging.Tests.Support; 8 | internal class LogEventPropertyFactory : ILogEventPropertyFactory 9 | { 10 | public LogEventProperty CreateProperty(string name, object? value, bool destructureObjects = false) 11 | { 12 | var scalarValue = new ScalarValue(value); 13 | return new LogEventProperty(name, scalarValue); 14 | } 15 | } 16 | --------------------------------------------------------------------------------