Skip to content

Commit ce5d637

Browse files
authored
Merge pull request #29 from vonhoff/27-feature-request-ability-to-clear-the-log
Add clear and restore functionality to RichTextBoxSink
2 parents 9b1453c + e43eeec commit ce5d637

File tree

10 files changed

+275
-33
lines changed

10 files changed

+275
-33
lines changed

.github/workflows/build.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,6 @@ jobs:
5151
$coveragePercent = [math]::Round([double]$coverage * 100, 2)
5252
Write-Host "Current line coverage: $coveragePercent%"
5353
if ($coveragePercent -lt 75) {
54-
Write-Error "Code coverage ($coveragePercent%) is below the required threshold of 70%"
54+
Write-Error "Code coverage ($coveragePercent%) is below the required threshold of 75%"
5555
exit 1
5656
}

CHANGES.md

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,12 @@
1-
## Release Notes - Serilog.Sinks.RichTextBox.WinForms.Colored v3.0.1
1+
## Release Notes - Serilog.Sinks.RichTextBox.WinForms.Colored v3.1.0
22

3-
### Minor Release
3+
### Feature Release
44

5-
This minor release focuses on config improvements and performance optimization.
5+
This feature release introduces the ability to clear and restore the RichTextBox sink output.
66

77
### What Changed
88

9-
- Adjusted MaxLogLines limit From 512 to 2048 lines.
10-
- Fixed a bug where the RichTextBox would not persist the zoom factor.
11-
- Optimized the Concurrent Circular Buffer
9+
- Added `Clear()` and `Restore()` operations to the circular buffer/RichTextBox sink.
1210

1311
### Resources
1412

Demo/Form1.Designer.cs

Lines changed: 32 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Demo/Form1.cs

