Skip to content

Commit 7a3b42a

Browse files
Add TestCaptureLogger.CreateLogger<T>
1 parent fc107b3 commit 7a3b42a

File tree

7 files changed

+207
-73
lines changed

7 files changed

+207
-73
lines changed

release-notes/wip-release-notes.md

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,18 @@
44

55
Date: ???
66

7+
### Breaking Changes
8+
9+
- Feature #123 changes the `TestCaptureLogger<T>` class to encapsulate an instance of `TestCaptureLogger` rather than inherit from it. If you're code relied on `TestCaptureLogger<T>` inheriting from `TestCaptureLogger` then it will likely break.
10+
711
### Bugs
812

913
### Features
1014

15+
- #123: `TestCaptureLoggerProvider.CreateLogger<T>()`
16+
- Potential breaking change: `TestCaptureLogger<T>` no longer inherits from `TestCaptureLogger`.
17+
- Add `ITestCaptureLogger` and have `TestCaptureLogger` and `TestCaptureLogger<T>` be concrete implementations of the interface so you can reference the interface and not care which concrete implementation you have.
18+
1119
### Miscellaneous
1220

1321
- #164: Update pipeline.
@@ -17,6 +25,3 @@ Date: ???
1725
- #166 Update package references:
1826
- .NET 8.0 targets:
1927
- Bump Microsoft.Extensions.Logging.Abstractions to 8.0.1
20-
21-
22-

src/Stravaig.Extensions.Logging.Diagnostics.Tests/TestCaptureLoggerProviderTests.cs

