Skip to content

Commit a7c24e6

Browse files
authored
Merge pull request #31 from vonhoff/30-including-sourcecontext-in-outputtemplate-causes-an-exception
30 including sourcecontext in outputtemplate causes an exception
2 parents ce5d637 + ec57280 commit a7c24e6

26 files changed

+511
-404
lines changed

CHANGES.md

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,8 @@
1-
## Release Notes - Serilog.Sinks.RichTextBox.WinForms.Colored v3.1.0
2-
3-
### Feature Release
4-
5-
This feature release introduces the ability to clear and restore the RichTextBox sink output.
1+
## Release Notes - Serilog.Sinks.RichTextBox.WinForms.Colored v3.1.1
62

73
### What Changed
84

9-
- Added `Clear()` and `Restore()` operations to the circular buffer/RichTextBox sink.
5+
- Fixed a bug where the TextFormatter would crash when the value or format was null or empty.
106

117
### Resources
128

Demo/Form1.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ private void Initialize()
5757

5858
// Intentional dead code for demonstration purposes.
5959
#pragma warning disable CS0162
60-
if (false)
60+
if (false)
6161
{
6262
// You can also use fluent syntax to configure the sink like this:
6363
Log.Logger = new LoggerConfiguration()

Serilog.Sinks.RichTextBox.WinForms.Colored.Test/Integration/SpecialCaseFormattingTests.cs

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using Serilog.Events;
2+
using Serilog.Sinks.RichTextBoxForms.Formatting;
23
using System.Globalization;
34
using Xunit;
45

@@ -117,5 +118,70 @@ public void JsonFormatting_Decimal_RendersCorrectly()
117118
// Act & Assert
118119
Assert.Equal(decVal.ToString(CultureInfo.InvariantCulture), RenderAndGetText(logEvent, "{Message:j}", CultureInfo.InvariantCulture));
119120
}
121+
122+
[Fact]
123+
public void OutputTemplate_WithSourceContextWithoutFormat_DoesNotCrash()
124+
{
125+
// Arrange - Test for bug fix where {SourceContext} without format would cause exception
126+
var sourceContextProp = new LogEventProperty("SourceContext", new ScalarValue("MyApp.Services.UserService"));
127+
var template = _parser.Parse("User created successfully");
128+
var logEvent = new LogEvent(
129+
DateTimeOffset.Now,
130+
LogEventLevel.Information,
131+
null,
132+
template,
133+
new[] { sourceContextProp });
134+
135+
var outputTemplate = "[{Timestamp:HH:mm:ss} {Level:u3}] [{SourceContext}] {Message:lj}{NewLine}{Exception}";
136+
137+
// Act & Assert - Should not throw an exception
138+
var result = RenderAndGetText(logEvent, outputTemplate);
139+
140+
// Verify the output contains expected parts
141+
Assert.Contains("[MyApp.Services.UserService]", result);
142+
Assert.Contains("User created successfully", result);
143+
Assert.Contains("INF", result); // Level:u3 formatting
144+
}
145+
146+
[Fact]
147+
public void OutputTemplate_WithSourceContextWithFormat_WorksCorrectly()
148+
{
149+
// Arrange - Test that format specifiers on SourceContext work correctly
150+
var sourceContextProp = new LogEventProperty("SourceContext", new ScalarValue("MyApp.Services.UserService"));
151+
var template = _parser.Parse("User login attempt");
152+
var logEvent = new LogEvent(
153+
DateTimeOffset.Now,
154+
LogEventLevel.Warning,
155+
null,
156+
template,
157+
new[] { sourceContextProp });
158+
159+
var outputTemplate = "[{Timestamp:HH:mm:ss} {Level:u3}] [{SourceContext:u}] {Message:lj}{NewLine}{Exception}";
160+
161+
// Act & Assert - Should not throw an exception
162+
var result = RenderAndGetText(logEvent, outputTemplate);
163+
164+
// Verify the output contains expected parts with uppercase SourceContext
165+
Assert.Contains("[MYAPP.SERVICES.USERSERVICE]", result);
166+
Assert.Contains("User login attempt", result);
167+
Assert.Contains("WRN", result); // Level:u3 formatting
168+
}
169+
170+
[Theory]
171+
[InlineData("", "u", "")]
172+
[InlineData("hello", "u", "HELLO")]
173+
[InlineData("", "w", "")]
174+
[InlineData("HELLO", "w", "hello")]
175+
[InlineData("", "t", "")]
176+
[InlineData("hello", "t", "Hello")]
177+
[InlineData("h", "t", "H")]
178+
[InlineData("", null, "")]
179+
[InlineData("hello", null, "hello")]
180+
public void TextFormatter_WithEmptyValues_DoesNotCrash(string value, string? format, string expected)
181+
{
182+
// Act & Assert - Should not throw an exception for empty values
183+
var result = TextFormatter.Format(value, format);
184+
Assert.Equal(expected, result);
185+
}
120186
}
121187
}