Lines changed: 46 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ namespace Demo
3333
public partial class Form1 : Form
3434
{
3535
private RichTextBoxSinkOptions? _options;
36+
private RichTextBoxSink? _sink;
3637
private bool _toolbarsVisible = true;
3738

3839
public Form1()
@@ -42,16 +43,37 @@ public Form1()
4243

4344
private void Initialize()
4445
{
46+
// This is one way to configure the sink:
4547
_options = new RichTextBoxSinkOptions(
4648
theme: ThemePresets.Literate,
4749
outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:l}{NewLine}{Exception}",
4850
formatProvider: new CultureInfo("en-US"));
4951

50-
var sink = new RichTextBoxSink(richTextBox1, _options);
52+
_sink = new RichTextBoxSink(richTextBox1, _options);
5153
Log.Logger = new LoggerConfiguration()
52-
.MinimumLevel.Verbose()
53-
.WriteTo.Sink(sink, LogEventLevel.Verbose)
54-
.CreateLogger();
54+
.MinimumLevel.Verbose()
55+
.WriteTo.Sink(_sink, LogEventLevel.Verbose)
56+
.CreateLogger();
57+
58+
// Intentional dead code for demonstration purposes.
59+
#pragma warning disable CS0162
60+
if (false)
61+
{
62+
// You can also use fluent syntax to configure the sink like this:
63+
Log.Logger = new LoggerConfiguration()
64+
.MinimumLevel.Verbose()
65+
.WriteTo.RichTextBox(richTextBox1, out _sink, formatProvider: new CultureInfo("en-US"))
66+
.CreateLogger();
67+
68+
// The out _sink is optional, but it allows you to access the sink instance.
69+
// This is useful if you need to access the sink's methods, such as Clear() or Restore().
70+
// If you don't need to access the sink, you can omit the out parameter like this:
71+
Log.Logger = new LoggerConfiguration()
72+
.MinimumLevel.Verbose()
73+
.WriteTo.RichTextBox(richTextBox1, formatProvider: new CultureInfo("en-US"))
74+
.CreateLogger();
75+
}
76+
#pragma warning restore CS0162
5577

5678
Log.Debug("Started logger.");
5779
btnDispose.Enabled = true;
@@ -337,5 +359,25 @@ private void Form1_KeyDown(object sender, KeyEventArgs e)
337359
toolStrip2.Visible = _toolbarsVisible;
338360
}
339361
}
362+
363+
private void btnClear_Click(object sender, EventArgs e)
364+
{
365+
if (_sink == null)
366+
{
367+
return;
368+
}
369+
370+
_sink.Clear();
371+
}
372+
373+
private void btnRestore_Click(object sender, EventArgs e)
374+
{
375+
if (_sink == null)
376+
{
377+
return;
378+
}
379+
380+
_sink.Restore();
381+
}
340382
}
341383
}
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
using Serilog.Sinks.RichTextBoxForms.Collections;
2+
using Xunit;
3+
4+
namespace Serilog.Tests.Collections
5+
{
6+
public class ConcurrentCircularBufferTests
7+
{
8+
[Fact]
9+
public void TakeSnapshot_WithItemsLessThanCapacity_ReturnsAllItemsInOrder()
10+
{
11+
// Arrange
12+
var buffer = new ConcurrentCircularBuffer<int>(3);
13+
buffer.Add(1);
14+
buffer.Add(2);
15+
16+
// Act
17+
var snapshot = new List<int>();
18+
buffer.TakeSnapshot(snapshot);
19+
20+
// Assert
21+
Assert.Equal(new[] { 1, 2 }, snapshot);
22+
}
23+
24+
[Fact]
25+
public void AddBeyondCapacity_OverwritesOldestItemsAndMaintainsOrder()
26+
{
27+
// Arrange
28+
var buffer = new ConcurrentCircularBuffer<int>(3);
29+
buffer.Add(1);
30+
buffer.Add(2);
31+
buffer.Add(3);
32+
buffer.Add(4); // Should overwrite the oldest item (1)
33+
34+
// Act
35+
var snapshot = new List<int>();
36+
buffer.TakeSnapshot(snapshot);
37+
38+
// Assert
39+
Assert.Equal(new[] { 2, 3, 4 }, snapshot);
40+
}
41+
42+
[Fact]
43+
public void Clear_FollowedByAdds_SnapshotContainsOnlyNewItems()
44+
{
45+
// Arrange
46+
var buffer = new ConcurrentCircularBuffer<int>(3);
47+
buffer.Add(1);
48+
buffer.Add(2);
49+
buffer.Add(3);
50+
51+
// Act & Assert - After clear, snapshot should be empty
52+
buffer.Clear();
53+
var snapshotAfterClear = new List<int>();
54+
buffer.TakeSnapshot(snapshotAfterClear);
55+
Assert.Empty(snapshotAfterClear);
56+
57+
// Add new item and verify snapshot contains only the new item
58+
buffer.Add(4);
59+
var snapshotAfterOneAdd = new List<int>();
60+
buffer.TakeSnapshot(snapshotAfterOneAdd);
61+
Assert.Equal(new[] { 4 }, snapshotAfterOneAdd);
62+
63+
// Add two more items to fill the buffer again
64+
buffer.Add(5);
65+
buffer.Add(6);
66+
var snapshotAfterMoreAdds = new List<int>();
67+
buffer.TakeSnapshot(snapshotAfterMoreAdds);
68+
Assert.Equal(new[] { 4, 5, 6 }, snapshotAfterMoreAdds);
69+
}
70+
71+
[Fact]
72+
public void Restore_AfterClear_ReturnsAllItemsAgain()
73+
{
74+
// Arrange
75+
var buffer = new ConcurrentCircularBuffer<int>(3);
76+
buffer.Add(1);
77+
buffer.Add(2);
78+
buffer.Add(3);
79+
buffer.Clear();
80+
81+
// Add two new items while buffer is in cleared state
82+
buffer.Add(4);
83+
buffer.Add(5);
84+
85+
// Act - Restore should make all items visible again
86+
buffer.Restore();
87+
var snapshot = new List<int>();
88+
buffer.TakeSnapshot(snapshot);
89+
90+
// Assert
91+
Assert.Equal(new[] { 3, 4, 5 }, snapshot);
92+
}
93+
}
94+
}

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

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
using Serilog.Sinks.RichTextBoxForms.Rendering;
2424
using Serilog.Sinks.RichTextBoxForms.Themes;
2525
using System;
26+
using System.Globalization;
2627
using System.Windows.Forms;
2728