Lines changed: 70 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,19 @@ namespace Stravaig.Extensions.Logging.Diagnostics.Tests
99
public class TestCaptureLoggerProviderTests
1010
{
1111
[Test]
12-
public void GetAllLogEntriesReturnsLogsInCorrectSequence()
12+
[TestCase(false)]
13+
[TestCase(true)]
14+
public void GetAllLogEntriesReturnsLogsInCorrectSequence(bool useTyped)
1315
{
1416
var provider = new TestCaptureLoggerProvider();
1517

16-
var logger1 = provider.CreateLogger("logger1");
17-
var logger2 = provider.CreateLogger("logger2");
18-
18+
ITestCaptureLogger logger1 = useTyped
19+
? provider.CreateLogger<Logger1>()
20+
: provider.CreateLogger("logger1");
21+
ITestCaptureLogger logger2 = useTyped
22+
? provider.CreateLogger<Logger2>()
23+
: provider.CreateLogger("logger2");
24+
1925
logger1.LogInformation("One");
2026
logger2.LogInformation("Two");
2127
logger1.LogInformation("Three");
@@ -29,15 +35,21 @@ public void GetAllLogEntriesReturnsLogsInCorrectSequence()
2935
allLogs[3].OriginalMessage.ShouldBe("Four");
3036
allLogs[4].OriginalMessage.ShouldBe("Five");
3137
}
32-
38+
3339
[Test]
34-
public void GetAllLogEntriesWithExceptionsReturnsLogsInCorrectSequence()
40+
[TestCase(false)]
41+
[TestCase(true)]
42+
public void GetAllLogEntriesWithExceptionsReturnsLogsInCorrectSequence(bool useTyped)
3543
{
3644
var provider = new TestCaptureLoggerProvider();
3745

38-
var logger1 = provider.CreateLogger("logger1");
39-
var logger2 = provider.CreateLogger("logger2");
40-
46+
ITestCaptureLogger logger1 = useTyped
47+
? provider.CreateLogger<Logger1>()
48+
: provider.CreateLogger("logger1");
49+
ITestCaptureLogger logger2 = useTyped
50+
? provider.CreateLogger<Logger2>()
51+
: provider.CreateLogger("logger2");
52+
4153
logger1.LogInformation(new Exception(), "One");
4254
logger2.LogInformation("Two");
4355
logger1.LogInformation("Three");
@@ -49,7 +61,7 @@ public void GetAllLogEntriesWithExceptionsReturnsLogsInCorrectSequence()
4961
allLogsWithExceptions[1].OriginalMessage.ShouldBe("Four");
5062
allLogsWithExceptions[2].OriginalMessage.ShouldBe("Five");
5163
}
52-
64+
5365
[Test]
5466
public void GetCategoriesReturnsListOfCategoryNames()
5567
{
@@ -60,64 +72,91 @@ public void GetCategoriesReturnsListOfCategoryNames()
6072
provider.CreateLogger("logger1");
6173
provider.CreateLogger("logger2");
6274
factory.CreateLogger<TestCaptureLoggerTests>();
63-
75+
6476
var categories = provider.GetCategories();
6577
categories.Count.ShouldBe(3);
6678
categories.ShouldContain("logger1");
6779
categories.ShouldContain("logger2");
6880
categories.ShouldContain(typeof(TestCaptureLoggerTests).FullName);
6981
}
70-
82+
7183
[Test]
72-
public void ResetAfterLoggingReturnsZeroLogs()
84+
[TestCase(false)]
85+
[TestCase(true)]
86+
public void ResetAfterLoggingReturnsZeroLogs(bool useTyped)
7387
{
7488
var provider = new TestCaptureLoggerProvider();
7589

76-
var logger1 = provider.CreateLogger("logger1");
77-
var logger2 = provider.CreateLogger("logger2");
78-
90+
ITestCaptureLogger logger1 = useTyped
91+
? provider.CreateLogger<Logger1>()
92+
: provider.CreateLogger("logger1");
93+
ITestCaptureLogger logger2 = useTyped
94+
? provider.CreateLogger<Logger2>()
95+
: provider.CreateLogger("logger2");
96+
7997
logger1.LogInformation("One");
8098
logger2.LogInformation("Two");
8199
logger1.LogInformation("Three");
82100
logger1.LogInformation("Four");
83101
logger2.LogInformation("Five");
84102

85103
provider.Reset();
86-
104+
87105
provider.GetAllLogEntries().ShouldBeEmpty();
88106
}
89-
107+
90108
[Test]
91-
public void ReuseAfterResetReturnsOnlyNewLogs()
109+
[TestCase(false)]
110+
[TestCase(true)]
111+
public void ReuseAfterResetReturnsOnlyNewLogs(bool useTyped)
92112
{
93113
var provider = new TestCaptureLoggerProvider();
94114

95-
var logger1 = provider.CreateLogger("logger1");
96-
var logger2 = provider.CreateLogger("logger2");
97-
115+
ITestCaptureLogger logger1 = useTyped
116+
? provider.CreateLogger<Logger1>()
117+
: provider.CreateLogger("Logger1");
118+
ITestCaptureLogger logger2 = useTyped
119+
? provider.CreateLogger<Logger2>()
120+
: provider.CreateLogger("Logger2");
121+
98122
logger1.LogInformation("Old-One");
99123
logger2.LogInformation("Old-Two");
100124

101125
provider.Reset();
102126

103-
var loggerNew1 = provider.CreateLogger("logger1");
104-
var loggerNew2 = provider.CreateLogger("logger2");
105-
var logger3 = provider.CreateLogger("logger3");
106-
127+
ITestCaptureLogger loggerNew1 = useTyped
128+
? provider.CreateLogger<Logger1>()
129+
: provider.CreateLogger("Logger1");
130+
ITestCaptureLogger loggerNew2 = useTyped
131+
? provider.CreateLogger<Logger2>()
132+
: provider.CreateLogger("Logger2");
133+
ITestCaptureLogger logger3 = useTyped
134+
? provider.CreateLogger<Logger3>()
135+
: provider.CreateLogger("Logger3");
136+
107137
loggerNew1.LogInformation("One");
108138
loggerNew2.LogInformation("Two");
109139
logger3.LogInformation("Three");
110-
140+
111141
var allLogs = provider.GetAllLogEntries();
112142
allLogs[0].OriginalMessage.ShouldBe("One");
113-
allLogs[0].CategoryName.ShouldBe("logger1");
143+
allLogs[0].CategoryName.ShouldContain("Logger1");
114144
allLogs[1].OriginalMessage.ShouldBe("Two");
115-
allLogs[1].CategoryName.ShouldBe("logger2");
145+
allLogs[1].CategoryName.ShouldContain("Logger2");
116146
allLogs[2].OriginalMessage.ShouldBe("Three");
117-
allLogs[2].CategoryName.ShouldBe("logger3");
147+
allLogs[2].CategoryName.ShouldContain("Logger3");
118148

119149
logger1.ShouldBeSameAs(loggerNew1);
120150
logger2.ShouldBeSameAs(loggerNew2);
121151
}
152+
153+
// Classes exist for to attach a logger.
154+
// ReSharper disable ClassNeverInstantiated.Local
155+
private class Logger1;
156+
157+
private class Logger2;
158+
159+
private class Logger3;
160+
// ReSharper restore ClassNeverInstantiated.Local
122161
}
123-
}
162+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
using System.Collections.Generic;
2+
using Microsoft.Extensions.Logging;
3+
4+
namespace Stravaig.Extensions.Logging.Diagnostics;
5+
6+
/// <summary>
7+
///
8+
/// </summary>
9+
public interface ITestCaptureLogger : ILogger
10+
{
11+
/// <summary>
12+
/// The name of the category the log entry belongs to.
13+
/// </summary>
14+
string CategoryName { get; }
15+
16+
/// <summary>
17+
/// Gets a read-only list of logs that is a snapshot of this logger.
18+
/// </summary>
19+
/// <remarks>Any additional logs added to the logger after this is
20+
/// called won't be available in the list, and it will have to be called again.</remarks>
21+
IReadOnlyList<LogEntry> GetLogs();
22+
23+
/// <summary>
24+
/// Gets a read-only list of logs that have an exception attached in sequential order.
25+
/// </summary>
26+
IReadOnlyList<LogEntry> GetLogEntriesWithExceptions();
27+
28+
/// <summary>
29+
/// Resets the logger by discarding the captured logs.
30+
/// </summary>
31+
void Reset();
32+
}
Lines changed: 58 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
using System;
2+
using System.Collections.Generic;
13
using Microsoft.Extensions.Logging;
24
using Stravaig.Extensions.Logging.Diagnostics.ExternalHelpers;
35