Serilog.Sinks.RichTextBox.WinForms.Colored/RichTextBoxSinkLoggerConfigurationExtensions.cs

Lines changed: 23 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -33,19 +33,19 @@ public static class RichTextBoxSinkLoggerConfigurationExtensions
3333
private const string OutputTemplate = "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}{Exception}";
3434

3535
/// <summary>
36-
/// Adds a sink that writes log events to the specified Windows Forms <see cref="RichTextBox"/>
37-
/// using colour-coded rich-text formatting.
36+
/// Adds a sink that writes log events to a Windows Forms <see cref="System.Windows.Forms.RichTextBox"/> with color-coded formatting.
3837
/// </summary>
39-
/// <param name="sinkConfiguration">The logger sink configuration this extension method operates on.</param>
40-
/// <param name="richTextBoxControl">The target <see cref="RichTextBox"/> instance that will display the log output.</param>
41-
/// <param name="theme">Optional theme controlling colours of individual message tokens. When <c>null</c>, <see cref="Serilog.Sinks.RichTextBoxForms.Themes.ThemePresets.Literate"/> is used.</param>
42-
/// <param name="autoScroll">When <c>true</c> (default) the control automatically scrolls to the newest log entry.</param>
43-
/// <param name="maxLogLines">Maximum number of log events retained in the circular buffer and rendered in the control.</param>
44-
/// <param name="outputTemplate">Message template that controls the textual representation of each log event.</param>
45-
/// <param name="formatProvider">Culture-specific or custom formatting provider, or <c>null</c> to use the invariant culture.</param>
46-
/// <param name="minimumLogEventLevel">Minimum level below which events are ignored by this sink.</param>
47-
/// <param name="levelSwitch">Optional switch allowing the minimum log level to be changed at runtime.</param>
48-
/// <returns>A <see cref="LoggerConfiguration"/> object that can be further configured.</returns>
38+
/// <param name="sinkConfiguration">The logger sink configuration.</param>
39+
/// <param name="richTextBoxControl">The <see cref="System.Windows.Forms.RichTextBox"/> to display log output.</param>
40+
/// <param name="richTextBoxSink">The created <see cref="RichTextBoxSink"/> instance.</param>
41+
/// <param name="theme">Optional theme for message colors. Defaults to <see cref="ThemePresets.Literate"/> if <c>null</c>.</param>
42+
/// <param name="autoScroll">If <c>true</c> (default), scrolls to the newest log entry automatically.</param>
43+
/// <param name="maxLogLines">Maximum number of log events retained and displayed.</param>
44+
/// <param name="outputTemplate">Template for formatting each log event.</param>
45+
/// <param name="formatProvider">Format provider, or <c>null</c> for invariant culture.</param>
46+
/// <param name="minimumLogEventLevel">Minimum log level for events to be written.</param>
47+
/// <param name="levelSwitch">Optional switch to change the minimum log level at runtime.</param>
48+
/// <returns>The logger configuration, for chaining.</returns>
4949
public static LoggerConfiguration RichTextBox(
5050
this LoggerSinkConfiguration sinkConfiguration,
5151
RichTextBox richTextBoxControl,
@@ -67,19 +67,18 @@ public static LoggerConfiguration RichTextBox(
6767
}
6868

6969
/// <summary>
70-
/// Adds a sink that writes log events to the specified Windows Forms <see cref="RichTextBox"/>
71-
/// using colour-coded rich-text formatting.
70+
/// Adds a sink that writes log events to a Windows Forms <see cref="System.Windows.Forms.RichTextBox"/> with color-coded formatting.
7271
/// </summary>
73-
/// <param name="sinkConfiguration">The logger sink configuration this extension method operates on.</param>
74-
/// <param name="richTextBoxControl">The target <see cref="RichTextBox"/> instance that will display the log output.</param>
75-
/// <param name="theme">Optional theme controlling colours of individual message tokens. When <c>null</c>, <see cref="Serilog.Sinks.RichTextBoxForms.Themes.ThemePresets.Literate"/> is used.</param>
76-
/// <param name="autoScroll">When <c>true</c> (default) the control automatically scrolls to the newest log entry.</param>
77-
/// <param name="maxLogLines">Maximum number of log events retained in the circular buffer and rendered in the control.</param>
78-
/// <param name="outputTemplate">Message template that controls the textual representation of each log event.</param>
79-
/// <param name="formatProvider">Culture-specific or custom formatting provider, or <c>null</c> to use the invariant culture.</param>
80-
/// <param name="minimumLogEventLevel">Minimum level below which events are ignored by this sink.</param>
81-
/// <param name="levelSwitch">Optional switch allowing the minimum log level to be changed at runtime.</param>
82-
/// <returns>A <see cref="LoggerConfiguration"/> object that can be further configured.</returns>
72+
/// <param name="sinkConfiguration">The logger sink configuration.</param>
73+
/// <param name="richTextBoxControl">The <see cref="System.Windows.Forms.RichTextBox"/> to display log output.</param>
74+
/// <param name="theme">Optional theme for message colors. Defaults to <see cref="ThemePresets.Literate"/> if <c>null</c>.</param>
75+
/// <param name="autoScroll">If <c>true</c> (default), scrolls to the newest log entry automatically.</param>
76+
/// <param name="maxLogLines">Maximum number of log events retained and displayed.</param>
77+
/// <param name="outputTemplate">Template for formatting each log event.</param>
78+
/// <param name="formatProvider">Format provider, or <c>null</c> for invariant culture.</param>
79+
/// <param name="minimumLogEventLevel">Minimum log level for events to be written.</param>
80+
/// <param name="levelSwitch">Optional switch to change the minimum log level at runtime.</param>
81+
/// <returns>The logger configuration, for chaining.</returns>
8382
public static LoggerConfiguration RichTextBox(
8483
this LoggerSinkConfiguration sinkConfiguration,
8584
RichTextBox richTextBoxControl,
Lines changed: 51 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,59 +1,59 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

3-
<PropertyGroup>
4-
<Authors>Simon Vonhoff</Authors>
5-
<Company>github.com/vonhoff</Company>
6-
<Copyright>Copyright © 2025 Simon Vonhoff - Provided under the Apache License, Version 2.0.</Copyright>
7-
<Description>A Serilog sink that writes log events to a WinForms RichTextBox control with colors and theme support.</Description>
8-
<EmbedUntrackedSources>true</EmbedUntrackedSources>
9-
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
10-
<IncludeSymbols>true</IncludeSymbols>
11-
<Nullable>enable</Nullable>
12-
<OutputType>Library</OutputType>
13-
<PackageIcon>serilog-sink-nuget.png</PackageIcon>
14-
<PackageId>Serilog.Sinks.RichTextBox.WinForms.Colored</PackageId>
15-
<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>
16-
<PackageProjectUrl>https://github.com/vonhoff/Serilog.Sinks.RichTextBox.WinForms.Colored</PackageProjectUrl>
17-
<PackageReadmeFile>README.md</PackageReadmeFile>
18-
<PackageTags>serilog;sinks;richtextbox;winforms;windows;sink;colors;theme;themes;serilog-sink;vonhoff;rich;textbox;logging;</PackageTags>
19-
<RepositoryUrl>https://github.com/vonhoff/Serilog.Sinks.RichTextBox.WinForms.Colored.git</RepositoryUrl>
20-
<RootNamespace>Serilog</RootNamespace>
21-
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
22-
<TargetFrameworks>net462;net471;net6.0-windows;net8.0-windows;net9.0-windows;netcoreapp3.0-windows;netcoreapp3.1-windows</TargetFrameworks>
23-
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
24-
<UseWindowsForms>true</UseWindowsForms>
25-
<Version>3.1.0</Version>
26-
<PackageReleaseNotes>
27-
- Added ability to clear and restore the RichTextBox sink output.
3+
<PropertyGroup>
4+
<Authors>Simon Vonhoff</Authors>
5+
<Company>github.com/vonhoff</Company>
6+
<Copyright>Copyright © 2025 Simon Vonhoff - Provided under the Apache License, Version 2.0.</Copyright>
7+
<Description>A Serilog sink that writes log events to a WinForms RichTextBox control with colors and theme support.</Description>
8+
<EmbedUntrackedSources>true</EmbedUntrackedSources>
9+
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
10+
<IncludeSymbols>true</IncludeSymbols>
11+
<Nullable>enable</Nullable>
12+
<OutputType>Library</OutputType>
13+
<PackageIcon>serilog-sink-nuget.png</PackageIcon>
14+
<PackageId>Serilog.Sinks.RichTextBox.WinForms.Colored</PackageId>
15+
<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>
16+
<PackageProjectUrl>https://github.com/vonhoff/Serilog.Sinks.RichTextBox.WinForms.Colored</PackageProjectUrl>
17+
<PackageReadmeFile>README.md</PackageReadmeFile>
18+
<PackageTags>serilog;sinks;richtextbox;winforms;windows;sink;colors;theme;themes;serilog-sink;vonhoff;rich;textbox;logging;</PackageTags>
19+
<RepositoryUrl>https://github.com/vonhoff/Serilog.Sinks.RichTextBox.WinForms.Colored.git</RepositoryUrl>
20+
<RootNamespace>Serilog</RootNamespace>
21+
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
22+
<TargetFrameworks>net462;net471;net6.0-windows;net8.0-windows;net9.0-windows;netcoreapp3.0-windows;netcoreapp3.1-windows</TargetFrameworks>
23+
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
24+
<UseWindowsForms>true</UseWindowsForms>
25+
<Version>3.1.1</Version>
26+
<PackageReleaseNotes>
27+
- Fixed a bug where the TextFormatter would crash when the value or format was null or empty.
2828

29-
See repository for more information:
30-
https://github.com/vonhoff/Serilog.Sinks.RichTextBox.WinForms.Colored
31-
</PackageReleaseNotes>
32-
<SupportedOSPlatformVersion>7.0</SupportedOSPlatformVersion>
33-
<SupportedOSPlatform>windows</SupportedOSPlatform>
34-
<NeutralLanguage>en-US</NeutralLanguage>
35-
<LangVersion>9.0</LangVersion>
36-
<WarningLevel>6</WarningLevel>
37-
</PropertyGroup>
29+
See repository for more information:
30+
https://github.com/vonhoff/Serilog.Sinks.RichTextBox.WinForms.Colored
31+
</PackageReleaseNotes>
32+
<SupportedOSPlatformVersion>7.0</SupportedOSPlatformVersion>
33+
<SupportedOSPlatform>windows</SupportedOSPlatform>
34+
<NeutralLanguage>en-US</NeutralLanguage>
35+
<LangVersion>9.0</LangVersion>
36+
<WarningLevel>6</WarningLevel>
37+
</PropertyGroup>
3838

39-
<ItemGroup>
40-
<None Include="..\Resources\serilog-sink-nuget.png">
41-
<Pack>True</Pack>
42-
<PackagePath>\</PackagePath>
43-
</None>
44-
<None Include="..\README.md">
45-
<Pack>True</Pack>
46-
<PackagePath>\</PackagePath>
47-
</None>
48-
</ItemGroup>
39+
<ItemGroup>
40+
<None Include="..\Resources\serilog-sink-nuget.png">
41+
<Pack>True</Pack>
42+
<PackagePath>\</PackagePath>
43+
</None>
44+
<None Include="..\README.md">
45+
<Pack>True</Pack>
46+
<PackagePath>\</PackagePath>
47+
</None>
48+
</ItemGroup>
4949

50-
<ItemGroup>
51-
<PackageReference Include="Serilog" Version="[2.12.0,)" />
52-
</ItemGroup>
50+
<ItemGroup>
51+
<PackageReference Include="Serilog" Version="[2.12.0,)"/>
52+
</ItemGroup>
5353

54-
<ItemGroup Condition=" '$(TargetFramework)' == 'net462' OR '$(TargetFramework)' == 'net471' ">
55-
<Reference Include="System.Drawing" />
56-
<Reference Include="System.Windows.Forms" />
57-
</ItemGroup>
54+
<ItemGroup Condition=" '$(TargetFramework)' == 'net462' OR '$(TargetFramework)' == 'net471' ">
55+
<Reference Include="System.Drawing"/>
56+
<Reference Include="System.Windows.Forms"/>
57+
</ItemGroup>
5858

5959
</Project>

Serilog.Sinks.RichTextBox.WinForms.Colored/Sinks/RichTextBoxForms/Collections/ConcurrentCircularBuffer.cs

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,21 @@
1+
#region Copyright 2025 Simon Vonhoff & Contributors
2+
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
//
16+
17+
#endregion
18+
119
using System.Collections.Generic;
220

321
namespace Serilog.Sinks.RichTextBoxForms.Collections
@@ -89,4 +107,4 @@ public void Restore()
89107
}
90108
}
91109
}
92-
}
110+
}

Serilog.Sinks.RichTextBox.WinForms.Colored/Sinks/RichTextBoxForms/Extensions/KeyValuePairExtensions.cs

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,22 @@
1-
using System.Collections.Generic;
1+
#region Copyright 2025 Simon Vonhoff & Contributors
2+
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
//
16+
17+
#endregion
18+
19+
using System.Collections.Generic;
220

321
namespace Serilog.Sinks.RichTextBoxForms.Extensions
422
{

0 commit comments

Comments
 (0)