Skip to content

Commit ce06941

Browse files
committed
Refactor Spectre Console Log Writers to Use ILogMessage Interface
- Updated ProgressConsoleMessageWriter to implement IAsyncLogMessageWriter for ProgressStartPayload and ProgressFinishedPayload using ILogMessage. - Refactored SpectreConsoleLogMessageWriter to accept a SpectreConsoleLogSink in the constructor and implement IAsyncLogMessageWriter for generic payloads. - Modified StatusConsoleMessageWriter to utilize ILogMessage for status updates. - Changed WidgetPayloadLogMessageWriter to accept ILogMessage for writing widget payloads. - Enhanced SpectreConsoleLogSink to manage log message writers and filters more effectively. - Added DebugTextStyle to LogLevelStylePalette for improved log level styling. - Improved LogMessageWidget rendering logic to handle various payload scenarios and ensure proper formatting. - Updated tests to reflect changes in log message writing and rendering, including new test cases for different log levels and payloads.
1 parent 63980f1 commit ce06941

29 files changed

Lines changed: 339 additions & 218 deletions

src/ActionDisposable.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
using System;
2+
using System.Threading.Tasks;
3+
4+
namespace WB.Logging.LogSinks.Console.Spectre;
5+
6+
internal sealed class ActionDisposable(Func<Task> action) : IAsyncDisposable
7+
{
8+
public async ValueTask DisposeAsync()
9+
=> await action().ConfigureAwait(false);
10+
}

