├── .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 [](https://github.com/serilog/serilog-extensions-logging/actions) [](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 |
--------------------------------------------------------------------------------