Skip to content

Commit 7650cc1

Browse files
authored
feat: Support of IMessageSink (#17)
* feat: Added functionallity for `IMessageSink` * fix: Added missing using
1 parent a8f33a1 commit 7650cc1

File tree

4 files changed

+165
-53
lines changed

4 files changed

+165
-53
lines changed

src/NetEvolve.Logging.XUnit/NetEvolve.Logging.XUnit.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
<PackageReference Include="NetEvolve.Arguments" />
2323
<PackageReference Include="NetEvolve.Logging.Abstractions" />
2424
<PackageReference Include="xunit.extensibility.core" />
25+
<PackageReference Include="xunit.extensibility.execution" />
2526
</ItemGroup>
2627

2728
</Project>

src/NetEvolve.Logging.XUnit/XUnitLogger.cs

Lines changed: 106 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,20 @@
88
using NetEvolve.Arguments;
99
using NetEvolve.Logging.Abstractions;
1010
using Xunit.Abstractions;
11+
using Xunit.Sdk;
1112

1213
/// <summary>
1314
/// Represents a logger that writes messages to xunit output.
1415
/// </summary>
1516
public class XUnitLogger : ILogger, ISupportExternalScope
1617
{
1718
private readonly IXUnitLoggerOptions _options;
18-
private readonly ITestOutputHelper _testOutputHelper;
1919
private readonly TimeProvider _timeProvider;
2020

2121
private readonly List<LoggedMessage> _loggedMessages;
2222

23+
private readonly Action<string> _writeToLog;
24+
2325
private const int DefaultCapacity = 1024;
2426

2527
[ThreadStatic]
@@ -33,6 +35,43 @@ public class XUnitLogger : ILogger, ISupportExternalScope
3335
/// <inheritdoc cref="IHasLoggedMessages.LoggedMessages"/>
3436
public IReadOnlyList<LoggedMessage> LoggedMessages => _loggedMessages.AsReadOnly();
3537

38+
/// <summary>
39+
/// Creates a new instance of <see cref="XUnitLogger"/>.
40+
/// </summary>
41+
/// <param name="messageSink">The <see cref="IMessageSink" /> to write the log messages to.</param>
42+
/// <param name="timeProvider">The <see cref="TimeProvider" /> to use to get the current time.</param>
43+
/// <param name="scopeProvider">The <see cref="IExternalScopeProvider" /> to use to get the current scope.</param>
44+
/// <param name="options">The options to control the behavior of the logger.</param>
45+
/// <returns>A cached or new instance of <see cref="XUnitLogger"/>.</returns>
46+
public static XUnitLogger CreateLogger(
47+
IMessageSink messageSink,
48+
TimeProvider timeProvider,
49+
IExternalScopeProvider? scopeProvider = null,
50+
IXUnitLoggerOptions? options = null
51+
)
52+
{
53+
Argument.ThrowIfNull(messageSink);
54+
55+
return new XUnitLogger(messageSink, timeProvider, scopeProvider, options);
56+
}
57+
58+
/// <summary>
59+
/// Creates a new instance of <see cref="XUnitLogger{T}"/>.
60+
/// </summary>
61+
/// <typeparam name="T">The type who's fullname is used as the category name for messages produced by the logger.</typeparam>
62+
/// <param name="messageSink">The <see cref="IMessageSink" /> to write the log messages to.</param>
63+
/// <param name="timeProvider">The <see cref="TimeProvider" /> to use to get the current time.</param>
64+
/// <param name="scopeProvider">The <see cref="IExternalScopeProvider" /> to use to get the current scope.</param>
65+
/// <param name="options">The options to control the behavior of the logger.</param>
66+
/// <returns>A cached or new instance of <see cref="XUnitLogger"/>.</returns>
67+
public static XUnitLogger<T> CreateLogger<T>(
68+
IMessageSink messageSink,
69+
TimeProvider timeProvider,
70+
IExternalScopeProvider? scopeProvider = null,
71+
IXUnitLoggerOptions? options = null
72+
)
73+
where T : notnull => new XUnitLogger<T>(messageSink, timeProvider, scopeProvider, options);
74+
3675
/// <summary>
3776
/// Creates a new instance of <see cref="XUnitLogger"/>.
3877
/// </summary>
@@ -82,11 +121,31 @@ private protected XUnitLogger(
82121
Argument.ThrowIfNull(timeProvider);
83122

84123
ScopeProvider = scopeProvider ?? NullExternalScopeProvider.Instance;
85-
_testOutputHelper = testOutputHelper;
86124
_timeProvider = timeProvider;
87125
_options = options ?? XUnitLoggerOptions.Default;
88126

89127
_loggedMessages = [];
128+
129+
_writeToLog = testOutputHelper.WriteLine;
130+
}
131+
132+
private protected XUnitLogger(
133+
IMessageSink messageSink,
134+
TimeProvider timeProvider,
135+
IExternalScopeProvider? scopeProvider,
136+
IXUnitLoggerOptions? options
137+
)
138+
{
139+
Argument.ThrowIfNull(messageSink);
140+
Argument.ThrowIfNull(timeProvider);
141+
142+
ScopeProvider = scopeProvider ?? NullExternalScopeProvider.Instance;
143+
_timeProvider = timeProvider;
144+
_options = options ?? XUnitLoggerOptions.Default;
145+
146+
_loggedMessages = [];
147+
148+
_writeToLog = message => _ = messageSink.OnMessage(new DiagnosticMessage(message));
90149
}
91150

92151
/// <inheritdoc cref="ILogger.BeginScope{TState}(TState)"/>
@@ -112,88 +171,84 @@ public void Log<TState>(
112171
return;
113172
}
114173

115-
var builder = _builder;
116-
_builder = null;
117-
builder ??= new StringBuilder(DefaultCapacity);
118-
119174
try
120175
{
121176
var message = formatter(state, exception);
122177
var now = _timeProvider.GetLocalNow();
123-
(builder, var scopes) = CreateMessage(
124-
logLevel,
125-
state,
126-
exception,
127-
builder,
128-
message,
129-
now
130-
);
178+
var (fullMessage, scopes) = CreateMessage(logLevel, state, exception, message, now);
131179

132180
_loggedMessages.Add(
133181
new LoggedMessage(now, logLevel, eventId, message, exception, scopes)
134182
);
135-
_testOutputHelper.WriteLine(builder.ToString());
183+
184+
_writeToLog.Invoke(fullMessage);
136185
}
137186
catch
138187
{
139188
// Ignore exception.
140189
// Unfortunately, this can happen if the process is terminated before the end of the test.
141190
}
142-
finally
143-
{
144-
_ = builder.Clear();
145-
if (builder.Capacity > DefaultCapacity)
146-
{
147-
builder.Capacity = DefaultCapacity;
148-
}
149-
_builder = builder;
150-
}
151191
}
152192

153-
private (StringBuilder, List<object?>) CreateMessage<TState>(
193+
private (string, List<object?>) CreateMessage<TState>(
154194
LogLevel logLevel,
155195
TState state,
156196
Exception? exception,
157-
StringBuilder builder,
158197
string message,
159198
DateTimeOffset now
160199
)
161200
{
162201
var scopes = new List<object?>();
163-
if (!_options.DisableTimestamp)
164-
{
165-
_ = builder
166-
.Append(now.ToString(_options.TimestampFormat, CultureInfo.InvariantCulture))
167-
.Append(' ');
168-
}
202+
var builder = _builder;
203+
_builder = null;
204+
builder ??= new StringBuilder(DefaultCapacity);
169205

170-
if (!_options.DisableLogLevel)
206+
try
171207
{
172-
_ = builder.Append('[').Append(LogLevelToString(logLevel)).Append("] ");
173-
}
208+
if (!_options.DisableTimestamp)
209+
{
210+
_ = builder
211+
.Append(now.ToString(_options.TimestampFormat, CultureInfo.InvariantCulture))
212+
.Append(' ');
213+
}
174214

175-
_ = builder.Append(message);
215+
if (!_options.DisableLogLevel)
216+
{
217+
_ = builder.Append('[').Append(LogLevelToString(logLevel)).Append("] ");
218+
}
176219

177-
if (exception is not null)
178-
{
179-
_ = builder.Append('\n').Append(exception);
180-
}
220+
_ = builder.Append(message);
181221

182-
if (
183-
!_options.DisableAdditionalInformation
184-
&& state is IReadOnlyList<KeyValuePair<string, object?>> additionalInformation
185-
)
186-
{
187-
_ = builder.Append('\n').Append('\t').Append("Additional Information");
188-
foreach (var info in additionalInformation)
222+
if (exception is not null)
189223
{
190-
AddAdditionalInformation(builder, info);
224+
_ = builder.Append('\n').Append(exception);
191225
}
192-
}
193226

194-
ScopeProvider.ForEachScope(IterateScopes, builder);
227+
if (
228+
!_options.DisableAdditionalInformation
229+
&& state is IReadOnlyList<KeyValuePair<string, object?>> additionalInformation
230+
)
231+
{
232+
_ = builder.Append('\n').Append('\t').Append("Additional Information");
233+
foreach (var info in additionalInformation)
234+
{
235+
AddAdditionalInformation(builder, info);
236+
}
237+
}
195238

196-
return (builder, scopes);
239+
ScopeProvider.ForEachScope(IterateScopes, builder);
240+
241+
return (builder.ToString(), scopes);
242+
}
243+
finally
244+
{
245+
_ = builder.Clear();
246+
if (builder.Capacity > DefaultCapacity)
247+
{
248+
builder.Capacity = DefaultCapacity;
249+
}
250+
_builder = builder;
251+
}
197252

198253
void IterateScopes(object? scope, StringBuilder state)
199254
{

src/NetEvolve.Logging.XUnit/XUnitLoggerProvider.cs

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ public ILogger CreateLogger(string categoryName)
6262
Argument.ThrowIfNullOrWhiteSpace(categoryName);
6363

6464
return _loggers.GetOrAdd(
65-
categoryName,
65+
$"{categoryName}_Default",
6666
name => XUnitLogger.CreateLogger(_testOutputHelper, _timeProvider, _scopeProvider, this)
6767
);
6868
}
@@ -71,10 +71,58 @@ public ILogger CreateLogger(string categoryName)
7171
public ILogger CreateLogger<T>()
7272
where T : notnull =>
7373
_loggers.GetOrAdd(
74-
typeof(T).FullName!,
74+
$"{typeof(T).FullName}_Default",
7575
_ => XUnitLogger.CreateLogger<T>(_testOutputHelper, _timeProvider, _scopeProvider, this)
7676
);
7777

78+
/// <inheritdoc cref="ILoggerProvider.CreateLogger(string)"/>
79+
public ILogger CreateLogger(string categoryName, IMessageSink messageSink)
80+
{
81+
Argument.ThrowIfNullOrWhiteSpace(categoryName);
82+
Argument.ThrowIfNull(messageSink);
83+
84+
return _loggers.GetOrAdd(
85+
$"{categoryName}_IMessageSink",
86+
name => XUnitLogger.CreateLogger(messageSink, _timeProvider, _scopeProvider, this)
87+
);
88+
}
89+
90+
/// <inheritdoc cref="ILoggerProvider.CreateLogger(string)"/>
91+
public ILogger CreateLogger<T>(IMessageSink messageSink)
92+
where T : notnull
93+
{
94+
Argument.ThrowIfNull(messageSink);
95+
96+
return _loggers.GetOrAdd(
97+
$"{typeof(T).FullName}_IMessageSink",
98+
_ => XUnitLogger.CreateLogger<T>(messageSink, _timeProvider, _scopeProvider, this)
99+
);
100+
}
101+
102+
/// <inheritdoc cref="ILoggerProvider.CreateLogger(string)"/>
103+
public ILogger CreateLogger(string categoryName, ITestOutputHelper testOutputHelper)
104+
{
105+
Argument.ThrowIfNullOrWhiteSpace(categoryName);
106+
Argument.ThrowIfNull(testOutputHelper);
107+
108+
return _loggers.GetOrAdd(
109+
$"{categoryName}_ITestOutputHelper",
110+
name => XUnitLogger.CreateLogger(testOutputHelper, _timeProvider, _scopeProvider, this)
111+
);
112+
}
113+
114+
/// <inheritdoc cref="ILoggerProvider.CreateLogger(string)"/>
115+
public ILogger CreateLogger<T>(ITestOutputHelper testOutputHelper)
116+
where T : notnull
117+
{
118+
Argument.ThrowIfNull(testOutputHelper);
119+
120+
return _loggers.GetOrAdd(
121+
$"{typeof(T).FullName}_ITestOutputHelper",
122+
_ => XUnitLogger.CreateLogger<T>(testOutputHelper, _timeProvider, _scopeProvider, this)
123+
);
124+
}
125+
78126
/// <inheritdoc cref="ISupportExternalScope.SetScopeProvider(IExternalScopeProvider)"/>
79127
public void SetScopeProvider(IExternalScopeProvider scopeProvider)
80128
{

src/NetEvolve.Logging.XUnit/XUnitLogger`T.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,14 @@
88
public sealed class XUnitLogger<T> : XUnitLogger, ILogger<T>
99
where T : notnull
1010
{
11+
internal XUnitLogger(
12+
IMessageSink messageSink,
13+
TimeProvider timeProvider,
14+
IExternalScopeProvider? scopeProvider,
15+
IXUnitLoggerOptions? options
16+
)
17+
: base(messageSink, timeProvider, scopeProvider, options) { }
18+
1119
internal XUnitLogger(
1220
ITestOutputHelper testOutputHelper,
1321
TimeProvider timeProvider,

0 commit comments

Comments
 (0)