Skip to content

Commit c602413

Browse files
committed
Minor refactors and additional tests.
Added logging output wrappers as utilities.
1 parent 89a6886 commit c602413

15 files changed

+388
-183
lines changed

Directory.Build.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
</PropertyGroup>
88

99
<ItemGroup>
10-
<PackageReference Include="Microsoft.Build.CopyOnWrite" Version="1.0.263" PrivateAssets="All" />
10+
<PackageReference Include="Microsoft.Build.CopyOnWrite" Version="1.0.265" PrivateAssets="All" />
1111
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />
1212
</ItemGroup>
1313
</Project>

src/C3D/Extensions/Logging/Xunit/C3D.Extensions.Logging.Xunit.csproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,13 @@
2121
</PackageReference>
2222
<PackageReference Include="xunit.abstractions" Version="2.0.3" />
2323
<PackageReference Include="xunit.extensibility.execution" Version="2.5.0" />
24+
</ItemGroup>
25+
<ItemGroup>
2426
<PackageReference Include="Microsoft.Extensions.Logging.Configuration" Version="6.0.0" />
2527
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="6.0.0" />
28+
<PackageReference Include="System.Collections.Immutable" Version="6.0.0" />
2629
</ItemGroup>
30+
2731
<ItemGroup Condition="'$(TargetFramework)'=='net7.0'">
2832
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="7.0.0" />
2933
</ItemGroup>

src/C3D/Extensions/Logging/Xunit/Extensions/MELXunitLoggerExtensions.cs

Lines changed: 6 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -2,56 +2,31 @@
22
using Microsoft.Extensions.DependencyInjection;
33
using Microsoft.Extensions.DependencyInjection.Extensions;
44
using Microsoft.Extensions.Logging.Configuration;
5-
using System.Diagnostics.CodeAnalysis;
65
using Xunit.Abstractions;
76

87
namespace Microsoft.Extensions.Logging;
98

109
public static class MELXunitLoggerExtensions
1110
{
12-
private static bool TryGetService<T>(this IServiceProvider services, [NotNullWhen(returnValue: true)] out T? service) where T : class
13-
{
14-
service = services.GetService<T>();
15-
return service is not null;
16-
}
17-
1811
#region "ILoggingBuilder"
19-
public static ILoggingBuilder AddXunit(this ILoggingBuilder builder)
12+
public static ILoggingBuilder AddXunit(this ILoggingBuilder builder, Action<XunitLoggerOptions>? configure = null)
2013
{
2114
LoggerProviderOptions.RegisterProviderOptions<XunitLoggerOptions, XunitLoggerProvider>(builder.Services);
15+
if (configure is not null) { builder.Services.Configure(configure); }
2216
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<ILoggerProvider, XunitLoggerProvider>());
2317
return builder;
2418
}
2519

26-
public static ILoggingBuilder AddXunit(this ILoggingBuilder builder, Action<XunitLoggerOptions>? configure)
27-
{
28-
var optionsBuilder = builder.Services.AddOptions<XunitLoggerOptions>();
29-
if (configure is not null) { optionsBuilder.Configure(configure); }
30-
builder.Services.TryAddEnumerable(
31-
ServiceDescriptor.Singleton<ILoggerProvider>(sp =>
32-
{
33-
if (sp.TryGetService<ITestOutputHelper>(out var testOutput)) { ActivatorUtilities.CreateInstance<XunitLoggerProvider>(sp,testOutput); }
34-
if (sp.TryGetService<IMessageSink>(out var messageSink)) { ActivatorUtilities.CreateInstance<XunitLoggerProvider>(sp, messageSink); }
35-
return Microsoft.Extensions.Logging.Abstractions.NullLoggerProvider.Instance;
36-
}));
37-
38-
return builder;
39-
}
40-
4120
public static ILoggingBuilder AddXunit(this ILoggingBuilder builder, ITestOutputHelper output, Action<XunitLoggerOptions>? configure = null)
4221
{
43-
var optionsBuilder = builder.Services.AddOptions<XunitLoggerOptions>();
44-
if (configure is not null) { optionsBuilder.Configure(configure); }
45-
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<ILoggerProvider>(sp=> ActivatorUtilities.CreateInstance<XunitLoggerProvider>(sp, output)));
46-
return builder;
22+
builder.Services.AddSingleton(output);
23+
return builder.AddXunit(configure);
4724
}
4825

4926
public static ILoggingBuilder AddXunit(this ILoggingBuilder builder, IMessageSink output, Action<XunitLoggerOptions>? configure = null)
5027
{
51-
var optionsBuilder = builder.Services.AddOptions<XunitLoggerOptions>();
52-
if (configure is not null) { optionsBuilder.Configure(configure); }
53-
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<ILoggerProvider>(sp => ActivatorUtilities.CreateInstance<XunitLoggerProvider>(sp, output)));
54-
return builder;
28+
builder.Services.AddSingleton(output);
29+
return builder.AddXunit(configure);
5530
}
5631
#endregion
5732

src/C3D/Extensions/Logging/Xunit/README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ logging
4444
});
4545
```
4646

47+
N.B. This will add the `output` object as a singleton to the `ILoggingBuilder` services.
48+
4749
### Direct injection
4850

4951
```c#
@@ -77,3 +79,13 @@ var loggerFactory = LoggerFactory.Create(builder => builder.AddXunit(output));
7779
The same mechanisms used in tests based on injecting `ITestOutputHelper` into the test class, can be used in fixtures.
7880
However, in fixtures you inject `IMessageSink`. The same extension methods are available for both interfaces.
7981