src/Extensions/ILoggerExtensions.cs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ public static class ILoggerExtensions
2121
/// <param name="this">The <see cref="ILogger"/> instance to attach the log sink to.</param>
2222
/// <param name="configure">An optional action to configure the <see cref="SpectreConsoleLogSink"/> after it has been created and attached.</param>
2323
/// <returns>An <see cref="IDisposable"/> that can be used to detach the log sink from the logger when it is no longer needed.</returns>
24-
public static IDisposable AttachSpectreConsole(this ILogger @this, Action<SpectreConsoleLogSink>? configure = null)
24+
public static IAsyncDisposable AttachSpectreConsole(this ILogger @this, Action<SpectreConsoleLogSink>? configure = null)
2525
{
2626
ArgumentNullException.ThrowIfNull(@this);
2727

@@ -31,7 +31,11 @@ public static IDisposable AttachSpectreConsole(this ILogger @this, Action<Spectr
3131

3232
configure?.Invoke(logSink);
3333

34-
return disposable;
34+
return new ActionDisposable(async () =>
35+
{
36+
disposable.Dispose();
37+
await logSink.DisposeAsync().ConfigureAwait(false);
38+
});
3539
}
3640

3741
/// <summary>

src/Extensions/StringExtensions.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,15 @@ namespace WB.Logging.LogSinks.Console.Spectre;
55

66
internal static class StringExtensions
77
{
8-
public static Markup ToMarkup(this string @this, Style? style = null)
8+
public static Markup ToMarkup(this string? @this, Style? style = null)
99
{
1010
try
1111
{
12-
return new Markup(@this, style);
12+
return new Markup(@this ?? string.Empty, style);
1313
}
1414
catch (InvalidOperationException)
1515
{
16-
return new Markup(@this.EscapeMarkup(), style);
16+
return new Markup(@this?.EscapeMarkup() ?? string.Empty, style);
1717
}
1818
}
1919
}

src/LogMessageWriters/ProgressConsoleMessageWriter.cs

Lines changed: 9 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.Collections.Generic;
33
using System.Diagnostics.CodeAnalysis;
4+
using System.Threading;
45
using System.Threading.Tasks;
56
using Spectre.Console;
67
using WB.Logging.LogSinks.Base;
@@ -11,44 +12,36 @@ namespace WB.Logging.LogSinks.Console.Spectre;
1112
/// A log message writer that uses Spectre.Console's progress bar to
1213
/// render progress updates in the console.
1314
/// </summary>
14-
internal sealed class ProgressConsoleMessageWriter
15-
: IAsyncLogMessageWriter<SpectreConsoleLogSink, ProgressStartPayload>
16-
, IAsyncLogMessageWriter<SpectreConsoleLogSink, ProgressFinishedPayload>
15+
internal sealed class ProgressConsoleMessageWriter(SpectreConsoleLogSink logSink)
16+
: IAsyncLogMessageWriter<ProgressStartPayload>
17+
, IAsyncLogMessageWriter<ProgressFinishedPayload>
1718
{
1819
private IDisposable? logSinkDisabledSubscription;
1920

20-
// ┌─────────────────────────────────────────────────────────────────────────────┐
21-
// │ Public Properties │
22-
// └─────────────────────────────────────────────────────────────────────────────┘
23-
24-
/// <inheritdoc/>
25-
[NotNull]
26-
public SpectreConsoleLogSink? LogSink { get; set; }
27-
2821
// ┌─────────────────────────────────────────────────────────────────────────────┐
2922
// │ Public Methods │
3023
// └─────────────────────────────────────────────────────────────────────────────┘
3124

32-
public ValueTask WriteAsync(DateTimeOffset timestamp, LogLevel? logLevel, IEnumerable<string> senders, ProgressStartPayload payload)
25+
public ValueTask WriteAsync(ILogMessage<ProgressStartPayload> logMessage, CancellationToken cancellationToken)
3326
{
3427
if (logSinkDisabledSubscription is null)
3528
{
36-
logSinkDisabledSubscription = LogSink.AddFilter<object>(lm => lm.Payload is ProgressFinishedPayload);
29+
logSinkDisabledSubscription = logSink.AddFilter<ProgressFinishedPayload>(lm => lm.Payload is ProgressFinishedPayload);
3730

38-
payload.SetProgress(LogSink.Console.Progress());
31+
logMessage.Payload.SetProgress(logSink.Console.Progress());
3932
}
4033

4134
return ValueTask.CompletedTask;
4235
}
4336

44-
public ValueTask WriteAsync(DateTimeOffset timestamp, LogLevel? logLevel, IEnumerable<string> senders, ProgressFinishedPayload payload)
37+
public ValueTask WriteAsync(ILogMessage<ProgressFinishedPayload> logMessage, CancellationToken cancellationToken)
4538
{
4639
if (logSinkDisabledSubscription is not null)
4740
{
4841
logSinkDisabledSubscription.Dispose();
4942
logSinkDisabledSubscription = null;
5043

51-
payload.SetFinished();
44+
logMessage.Payload.SetFinished();
5245
}
5346

5447
return ValueTask.CompletedTask;

src/LogMessageWriters/SpectreConsoleLogMessageWriter{TValue}.cs

Lines changed: 35 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
using System;
22
using System.Collections.Generic;
3-
using System.Diagnostics.CodeAnalysis;
43
using System.Linq;
4+
using System.Threading;
55
using System.Threading.Tasks;
66
using Spectre.Console;
77
using Spectre.Console.Rendering;
@@ -16,12 +16,16 @@ namespace WB.Logging.LogSinks.Console.Spectre;
1616
/// to render these parts. Derived classes can override the rendering methods to customize the appearance of log messages.
1717
/// </summary>
1818
/// <typeparam name="TValue"></typeparam>
19-
public class SpectreConsoleLogMessageWriter<TValue> : IAsyncLogMessageWriter<SpectreConsoleLogSink, TValue>
19+
public class SpectreConsoleLogMessageWriter<TValue> : IAsyncLogMessageWriter<TValue>
2020
where TValue : notnull
2121
{
2222
// ┌─────────────────────────────────────────────────────────────────────────────┐
2323
// │ Private Fields │
2424
// └─────────────────────────────────────────────────────────────────────────────┘
25+
private readonly SpectreConsoleLogSink logSink;
26+
27+
private readonly BadgeWidget debugBadge;
28+
2529
private readonly BadgeWidget infoBadge;
2630

2731
private readonly BadgeWidget warningBadge;
@@ -39,12 +43,20 @@ public class SpectreConsoleLogMessageWriter<TValue> : IAsyncLogMessageWriter<Spe
3943
// └─────────────────────────────────────────────────────────────────────────────┘
4044

4145
/// <summary>
42-
/// Initializes a new instance of the <see cref="SpectreConsoleLogMessageWriter{TValue}"/> class with default styles for log levels. The constructor creates badge widgets for different log levels (info, warning, error, unknown)
46+
/// Initializes a new instance of the <see cref="SpectreConsoleLogMessageWriter{TValue}"/> class with default styles for log levels. The constructor creates badge widgets for different log levels (debug, info, warning, error, none, unknown)
4347
/// using the styles defined in the <see cref="StylePalette"/>. These badges are used to render the log level
4448
/// part of log messages when the <see cref="ShowLogLevel"/> property is set to <c>true</c>.
4549
/// </summary>
46-
public SpectreConsoleLogMessageWriter()
50+
public SpectreConsoleLogMessageWriter(SpectreConsoleLogSink logSink)
4751
{
52+
this.logSink = logSink ?? throw new ArgumentNullException(nameof(logSink));
53+
54+
debugBadge = new BadgeWidget("DEBU")
55+
{
56+
BracketStyle = StylePalette.LogLevelStyle.BracketStyle,
57+
TextStyle = StylePalette.LogLevelStyle.DebugTextStyle,
58+
};
59+
4860
infoBadge = new BadgeWidget("INFO")
4961
{
5062
BracketStyle = StylePalette.LogLevelStyle.BracketStyle,
@@ -80,29 +92,6 @@ public SpectreConsoleLogMessageWriter()
8092
// │ Public Properties │
8193
// └─────────────────────────────────────────────────────────────────────────────┘
8294

83-
// ┌─────────────────────────────────────────────────────────────────────────────┐
84-
// │ Public Methods │
85-
// └─────────────────────────────────────────────────────────────────────────────┘
86-
87-
/// <inheritdoc/>
88-
public virtual ValueTask WriteAsync(DateTimeOffset timestamp, LogLevel? logLevel, IEnumerable<string> senders, TValue payload)
89-
{
90-
logMessageWidget.Reset();
91-
92-
logMessageWidget.Timestamp = ShowTimestamp ? RenderTimestamp(timestamp) : null;
93-
logMessageWidget.LogLevel = ShowLogLevel ? RenderLogLevel(logLevel) : null;
94-
logMessageWidget.Senders = ShowSenders ? RenderSenders(senders) : null;
95-
logMessageWidget.Payload = ShowPayload ? RenderPayload(payload) : null;
96-
97-
LogSink?.Console.Write(logMessageWidget);
98-
99-
return ValueTask.CompletedTask;
100-
}
101-
102-
// ┌─────────────────────────────────────────────────────────────────────────────┐
103-
// │ Public Properties │
104-
// └─────────────────────────────────────────────────────────────────────────────┘
105-
10695
/// <summary>
10796
/// Gets or sets the <see cref="StylePalette"/> used to style different parts of log messages.
10897
/// </summary>
@@ -128,9 +117,26 @@ public virtual ValueTask WriteAsync(DateTimeOffset timestamp, LogLevel? logLevel
128117
/// </summary>
129118
public bool ShowPayload { get; set; } = true;
130119

120+
// ┌─────────────────────────────────────────────────────────────────────────────┐
121+
// │ Public Methods │
122+
// └─────────────────────────────────────────────────────────────────────────────┘
123+
131124
/// <inheritdoc/>
132-
[NotNull]
133-
public SpectreConsoleLogSink? LogSink { get; set; }
125+
public virtual ValueTask WriteAsync(ILogMessage<TValue> logMessage, CancellationToken cancellationToken)
126+
{
127+
ArgumentNullException.ThrowIfNull(logMessage);
128+
129+
logMessageWidget.Reset();
130+
131+
logMessageWidget.Timestamp = ShowTimestamp ? RenderTimestamp(logMessage.Timestamp) : null;
132+
logMessageWidget.LogLevel = ShowLogLevel ? RenderLogLevel(logMessage.LogLevel) : null;
133+
logMessageWidget.Senders = ShowSenders && logMessage.Senders.Count > 0 ? RenderSenders(logMessage.Senders) : null;
134+
logMessageWidget.Payload = ShowPayload ? RenderPayload(logMessage.Payload) : null;
135+
136+
logSink.Console.Write(logMessageWidget);
137+
138+
return ValueTask.CompletedTask;
139+
}
134140

135141
// ┌─────────────────────────────────────────────────────────────────────────────┐
136142
// │ Protected Methods │

src/LogMessageWriters/StatusConsoleMessageWriter.cs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.Collections.Generic;
33
using System.Diagnostics.CodeAnalysis;
4+
using System.Threading;
45
using System.Threading.Tasks;
56
using Spectre.Console;
67
using WB.Logging.LogSinks.Base;
@@ -12,8 +13,8 @@ namespace WB.Logging.LogSinks.Console.Spectre;
1213
/// render status updates in the console.
1314
/// </summary>
1415
internal sealed class StatusConsoleMessageWriter
15-
: IAsyncLogMessageWriter<SpectreConsoleLogSink, StatusStartPayload>
16-
, IAsyncLogMessageWriter<SpectreConsoleLogSink, StatusFinishedPayload>
16+
: IAsyncLogMessageWriter<StatusStartPayload>
17+
, IAsyncLogMessageWriter<StatusFinishedPayload>
1718
{
1819
private IDisposable? logSinkDisabledSubscription;
1920

@@ -32,26 +33,26 @@ internal sealed class StatusConsoleMessageWriter
3233
// │ Public Methods │
3334
// └─────────────────────────────────────────────────────────────────────────────┘
3435

35-
public ValueTask WriteAsync(DateTimeOffset timestamp, LogLevel? logLevel, IEnumerable<string> senders, StatusStartPayload payload)
36+
public ValueTask WriteAsync(ILogMessage<StatusStartPayload> logMessage, CancellationToken cancellationToken)
3637
{
3738
if (logSinkDisabledSubscription is null && LogSink is not null)
3839
{
3940
logSinkDisabledSubscription = LogSink.AddFilter<object>(lm => lm.Payload is StatusFinishedPayload);
4041

41-
payload.SetStatus(LogSink.Console.Status());
42+
logMessage.Payload.SetStatus(LogSink.Console.Status());
4243
}
4344

4445
return ValueTask.CompletedTask;
4546
}
4647

47-
public ValueTask WriteAsync(DateTimeOffset timestamp, LogLevel? logLevel, IEnumerable<string> senders, StatusFinishedPayload payload)
48+
public ValueTask WriteAsync(ILogMessage<StatusFinishedPayload> logMessage, CancellationToken cancellationToken)
4849
{
4950
if (logSinkDisabledSubscription is not null)
5051
{
5152
logSinkDisabledSubscription.Dispose();
5253
logSinkDisabledSubscription = null;
5354

54-
payload.SetFinished();
55+
logMessage.Payload.SetFinished();
5556
}
5657

5758
return ValueTask.CompletedTask;

src/LogMessageWriters/WidgetPayloadLogMessageWriter.cs

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,14 @@
1-
using System;
2-
using System.Collections.Generic;
3-
using System.Diagnostics.CodeAnalysis;
1+
using System.Threading;
42
using System.Threading.Tasks;
5-
using Spectre.Console;
63
using WB.Logging.LogSinks.Base;
74

85
namespace WB.Logging.LogSinks.Console.Spectre;
96

10-
internal sealed class PayloadLogMessageWriter : IAsyncLogMessageWriter<SpectreConsoleLogSink, WidgetPayload>
7+
internal sealed class WidgetPayloadLogMessageWriter(SpectreConsoleLogSink logSink) : IAsyncLogMessageWriter<WidgetPayload>
118
{
12-
[NotNull]
13-
public SpectreConsoleLogSink? LogSink { get; set; }
14-
15-
public ValueTask WriteAsync(DateTimeOffset timestamp, LogLevel? logLevel, IEnumerable<string> senders, WidgetPayload? payload)
9+
public ValueTask WriteAsync(ILogMessage<WidgetPayload> logMessage, CancellationToken cancellationToken)
1610
{
17-
if (payload is not null)
18-
{
19-
LogSink?.Console.Write(payload.Widget);
20-
}
11+
logSink.Console.Write(logMessage.Payload.Widget);
2112

2213
return ValueTask.CompletedTask;
2314
}

0 commit comments

Comments
 (0)