├── .config └── dotnet-tools.json ├── .editorconfig ├── .gitattributes ├── .github ├── FUNDING.yml ├── dependabot.yml └── workflows │ ├── ci.yml │ └── dependabot-cake.yml ├── .gitignore ├── CHANGES.md ├── CODEOWNERS ├── Directory.Build.props ├── LICENSE ├── README.md ├── assets ├── Serilog.snk ├── reddit-comment-note-ad-as-debug-console.png ├── serilog-sink-nuget.png └── serilog-sinks-notepad-screenshot.png ├── build.cake ├── build.cmd ├── build.ps1 ├── build.sh ├── cake.config ├── global.json ├── nuget.config ├── sample └── ConsoleDemo │ ├── ConsoleDemo.csproj │ └── Program.cs ├── serilog-sinks-notepad.sln ├── serilog-sinks-notepad.sln.DotSettings ├── src └── Serilog.Sinks.Notepad │ ├── NotepadLoggerConfigurationExtensions.cs │ ├── Serilog.Sinks.Notepad.csproj │ └── Sinks │ ├── Notepad │ ├── Interop │ │ ├── NotepadTextWriter.cs │ │ └── User32.cs │ ├── NotepadSink.cs │ ├── NotepadWindow.cs │ └── NullSink.cs │ └── TextWriter │ └── TextWriterSink.cs └── test └── Serilog.Sinks.Notepad.Tests ├── Serilog.Sinks.Notepad.Tests.csproj ├── Sinks └── TextWriter │ └── TextWriterSinkTests.cs └── Support └── Some.cs /.config/dotnet-tools.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "isRoot": true, 4 | "tools": { 5 | "cake.tool": { 6 | "version": "3.0.0", 7 | "commands": [ 8 | "dotnet-cake" 9 | ] 10 | }, 11 | "minver-cli": { 12 | "version": "2.5.0", 13 | "commands": [ 14 | "minver" 15 | ] 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | [*] 7 | end_of_line = unset 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | indent_style = space 11 | indent_size = 4 12 | 13 | [*.{xml,config,nuspec,csproj,props,targets,ps1}] 14 | indent_size = 2 15 | 16 | [*.{sh}] 17 | end_of_line = lf 18 | 19 | [*.{dotsettings}] 20 | end_of_line = lf 21 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Set the default behavior, in case people don't have core.autocrlf set 2 | * text=auto 3 | 4 | # Explicitly declare files that should always be converted to LF regardless of platform 5 | *.sh text eol=lf 6 | *.dotsettings text eol=lf 7 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: augustoproiete 2 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "nuget" 4 | directory: "/src" 5 | schedule: 6 | interval: "daily" 7 | target-branch: "master" 8 | ignore: 9 | - dependency-name: "Serilog" 10 | - package-ecosystem: "nuget" 11 | directory: "/test" 12 | schedule: 13 | interval: "daily" 14 | target-branch: "master" 15 | - package-ecosystem: "nuget" 16 | directory: "/sample" 17 | schedule: 18 | interval: "daily" 19 | target-branch: "master" 20 | - package-ecosystem: "github-actions" 21 | directory: "/" 22 | schedule: 23 | interval: "daily" 24 | target-branch: "master" 25 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - master 5 | - develop 6 | - "feature/**" 7 | - "release/**" 8 | - "hotfix/**" 9 | tags: 10 | - "*.*.*" 11 | paths-ignore: 12 | - "README.md" 13 | 14 | pull_request: 15 | 16 | workflow_dispatch: 17 | 18 | env: 19 | DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true 20 | DOTNET_CLI_TELEMETRY_OPTOUT: true 21 | DOTNET_NOLOGO: true 22 | 23 | jobs: 24 | build: 25 | strategy: 26 | fail-fast: false 27 | matrix: 28 | job: 29 | - os: windows-2022 30 | build: ./build.cmd 31 | push: true 32 | name: ${{ matrix.job.os }} 33 | runs-on: ${{ matrix.job.os }} 34 | steps: 35 | - name: Setup netcoreapp3.1 36 | uses: actions/setup-dotnet@v4.3.1 37 | with: 38 | dotnet-version: "3.1.425" 39 | - name: Setup net5.0 40 | uses: actions/setup-dotnet@v4.3.1 41 | with: 42 | dotnet-version: "5.0.408" 43 | - name: Setup net6.0 44 | uses: actions/setup-dotnet@v4.3.1 45 | with: 46 | dotnet-version: "6.0.403" 47 | - name: Setup net7.0 48 | uses: actions/setup-dotnet@v4.3.1 49 | with: 50 | dotnet-version: "7.0.100" 51 | - name: Run dotnet --info 52 | run: dotnet --info 53 | - uses: actions/checkout@v4.2.2 54 | with: 55 | fetch-depth: 0 56 | - name: Build 57 | run: ${{ matrix.job.build }} --verbosity=diagnostic --target=pack 58 | - name: Publish artifacts 59 | if: matrix.job.push && (github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags/')) 60 | uses: actions/upload-artifact@v4.6.2 61 | with: 62 | if-no-files-found: warn 63 | name: package 64 | path: artifact/nuget/**/* 65 | -------------------------------------------------------------------------------- /.github/workflows/dependabot-cake.yml: -------------------------------------------------------------------------------- 1 | on: 2 | schedule: 3 | # every Sunday at 6am 4 | - cron: '0 6 * * SUN' 5 | 6 | workflow_dispatch: 7 | 8 | jobs: 9 | dependabot-cake: 10 | runs-on: ubuntu-20.04 11 | steps: 12 | - name: check/update cake dependencies 13 | uses: augustoproiete-actions/nils-org--dependabot-cake-action@v1.1.0 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | #windows 2 | Thumbs.db 3 | 4 | #osx 5 | .DS_Store 6 | ._* 7 | 8 | #visual-studio 9 | .vs/ 10 | *.user 11 | *.suo 12 | *.tmp_proj 13 | *.cache 14 | *.vsdoc 15 | [Oo]bj/ 16 | [Bb]in/ 17 | [Dd]ebug/ 18 | [Rr]elease/ 19 | [Ll]og/ 20 | [Tt]emp/ 21 | 22 | #nuget 23 | *.nupkg 24 | **/packages/* 25 | 26 | #cake 27 | .cake/ 28 | /artifact/ 29 | -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | See: https://github.com/serilog-contrib/serilog-sinks-notepad/releases 2 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @augustoproiete 2 | -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 11.0 5 | $(NoWarn);NU5048;CS8032 6 | true 7 | 8 | false 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /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 2020-2023 C. Augusto Proiete & Contributors 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | | README.md | 2 | |:---| 3 | 4 |
5 | 6 | Serilog.Sinks.Notepad 7 | 8 |
9 | 10 |