82+
### Utilities
83+
84+
As you may want to check the logged output for content in your tests (either for something existing or not existing), there are 2 utility classes provided.
85+
86+
- `LoggingMessageSink` which implements `IMessageSink`
87+
- `LoggingTestOutputHelper` which implements `ITestOutputHelper`
88+
89+
These both have a `ClearMessages` function (which may be required in a fixture to ensure output from one test does not affect another), and a `Messages` property.
90+
91+
Examples of how to use these can be found in the unit tests at [CZEMacLeod/C3D.Extensions.Logging](https://github.com/CZEMacLeod/C3D.Extensions.Logging/tree/main/test/C3D/Extensions/Logging/Xunit/Test).
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
using System.Collections.Immutable;
2+
using Xunit.Abstractions;
3+
4+
namespace C3D.Extensions.Logging.Xunit.Utilities;
5+
6+
public class LoggingMessageSink : IMessageSink
7+
{
8+
private readonly List<IMessageSinkMessage> messages = new();
9+
private readonly IMessageSink output;
10+
11+
public LoggingMessageSink(IMessageSink output) => this.output = output;
12+
13+
#if NET6_0_OR_GREATER
14+
public IReadOnlyList<IMessageSinkMessage> Messages => messages.ToImmutableList();
15+
#else
16+
public IReadOnlyList<IMessageSinkMessage> Messages => ImmutableList<IMessageSinkMessage>.Empty.AddRange(messages);
17+
#endif
18+
19+
public bool OnMessage(IMessageSinkMessage message)
20+
{
21+
messages.Add(message);
22+
return output.OnMessage(message);
23+
}
24+
25+
public void Clear() => messages.Clear();
26+
}
27+
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
using System.Collections.Immutable;
2+
using Xunit.Abstractions;
3+
4+
namespace C3D.Extensions.Logging.Xunit.Utilities;
5+
6+
public class LoggingTestOutputHelper : ITestOutputHelper
7+
{
8+
private readonly ITestOutputHelper output;
9+
private readonly List<string> messages = new();
10+
11+
#if NET6_0_OR_GREATER
12+
public IReadOnlyList<string> Messages => messages.ToImmutableList();
13+
#else
14+
public IReadOnlyList<string> Messages => ImmutableList<string>.Empty.AddRange(messages);
15+
#endif
16+
17+
public void ClearMessages() { messages.Clear(); }
18+
19+
public LoggingTestOutputHelper(ITestOutputHelper output) => this.output = output;
20+
21+
public void WriteLine(string message)
22+
{
23+
output.WriteLine(message);
24+
messages.Add(message);
25+
}
26+
27+
public void WriteLine(string format, params object[] args)
28+
{
29+
output.WriteLine(format, args);
30+
messages.Add(string.Format(format, args));
31+
}
32+
}

src/C3D/Extensions/Logging/Xunit/XunitLoggerOptions.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,11 @@ public enum XunitLoggerTimeStamp
1111

1212
public class XunitLoggerOptions
1313
{
14-
public XunitLoggerOptions(Func<DateTimeOffset>? getUtcNow = null)
14+
public XunitLoggerOptions() : this(() => DateTimeOffset.UtcNow) { }
15+
16+
public XunitLoggerOptions(Func<DateTimeOffset> getUtcNow)
1517
{
16-
GetUtcNow = getUtcNow ?? (() => DateTimeOffset.UtcNow);
18+
GetUtcNow = getUtcNow;
1719
LogStart = GetUtcNow();
1820
}
1921

src/C3D/Extensions/Logging/Xunit/XunitLoggerProvider.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ public sealed class XunitLoggerProvider : ILoggerProvider
2020

2121
public XunitLoggerProvider(IMessageSink output, IOptionsMonitor<XunitLoggerOptions> options)
2222
{
23-
output2= output;
23+
output2 = output;
2424
onChange = options.OnChange(SetOptions);
2525
SetOptions(options.CurrentValue);
2626
}
@@ -50,9 +50,9 @@ internal XunitLoggerProvider(ITestOutputHelper output, XunitLoggerOptions option
5050
internal XunitLoggerOptions GetOptions() => options;
5151

5252
public ILogger CreateLogger(string categoryName) =>
53-
_loggers.GetOrAdd(categoryName,
54-
name => output1 is null ?
55-
(output2 is null ? NullLogger.Instance :
53+
_loggers.GetOrAdd(categoryName,
54+
name => output1 is null ?
55+
(output2 is null ? NullLogger.Instance :
5656
new MessageSinkLogger(name, output2, GetOptions)) :
5757
new TextOutputLogger(name, output1, GetOptions));
5858

src/C3D/Extensions/Logging/Xunit/version.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/v3.3.37/src/NerdBank.GitVersioning/version.schema.json",
3-
"version": "0.1",
3+
"version": "0.2",
44
"publicReleaseRefSpec": [
55
"^refs/heads/main$", // we release out of main
66
"^refs/heads/rel/v\\d+\\.\\d+" // we also release tags starting with rel/N.N

test/C3D/Extensions/Logging/Xunit/Test/C3D.Extensions.Logging.Xunit.Test.csproj

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
4-
<TargetFrameworks>net6.0;net48</TargetFrameworks>
4+
<TargetFrameworks>net6.0;net48;net7.0</TargetFrameworks>
55
<ImplicitUsings>enable</ImplicitUsings>
66
<Nullable>enable</Nullable>
77
<LangVersion>10.0</LangVersion>
@@ -22,6 +22,8 @@
2222
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="6.0.0" />
2323

2424
<PackageReference Include="System.Collections.Immutable" Version="6.0.0" />
25+
26+
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="6.0.0" />
2527
</ItemGroup>
2628

2729
<ItemGroup>

0 commit comments

Comments
 (0)