Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,6 @@ jobs:
$coveragePercent = [math]::Round([double]$coverage * 100, 2)
Write-Host "Current line coverage: $coveragePercent%"
if ($coveragePercent -lt 75) {
Write-Error "Code coverage ($coveragePercent%) is below the required threshold of 70%"
Write-Error "Code coverage ($coveragePercent%) is below the required threshold of 75%"
exit 1
}
10 changes: 4 additions & 6 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
## Release Notes - Serilog.Sinks.RichTextBox.WinForms.Colored v3.0.1
## Release Notes - Serilog.Sinks.RichTextBox.WinForms.Colored v3.1.0

### Minor Release
### Feature Release

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

### What Changed

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

### Resources

Expand Down
40 changes: 32 additions & 8 deletions Demo/Form1.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

50 changes: 46 additions & 4 deletions Demo/Form1.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ namespace Demo
public partial class Form1 : Form
{
private RichTextBoxSinkOptions? _options;
private RichTextBoxSink? _sink;
private bool _toolbarsVisible = true;

public Form1()
Expand All @@ -42,16 +43,37 @@ public Form1()

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

var sink = new RichTextBoxSink(richTextBox1, _options);
_sink = new RichTextBoxSink(richTextBox1, _options);
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Verbose()
.WriteTo.Sink(sink, LogEventLevel.Verbose)
.CreateLogger();
.MinimumLevel.Verbose()
.WriteTo.Sink(_sink, LogEventLevel.Verbose)
.CreateLogger();

// Intentional dead code for demonstration purposes.
#pragma warning disable CS0162
if (false)
{
// You can also use fluent syntax to configure the sink like this:
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Verbose()
.WriteTo.RichTextBox(richTextBox1, out _sink, formatProvider: new CultureInfo("en-US"))
.CreateLogger();

// The out _sink is optional, but it allows you to access the sink instance.
// This is useful if you need to access the sink's methods, such as Clear() or Restore().
// If you don't need to access the sink, you can omit the out parameter like this:
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Verbose()
.WriteTo.RichTextBox(richTextBox1, formatProvider: new CultureInfo("en-US"))
.CreateLogger();
}
#pragma warning restore CS0162

Log.Debug("Started logger.");
btnDispose.Enabled = true;
Expand Down Expand Up @@ -337,5 +359,25 @@ private void Form1_KeyDown(object sender, KeyEventArgs e)
toolStrip2.Visible = _toolbarsVisible;
}
}

private void btnClear_Click(object sender, EventArgs e)
{
if (_sink == null)
{
return;
}

_sink.Clear();
}