@@ -8,14 +10,66 @@ namespace Stravaig.Extensions.Logging.Diagnostics;
810
/// programatically, such as in unit tests.
911
/// </summary>
1012
/// <typeparam name="TCategoryType"></typeparam>
11-
public class TestCaptureLogger<TCategoryType>
12-
: TestCaptureLogger, ILogger<TCategoryType>
13+
public class TestCaptureLogger<TCategoryType> : ITestCaptureLogger, ILogger<TCategoryType>
1314
{
15+
private readonly TestCaptureLogger _logger;
16+
1417
/// <summary>
1518
/// Initialises a new instance of the <see cref="T:Stravaig.Extensions.Logging.Diagnostics.TestCaptureLogger&lt;TCategoryType>"/> class.
1619
/// </summary>
1720
public TestCaptureLogger()
18-
: base(TypeNameHelper.GetTypeDisplayName(typeof(TCategoryType)))
1921
{
22+
_logger = new TestCaptureLogger(TypeNameHelper.GetTypeDisplayName(typeof(TCategoryType)));
23+
}
24+
25+
/// <summary>
26+
/// Initialises a new instance of the <see cref="T:Stravaig.Extensions.Logging.Diagnostics.TestCaptureLogger&lt;TCategoryType>"/>
27+
/// class, using an existing logger as the underlying logger.
28+
/// </summary>
29+
public TestCaptureLogger(TestCaptureLogger logger)
30+
{
31+
var expectedCategoryName = TypeNameHelper.GetTypeDisplayName(typeof(TCategoryType));
32+
if (logger.CategoryName != expectedCategoryName)
33+
throw new InvalidOperationException(
34+
$"The category name does not match the type of this logger. Expected \"{expectedCategoryName}\", got \"{logger.CategoryName}\".");
35+
_logger = logger;
2036
}
21-
}
37+
38+
/// <inheritdoc />
39+
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter)
40+
=> _logger.Log(logLevel, eventId, state, exception, formatter);
41+
42+
/// <inheritdoc />
43+
public bool IsEnabled(LogLevel logLevel)
44+
=> _logger.IsEnabled(logLevel);
45+
46+
/// <inheritdoc />
47+
public IDisposable? BeginScope<TState>(TState state) where TState : notnull
48+
=> _logger.BeginScope(state);
49+
50+
/// <summary>
51+
/// Resets the logger by discarding the captured logs.
52+
/// </summary>
53+
public void Reset()
54+
=> _logger.Reset();
55+
56+
/// <summary>
57+
/// Gets a read-only list of logs that is a snapshot of this logger.
58+
/// </summary>
59+
/// <remarks>Any additional logs added to the logger after this is
60+
/// called won't be available in the list, and it will have to be called again.</remarks>
61+
public IReadOnlyList<LogEntry> GetLogs()
62+
=> _logger.GetLogs();
63+
64+
/// <summary>
65+
/// Gets a read-only list of logs that have an exception attached in sequential order.
66+
/// </summary>
67+
public IReadOnlyList<LogEntry> GetLogEntriesWithExceptions()
68+
=> _logger.GetLogEntriesWithExceptions();
69+
70+
/// <summary>
71+
/// The name of the category the log entry belongs to.
72+
/// </summary>
73+
public string CategoryName
74+
=> _logger.CategoryName;
75+
}