2829
namespace Serilog
@@ -48,6 +49,7 @@ public static class RichTextBoxSinkLoggerConfigurationExtensions
4849
public static LoggerConfiguration RichTextBox(
4950
this LoggerSinkConfiguration sinkConfiguration,
5051
RichTextBox richTextBoxControl,
52+
out RichTextBoxSink richTextBoxSink,
5153
Theme? theme = null,
5254
bool autoScroll = true,
5355
int maxLogLines = 256,
@@ -57,10 +59,49 @@ public static LoggerConfiguration RichTextBox(
5759
LoggingLevelSwitch? levelSwitch = null)
5860
{
5961
var appliedTheme = theme ?? ThemePresets.Literate;
60-
var renderer = new TemplateRenderer(appliedTheme, outputTemplate, formatProvider);
61-
var options = new RichTextBoxSinkOptions(appliedTheme, autoScroll, maxLogLines, outputTemplate, formatProvider);
62-
var sink = new RichTextBoxSink(richTextBoxControl, options, renderer);
63-
return sinkConfiguration.Sink(sink, minimumLogEventLevel, levelSwitch);
62+
var appliedFormatProvider = formatProvider ?? CultureInfo.InvariantCulture;
63+
var renderer = new TemplateRenderer(appliedTheme, outputTemplate, appliedFormatProvider);
64+
var options = new RichTextBoxSinkOptions(appliedTheme, autoScroll, maxLogLines, outputTemplate, appliedFormatProvider);
65+
richTextBoxSink = new RichTextBoxSink(richTextBoxControl, options, renderer);
66+
return sinkConfiguration.Sink(richTextBoxSink, minimumLogEventLevel, levelSwitch);
67+
}
68+
69+
/// <summary>
70+
/// Adds a sink that writes log events to the specified Windows Forms <see cref="RichTextBox"/>
71+
/// using colour-coded rich-text formatting.
72+
/// </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>
83+
public static LoggerConfiguration RichTextBox(
84+
this LoggerSinkConfiguration sinkConfiguration,
85+
RichTextBox richTextBoxControl,
86+
Theme? theme = null,
87+
bool autoScroll = true,
88+
int maxLogLines = 256,
89+
string outputTemplate = OutputTemplate,
90+
IFormatProvider? formatProvider = null,
91+
LogEventLevel minimumLogEventLevel = LogEventLevel.Verbose,
92+
LoggingLevelSwitch? levelSwitch = null)
93+
{
94+
return RichTextBox(
95+
sinkConfiguration,
96+
richTextBoxControl,
97+
out _,
98+
theme,
99+
autoScroll,
100+
maxLogLines,
101+
outputTemplate,
102+
formatProvider,
103+
minimumLogEventLevel,
104+
levelSwitch);
64105
}
65106
}
66107
}

Serilog.Sinks.RichTextBox.WinForms.Colored/Serilog.Sinks.RichTextBox.WinForms.Colored.csproj

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,9 @@
2222
<TargetFrameworks>net462;net471;net6.0-windows;net8.0-windows;net9.0-windows;netcoreapp3.0-windows;netcoreapp3.1-windows</TargetFrameworks>
2323
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
2424
<UseWindowsForms>true</UseWindowsForms>
25-
<Version>3.0.1</Version>
25+
<Version>3.1.0</Version>
2626
<PackageReleaseNotes>
27-
- Adjusted MaxLogLines constraint from 512 to 2048 lines.
28-
- Fixed bug where the RTB control would not persist the zoom factor.
29-
- Minor improvements to the concurrent circular buffer.
27+
- Added ability to clear and restore the RichTextBox sink output.
3028

3129
See repository for more information:
3230
https://github.com/vonhoff/Serilog.Sinks.RichTextBox.WinForms.Colored

0 commit comments

Comments
 (0)