├── .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 |

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 | [](https://www.nuget.org/packages/Serilog.Sinks.Notepad) [](http://stackoverflow.com/questions/tagged/serilog)
16 |
17 | 
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 | [](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 |
--------------------------------------------------------------------------------