Serilog.Sinks.Notepad

11 |
12 | 13 | A [Serilog](https://serilog.net) sink that writes log events to Notepad (_Yes, you've read it right!_). Simply open Notepad and immediately start receiving logs from your application, without even touching the filesystem. 14 | 15 | [![NuGet Version](http://img.shields.io/nuget/v/Serilog.Sinks.Notepad.svg?style=flat)](https://www.nuget.org/packages/Serilog.Sinks.Notepad) [![Stack Overflow](https://img.shields.io/badge/stack%20overflow-serilog-orange.svg)](http://stackoverflow.com/questions/tagged/serilog) 16 | 17 | ![Screenshot of Serilog.Sinks.Notepad in action](assets/serilog-sinks-notepad-screenshot.png) 18 | 19 |
20 | 21 | `Serilog.Sinks.Notepad` writes messages to the most recent `notepad.exe` started on current user's session, by default. This behavior can be changed in the sink configuration. 22 | 23 | The default output is plain text; JSON formatting can be plugged in using a package such as [_Serilog.Formatting.Compact_](https://github.com/serilog/serilog-formatting-compact). 24 | 25 | ## Give a Star! :star: 26 | 27 | If you like or are using this project please give it a star. Thanks! 28 | 29 | ## Getting started :rocket: 30 | 31 | Install the [Serilog.Sinks.Notepad](https://www.nuget.org/packages/Serilog.Sinks.Notepad) package from NuGet: 32 | 33 | ```powershell 34 | Install-Package Serilog.Sinks.Notepad 35 | ``` 36 | 37 | To configure the sink in C# code, call `WriteTo.Notepad()` during logger configuration: 38 | 39 | ```csharp 40 | Log.Logger = new LoggerConfiguration() 41 | .WriteTo.Notepad() 42 | .CreateLogger(); 43 | 44 | Log.Information("Hello, world!"); 45 | 46 | Log.CloseAndFlush(); 47 | ``` 48 | 49 | Open Notepad, and you should see the logs appear in that Notepad window you've just opened. By default, `Serilog.Sinks.Notepad` writes messages to the most recent `notepad.exe` started by the user. This behavior can be changed in the sink configuration. 50 | 51 | ## Background 52 | 53 | I created this sink just for fun, after reading [this comment on Reddit](https://www.reddit.com/r/programming/comments/gnazif/ray_tracing_in_notepadexe_at_30_fps/fr8uy2l/): 54 | 55 | [![Screenshot of Serilog.Sinks.Notepad in action](assets/reddit-comment-note-ad-as-debug-console.png)](https://www.reddit.com/r/programming/comments/gnazif/ray_tracing_in_notepadexe_at_30_fps/fr8uy2l/) 56 | 57 | I thought it was a clever idea to be able to simply open a Notepad instance and immediately start receiving logs from your application, and I can imagine this actually being useful for troubleshooting issues with applications. 58 | 59 | ## Configuration 60 | 61 | ### Output templates 62 | 63 | The format of events written to Notepad can be modified using the `outputTemplate` configuration parameter: 64 | 65 | ```csharp 66 | .WriteTo.Notepad( 67 | outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}{Exception}") 68 | ``` 69 | 70 | The default template, shown in the example above, uses built-in properties like `Timestamp` and `Level`. Properties from events, including those attached using [enrichers](https://github.com/serilog/serilog/wiki/Enrichment), can also appear in the output template. 71 | 72 | ### JSON output 73 | 74 | The sink can write JSON output instead of plain text. `CompactJsonFormatter` or `RenderedCompactJsonFormatter` from [Serilog.Formatting.Compact](https://github.com/serilog/serilog-formatting-compact) is recommended: 75 | 76 | ```powershell 77 | Install-Package Serilog.Formatting.Compact 78 | ``` 79 | 80 | Pass a formatter to the `Notepad()` configuration method: 81 | 82 | ```csharp 83 | .WriteTo.Notepad(new RenderedCompactJsonFormatter()) 84 | ``` 85 | 86 | ### XML `` configuration 87 | 88 | To use the Notepad sink with the [Serilog.Settings.AppSettings](https://github.com/serilog/serilog-settings-appsettings) package, first install that package if you haven't already done so: 89 | 90 | ```powershell 91 | Install-Package Serilog.Settings.AppSettings 92 | ``` 93 | 94 | Instead of configuring the logger in code, call `ReadFrom.AppSettings()`: 95 | 96 | ```csharp 97 | Log.Logger = new LoggerConfiguration() 98 | .ReadFrom.AppSettings() 99 | .CreateLogger(); 100 | ``` 101 | 102 | In your application's `App.config` or `Web.config` file, specify the Notepad sink assembly under the `` node: 103 | 104 | ```xml 105 | 106 | 107 | 108 | 109 | ``` 110 | 111 | To configure the Notepad sink and include the `SourceContext` in the output, change your `App.config`/`Web.config` to: 112 | ```xml 113 | 114 | 115 | 116 | 117 | ``` 118 | 119 | ### JSON `appsettings.json` configuration 120 | 121 | To use the Notepad sink with _Microsoft.Extensions.Configuration_, for example with ASP.NET Core or .NET Core, use the [Serilog.Settings.Configuration](https://github.com/serilog/serilog-settings-configuration) package. First install that package if you have not already done so: 122 | 123 | ```powershell 124 | Install-Package Serilog.Settings.Configuration 125 | ``` 126 | 127 | Instead of configuring the sink directly in code, call `ReadFrom.Configuration()`: 128 | 129 | ```csharp 130 | var configuration = new ConfigurationBuilder() 131 | .AddJsonFile("appsettings.json") 132 | .Build(); 133 | 134 | Log.Logger = new LoggerConfiguration() 135 | .ReadFrom.Configuration(configuration) 136 | .CreateLogger(); 137 | ``` 138 | 139 | In your `appsettings.json` file, under the `Serilog` node, : 140 | ```json 141 | { 142 | "Serilog": { 143 | "WriteTo": [{"Name": "Notepad"}] 144 | } 145 | } 146 | ``` 147 | 148 | To configure the Notepad sink and include the `SourceContext` in the output, change your `appsettings.json` to: 149 | ```json 150 | { 151 | "Serilog": { 152 | "WriteTo": [ 153 | { 154 | "Name": "Notepad", 155 | "Args": { 156 | "outputTemplate": "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj} {NewLine}{Exception}" 157 | } 158 | } 159 | ] 160 | } 161 | } 162 | ``` 163 | 164 | ## Debugging and Diagnostics 165 | 166 | When Serilog is not behaving as you expect, this may be caused by an internal exception or configuration issue. Serilog writes simple diagnostic messages to [SelfLog](https://github.com/serilog/serilog/wiki/Debugging-and-Diagnostics#selflog), which can be forwarded to Notepad, using the `NotepadWindow` static class: 167 | 168 | ```csharp 169 | Serilog.Debugging.SelfLog.Enable(s => NotepadWindow.WriteLine($"Internal Error with Serilog: {s}")); 170 | ``` 171 | 172 | The above will attempt to write Serilog's diagnostic messages to the most recent Notepad process open in the user's session. 173 | 174 | ## Release History 175 | 176 | Click on the [Releases](https://github.com/serilog-contrib/serilog-sinks-notepad/releases) tab on GitHub. 177 | 178 | --- 179 | 180 | _Copyright © 2020-2023 C. Augusto Proiete & Contributors - Provided under the [Apache License, Version 2.0](LICENSE)._ 181 | -------------------------------------------------------------------------------- /assets/Serilog.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/serilog-contrib/serilog-sinks-notepad/3714bad45bfb4c2d2ed2beee7859417df8656c45/assets/Serilog.snk -------------------------------------------------------------------------------- /assets/reddit-comment-note-ad-as-debug-console.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/serilog-contrib/serilog-sinks-notepad/3714bad45bfb4c2d2ed2beee7859417df8656c45/assets/reddit-comment-note-ad-as-debug-console.png -------------------------------------------------------------------------------- /assets/serilog-sink-nuget.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/serilog-contrib/serilog-sinks-notepad/3714bad45bfb4c2d2ed2beee7859417df8656c45/assets/serilog-sink-nuget.png -------------------------------------------------------------------------------- /assets/serilog-sinks-notepad-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/serilog-contrib/serilog-sinks-notepad/3714bad45bfb4c2d2ed2beee7859417df8656c45/assets/serilog-sinks-notepad-screenshot.png -------------------------------------------------------------------------------- /build.cake: -------------------------------------------------------------------------------- 1 | #addin "nuget:?package=Cake.MinVer&version=3.0.0" 2 | #addin "nuget:?package=Cake.Args&version=3.0.0" 3 | 4 | var target = ArgumentOrDefault("target") ?? "pack"; 5 | var buildVersion = MinVer(s => s.WithTagPrefix("v").WithDefaultPreReleasePhase("preview")); 6 | 7 | Task("clean") 8 | .Does(() => 9 | { 10 | CleanDirectories("./artifact/**"); 11 | CleanDirectories("./**/^{bin,obj}"); 12 | }); 13 | 14 | Task("restore") 15 | .IsDependentOn("clean") 16 | .Does(() => 17 | { 18 | DotNetRestore("./serilog-sinks-notepad.sln", new DotNetRestoreSettings 19 | { 20 | LockedMode = true, 21 | }); 22 | }); 23 | 24 | Task("build") 25 | .IsDependentOn("restore") 26 | .DoesForEach(new[] { "Debug", "Release" }, (configuration) => 27 | { 28 | DotNetBuild("./serilog-sinks-notepad.sln", new DotNetBuildSettings 29 | { 30 | Configuration = configuration, 31 | NoRestore = true, 32 | NoIncremental = false, 33 | MSBuildSettings = new DotNetMSBuildSettings() 34 | .SetVersion(buildVersion.Version) 35 | .SetAssemblyVersion(buildVersion.AssemblyVersion) 36 | .SetFileVersion(buildVersion.FileVersion) 37 | .SetContinuousIntegrationBuild() 38 | }); 39 | }); 40 | 41 | Task("test") 42 | .IsDependentOn("build") 43 | .Does(() => 44 | { 45 | var settings = new DotNetTestSettings 46 | { 47 | Configuration = "Release", 48 | NoRestore = true, 49 | NoBuild = true, 50 | }; 51 | 52 | var projectFiles = GetFiles("./test/**/*.csproj"); 53 | foreach (var file in projectFiles) 54 | { 55 | DotNetTest(file.FullPath, settings); 56 | } 57 | }); 58 | 59 | Task("pack") 60 | .IsDependentOn("test") 61 | .Does(() => 62 | { 63 | var releaseNotes = $"https://github.com/serilog-contrib/serilog-sinks-notepad/releases/tag/v{buildVersion.Version}"; 64 | 65 | DotNetPack("./src/Serilog.Sinks.Notepad/Serilog.Sinks.Notepad.csproj", new DotNetPackSettings 66 | { 67 | Configuration = "Release", 68 | NoRestore = true, 69 | NoBuild = true, 70 | OutputDirectory = "./artifact/nuget", 71 | MSBuildSettings = new DotNetMSBuildSettings 72 | { 73 | Version = buildVersion.Version, 74 | PackageReleaseNotes = releaseNotes, 75 | }, 76 | }); 77 | }); 78 | 79 | Task("push") 80 | .IsDependentOn("pack") 81 | .Does(context => 82 | { 83 | var url = context.EnvironmentVariable("NUGET_URL"); 84 | if (string.IsNullOrWhiteSpace(url)) 85 | { 86 | context.Information("No NuGet URL specified. Skipping publishing of NuGet packages"); 87 | return; 88 | } 89 | 90 | var apiKey = context.EnvironmentVariable("NUGET_API_KEY"); 91 | if (string.IsNullOrWhiteSpace(apiKey)) 92 | { 93 | context.Information("No NuGet API key specified. Skipping publishing of NuGet packages"); 94 | return; 95 | } 96 | 97 | var nugetPushSettings = new DotNetNuGetPushSettings 98 | { 99 | Source = url, 100 | ApiKey = apiKey, 101 | }; 102 | 103 | foreach (var nugetPackageFile in GetFiles("./artifact/nuget/*.nupkg")) 104 | { 105 | DotNetNuGetPush(nugetPackageFile.FullPath, nugetPushSettings); 106 | } 107 | }); 108 | 109 | RunTarget(target); 110 | -------------------------------------------------------------------------------- /build.cmd: -------------------------------------------------------------------------------- 1 | @echo on 2 | @cd %~dp0 3 | 4 | set DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1 5 | set DOTNET_CLI_TELEMETRY_OPTOUT=1 6 | set DOTNET_NOLOGO=1 7 | 8 | dotnet tool restore 9 | @if %ERRORLEVEL% neq 0 goto :eof 10 | 11 | dotnet cake %* 12 | -------------------------------------------------------------------------------- /build.ps1: -------------------------------------------------------------------------------- 1 | $ErrorActionPreference = 'Stop' 2 | 3 | Set-Location -LiteralPath $PSScriptRoot 4 | 5 | $env:DOTNET_SKIP_FIRST_TIME_EXPERIENCE = '1' 6 | $env:DOTNET_CLI_TELEMETRY_OPTOUT = '1' 7 | $env:DOTNET_NOLOGO = '1' 8 | 9 | dotnet tool restore 10 | if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } 11 | 12 | dotnet cake @args 13 | if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } 14 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euox pipefail 3 | 4 | cd "$(dirname "${BASH_SOURCE[0]}")" 5 | 6 | export DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1 7 | export DOTNET_CLI_TELEMETRY_OPTOUT=1 8 | export DOTNET_NOLOGO=1 9 | 10 | dotnet tool restore 11 | 12 | dotnet cake "$@" 13 | -------------------------------------------------------------------------------- /cake.config: -------------------------------------------------------------------------------- 1 | [Nuget] 2 | Source=https://api.nuget.org/v3/index.json 3 | UseInProcessClient=true 4 | LoadDependencies=false 5 | 6 | [Paths] 7 | Tools=./.cake 8 | Addins=./.cake/addins 9 | Modules=./.cake/modules 10 | 11 | [Settings] 12 | SkipVerification=false 13 | -------------------------------------------------------------------------------- /global.json: -------------------------------------------------------------------------------- 1 | { 2 | "sdk": { 3 | "allowPrerelease": false, 4 | "version": "7.0.100", 5 | "rollForward": "latestFeature" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /nuget.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /sample/ConsoleDemo/ConsoleDemo.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net6.0;net5.0;netcoreapp3.1;net45 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /sample/ConsoleDemo/Program.cs: -------------------------------------------------------------------------------- 1 | #region Copyright 2020-2023 C. Augusto Proiete & 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 | #endregion 16 | 17 | using System; 18 | using System.Threading; 19 | using Serilog; 20 | 21 | namespace ConsoleDemo 22 | { 23 | internal class Program 24 | { 25 | private static void Main(string[] args) 26 | { 27 | Serilog.Debugging.SelfLog.Enable(s => Console.WriteLine($"Internal Error with Serilog: {s}")); 28 | 29 | Log.Logger = new LoggerConfiguration() 30 | .MinimumLevel.Verbose() 31 | .WriteTo.Notepad() 32 | .CreateLogger(); 33 | 34 | try 35 | { 36 | Console.WriteLine("Open a `notepad.exe` instance and press to continue..."); 37 | Console.ReadLine(); 38 | 39 | Console.WriteLine("Writing messages to the most recent Notepad you opened..."); 40 | 41 | Log.Debug("Getting started"); 42 | 43 | Log.Information("Hello {Name} from thread {ThreadId}", Environment.GetEnvironmentVariable("USERNAME"), 44 | Thread.CurrentThread.ManagedThreadId); 45 | 46 | Log.Warning("No coins remain at position {@Position}", new { Lat = 25, Long = 134 }); 47 | 48 | try 49 | { 50 | Fail(); 51 | } 52 | catch (Exception ex) 53 | { 54 | Log.Error(ex, "Oops... Something went wrong"); 55 | } 56 | 57 | Console.WriteLine("Done."); 58 | } 59 | finally 60 | { 61 | Log.CloseAndFlush(); 62 | } 63 | } 64 | 65 | private static void Fail() 66 | { 67 | throw new DivideByZeroException(); 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /serilog-sinks-notepad.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.30104.148 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "assets", "assets", "{98FECEDE-5590-406F-A327-AA7F6CBE07A7}" 7 | ProjectSection(SolutionItems) = preProject 8 | .editorconfig = .editorconfig 9 | .gitattributes = .gitattributes 10 | .gitignore = .gitignore 11 | CHANGES.md = CHANGES.md 12 | CODEOWNERS = CODEOWNERS 13 | LICENSE = LICENSE 14 | README.md = README.md 15 | EndProjectSection 16 | EndProject 17 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "sample", "sample", "{C73ACC17-798C-44E7-BC96-4BEADBD4085D}" 18 | EndProject 19 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConsoleDemo", "sample\ConsoleDemo\ConsoleDemo.csproj", "{B3DD36B1-EEFA-4E7C-82B8-60728AFACCF7}" 20 | EndProject 21 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{A788D7A8-C32F-4EFC-ADD0-2A1791595225}" 22 | EndProject 23 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Serilog.Sinks.Notepad", "src\Serilog.Sinks.Notepad\Serilog.Sinks.Notepad.csproj", "{CC7EBF80-9141-489F-A7BC-D59D18971279}" 24 | EndProject 25 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{1E653E10-F79F-4BFF-9A49-30F4B7DCF851}" 26 | EndProject 27 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Serilog.Sinks.Notepad.Tests", "test\Serilog.Sinks.Notepad.Tests\Serilog.Sinks.Notepad.Tests.csproj", "{FC1DB800-0FD6-489A-96B6-B4C880EC409B}" 28 | EndProject 29 | Global 30 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 31 | Debug|Any CPU = Debug|Any CPU 32 | Release|Any CPU = Release|Any CPU 33 | EndGlobalSection 34 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 35 | {B3DD36B1-EEFA-4E7C-82B8-60728AFACCF7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 36 | {B3DD36B1-EEFA-4E7C-82B8-60728AFACCF7}.Debug|Any CPU.Build.0 = Debug|Any CPU 37 | {B3DD36B1-EEFA-4E7C-82B8-60728AFACCF7}.Release|Any CPU.ActiveCfg = Release|Any CPU 38 | {B3DD36B1-EEFA-4E7C-82B8-60728AFACCF7}.Release|Any CPU.Build.0 = Release|Any CPU 39 | {CC7EBF80-9141-489F-A7BC-D59D18971279}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 40 | {CC7EBF80-9141-489F-A7BC-D59D18971279}.Debug|Any CPU.Build.0 = Debug|Any CPU 41 | {CC7EBF80-9141-489F-A7BC-D59D18971279}.Release|Any CPU.ActiveCfg = Release|Any CPU 42 | {CC7EBF80-9141-489F-A7BC-D59D18971279}.Release|Any CPU.Build.0 = Release|Any CPU 43 | {FC1DB800-0FD6-489A-96B6-B4C880EC409B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 44 | {FC1DB800-0FD6-489A-96B6-B4C880EC409B}.Debug|Any CPU.Build.0 = Debug|Any CPU 45 | {FC1DB800-0FD6-489A-96B6-B4C880EC409B}.Release|Any CPU.ActiveCfg = Release|Any CPU 46 | {FC1DB800-0FD6-489A-96B6-B4C880EC409B}.Release|Any CPU.Build.0 = Release|Any CPU 47 | EndGlobalSection 48 | GlobalSection(SolutionProperties) = preSolution 49 | HideSolutionNode = FALSE 50 | EndGlobalSection 51 | GlobalSection(NestedProjects) = preSolution 52 | {B3DD36B1-EEFA-4E7C-82B8-60728AFACCF7} = {C73ACC17-798C-44E7-BC96-4BEADBD4085D} 53 | {CC7EBF80-9141-489F-A7BC-D59D18971279} = {A788D7A8-C32F-4EFC-ADD0-2A1791595225} 54 | {FC1DB800-0FD6-489A-96B6-B4C880EC409B} = {1E653E10-F79F-4BFF-9A49-30F4B7DCF851} 55 | EndGlobalSection 56 | GlobalSection(ExtensibilityGlobals) = postSolution 57 | SolutionGuid = {53FA8B3B-657E-4173-B3F9-EA434D5D5BB0} 58 | EndGlobalSection 59 | EndGlobal 60 | -------------------------------------------------------------------------------- /serilog-sinks-notepad.sln.DotSettings: -------------------------------------------------------------------------------- 1 |  2 | 1000 3 | 3000 4 | DO_NOT_SHOW 5 | DO_NOT_SHOW 6 | DO_NOT_SHOW 7 | DO_NOT_SHOW 8 | DO_NOT_SHOW 9 | DO_NOT_SHOW 10 | DO_NOT_SHOW 11 | DO_NOT_SHOW 12 | DO_NOT_SHOW 13 | DO_NOT_SHOW 14 | DO_NOT_SHOW 15 | <Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /> 16 | <Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /> -------------------------------------------------------------------------------- /src/Serilog.Sinks.Notepad/NotepadLoggerConfigurationExtensions.cs: -------------------------------------------------------------------------------- 1 | #region Copyright 2020-2023 C. Augusto Proiete & 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 | #endregion 16 | 17 | using System; 18 | using System.ComponentModel; 19 | using System.Diagnostics; 20 | using System.Runtime.InteropServices; 21 | using Serilog.Configuration; 22 | using Serilog.Core; 23 | using Serilog.Events; 24 | using Serilog.Formatting; 25 | using Serilog.Formatting.Display; 26 | using Serilog.Sinks.Notepad; 27 | using Serilog.Sinks.Notepad.Interop; 28 | 29 | namespace Serilog 30 | { 31 | /// 32 | /// Adds the WriteTo.Notepad() extension method to . 33 | /// 34 | public static class NotepadLoggerConfigurationExtensions 35 | { 36 | private static readonly object _defaultSyncRoot = new object(); 37 | private const string _defaultNotepadOutputTemplate = "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{NewLine}{Exception}"; 38 | 39 | /// 40 | /// Writes log events to Notepad. 41 | /// 42 | /// Logger sink configuration. 43 | /// The minimum level for 44 | /// events passed through the sink. Ignored when is specified. 45 | /// A message template describing the format used to write to the sink. 46 | /// The default is "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{NewLine}{Exception}". 47 | /// Supplies culture-specific formatting information, or null. 48 | /// A switch allowing the pass-through minimum level 49 | /// to be changed at runtime. 50 | /// A strategy to find the target `notepad.exe` process where log messages will be sent to. 51 | /// An object that will be used to `lock` (sync) access to the Notepad output. If you specify this, you 52 | /// will have the ability to lock on this object, and guarantee that the Notepad sink will not be able to output anything while 53 | /// the lock is held. 54 | /// Configuration object allowing method chaining. 55 | public static LoggerConfiguration Notepad( 56 | this LoggerSinkConfiguration sinkConfiguration, 57 | LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum, 58 | string outputTemplate = _defaultNotepadOutputTemplate, 59 | IFormatProvider formatProvider = null, 60 | LoggingLevelSwitch levelSwitch = null, 61 | Func notepadProcessFinderFunc = null, 62 | object syncRoot = null) 63 | { 64 | if (sinkConfiguration is null) throw new ArgumentNullException(nameof(sinkConfiguration)); 65 | 66 | if (!Enum.IsDefined(typeof(LogEventLevel), restrictedToMinimumLevel)) 67 | throw new InvalidEnumArgumentException(nameof(restrictedToMinimumLevel), (int)restrictedToMinimumLevel, 68 | typeof(LogEventLevel)); 69 | 70 | syncRoot ??= _defaultSyncRoot; 71 | 72 | var formatter = new MessageTemplateTextFormatter(outputTemplate, formatProvider); 73 | 74 | return RuntimeInformation.IsOSPlatform(OSPlatform.Windows) 75 | ? sinkConfiguration.Sink(new NotepadSink(new NotepadTextWriter(notepadProcessFinderFunc), formatter, syncRoot), restrictedToMinimumLevel, levelSwitch) 76 | : sinkConfiguration.Sink(new NullSink(), restrictedToMinimumLevel, levelSwitch); 77 | 78 | } 79 | 80 | /// 81 | /// Writes log events to Notepad. 82 | /// 83 | /// Logger sink configuration. 84 | /// Controls the rendering of log events into text, for example to log JSON. To 85 | /// control plain text formatting, use the overload that accepts an output template. 86 | /// An object that will be used to `lock` (sync) access to the Notepad output. If you specify this, you 87 | /// will have the ability to lock on this object, and guarantee that the Notepad sink will not be able to output anything while 88 | /// the lock is held. 89 | /// The minimum level for 90 | /// events passed through the sink. Ignored when is specified. 91 | /// A switch allowing the pass-through minimum level 92 | /// to be changed at runtime. 93 | /// A strategy to find the target `notepad.exe` process where log messages will be sent to. 94 | /// Configuration object allowing method chaining. 95 | public static LoggerConfiguration Notepad( 96 | this LoggerSinkConfiguration sinkConfiguration, 97 | ITextFormatter formatter, 98 | LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum, 99 | LoggingLevelSwitch levelSwitch = null, 100 | Func notepadProcessFinderFunc = null, 101 | object syncRoot = null) 102 | { 103 | if (sinkConfiguration is null) throw new ArgumentNullException(nameof(sinkConfiguration)); 104 | 105 | if (formatter is null) throw new ArgumentNullException(nameof(formatter)); 106 | 107 | if (!Enum.IsDefined(typeof(LogEventLevel), restrictedToMinimumLevel)) 108 | throw new InvalidEnumArgumentException(nameof(restrictedToMinimumLevel), (int)restrictedToMinimumLevel, 109 | typeof(LogEventLevel)); 110 | 111 | syncRoot ??= _defaultSyncRoot; 112 | 113 | return RuntimeInformation.IsOSPlatform(OSPlatform.Windows) 114 | ? sinkConfiguration.Sink(new NotepadSink(new NotepadTextWriter(notepadProcessFinderFunc), formatter, syncRoot), restrictedToMinimumLevel, levelSwitch) 115 | : sinkConfiguration.Sink(new NullSink(), restrictedToMinimumLevel, levelSwitch); 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.Notepad/Serilog.Sinks.Notepad.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | net6.0;net5.0;netstandard2.0;net45 4 | Serilog 5 | 6 | Serilog.Sinks.Notepad 7 | 1.0.0.0 8 | true 9 | true 10 | false 11 | 12 | true 13 | portable 14 | true 15 | true 16 | snupkg 17 | $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb 18 | 19 | true 20 | ../../assets/Serilog.snk 21 | 22 | $(NoWarn);NU5048 23 | true 24 | 25 | 26 | 27 | 28 | 3.3 29 | Serilog.Sinks.Notepad 30 | 0.0.1-local 31 | C. Augusto Proiete & Contributors 32 | augustoproiete.net 33 | A Serilog sink that writes log events to Notepad. 34 | Copyright 2020-2023 C. Augusto Proiete & Contributors - Provided under the Apache License, Version 2.0 35 | serilog;notepad;sink;selflog;diagnostic;debug;troubleshoot;serilog-sink;serilog-contrib;augustoproiete;augusto-proiete 36 | Apache-2.0 37 | images\icon.png 38 | http://serilog.net/images/serilog-sink-nuget.png 39 | https://github.com/serilog-contrib/serilog-sinks-notepad 40 | https://github.com/serilog-contrib/serilog-sinks-notepad/releases 41 | git 42 | https://github.com/serilog-contrib/serilog-sinks-notepad.git 43 | 44 | 45 | 46 | true 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | <_Parameter1>Serilog.Sinks.Notepad.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100fb8d13fd344a1c6fe0fe83ef33c1080bf30690765bc6eb0df26ebfdf8f21670c64265b30db09f73a0dea5b3db4c9d18dbf6d5a25af5ce9016f281014d79dc3b4201ac646c451830fc7e61a2dfd633d34c39f87b81894191652df5ac63cc40c77f3542f702bda692e6e8a9158353df189007a49da0f3cfd55eb250066b19485ec 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.Notepad/Sinks/Notepad/Interop/NotepadTextWriter.cs: -------------------------------------------------------------------------------- 1 | #region Copyright 2020-2023 C. Augusto Proiete & 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 | #endregion 16 | 17 | using System; 18 | using System.Diagnostics; 19 | using System.IO; 20 | using System.Linq; 21 | using System.Text; 22 | using Serilog.Debugging; 23 | 24 | namespace Serilog.Sinks.Notepad.Interop 25 | { 26 | internal class NotepadTextWriter : StringWriter 27 | { 28 | private readonly Func _notepadProcessFinderFunc; 29 | private Process _currentNotepadProcess; 30 | private IntPtr _currentNotepadEditorHandle; 31 | private bool _disposed; 32 | 33 | public NotepadTextWriter(Func notepadProcessFinderFunc = null) 34 | : base(new StringBuilder()) 35 | { 36 | _notepadProcessFinderFunc = notepadProcessFinderFunc ?? TryFindMostRecentNotepadProcess; 37 | } 38 | 39 | public override void Flush() 40 | { 41 | EnsureNotDisposed(); 42 | 43 | base.Flush(); 44 | 45 | var currentNotepadProcess = _currentNotepadProcess; 46 | var targetNotepadProcess = _notepadProcessFinderFunc(); 47 | 48 | if (currentNotepadProcess is null || targetNotepadProcess is null || currentNotepadProcess.Id != targetNotepadProcess.Id) 49 | { 50 | _currentNotepadProcess = currentNotepadProcess = targetNotepadProcess; 51 | _currentNotepadEditorHandle = IntPtr.Zero; 52 | 53 | if (currentNotepadProcess is null || currentNotepadProcess.HasExited) 54 | { 55 | // No instances of Notepad found... Nothing to do 56 | return; 57 | } 58 | 59 | var notepadWindowHandle = currentNotepadProcess.MainWindowHandle; 60 | 61 | var notepadEditorHandle = FindNotepadEditorHandle(notepadWindowHandle); 62 | if (notepadEditorHandle == IntPtr.Zero) 63 | { 64 | SelfLog.WriteLine($"Unable to access a Notepad Editor on process {currentNotepadProcess.ProcessName} ({currentNotepadProcess.Id})"); 65 | return; 66 | } 67 | 68 | _currentNotepadEditorHandle = notepadEditorHandle; 69 | } 70 | 71 | // Get how many characters are in the Notepad editor already 72 | var textLength = User32.SendMessage(_currentNotepadEditorHandle, User32.WM_GETTEXTLENGTH, IntPtr.Zero, IntPtr.Zero); 73 | 74 | // Set the caret position to the end of the text 75 | User32.SendMessage(_currentNotepadEditorHandle, User32.EM_SETSEL, (IntPtr)textLength, (IntPtr)textLength); 76 | 77 | var buffer = base.GetStringBuilder(); 78 | var message = buffer.ToString(); 79 | 80 | // Write the log message to Notepad 81 | User32.SendMessage(_currentNotepadEditorHandle, User32.EM_REPLACESEL, (IntPtr)1, message); 82 | 83 | buffer.Clear(); 84 | } 85 | 86 | protected override void Dispose(bool disposing) 87 | { 88 | if (_disposed) 89 | { 90 | return; 91 | } 92 | 93 | if (disposing) 94 | { 95 | _currentNotepadProcess = null; 96 | _currentNotepadEditorHandle = IntPtr.Zero; 97 | _disposed = true; 98 | } 99 | 100 | base.Dispose(disposing); 101 | } 102 | 103 | private static Process TryFindMostRecentNotepadProcess() 104 | { 105 | var mostRecentNotepadProcess = Process.GetProcessesByName("notepad") 106 | .Where(p => !p.HasExited) 107 | .OrderByDescending(p => p.StartTime) 108 | .FirstOrDefault(); 109 | 110 | return mostRecentNotepadProcess; 111 | } 112 | 113 | private static IntPtr FindNotepadEditorHandle(IntPtr notepadWindowHandle) 114 | { 115 | // Windows 11 uses the new RichEditD2DPT class: 116 | // https://devblogs.microsoft.com/math-in-office/windows-11-notepad/#some-implementation-details 117 | if (User32.FindWindowEx(notepadWindowHandle, IntPtr.Zero, "RichEditD2DPT", null) is var richEditHandle 118 | && richEditHandle != IntPtr.Zero) 119 | { 120 | return richEditHandle; 121 | } 122 | 123 | return User32.FindWindowEx(notepadWindowHandle, IntPtr.Zero, "Edit", null); 124 | } 125 | 126 | private void EnsureNotDisposed() 127 | { 128 | if (_disposed) 129 | { 130 | throw new ObjectDisposedException(GetType().Name); 131 | } 132 | } 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.Notepad/Sinks/Notepad/Interop/User32.cs: -------------------------------------------------------------------------------- 1 | #region Copyright 2020-2023 C. Augusto Proiete & 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 | #endregion 16 | 17 | using System; 18 | using System.Runtime.InteropServices; 19 | 20 | namespace Serilog.Sinks.Notepad.Interop 21 | { 22 | internal class User32 23 | { 24 | // ReSharper disable InconsistentNaming 25 | public const int WM_GETTEXTLENGTH = 0x000E; 26 | public const int EM_SETSEL = 0x00B1; 27 | public const int EM_REPLACESEL = 0x00C2; 28 | // ReSharper restore InconsistentNaming 29 | 30 | [DllImport("User32.dll", CharSet = CharSet.Unicode, SetLastError = true)] 31 | public static extern IntPtr FindWindowEx(IntPtr hWndParent, IntPtr hWndChildAfter, string lpszClass, string lpszWindow); 32 | 33 | [DllImport("User32.dll", CharSet = CharSet.Unicode, SetLastError = true)] 34 | public static extern int SendMessage(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam); 35 | 36 | [DllImport("User32.dll", CharSet = CharSet.Unicode, SetLastError = true)] 37 | public static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wParam, [MarshalAs(UnmanagedType.LPWStr)] string lParam); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.Notepad/Sinks/Notepad/NotepadSink.cs: -------------------------------------------------------------------------------- 1 | #region Copyright 2020-2023 C. Augusto Proiete & 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 | #endregion 16 | 17 | using Serilog.Core; 18 | using Serilog.Formatting; 19 | using Serilog.Sinks.Notepad.Interop; 20 | using Serilog.Sinks.TextWriter; 21 | 22 | namespace Serilog.Sinks.Notepad 23 | { 24 | internal class NotepadSink : TextWriterSink, ILogEventSink 25 | { 26 | public NotepadSink(NotepadTextWriter textWriter, ITextFormatter formatter, object syncRoot) 27 | : base(textWriter, formatter, syncRoot) 28 | { 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.Notepad/Sinks/Notepad/NotepadWindow.cs: -------------------------------------------------------------------------------- 1 | #region Copyright 2020-2023 C. Augusto Proiete & 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 | #endregion 16 | 17 | using System; 18 | using System.Runtime.InteropServices; 19 | using Serilog.Sinks.Notepad.Interop; 20 | 21 | namespace Serilog.Sinks.Notepad 22 | { 23 | /// 24 | /// Writes messages directly to the most recent `notepad.exe` process 25 | /// 26 | /// 27 | /// Capture internal errors from Serilog sinks via : 28 | /// 29 | /// Serilog.Debugging.SelfLog.Enable(s => NotepadWindow.WriteLine($"Internal Error with Serilog: {s}")); 30 | /// 31 | /// 32 | public static class NotepadWindow 33 | { 34 | private static readonly Lazy _notepadWindow = 35 | new Lazy(() => new NotepadTextWriter()); 36 | 37 | /// 38 | /// Writes a string to the most recent Notepad window open. 39 | /// If the given string is null, nothing is written to the text stream. 40 | /// 41 | /// The string to be written 42 | public static void Write(string value) 43 | { 44 | if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) 45 | { 46 | return; 47 | } 48 | 49 | var notepadWindow = _notepadWindow.Value; 50 | notepadWindow.Write(value); 51 | notepadWindow.Flush(); 52 | } 53 | 54 | /// 55 | /// Writes a line terminator to the most recent Notepad window open. 56 | /// 57 | public static void WriteLine() 58 | { 59 | if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) 60 | { 61 | return; 62 | } 63 | 64 | var notepadWindow = _notepadWindow.Value; 65 | notepadWindow.WriteLine(); 66 | notepadWindow.Flush(); 67 | } 68 | 69 | /// 70 | /// Writes a line terminator to the most recent Notepad window open. 71 | /// 72 | /// The string to be written 73 | public static void WriteLine(string value) 74 | { 75 | if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) 76 | { 77 | return; 78 | } 79 | 80 | var notepadWindow = _notepadWindow.Value; 81 | notepadWindow.WriteLine(value); 82 | notepadWindow.Flush(); 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.Notepad/Sinks/Notepad/NullSink.cs: -------------------------------------------------------------------------------- 1 | #region Copyright 2020-2023 C. Augusto Proiete & 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 | #endregion 16 | 17 | using Serilog.Core; 18 | using Serilog.Events; 19 | 20 | namespace Serilog.Sinks.Notepad 21 | { 22 | internal class NullSink : ILogEventSink 23 | { 24 | public void Emit(LogEvent logEvent) 25 | { 26 | // Do nothing 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.Notepad/Sinks/TextWriter/TextWriterSink.cs: -------------------------------------------------------------------------------- 1 | #region Copyright 2020-2023 C. Augusto Proiete & 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 | #endregion 16 | 17 | using System; 18 | using Serilog.Core; 19 | using Serilog.Events; 20 | using Serilog.Formatting; 21 | 22 | namespace Serilog.Sinks.TextWriter 23 | { 24 | internal class TextWriterSink : ILogEventSink, IDisposable 25 | { 26 | private readonly System.IO.TextWriter _textWriter; 27 | private readonly ITextFormatter _formatter; 28 | private readonly object _syncRoot; 29 | private bool _disposed; 30 | 31 | public TextWriterSink(System.IO.TextWriter textWriter, ITextFormatter formatter, object syncRoot) 32 | { 33 | _textWriter = textWriter ?? throw new ArgumentNullException(nameof(textWriter)); 34 | _formatter = formatter ?? throw new ArgumentNullException(nameof(formatter)); 35 | _syncRoot = syncRoot ?? throw new ArgumentNullException(nameof(syncRoot)); 36 | } 37 | 38 | public void Emit(LogEvent logEvent) 39 | { 40 | EnsureNotDisposed(); 41 | 42 | lock (_syncRoot) 43 | { 44 | _formatter.Format(logEvent, _textWriter); 45 | _textWriter.Flush(); 46 | } 47 | } 48 | 49 | public void Dispose() 50 | { 51 | Dispose(true); 52 | } 53 | 54 | protected virtual void Dispose(bool disposing) 55 | { 56 | if (_disposed) return; 57 | 58 | _textWriter.Dispose(); 59 | _disposed = true; 60 | } 61 | 62 | private void EnsureNotDisposed() 63 | { 64 | if (_disposed) 65 | { 66 | throw new ObjectDisposedException(GetType().Name); 67 | } 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /test/Serilog.Sinks.Notepad.Tests/Serilog.Sinks.Notepad.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net6.0;net5.0;netcoreapp3.1 5 | Serilog.Sinks.Notepad.Tests 6 | false 7 | ../../assets/Serilog.snk 8 | true 9 | True 10 | 11 | Serilog 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | runtime; build; native; contentfiles; analyzers; buildtransitive 24 | all 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /test/Serilog.Sinks.Notepad.Tests/Sinks/TextWriter/TextWriterSinkTests.cs: -------------------------------------------------------------------------------- 1 | #region Copyright 2020-2023 C. Augusto Proiete & 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 | #endregion 16 | 17 | using System.Globalization; 18 | using System.IO; 19 | using FluentAssertions; 20 | using Serilog.Formatting.Display; 21 | using Serilog.Support; 22 | using Xunit; 23 | 24 | namespace Serilog.Sinks.TextWriter 25 | { 26 | public class TextWriterSinkTests 27 | { 28 | private const string _outputTemplate = "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level}] {Message}{NewLine}{Exception}"; 29 | private readonly object _syncRoot = new object(); 30 | 31 | [Fact] 32 | public void EventsAreWrittenToTheTextWriter() 33 | { 34 | var sw = new StringWriter(); 35 | var formatter = new MessageTemplateTextFormatter(_outputTemplate, null); 36 | 37 | var log = new LoggerConfiguration() 38 | .WriteTo.Sink(new TextWriterSink(sw, formatter, _syncRoot)) 39 | .CreateLogger(); 40 | 41 | var mt = Some.String(); 42 | log.Information(mt); 43 | 44 | var s = sw.ToString(); 45 | s.Should().Contain(mt); 46 | } 47 | 48 | [Fact] 49 | public void EventsAreWrittenToTheTextWriterUsingFormatProvider() 50 | { 51 | var sw = new StringWriter(); 52 | var brazilianPortuguese = new CultureInfo("pt-BR"); 53 | var formatter = new MessageTemplateTextFormatter(_outputTemplate, brazilianPortuguese); 54 | 55 | var log = new LoggerConfiguration() 56 | .WriteTo.Sink(new TextWriterSink(sw, formatter, _syncRoot)) 57 | .CreateLogger(); 58 | 59 | var mt = string.Format(brazilianPortuguese, "{0}", 1_234.567); 60 | log.Information("{0}", 1_234.567); 61 | 62 | var s = sw.ToString(); 63 | s.Should().Contain(mt); 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /test/Serilog.Sinks.Notepad.Tests/Support/Some.cs: -------------------------------------------------------------------------------- 1 | #region Copyright 2020-2023 C. Augusto Proiete & 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 | #endregion 16 | 17 | using System.Threading; 18 | 19 | namespace Serilog.Support 20 | { 21 | internal static class Some 22 | { 23 | private static int _counter; 24 | 25 | public static int Int() 26 | { 27 | return Interlocked.Increment(ref _counter); 28 | } 29 | 30 | public static string String(string tag = null) 31 | { 32 | return (tag ?? "") + "__" + Int(); 33 | } 34 | } 35 | } 36 | --------------------------------------------------------------------------------