src/Stravaig.Extensions.Logging.Diagnostics/TestCaptureLogger.cs

Lines changed: 12 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ namespace Stravaig.Extensions.Logging.Diagnostics;
99
/// A logger that writes messages to a store that can later be examined
1010
/// programatically, such as in unit tests.
1111
/// </summary>
12-
public class TestCaptureLogger : ILogger
12+
public class TestCaptureLogger : ITestCaptureLogger
1313
{
1414
private readonly List<LogEntry> _logs;
1515
private readonly object _syncRoot;
@@ -34,16 +34,12 @@ public TestCaptureLogger(string categoryName)
3434
CategoryName = categoryName;
3535
}
3636

37-
/// <summary>
38-
/// The name of the category the log entry belongs to.
39-
/// </summary>
37+
38+
/// <inheritdoc />
4039
public string CategoryName { get; }
41-
42-
/// <summary>
43-
/// Gets a read-only list of logs that is a snapshot of this logger.
44-
/// </summary>
45-
/// <remarks>Any additional logs added to the logger after this is
46-
/// called won't be available in the list and it will have to be called again.</remarks>
40+
41+
42+
/// <inheritdoc />
4743
public IReadOnlyList<LogEntry> GetLogs()
4844
{
4945
lock (_syncRoot)
@@ -52,10 +48,9 @@ public IReadOnlyList<LogEntry> GetLogs()
5248
return _logs.ToArray();
5349
}
5450
}
55-
56-
/// <summary>
57-
/// Gets a read-only list of logs that have an exception attached in sequential order.
58-
/// </summary>
51+
52+
53+
/// <inheritdoc />
5954
public IReadOnlyList<LogEntry> GetLogEntriesWithExceptions()
6055
{
6156
List<LogEntry> result;
@@ -124,22 +119,20 @@ private LogEntry CreateLogEntry<TState>(LogLevel logLevel, EventId eventId, TSta
124119
public IDisposable BeginScope<TState>(TState state)
125120
=> DoNothing.Instance;
126121
#endif
127-
122+
128123
private class DoNothing : IDisposable
129124
{
130125
public static readonly DoNothing Instance = new ();
131126
public void Dispose()
132127
{ }
133128
}
134129

135-
/// <summary>
136-
/// Resets the logger by discarding the captured logs.
137-
/// </summary>
130+
/// <inheritdoc />
138131
public void Reset()
139132
{
140133
lock (_syncRoot)
141134
{
142135
_logs.Clear();
143136
}
144137
}
145-
}
138+
}

0 commit comments

Comments
 (0)