├── .DS_Store ├── .editorconfig ├── .gitattributes ├── .gitignore ├── .idea └── .idea.serilog-sinks-eventlog │ └── .idea │ ├── .gitignore │ ├── encodings.xml │ ├── indexLayout.xml │ └── vcs.xml ├── Build.ps1 ├── CHANGES.md ├── LICENSE ├── NuGet.config ├── README.md ├── appveyor.yml ├── assets ├── Screenshot.png ├── Serilog.snk └── serilog-sink-nuget.png ├── sample └── Sample │ ├── Program.cs │ └── Sample.csproj ├── serilog-sinks-eventlog.sln ├── src └── Serilog.Sinks.EventLog │ ├── LoggerConfigurationEventLogExtensions.cs │ ├── Serilog.Sinks.EventLog.csproj │ └── Sinks │ ├── EventLog │ ├── EventIdHashProvider.cs │ ├── EventLogSink.cs │ ├── ICategoryProvider.cs │ ├── IEventIdProvider.cs │ └── NullCategoryProvider.cs │ └── NullSink.cs └── test └── Serilog.Sinks.EventLog.Tests ├── EventLogSinkTests.cs ├── Serilog.Sinks.EventLog.Tests.csproj └── appsettings.xml /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/serilog/serilog-sinks-eventlog/b8701e49f051c88f482dd9f4aa664279dd81f238/.DS_Store -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://EditorConfig.org 2 | 3 | [*] 4 | insert_final_newline = true 5 | trim_trailing_whitespace = true 6 | indent_style = space 7 | indent_size = 4 8 | 9 | [*.cs] 10 | charset = utf-8 11 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | 3 | * text=auto 4 | -------------------------------------------------------------------------------- /.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 | *.sln.docstates 8 | 9 | # Build results 10 | [Dd]ebug/ 11 | [Dd]ebugPublic/ 12 | [Rr]elease/ 13 | [Rr]eleases/ 14 | x64/ 15 | x86/ 16 | build/ 17 | bld/ 18 | [Bb]in/ 19 | [Oo]bj/ 20 | 21 | # Roslyn cache directories 22 | *.ide/ 23 | 24 | # MSTest test Results 25 | [Tt]est[Rr]esult*/ 26 | [Bb]uild[Ll]og.* 27 | 28 | #NUNIT 29 | *.VisualState.xml 30 | TestResult.xml 31 | 32 | # Build Results of an ATL Project 33 | [Dd]ebugPS/ 34 | [Rr]eleasePS/ 35 | dlldata.c 36 | 37 | *_i.c 38 | *_p.c 39 | *_i.h 40 | *.ilk 41 | *.meta 42 | *.obj 43 | *.pch 44 | *.pdb 45 | *.pgc 46 | *.pgd 47 | *.rsp 48 | *.sbr 49 | *.tlb 50 | *.tli 51 | *.tlh 52 | *.tmp 53 | *.tmp_proj 54 | *.log 55 | *.vspscc 56 | *.vssscc 57 | .builds 58 | *.pidb 59 | *.svclog 60 | *.scc 61 | 62 | # Chutzpah Test files 63 | _Chutzpah* 64 | 65 | # Visual C++ cache files 66 | ipch/ 67 | *.aps 68 | *.ncb 69 | *.opensdf 70 | *.sdf 71 | *.cachefile 72 | 73 | # Visual Studio profiler 74 | *.psess 75 | *.vsp 76 | *.vspx 77 | 78 | # TFS 2012 Local Workspace 79 | $tf/ 80 | 81 | # Guidance Automation Toolkit 82 | *.gpState 83 | 84 | # ReSharper is a .NET coding add-in 85 | _ReSharper*/ 86 | *.[Rr]e[Ss]harper 87 | *.DotSettings.user 88 | 89 | # JustCode is a .NET coding addin-in 90 | .JustCode 91 | 92 | # TeamCity is a build add-in 93 | _TeamCity* 94 | 95 | # DotCover is a Code Coverage Tool 96 | *.dotCover 97 | 98 | # NCrunch 99 | _NCrunch_* 100 | .*crunch*.local.xml 101 | 102 | # MightyMoose 103 | *.mm.* 104 | AutoTest.Net/ 105 | 106 | # Web workbench (sass) 107 | .sass-cache/ 108 | 109 | # Installshield output folder 110 | [Ee]xpress/ 111 | 112 | # DocProject is a documentation generator add-in 113 | DocProject/buildhelp/ 114 | DocProject/Help/*.HxT 115 | DocProject/Help/*.HxC 116 | DocProject/Help/*.hhc 117 | DocProject/Help/*.hhk 118 | DocProject/Help/*.hhp 119 | DocProject/Help/Html2 120 | DocProject/Help/html 121 | 122 | # Click-Once directory 123 | publish/ 124 | 125 | # Publish Web Output 126 | *.[Pp]ublish.xml 127 | *.azurePubxml 128 | # TODO: Comment the next line if you want to checkin your web deploy settings 129 | # but database connection strings (with potential passwords) will be unencrypted 130 | *.pubxml 131 | *.publishproj 132 | 133 | # NuGet Packages 134 | *.nupkg 135 | # The packages folder can be ignored because of Package Restore 136 | **/packages/* 137 | # except build/, which is used as an MSBuild target. 138 | !**/packages/build/ 139 | # If using the old MSBuild-Integrated Package Restore, uncomment this: 140 | #!**/packages/repositories.config 141 | 142 | # Windows Azure Build Output 143 | csx/ 144 | *.build.csdef 145 | 146 | # Windows Store app package directory 147 | AppPackages/ 148 | 149 | # Others 150 | sql/ 151 | *.Cache 152 | ClientBin/ 153 | [Ss]tyle[Cc]op.* 154 | ~$* 155 | *~ 156 | *.dbmdl 157 | *.dbproj.schemaview 158 | *.pfx 159 | *.publishsettings 160 | node_modules/ 161 | 162 | # RIA/Silverlight projects 163 | Generated_Code/ 164 | 165 | # Backup & report files from converting an old project file 166 | # to a newer Visual Studio version. Backup files are not needed, 167 | # because we have git ;-) 168 | _UpgradeReport_Files/ 169 | Backup*/ 170 | UpgradeLog*.XML 171 | UpgradeLog*.htm 172 | 173 | # SQL Server files 174 | *.mdf 175 | *.ldf 176 | 177 | # Business Intelligence projects 178 | *.rdl.data 179 | *.bim.layout 180 | *.bim_*.settings 181 | 182 | # Microsoft Fakes 183 | FakesAssemblies/ 184 | 185 | .vs/ 186 | project.lock.json 187 | -------------------------------------------------------------------------------- /.idea/.idea.serilog-sinks-eventlog/.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Rider ignored files 5 | /contentModel.xml 6 | /projectSettingsUpdater.xml 7 | /modules.xml 8 | /.idea.serilog-sinks-eventlog.iml 9 | # Editor-based HTTP Client requests 10 | /httpRequests/ 11 | # Datasource local storage ignored files 12 | /dataSources/ 13 | /dataSources.local.xml 14 | -------------------------------------------------------------------------------- /.idea/.idea.serilog-sinks-eventlog/.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.idea/.idea.serilog-sinks-eventlog/.idea/indexLayout.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/.idea.serilog-sinks-eventlog/.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /Build.ps1: -------------------------------------------------------------------------------- 1 | echo "build: Build started" 2 | 3 | Push-Location $PSScriptRoot 4 | 5 | if(Test-Path .\artifacts) { 6 | echo "build: Cleaning .\artifacts" 7 | Remove-Item .\artifacts -Force -Recurse 8 | } 9 | 10 | & dotnet restore --no-cache 11 | 12 | $branch = @{ $true = $env:APPVEYOR_REPO_BRANCH; $false = $(git symbolic-ref --short -q HEAD) }[$env:APPVEYOR_REPO_BRANCH -ne $NULL]; 13 | $revision = @{ $true = "{0:00000}" -f [convert]::ToInt32("0" + $env:APPVEYOR_BUILD_NUMBER, 10); $false = "local" }[$env:APPVEYOR_BUILD_NUMBER -ne $NULL]; 14 | $suffix = @{ $true = ""; $false = "$($branch.Substring(0, [math]::Min(10,$branch.Length)))-$revision"}[$branch -eq "main" -and $revision -ne "local"] 15 | $commitHash = $(git rev-parse --short HEAD) 16 | $buildSuffix = @{ $true = "$($suffix)-$($commitHash)"; $false = "$($branch)-$($commitHash)" }[$suffix -ne ""] 17 | 18 | echo "build: Package version suffix is $suffix" 19 | echo "build: Build version suffix is $buildSuffix" 20 | 21 | foreach ($src in ls src/*) { 22 | Push-Location $src 23 | 24 | echo "build: Packaging project in $src" 25 | 26 | & dotnet build -c Release --version-suffix=$buildSuffix -p:EnableSourceLink=true 27 | if ($suffix) { 28 | & dotnet pack -c Release -o ..\..\artifacts --version-suffix=$suffix --no-build 29 | } else { 30 | & dotnet pack -c Release -o ..\..\artifacts --no-build 31 | } 32 | if($LASTEXITCODE -ne 0) { throw "build failed" } 33 | 34 | Pop-Location 35 | } 36 | 37 | foreach ($test in ls test/*.Tests) { 38 | Push-Location $test 39 | 40 | echo "build: Testing project in $test" 41 | 42 | & dotnet test -c Release 43 | if($LASTEXITCODE -ne 0) { throw "tests failed" } 44 | 45 | Pop-Location 46 | } 47 | 48 | Pop-Location 49 | -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | 3.0 2 | * Don't manage event sources by default 3 | 4 | 1.5 5 | * Moved from serilog/serilog 6 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /NuGet.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Serilog.Sinks.EventLog 2 | 3 | [![Build status](https://ci.appveyor.com/api/projects/status/j1iodeatf9ykrluf/branch/master?svg=true)](https://ci.appveyor.com/project/serilog/serilog-sinks-eventlog/branch/master) [![NuGet Version](http://img.shields.io/nuget/v/Serilog.Sinks.EventLog.svg?style=flat)](https://www.nuget.org/packages/Serilog.Sinks.EventLog/) 4 | 5 | A Serilog sink that writes events to the Windows Event Log. 6 | 7 | > [!IMPORTANT] 8 | > Version 3.0 of this sink changed the default value of `manageEventSource` from `true` to `false`. Applications that run with administrative privileges, and that can therefore create event sources on-the-fly, can opt in by providing `manageEventSource: true` as a configuration option. 9 | 10 | ### Getting started 11 | 12 | First, install the package from NuGet: 13 | 14 | ``` 15 | dotnet add package Serilog.Sinks.EventLog 16 | ``` 17 | 18 | The sink is configured by calling `WriteTo.EventLog()` on the `LoggerConfiguration`: 19 | 20 | ```csharp 21 | Log.Logger = new LoggerConfiguration() 22 | .WriteTo.EventLog("Sample App", manageEventSource: true) 23 | .CreateLogger(); 24 | 25 | Log.Information("Hello, Windows Event Log!"); 26 | 27 | Log.CloseAndFlush(); 28 | ``` 29 | 30 | Events will appear under the Application log with the specified source name: 31 | 32 | ![Screenshot](https://raw.githubusercontent.com/serilog/serilog-sinks-eventlog/dev/assets/Screenshot.png) 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | version: '{build}' 2 | skip_tags: true 3 | image: Visual Studio 2022 4 | build_script: 5 | - pwsh: ./Build.ps1 6 | test: off 7 | artifacts: 8 | - path: artifacts/Serilog.*.nupkg 9 | deploy: 10 | - provider: NuGet 11 | api_key: 12 | secure: ZpUO4ECx4c/V0Ecj04cfV1UGd+ZABeEG9DDW2fjG8vITjNYhmbiiJH0qNOnRy2G3 13 | skip_symbols: true 14 | on: 15 | branch: /^(main|dev)$/ 16 | - provider: GitHub 17 | auth_token: 18 | secure: p4LpVhBKxGS5WqucHxFQ5c7C8cP74kbNB0Z8k9Oxx/PMaDQ1+ibmoexNqVU5ZlmX 19 | artifact: /Serilog.*\.nupkg/ 20 | tag: v$(appveyor_build_version) 21 | on: 22 | branch: main 23 | -------------------------------------------------------------------------------- /assets/Screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/serilog/serilog-sinks-eventlog/b8701e49f051c88f482dd9f4aa664279dd81f238/assets/Screenshot.png -------------------------------------------------------------------------------- /assets/Serilog.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/serilog/serilog-sinks-eventlog/b8701e49f051c88f482dd9f4aa664279dd81f238/assets/Serilog.snk -------------------------------------------------------------------------------- /assets/serilog-sink-nuget.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/serilog/serilog-sinks-eventlog/b8701e49f051c88f482dd9f4aa664279dd81f238/assets/serilog-sink-nuget.png -------------------------------------------------------------------------------- /sample/Sample/Program.cs: -------------------------------------------------------------------------------- 1 | using Serilog; 2 | 3 | Log.Logger = new LoggerConfiguration() 4 | .WriteTo.EventLog("Sample App", manageEventSource: true) 5 | .CreateLogger(); 6 | 7 | Log.Information("Hello, Windows Event Log!"); 8 | 9 | Log.CloseAndFlush(); 10 | -------------------------------------------------------------------------------- /sample/Sample/Sample.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net48;net6.0-windows;net8.0-windows 5 | Exe 6 | 12 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /serilog-sinks-eventlog.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26430.6 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{BACB7AEE-15AC-4C6F-9776-81E3F5527A1D}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{3F4F5176-8954-4058-970C-062A2244A6C4}" 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "assets", "assets", "{E6658BF7-57B7-43E0-96CC-A86555CE855E}" 11 | ProjectSection(SolutionItems) = preProject 12 | .editorconfig = .editorconfig 13 | appveyor.yml = appveyor.yml 14 | Build.ps1 = Build.ps1 15 | CHANGES.md = CHANGES.md 16 | LICENSE = LICENSE 17 | NuGet.config = NuGet.config 18 | README.md = README.md 19 | assets\Serilog.snk = assets\Serilog.snk 20 | EndProjectSection 21 | EndProject 22 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "sample", "sample", "{901A383E-2BD3-448A-9347-930F583D2FA9}" 23 | EndProject 24 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Serilog.Sinks.EventLog", "src\Serilog.Sinks.EventLog\Serilog.Sinks.EventLog.csproj", "{890409CD-40C6-4108-9CA8-AEBD431B644C}" 25 | EndProject 26 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Serilog.Sinks.EventLog.Tests", "test\Serilog.Sinks.EventLog.Tests\Serilog.Sinks.EventLog.Tests.csproj", "{431537CB-3AA2-44AD-87B0-ACC19FF173B7}" 27 | EndProject 28 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sample", "sample\Sample\Sample.csproj", "{66B8C35C-5035-433B-9D7E-9F50D75383A6}" 29 | EndProject 30 | Global 31 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 32 | Debug|Any CPU = Debug|Any CPU 33 | Release|Any CPU = Release|Any CPU 34 | EndGlobalSection 35 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 36 | {890409CD-40C6-4108-9CA8-AEBD431B644C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 37 | {890409CD-40C6-4108-9CA8-AEBD431B644C}.Debug|Any CPU.Build.0 = Debug|Any CPU 38 | {890409CD-40C6-4108-9CA8-AEBD431B644C}.Release|Any CPU.ActiveCfg = Release|Any CPU 39 | {890409CD-40C6-4108-9CA8-AEBD431B644C}.Release|Any CPU.Build.0 = Release|Any CPU 40 | {431537CB-3AA2-44AD-87B0-ACC19FF173B7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 41 | {431537CB-3AA2-44AD-87B0-ACC19FF173B7}.Debug|Any CPU.Build.0 = Debug|Any CPU 42 | {431537CB-3AA2-44AD-87B0-ACC19FF173B7}.Release|Any CPU.ActiveCfg = Release|Any CPU 43 | {431537CB-3AA2-44AD-87B0-ACC19FF173B7}.Release|Any CPU.Build.0 = Release|Any CPU 44 | {66B8C35C-5035-433B-9D7E-9F50D75383A6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 45 | {66B8C35C-5035-433B-9D7E-9F50D75383A6}.Debug|Any CPU.Build.0 = Debug|Any CPU 46 | {66B8C35C-5035-433B-9D7E-9F50D75383A6}.Release|Any CPU.ActiveCfg = Release|Any CPU 47 | {66B8C35C-5035-433B-9D7E-9F50D75383A6}.Release|Any CPU.Build.0 = Release|Any CPU 48 | EndGlobalSection 49 | GlobalSection(SolutionProperties) = preSolution 50 | HideSolutionNode = FALSE 51 | EndGlobalSection 52 | GlobalSection(NestedProjects) = preSolution 53 | {890409CD-40C6-4108-9CA8-AEBD431B644C} = {BACB7AEE-15AC-4C6F-9776-81E3F5527A1D} 54 | {431537CB-3AA2-44AD-87B0-ACC19FF173B7} = {3F4F5176-8954-4058-970C-062A2244A6C4} 55 | {66B8C35C-5035-433B-9D7E-9F50D75383A6} = {901A383E-2BD3-448A-9347-930F583D2FA9} 56 | EndGlobalSection 57 | EndGlobal 58 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.EventLog/LoggerConfigurationEventLogExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2014 Serilog Contributors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | using System; 16 | using Serilog.Configuration; 17 | using Serilog.Events; 18 | using Serilog.Formatting.Display; 19 | using Serilog.Sinks.EventLog; 20 | using Serilog.Formatting; 21 | 22 | #if NET5_0_OR_GREATER 23 | [assembly: System.Runtime.Versioning.SupportedOSPlatform("windows")] 24 | #endif 25 | 26 | namespace Serilog; 27 | 28 | /// 29 | /// Adds the WriteTo.EventLog() extension method to . 30 | /// 31 | public static class LoggerConfigurationEventLogExtensions 32 | { 33 | const string DefaultOutputTemplate = "{Message}{NewLine}{Exception}"; 34 | 35 | /// 36 | /// Adds a sink that writes log events to the Windows event log. 37 | /// 38 | /// The logger configuration. 39 | /// The source name by which the application is registered on the local computer. 40 | /// The name of the log the source's entries are written to. Possible values include Application, System, or a custom event log. 41 | /// The name of the machine hosting the event log written to. The local machine by default. 42 | /// If true, check/create event source as required. Defaults to false i.e. do not allow sink to manage event source creation. 43 | /// A message template describing the format used to write to the sink. The default is "{Timestamp} [{Level}] {Message}{NewLine}{Exception}". 44 | /// The minimum log event level required in order to write an event to the sink. 45 | /// Supplies culture-specific formatting information, or null. 46 | /// Supplies event ids for emitted log events. 47 | /// Supplies categories for emitted log events. 48 | /// Logger configuration, allowing configuration to continue. 49 | /// A required parameter is null. 50 | public static LoggerConfiguration EventLog( 51 | this LoggerSinkConfiguration loggerConfiguration, 52 | string source, 53 | string? logName = null, 54 | string machineName = ".", 55 | bool manageEventSource = false, 56 | string outputTemplate = DefaultOutputTemplate, 57 | IFormatProvider? formatProvider = null, 58 | LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum, 59 | IEventIdProvider? eventIdProvider = null, 60 | ICategoryProvider? categoryProvider = null) 61 | { 62 | if (!IsWindows()) 63 | { 64 | return loggerConfiguration.Sink(restrictedToMinimumLevel); 65 | } 66 | 67 | if (loggerConfiguration == null) 68 | { 69 | throw new ArgumentNullException(nameof(loggerConfiguration)); 70 | } 71 | 72 | var formatter = new MessageTemplateTextFormatter(outputTemplate, formatProvider); 73 | 74 | return loggerConfiguration.Sink(new EventLogSink(source, logName, formatter, machineName, manageEventSource, eventIdProvider, categoryProvider), restrictedToMinimumLevel); 75 | } 76 | 77 | /// 78 | /// Adds a sink that writes log events to the Windows event log. 79 | /// 80 | /// The logger configuration. 81 | /// The source name by which the application is registered on the local computer. 82 | /// The name of the log the source's entries are written to. Possible values include Application, System, or a custom event log. 83 | /// The name of the machine hosting the event log written to. The local machine by default. 84 | /// If false does not check/create event source. Defaults to true i.e. allow sink to manage event source creation 85 | /// The minimum log event level required in order to write an event to the sink. 86 | /// Formatter to control how events are rendered into the file. To control 87 | /// plain text formatting, use the overload that accepts an output template instead. 88 | /// Supplies event ids for emitted log events. 89 | /// Supplies categories for emitted log events. 90 | /// 91 | /// Logger configuration, allowing configuration to continue. 92 | /// 93 | /// loggerConfiguration 94 | /// A required parameter is null. 95 | public static LoggerConfiguration EventLog( 96 | this LoggerSinkConfiguration loggerConfiguration, 97 | ITextFormatter formatter, 98 | string source, 99 | string? logName = null, 100 | string machineName = ".", 101 | bool manageEventSource = false, 102 | LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum, 103 | IEventIdProvider? eventIdProvider = null, 104 | ICategoryProvider? categoryProvider = null) 105 | { 106 | if (!IsWindows()) 107 | { 108 | return loggerConfiguration.Sink(restrictedToMinimumLevel); 109 | } 110 | 111 | if (loggerConfiguration == null) throw new ArgumentNullException(nameof(loggerConfiguration)); 112 | if (formatter == null) throw new ArgumentNullException(nameof(formatter)); 113 | 114 | return loggerConfiguration.Sink(new EventLogSink(source, logName, formatter, machineName, manageEventSource, eventIdProvider, categoryProvider), restrictedToMinimumLevel); 115 | } 116 | 117 | private static bool IsWindows() 118 | { 119 | #if NET5_0_OR_GREATER 120 | return OperatingSystem.IsWindows(); 121 | #elif NET462 122 | return true; 123 | #else 124 | return System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.Windows); 125 | #endif 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.EventLog/Serilog.Sinks.EventLog.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Serilog event sink that writes to the Windows Event Log. 5 | Copyright © Serilog Contributors 6 | 5.0.0 7 | 5.0.0.0 8 | Jeremy Clarke;Fabian Wetzel 9 | 10 | net471;net462 11 | 14 | $(TargetFrameworks);net8.0;net6.0;netstandard2.0 15 | true 16 | serilog;logging;eventlog;event;log;viewer 17 | true 18 | Serilog 19 | ../../assets/Serilog.snk 20 | true 21 | true 22 | serilog-sink-nuget.png 23 | https://serilog.net 24 | Apache-2.0 25 | true 26 | README.md 27 | 12 28 | enable 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.EventLog/Sinks/EventLog/EventIdHashProvider.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2016 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; 17 | 18 | namespace Serilog.Sinks.EventLog; 19 | 20 | /// 21 | /// Hash functions for message templates. See . 22 | /// 23 | sealed class EventIdHashProvider : IEventIdProvider 24 | { 25 | /// 26 | /// Computes an Event Id for the given log event. 27 | /// 28 | /// The log event to compute the event id from. 29 | /// Computed event id based off the given log. 30 | public ushort ComputeEventId(LogEvent logEvent) => (ushort)Compute(logEvent.MessageTemplate.Text); 31 | 32 | /// 33 | /// Compute a 32-bit hash of the provided . The 34 | /// resulting hash value can be uses as an event id in lieu of transmitting the 35 | /// full template string. 36 | /// 37 | /// A message template. 38 | /// A 32-bit hash of the template. 39 | static int Compute(string messageTemplate) 40 | { 41 | if (messageTemplate == null) throw new ArgumentNullException(nameof(messageTemplate)); 42 | 43 | // Jenkins one-at-a-time https://en.wikipedia.org/wiki/Jenkins_hash_function 44 | unchecked 45 | { 46 | uint hash = 0; 47 | for (var i = 0; i < messageTemplate.Length; ++i) 48 | { 49 | hash += messageTemplate[i]; 50 | hash += (hash << 10); 51 | hash ^= (hash >> 6); 52 | } 53 | hash += (hash << 3); 54 | hash ^= (hash >> 11); 55 | hash += (hash << 15); 56 | 57 | //even though the api is type int, eventID must be between 0 and 65535 58 | //https://msdn.microsoft.com/en-us/library/d3159s0c(v=vs.110).aspx 59 | return (ushort) hash; 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.EventLog/Sinks/EventLog/EventLogSink.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2014 Serilog Contributors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | using System; 16 | using System.Diagnostics; 17 | using System.IO; 18 | using Serilog.Core; 19 | using Serilog.Debugging; 20 | using Serilog.Events; 21 | using Serilog.Formatting; 22 | 23 | namespace Serilog.Sinks.EventLog; 24 | 25 | /// 26 | /// Writes log events as documents to the Windows event log. 27 | /// 28 | /// Beware of changing the source/log name, see: http://stackoverflow.com/questions/804284/how-do-i-write-to-a-custom-windows-event-log?rq=1 29 | public class EventLogSink : ILogEventSink 30 | { 31 | const string ApplicationLogName = "Application"; 32 | const int MaximumPayloadLengthChars = 31839; 33 | const int MaximumSourceNameLengthChars = 212; 34 | const int SourceMovedEventId = 3; 35 | 36 | readonly IEventIdProvider _eventIdProvider; 37 | readonly ICategoryProvider _categoryProvider; 38 | readonly ITextFormatter _textFormatter; 39 | readonly System.Diagnostics.EventLog _log; 40 | 41 | /// 42 | /// Construct a sink posting to the Windows event log, creating the specified if it does not exist. 43 | /// 44 | /// The source name by which the application is registered on the local computer. 45 | /// The name of the log the source's entries are written to. Possible values include Application, System, or a custom event log. 46 | /// Supplies culture-specific formatting information, or null. 47 | /// The name of the machine hosting the event log written to. 48 | /// If false does not check/create event source. Defaults to true i.e. allow sink to manage event source creation 49 | /// Supplies event ids for emitted log events. 50 | /// Supplies categories for emitted log events. 51 | public EventLogSink(string source, string? logName, ITextFormatter textFormatter, string machineName, bool manageEventSource, IEventIdProvider? eventIdProvider = null, ICategoryProvider? categoryProvider = null) 52 | { 53 | if (source == null) throw new ArgumentNullException(nameof(source)); 54 | if (textFormatter == null) throw new ArgumentNullException(nameof(textFormatter)); 55 | 56 | // The source is limited in length and allowed chars, see: https://msdn.microsoft.com/en-us/library/e29k5ebc%28v=vs.110%29.aspx 57 | if (source.Length > MaximumSourceNameLengthChars) 58 | { 59 | SelfLog.WriteLine("Trimming long event log source name to {0} characters", MaximumSourceNameLengthChars); 60 | source = source[..MaximumSourceNameLengthChars]; 61 | } 62 | 63 | source = source.Replace("<", "_"); 64 | source = source.Replace(">", "_"); 65 | 66 | _eventIdProvider = eventIdProvider ?? new EventIdHashProvider(); 67 | _categoryProvider = categoryProvider ?? new NullCategoryProvider(); 68 | _textFormatter = textFormatter; 69 | _log = new System.Diagnostics.EventLog(string.IsNullOrWhiteSpace(logName) ? ApplicationLogName : logName, machineName); 70 | 71 | if (manageEventSource) 72 | { 73 | ConfigureSource(_log, source); 74 | } 75 | else 76 | { 77 | _log.Source = source; 78 | } 79 | } 80 | 81 | static void ConfigureSource(System.Diagnostics.EventLog log, string source) 82 | { 83 | var sourceData = new EventSourceCreationData(source, log.Log) {MachineName = log.MachineName}; 84 | string? oldLogName = null; 85 | 86 | if (System.Diagnostics.EventLog.SourceExists(source, log.MachineName)) 87 | { 88 | var existingLogWithSourceName = System.Diagnostics.EventLog.LogNameFromSourceName(source, log.MachineName); 89 | 90 | if (!string.IsNullOrWhiteSpace(existingLogWithSourceName) && 91 | !log.Log.Equals(existingLogWithSourceName, StringComparison.OrdinalIgnoreCase)) 92 | { 93 | // Remove the source from the previous log so we can associate it with the current log name 94 | System.Diagnostics.EventLog.DeleteEventSource(source, log.MachineName); 95 | oldLogName = existingLogWithSourceName; 96 | } 97 | } 98 | else 99 | { 100 | System.Diagnostics.EventLog.CreateEventSource(sourceData); 101 | } 102 | 103 | if (oldLogName != null) 104 | { 105 | var metaSource = $"serilog-{log.Log}"; 106 | if (!System.Diagnostics.EventLog.SourceExists(metaSource, log.MachineName)) 107 | System.Diagnostics.EventLog.CreateEventSource(new EventSourceCreationData(metaSource, log.Log) 108 | { 109 | MachineName = log.MachineName 110 | }); 111 | 112 | log.Source = metaSource; 113 | log.WriteEntry( 114 | $"Event source {source} was previously registered in log {oldLogName}. " + 115 | $"The source has been registered with this log, {log.Log}, however a computer restart may be required " + 116 | $"before event logs will appear in {log.Log} with source {source}. Until then, messages may be logged to {oldLogName}.", 117 | EventLogEntryType.Warning, 118 | SourceMovedEventId); 119 | } 120 | 121 | log.Source = source; 122 | } 123 | 124 | /// 125 | /// Emit the provided log event to the sink. 126 | /// 127 | /// The log event to write. 128 | /// 129 | /// , and are registered as . 130 | /// , are registered as . 131 | /// are registered as . 132 | /// The Event ID in the Windows log will be set to the integer value of the 's property, so that the log can be filtered with more granularity. 133 | public void Emit(LogEvent logEvent) 134 | { 135 | if (logEvent == null) throw new ArgumentNullException(nameof(logEvent)); 136 | 137 | var type = LevelToEventLogEntryType(logEvent.Level); 138 | 139 | var payloadWriter = new StringWriter(); 140 | _textFormatter.Format(logEvent, payloadWriter); 141 | 142 | // The payload is limited in length and allowed chars, see: https://msdn.microsoft.com/en-us/library/e29k5ebc%28v=vs.110%29.aspx 143 | var payload = payloadWriter.ToString(); 144 | if (payload.Length > MaximumPayloadLengthChars) 145 | { 146 | SelfLog.WriteLine("Trimming long event log entry payload to {0} characters", MaximumPayloadLengthChars); 147 | payload = payload[..MaximumPayloadLengthChars]; 148 | } 149 | 150 | _log.WriteEntry(payload, type, _eventIdProvider.ComputeEventId(logEvent), category: _categoryProvider.ComputeCategory(logEvent)); 151 | } 152 | 153 | static EventLogEntryType LevelToEventLogEntryType(LogEventLevel logEventLevel) 154 | { 155 | switch (logEventLevel) 156 | { 157 | case LogEventLevel.Debug: 158 | case LogEventLevel.Information: 159 | case LogEventLevel.Verbose: 160 | return EventLogEntryType.Information; 161 | 162 | case LogEventLevel.Error: 163 | case LogEventLevel.Fatal: 164 | return EventLogEntryType.Error; 165 | 166 | case LogEventLevel.Warning: 167 | return EventLogEntryType.Warning; 168 | 169 | default: 170 | SelfLog.WriteLine("Unexpected logging level {0}, writing to the event log as `Information`", logEventLevel); 171 | return EventLogEntryType.Information; 172 | } 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.EventLog/Sinks/EventLog/ICategoryProvider.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2025 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 | 17 | namespace Serilog.Sinks.EventLog 18 | { 19 | /// 20 | /// Task category provider for log events 21 | /// 22 | public interface ICategoryProvider 23 | { 24 | /// 25 | /// Computes an task category for the given log event. 26 | /// 27 | /// The log event to compute the task category from. 28 | /// Computed task category based off the given log. 29 | short ComputeCategory(LogEvent logEvent); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.EventLog/Sinks/EventLog/IEventIdProvider.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2016 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 | 17 | namespace Serilog.Sinks.EventLog 18 | { 19 | /// 20 | /// Event Id provider for log events 21 | /// 22 | public interface IEventIdProvider 23 | { 24 | /// 25 | /// Computes an Event Id for the given log event. 26 | /// 27 | /// The log event to compute the event id from. 28 | /// Computed event id based off the given log. 29 | ushort ComputeEventId(LogEvent logEvent); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.EventLog/Sinks/EventLog/NullCategoryProvider.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2025 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 | 17 | namespace Serilog.Sinks.EventLog; 18 | 19 | /// 20 | /// Always returns 0 as category number 21 | /// 22 | sealed class NullCategoryProvider : ICategoryProvider 23 | { 24 | public short ComputeCategory(LogEvent logEvent) => 0; 25 | } 26 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.EventLog/Sinks/NullSink.cs: -------------------------------------------------------------------------------- 1 | using Serilog.Core; 2 | using Serilog.Events; 3 | 4 | namespace Serilog.Sinks.EventLog 5 | { 6 | internal class NullSink : ILogEventSink 7 | { 8 | public void Emit(LogEvent logEvent) 9 | { 10 | // Do nothing 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /test/Serilog.Sinks.EventLog.Tests/EventLogSinkTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Linq; 4 | using Serilog.Formatting.Json; 5 | using Newtonsoft.Json.Linq; 6 | using Microsoft.Extensions.Configuration; 7 | using Serilog.Events; 8 | using Xunit; 9 | 10 | #pragma warning disable Serilog004 // Allow non-constant message templates 11 | 12 | namespace Serilog.Sinks.EventLog.Tests 13 | { 14 | public class EventLogSinkTests 15 | { 16 | const string CustomLogName = "serilog-eventlog-sink"; 17 | const string EventLogSource = "EventLogSinkTests"; 18 | 19 | [Fact] 20 | public void EmittingJsonFormattedEventsWorks() 21 | { 22 | var log = new LoggerConfiguration() 23 | .WriteTo.EventLog(new JsonFormatter(), EventLogSource, manageEventSource: true) 24 | .CreateLogger(); 25 | 26 | var message = $"This is a JSON message with a {Guid.NewGuid():D}"; 27 | log.Information(message); 28 | var messageFromLogEvent = EventLogMessageWithSpecificBody(message); 29 | Assert.NotNull(messageFromLogEvent); 30 | AssertJsonCarriesMessageTemplate(messageFromLogEvent, message); 31 | } 32 | 33 | [Fact] 34 | public void EmittingJsonFormattedEventsFromAppSettingsWorks() 35 | { 36 | var log = new LoggerConfiguration() 37 | .ReadFrom.Configuration( 38 | new ConfigurationBuilder() 39 | .SetBasePath(AppDomain.CurrentDomain.BaseDirectory) 40 | .AddXmlFile("appsettings.xml") 41 | .Build()) 42 | .CreateLogger(); 43 | 44 | var message = $"This is a JSON message with a {Guid.NewGuid():D}"; 45 | log.Information(message); 46 | var messageFromLogEvent = EventLogMessageWithSpecificBody(message); 47 | Assert.NotNull(messageFromLogEvent); 48 | AssertJsonCarriesMessageTemplate(messageFromLogEvent, message); 49 | 50 | } 51 | 52 | static void AssertJsonCarriesMessageTemplate(string json, string messageTemplate) 53 | { 54 | var jsonObject = JObject.Parse(json); 55 | Assert.Equal(messageTemplate, (string)jsonObject["MessageTemplate"]); 56 | } 57 | 58 | [Fact] 59 | public void EmittingNormalEventsWorks() 60 | { 61 | var log = new LoggerConfiguration() 62 | .WriteTo.EventLog(EventLogSource, manageEventSource: true) 63 | .CreateLogger(); 64 | 65 | var guid = Guid.NewGuid().ToString("D"); 66 | log.Information("This is a normal message with a {Guid}", guid); 67 | Assert.True(EventLogMessageWithSpecificBodyExists(guid), 68 | "The message was not found in the event log."); 69 | } 70 | 71 | [Fact] 72 | public void UsingAngleBracketsInSourceWorks() 73 | { 74 | var log = new LoggerConfiguration() 75 | .WriteTo.EventLog("EventLogSink", manageEventSource: true) 76 | .CreateLogger(); 77 | 78 | var guid = Guid.NewGuid().ToString("D"); 79 | log.Information("This is a normal message with a {Guid}", guid); 80 | 81 | Assert.True(EventLogMessageWithSpecificBodyExists(guid), 82 | "The message was not found in the event log."); 83 | } 84 | 85 | [Fact] 86 | public void UsingSuperLongSourceNamesAreCorrectlyTrimmed() 87 | { 88 | for (var i = 199; i < 270; i+=10) 89 | { 90 | var log = new LoggerConfiguration() 91 | .WriteTo.EventLog(EventLogSource + new string('x', i - EventLogSource.Length), manageEventSource: true) 92 | .CreateLogger(); 93 | 94 | var guid = Guid.NewGuid().ToString("D"); 95 | log.Information("This is a message with a {Guid}, source had length {length}", guid, i); 96 | 97 | Assert.True(EventLogMessageWithSpecificBodyExists(guid), "The message was not found in the event log. SourceLength was " + i); 98 | } 99 | } 100 | 101 | [Fact] 102 | public void UsingSuperLongLogMessageWorks() 103 | { 104 | var charcounts = new[] 105 | { 106 | 10*1000, 107 | 20*1000, 108 | 30*1000, 109 | 40*1000, 110 | 70*1000 111 | }; 112 | foreach (var charcount in charcounts) 113 | { 114 | var log = new LoggerConfiguration() 115 | .WriteTo.EventLog(EventLogSource, manageEventSource: true) 116 | .CreateLogger(); 117 | 118 | var guid = Guid.NewGuid().ToString("D"); 119 | log.Information("This is a super long message which might be trimmed. Guid is {Guid}.The following text has {charcount} chars: {LongText}" 120 | , guid 121 | , charcount 122 | , new string('x', charcount)); 123 | 124 | Assert.True(EventLogMessageWithSpecificBodyExists(guid), "The message was not found in the event log. Char count was " + charcount); 125 | } 126 | } 127 | 128 | [Fact] 129 | public void UsingSpecialCharsWorks() 130 | { 131 | var log = new LoggerConfiguration() 132 | .WriteTo.EventLog(EventLogSource, manageEventSource: true) 133 | .CreateLogger(); 134 | 135 | var guid = Guid.NewGuid().ToString("D"); 136 | log.Information("This is a message with a {Guid} and a special char {char}", guid, "%1"); 137 | 138 | Assert.True(EventLogMessageWithSpecificBodyExists(guid), "The message was not found in the event log."); 139 | } 140 | 141 | [Fact] 142 | public void UsingCustomEventLogWorks() 143 | { 144 | var log = new LoggerConfiguration() 145 | .WriteTo.EventLog( 146 | //can't use same source in different log 147 | source: $"{EventLogSource}-{CustomLogName}", 148 | logName: CustomLogName, 149 | manageEventSource: true) 150 | .CreateLogger(); 151 | 152 | var guid = Guid.NewGuid().ToString("D"); 153 | log.Information("This is a normal message with a {Guid} in log {CUSTOM_LOG_NAME}", guid, CustomLogName); 154 | 155 | Assert.True(EventLogMessageWithSpecificBodyExists(guid, CustomLogName), 156 | "The message was not found in the event log."); 157 | } 158 | 159 | [Fact] 160 | public void UsingExistingSourceInCustomEventLogLogsRestartWarningAndLogsToApplicationLog() 161 | { 162 | var source = Guid.NewGuid().ToString("D"); 163 | //create our source in the app log first 164 | System.Diagnostics.EventLog.CreateEventSource(new EventSourceCreationData(source, "Application")); 165 | 166 | //then try to use it in our custom log 167 | var log = new LoggerConfiguration() 168 | .WriteTo.EventLog(source: source, logName: CustomLogName, manageEventSource: true) 169 | .CreateLogger(); 170 | 171 | var guid = Guid.NewGuid().ToString("D"); 172 | log.Information("This is a normal message with a {Guid} in log {customLogName}", guid, CustomLogName); 173 | 174 | if (!EventLogMessageWithSpecificBodyExists(guid, "Application")) 175 | Assert.True(EventLogMessageWithSpecificBodyExists(guid, CustomLogName), "The message was not found in either the original or new event log."); 176 | 177 | 178 | Assert.True(EventLogMessageWithSpecificBodyExists(source, CustomLogName), 179 | "The message was not found in target event log."); 180 | 181 | System.Diagnostics.EventLog.DeleteEventSource(source); 182 | } 183 | 184 | [Fact] 185 | public void UsingCustomEventIdProviderLogsMessagesWithSuppliedEventId() 186 | { 187 | var log = new LoggerConfiguration() 188 | .WriteTo.EventLog(EventLogSource, manageEventSource: true, eventIdProvider: new CustomEventIdProvider()) 189 | .CreateLogger(); 190 | 191 | Assert.NotEqual(CustomEventIdProvider.MessageWithKnownIdEventId, CustomEventIdProvider.UnknownEventId); 192 | 193 | var knownIdGuid = Guid.NewGuid().ToString("D"); 194 | log.Information(CustomEventIdProvider.MessageWithKnownId, knownIdGuid); 195 | 196 | Assert.True(EventLogMessageWithSpecificBodyAndEventIdExists(knownIdGuid, CustomEventIdProvider.MessageWithKnownIdEventId), 197 | "The message was with known event id not found in target event log."); 198 | 199 | var unknownIdGuid = Guid.NewGuid().ToString("D"); 200 | log.Information("unknown message {Guid}", unknownIdGuid); 201 | 202 | Assert.True(EventLogMessageWithSpecificBodyAndEventIdExists(unknownIdGuid, CustomEventIdProvider.UnknownEventId), 203 | "The message was with unknown event id not found in target event log."); 204 | } 205 | 206 | [Fact] 207 | public void UsingCustomCategoryProviderLogsMessagesWithSuppliedCategory() 208 | { 209 | var log = new LoggerConfiguration() 210 | .WriteTo.EventLog(EventLogSource, manageEventSource: true, categoryProvider: new CustomCategoryNumberProvider()) 211 | .CreateLogger(); 212 | 213 | Assert.NotEqual(CustomCategoryNumberProvider.MessageWithKnownCategoryNumber, CustomCategoryNumberProvider.DefaultCategoryNumber); 214 | 215 | var knownIdGuid = Guid.NewGuid().ToString("D"); 216 | log.Information(CustomCategoryNumberProvider.MessageWithKnownCategory, knownIdGuid); 217 | 218 | Assert.True(EventLogMessageWithSpecificBodyAndCategoryExists(knownIdGuid, CustomCategoryNumberProvider.MessageWithKnownCategoryNumber), 219 | "The message was with known category not found in target event log."); 220 | 221 | var unknownIdGuid = Guid.NewGuid().ToString("D"); 222 | log.Information("unknown message {Guid}", unknownIdGuid); 223 | 224 | Assert.True(EventLogMessageWithSpecificBodyAndCategoryExists(unknownIdGuid, CustomCategoryNumberProvider.DefaultCategoryNumber), 225 | "The message was with default category not found in target event log."); 226 | } 227 | 228 | static bool EventLogMessageWithSpecificBodyAndEventIdExists(string partOfBody, int eventId) 229 | { 230 | return ApplicationLog 231 | .Entries 232 | .Cast() 233 | .Any(entry => entry.InstanceId == eventId 234 | && entry.Message.Contains(partOfBody)); 235 | } 236 | 237 | static bool EventLogMessageWithSpecificBodyAndCategoryExists(string partOfBody, short category) 238 | { 239 | return ApplicationLog 240 | .Entries 241 | .Cast() 242 | .Any(entry => entry.CategoryNumber == category 243 | && entry.Message.Contains(partOfBody)); 244 | } 245 | 246 | static bool EventLogMessageWithSpecificBodyExists(string partOfBody, string logName = "") 247 | { 248 | var log = string.IsNullOrWhiteSpace(logName) ? ApplicationLog : GetLog(logName); 249 | return log.Entries.Cast().Any(entry => entry.Message.Contains(partOfBody)); 250 | } 251 | 252 | static string EventLogMessageWithSpecificBody(string partOfBody) 253 | { 254 | return ApplicationLog.Entries.Cast().FirstOrDefault(entry => entry.Message.Contains(partOfBody))?.Message; 255 | } 256 | 257 | static System.Diagnostics.EventLog ApplicationLog => GetLog("Application"); 258 | 259 | static System.Diagnostics.EventLog GetLog(string logName) 260 | { 261 | var eventLog = System.Diagnostics.EventLog.GetEventLogs().FirstOrDefault(log => log.Log == logName); 262 | if (eventLog == null) 263 | throw new Exception($"Cannot find log \"{logName}\""); 264 | return eventLog; 265 | } 266 | 267 | sealed class CustomEventIdProvider : IEventIdProvider 268 | { 269 | public const ushort UnknownEventId = 1; 270 | 271 | public const ushort MessageWithKnownIdEventId = 12; 272 | public const string MessageWithKnownId = "Event {Guid} - this message has a known id"; 273 | 274 | public ushort ComputeEventId(LogEvent logEvent) 275 | { 276 | return string.Equals(logEvent.MessageTemplate.Text, MessageWithKnownId) ? MessageWithKnownIdEventId : UnknownEventId; 277 | } 278 | } 279 | 280 | sealed class CustomCategoryNumberProvider : ICategoryProvider 281 | { 282 | public const short DefaultCategoryNumber = 1; 283 | 284 | public const short MessageWithKnownCategoryNumber = 12; 285 | public const string MessageWithKnownCategory = "Event {Guid} - this message has a known category"; 286 | 287 | public short ComputeCategory(LogEvent logEvent) 288 | { 289 | return string.Equals(logEvent.MessageTemplate.Text, MessageWithKnownCategory) ? MessageWithKnownCategoryNumber : DefaultCategoryNumber; 290 | } 291 | } 292 | } 293 | } 294 | -------------------------------------------------------------------------------- /test/Serilog.Sinks.EventLog.Tests/Serilog.Sinks.EventLog.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net48;net6.0;net8.0 5 | ../../assets/Serilog.snk 6 | true 7 | true 8 | true 9 | CA1416 10 | 11 | 12 | 13 | 14 | PreserveNewest 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | all 26 | runtime; build; native; contentfiles; analyzers 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /test/Serilog.Sinks.EventLog.Tests/appsettings.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 8 | 9 | 10 | 11 | --------------------------------------------------------------------------------