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
81 changes: 7 additions & 74 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,81 +1,14 @@
## Release Notes - Serilog.Sinks.RichTextBox.WinForms.Colored v3.0.0
## Release Notes - Serilog.Sinks.RichTextBox.WinForms.Colored v3.0.1

### Major Release - Breaking Changes
### Minor Release

This major release focuses on performance optimization, UI stability, and streamlined configuration while simplifying the configuration API.
This minor release focuses on config improvements and performance optimization.

### Breaking Changes
### What Changed

- **Simplified Configuration Options**: Reduced configurable options to focus only on the most relevant and commonly used settings:
- Removed `messageBatchSize` parameter (no longer needed)
- Removed `messagePendingInterval` parameter (no longer needed)
- Renamed `appliedTheme` parameter to `theme` for consistency

- **Theme System Overhaul**: Complete redesign of the theme system to align with the Serilog WPF sink. Previous theme names (`Dark`, `Light`, `DarkClassic`, `LightClassic`) have been replaced with new theme presets. All themes now include WCAG compliance with proper contrast ratio.

- **Enhanced Memory Management**: The `maxLogLines` parameter now has improved validation (1-512 range) with a default of 256 lines to ensure optimal performance. While not mandatory, proper configuration is recommended to prevent performance degradation from excessive log entries in the WinForms control.

### New Theme System

Available built-in themes:

| Theme | Description |
|-----------------------------|------------------------------------------------------------------------------|
| `ThemePresets.Literate` | Styled to replicate the default theme of Serilog.Sinks.Console (default) |
| `ThemePresets.Grayscale` | A theme using only shades of gray, white, and black |
| `ThemePresets.Colored` | A theme based on the original Serilog.Sinks.ColoredConsole sink |
| `ThemePresets.Luminous` | A new light theme with high contrast for accessibility |

The themes based on the original sinks are slightly adjusted to be WCAG compliant, ensuring that the contrast ratio between text and background colors is at least 4.5:1. `Luminous` is a new theme specifically created for this sink.

### Bug Fixes

- **Fixed UI Freezing**: Resolved critical UI freeze issues caused by SystemEvents when using RichTextBox controls on background threads.

- **Fixed Auto-scroll on .NET Framework**: Corrected auto-scroll behavior that wasn't working properly on .NET Framework applications.

### Performance Improvements

- **Optimized Rendering Logic**: Removed the off-screen RichTextBox dependency and improved the rendering pipeline for better performance and reduced memory usage.

- **Streamlined Processing**: Removed unnecessary batching parameters to simplify internal processing logic.

### Migration Guide

Due to breaking changes, please update your existing configurations:

1. **Update Theme Names**: Replace old theme names with new equivalents:
- `Dark` or `DarkClassic` → `ThemePresets.Colored` or `ThemePresets.Grayscale` or `ThemePresets.Literate`
- `Light` or `LightClassic` → `ThemePresets.Luminous`

2. **Update Parameter Names**:
- `appliedTheme` → `theme`
- Remove `messageBatchSize` and `messagePendingInterval` parameters (no longer supported)

I recommend pairing this sink with a file sink for persistent logging storage, as it's not practical to have thousands of log entries displayed in a RichTextBox control.

### Recommended Configuration

```csharp
Log.Logger = new LoggerConfiguration()
.WriteTo.RichTextBox(richTextBox1,
theme: ThemePresets.Literate,
maxLogLines: 64) // Optional, defaults to 256
.WriteTo.File("logs/app-.txt", rollingInterval: RollingInterval.Day) // Recommended for persistence
.CreateLogger();
```

### Full Changelog

- Reduced configurable options to only the most relevant ones (breaking change)
- Renamed `appliedTheme` parameter to `theme` (breaking change)
- Removed `messageBatchSize` and `messagePendingInterval` parameters (breaking change)
- Completely redesigned theme system with new theme names and WCAG compliance (breaking change)
- Added new `Luminous` theme for high contrast accessibility
- Enhanced `maxLogLines` validation with 1-512 range limit
- Fixed UI freeze caused by SystemEvents on background-thread RichTextBox
- Optimized performance by removing the off-screen RichTextBox and improving rendering logic
- Fixed auto-scroll issue on .NET Framework
- 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