private void btnRestore_Click(object sender, EventArgs e)
{
if (_sink == null)
{
return;
}

_sink.Restore();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
using Serilog.Sinks.RichTextBoxForms.Collections;
using Xunit;

namespace Serilog.Tests.Collections
{
public class ConcurrentCircularBufferTests
{
[Fact]
public void TakeSnapshot_WithItemsLessThanCapacity_ReturnsAllItemsInOrder()
{
// Arrange
var buffer = new ConcurrentCircularBuffer<int>(3);
buffer.Add(1);
buffer.Add(2);

// Act
var snapshot = new List<int>();
buffer.TakeSnapshot(snapshot);

// Assert
Assert.Equal(new[] { 1, 2 }, snapshot);
}

[Fact]
public void AddBeyondCapacity_OverwritesOldestItemsAndMaintainsOrder()
{
// Arrange
var buffer = new ConcurrentCircularBuffer<int>(3);
buffer.Add(1);
buffer.Add(2);
buffer.Add(3);
buffer.Add(4); // Should overwrite the oldest item (1)

// Act
var snapshot = new List<int>();
buffer.TakeSnapshot(snapshot);

// Assert
Assert.Equal(new[] { 2, 3, 4 }, snapshot);
}

[Fact]
public void Clear_FollowedByAdds_SnapshotContainsOnlyNewItems()
{
// Arrange
var buffer = new ConcurrentCircularBuffer<int>(3);
buffer.Add(1);
buffer.Add(2);
buffer.Add(3);

// Act & Assert - After clear, snapshot should be empty
buffer.Clear();
var snapshotAfterClear = new List<int>();
buffer.TakeSnapshot(snapshotAfterClear);
Assert.Empty(snapshotAfterClear);

// Add new item and verify snapshot contains only the new item
buffer.Add(4);
var snapshotAfterOneAdd = new List<int>();
buffer.TakeSnapshot(snapshotAfterOneAdd);
Assert.Equal(new[] { 4 }, snapshotAfterOneAdd);

// Add two more items to fill the buffer again
buffer.Add(5);
buffer.Add(6);
var snapshotAfterMoreAdds = new List<int>();
buffer.TakeSnapshot(snapshotAfterMoreAdds);
Assert.Equal(new[] { 4, 5, 6 }, snapshotAfterMoreAdds);
}

[Fact]
public void Restore_AfterClear_ReturnsAllItemsAgain()
{
// Arrange
var buffer = new ConcurrentCircularBuffer<int>(3);
buffer.Add(1);
buffer.Add(2);
buffer.Add(3);
buffer.Clear();

// Add two new items while buffer is in cleared state
buffer.Add(4);
buffer.Add(5);

// Act - Restore should make all items visible again
buffer.Restore();
var snapshot = new List<int>();
buffer.TakeSnapshot(snapshot);

// Assert
Assert.Equal(new[] { 3, 4, 5 }, snapshot);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
using Serilog.Sinks.RichTextBoxForms.Rendering;
using Serilog.Sinks.RichTextBoxForms.Themes;
using System;
using System.Globalization;
using System.Windows.Forms;

namespace Serilog
Expand All @@ -48,6 +49,7 @@ public static class RichTextBoxSinkLoggerConfigurationExtensions
public static LoggerConfiguration RichTextBox(
this LoggerSinkConfiguration sinkConfiguration,
RichTextBox richTextBoxControl,
out RichTextBoxSink richTextBoxSink,
Theme? theme = null,
bool autoScroll = true,
int maxLogLines = 256,
Expand All @@ -57,10 +59,49 @@ public static LoggerConfiguration RichTextBox(
LoggingLevelSwitch? levelSwitch = null)
{
var appliedTheme = theme ?? ThemePresets.Literate;
var renderer = new TemplateRenderer(appliedTheme, outputTemplate, formatProvider);
var options = new RichTextBoxSinkOptions(appliedTheme, autoScroll, maxLogLines, outputTemplate, formatProvider);
var sink = new RichTextBoxSink(richTextBoxControl, options, renderer);
return sinkConfiguration.Sink(sink, minimumLogEventLevel, levelSwitch);
var appliedFormatProvider = formatProvider ?? CultureInfo.InvariantCulture;
var renderer = new TemplateRenderer(appliedTheme, outputTemplate, appliedFormatProvider);
var options = new RichTextBoxSinkOptions(appliedTheme, autoScroll, maxLogLines, outputTemplate, appliedFormatProvider);
richTextBoxSink = new RichTextBoxSink(richTextBoxControl, options, renderer);
return sinkConfiguration.Sink(richTextBoxSink, minimumLogEventLevel, levelSwitch);
}

/// <summary>
/// Adds a sink that writes log events to the specified Windows Forms <see cref="RichTextBox"/>
/// using colour-coded rich-text formatting.
/// </summary>
/// <param name="sinkConfiguration">The logger sink configuration this extension method operates on.</param>
/// <param name="richTextBoxControl">The target <see cref="RichTextBox"/> instance that will display the log output.</param>
/// <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>
/// <param name="autoScroll">When <c>true</c> (default) the control automatically scrolls to the newest log entry.</param>
/// <param name="maxLogLines">Maximum number of log events retained in the circular buffer and rendered in the control.</param>
/// <param name="outputTemplate">Message template that controls the textual representation of each log event.</param>
/// <param name="formatProvider">Culture-specific or custom formatting provider, or <c>null</c> to use the invariant culture.</param>
/// <param name="minimumLogEventLevel">Minimum level below which events are ignored by this sink.</param>
/// <param name="levelSwitch">Optional switch allowing the minimum log level to be changed at runtime.</param>
/// <returns>A <see cref="LoggerConfiguration"/> object that can be further configured.</returns>
public static LoggerConfiguration RichTextBox(
this LoggerSinkConfiguration sinkConfiguration,
RichTextBox richTextBoxControl,
Theme? theme = null,
bool autoScroll = true,
int maxLogLines = 256,
string outputTemplate = OutputTemplate,
IFormatProvider? formatProvider = null,
LogEventLevel minimumLogEventLevel = LogEventLevel.Verbose,
LoggingLevelSwitch? levelSwitch = null)
{
return RichTextBox(
sinkConfiguration,
richTextBoxControl,
out _,
theme,
autoScroll,
maxLogLines,
outputTemplate,
formatProvider,
minimumLogEventLevel,
levelSwitch);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,9 @@
<TargetFrameworks>net462;net471;net6.0-windows;net8.0-windows;net9.0-windows;netcoreapp3.0-windows;netcoreapp3.1-windows</TargetFrameworks>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<UseWindowsForms>true</UseWindowsForms>
<Version>3.0.1</Version>
<Version>3.1.0</Version>
<PackageReleaseNotes>
- Adjusted MaxLogLines constraint from 512 to 2048 lines.
- Fixed bug where the RTB control would not persist the zoom factor.
- Minor improvements to the concurrent circular buffer.
- Added ability to clear and restore the RichTextBox sink output.

See repository for more information:
https://github.com/vonhoff/Serilog.Sinks.RichTextBox.WinForms.Colored
Expand Down
Loading