### Resources

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,11 @@
<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.0</Version>
<Version>3.0.1</Version>
<PackageReleaseNotes>
- Reduced configurable options to only the most relevant ones (breaking change).
- Aligned theme colors with Serilog WPF sink including WCAG compliance (breaking change).
- Fixed UI freeze caused by SystemEvents on background-thread RichTextBox.
- Optimized performance by removing the off-screen RichTextBox and improving rendering logic.
- Fixed auto-scroll issue on .NET Framework.
- 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.

See repository for more information:
https://github.com/vonhoff/Serilog.Sinks.RichTextBox.WinForms.Colored
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,35 +4,39 @@ namespace Serilog.Sinks.RichTextBoxForms.Collections
{
internal sealed class ConcurrentCircularBuffer<T>
{
private readonly object _sync = new();
private readonly T[] _buffer;
private readonly int _capacity;
private int _head;
private int _count;

private readonly object _sync = new();

public ConcurrentCircularBuffer(int capacity)
{
_capacity = capacity > 0 ? capacity : 1;
_buffer = new T[_capacity];
_head = 0;
_count = 0;
}

public void Add(T item)
{
lock (_sync)
{
var tail = (_head + _count) % _capacity;
_buffer[tail] = item;
var tail = _head + _count;
if (tail >= _capacity)
{
tail -= _capacity;
}

_buffer[tail] = item;
if (_count == _capacity)
{
_head = (_head + 1) % _capacity;
if (++_head == _capacity)
{
_head = 0;
}
}
else
{
_count++;
++_count;
}
}
}
Expand All @@ -42,13 +46,18 @@ public void TakeSnapshot(List<T> target)
lock (_sync)
{
target.Clear();
target.Capacity = _count;

for (var i = 0; i < _count; i++)
for (var i = 0; i < _count; ++i)
{
target.Add(_buffer[(_head + i) % _capacity]);
var index = _head + i;
if (index >= _capacity)
{
index -= _capacity;
}

target.Add(_buffer[index]);
}
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ public static void SetRtf(this RichTextBox richTextBox, string rtf, bool autoScr
private static void SetRtfInternal(RichTextBox richTextBox, string rtf, bool autoScroll)
{
richTextBox.Suspend();

var originalZoomFactor = richTextBox.ZoomFactor;
var scrollPoint = new Point();

if (!autoScroll)
Expand All @@ -78,6 +78,12 @@ private static void SetRtfInternal(RichTextBox richTextBox, string rtf, bool aut

richTextBox.Rtf = rtf;

// Re-apply the zoom level, as assigning to the Rtf property resets it back to 1.0.
if (Math.Abs(richTextBox.ZoomFactor - originalZoomFactor) > float.Epsilon)
{
richTextBox.ZoomFactor = originalZoomFactor;
}

if (!autoScroll)
{
SendMessage(richTextBox.Handle, EM_SETSCROLLPOS, 0, ref scrollPoint);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public class RichTextBoxSinkOptions
/// </summary>
/// <param name="theme">The colour theme applied when rendering individual message tokens.</param>
/// <param name="autoScroll">When <c>true</c> (default) the target control scrolls automatically to the most recent log line.</param>
/// <param name="maxLogLines">Maximum number of log events retained in the in-memory circular buffer and rendered in the control. Must be between 1 and 10,000 (default: 256).</param>
/// <param name="maxLogLines">Maximum number of log events retained in the in-memory circular buffer and rendered in the control. Must be between 1 and 2048 (default: 256).</param>
/// <param name="flushInterval">Timeout, in milliseconds, after which buffered events are flushed to the control if a batch has not yet been triggered. Must be between 250ms and 30 seconds (default: 500ms).</param>
/// <param name="outputTemplate">Serilog output template that controls textual formatting of each log event.</param>
/// <param name="formatProvider">Optional culture-specific or custom formatting provider used when rendering scalar values; <c>null</c> for the invariant culture.</param>
Expand Down Expand Up @@ -60,7 +60,7 @@ public int MaxLogLines
private set => _maxLogLines = value switch
{
< 1 => 1,
> 512 => 512,
> 2048 => 2048,
_ => value
};
}
Expand Down