Skip to content

Commit 9d03110

Browse files
Merge pull request #177 from Stravaig-Projects/#171/get-logs-with-predicate
#171/get logs with predicate
2 parents 0b1316b + 160a7c3 commit 9d03110

File tree

9 files changed

+129
-19
lines changed

9 files changed

+129
-19
lines changed

docs/docs/library/test-capture-logger-provider.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,33 @@ var allLogs = logProvider.GetAllLogEntries();
7878

7979
The result of the method can be passed into `RenderLogs()` extension method. See [Renderer](log-entry-renderer-extensions.md)
8080

81+
---
82+
83+
## GetLogs(predicate)
84+
85+
The `TestCaptureLoggerProvider.GetLogs(predicate)` allows you to retrieve specific logs within your test method that match the predicate. The log entries will be in sequence, timestamps will be incremental, however adjacent log entries created sufficiently close to one another may contain the same timestamp due to the resolution of the clock.
86+
87+
#### Returns
88+
89+
`IReadOnlyList<LogEntry>`: A read only list of log entries. See [LogEntry](log-entry.md)
90+
91+
##### Example
92+
93+
This example checks that a specific log entry was generated.
94+
95+
```csharp
96+
// Arrange
97+
var logProvider = new TestCaptureLoggerProvider();
98+
99+
// Act: Do something using the log provider.
100+
101+
// Assert
102+
var logs = logProvider.GetLogs(
103+
static le => le.LogLevel == LogLevel.Warning &&
104+
le.OriginalMessage == "A thing happened.");
105+
logs.Count.ShouldBe(1);
106+
```
107+
81108
---
82109
## GetAllLogEntriesWithExceptions()
83110

docs/docs/library/test-capture-logger.md

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ This class implements
1313

1414
## GetLogs()
1515

16-
The `TestCaptureLogger.GetLogs()` allows you to retrieve the logs within your test method. The property will be in sequence, timestamps will be incremental, however adjacent log entries created sufficiently close to one another may contain the same timestamp due to the resolution of the clock.
16+
The `TestCaptureLogger.GetLogs()` allows you to retrieve the logs within your test method. The log entries will be in sequence, timestamps will be incremental, however adjacent log entries created sufficiently close to one another may contain the same timestamp due to the resolution of the clock.
1717

1818
#### Returns
1919

@@ -23,6 +23,33 @@ The `TestCaptureLogger.GetLogs()` allows you to retrieve the logs within your te
2323

2424
The result of the method can be passed into `RenderLogs()` extension method. See [Renderer](log-entry-renderer-extensions.md)
2525

26+
---
27+
## GetLogs(predicate)
28+
29+
The `TestCaptureLogger.GetLogs(predicate)` allows you to retrieve specific logs within your test method that match the predicate. The log entries will be in sequence, timestamps will be incremental, however adjacent log entries created sufficiently close to one another may contain the same timestamp due to the resolution of the clock.
30+
31+
#### Returns
32+
33+
`IReadOnlyList<LogEntry>`: A read only list of log entries. See [LogEntry](log-entry.md)
34+
35+
##### Example
36+
37+
This example checks that a specific log entry was generated.
38+
39+
```csharp
40+
// Arrange
41+
var logger = new TestCaptureLogger();
42+
43+
// Act: Do something using the logger
44+
45+
// Assert
46+
var logs = logger.GetLogs(
47+
static le => le.LogLevel == LogLevel.Warning &&
48+
le.OriginalMessage == "A thing happened.");
49+
logs.Count.ShouldBe(1);
50+
```
51+
52+
2653

2754
---
2855
## GetLogEntriesWithExceptions()

release-notes/wip-release-notes.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ Date: ???
1919
- #170: Additional xunit extension methods to write out all log messages to the `ITestOutputHelper`.
2020
- `ITestOutputHelper.WriteLogs(ITestCaptureLogger...)`
2121
- `ITestOutputHelper.WriteLogs(TestCaptureLoggerProvider...)`
22+
- #171: Add `GetLogs(predicate)` to `TestCaptureLogger` and `TestCaptureLoggerProvider`.
2223

2324
### Miscellaneous
2425

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

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,21 @@ namespace Stravaig.Extensions.Logging.Diagnostics.Tests;
88
[TestFixture]
99
public class TestCaptureLoggerOfTTests
1010
{
11+
[Test]
12+
public void ConstructorWithNullLoggerThrows()
13+
{
14+
Should.Throw<ArgumentNullException>(() => new TestCaptureLogger<object>(null!))
15+
.ParamName.ShouldBe("logger");
16+
}
17+
18+
[Test]
19+
public void ConstructorCategoryMismatchThrows()
20+
{
21+
var underlyingLogger = new TestCaptureLogger("NotTheRightCategory");
22+
Should.Throw<InvalidOperationException>(() => new TestCaptureLogger<object>(underlyingLogger))
23+
.Message.ShouldBe("The category name does not match the type of this logger. Expected \"object\", got \"NotTheRightCategory\".");
24+
}
25+
1126
[Test]
1227
public void CategoryNameIsBasedOnType()
1328
{
@@ -52,4 +67,20 @@ public void GetLogsWithExceptionsWillFilterOutNonExceptionLogs()
5267
logger.GetLogEntriesWithExceptions().Count.ShouldBe(2);
5368
logger.GetLogs().Count.ShouldBe(4);
5469
}
70+
71+
[Test]
72+
public void GetLogsMatchingPredicateWillFilterOutUnwantedLogs()
73+
{
74+
var logger = new TestCaptureLogger<TestCaptureLoggerOfTTests>();
75+
logger.LogInformation("Hello");
76+
logger.LogWarning("Hello, {Location}!", "World");
77+
logger.LogInformation("This is a log.");
78+
logger.LogError(new Exception(), "This has an exception.");
79+
80+
logger.GetLogs(static l => l.LogLevel == LogLevel.Information).Count.ShouldBe(2);
81+
82+
logger.GetLogs(static l => l.PropertyDictionary.ContainsKey("Location") && (string)l.PropertyDictionary["Location"] == "World")
83+
.Count.ShouldBe(1);
84+
logger.GetLogs().Count.ShouldBe(4);
85+
}
5586
}

src/Stravaig.Extensions.Logging.Diagnostics.XUnit/OutputTestHelperExtensions.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
using System;
22
using System.Collections.Generic;
3-
using Microsoft.Extensions.Logging;
43
using Stravaig.Extensions.Logging.Diagnostics.Render;
54
using Xunit.Abstractions;
65

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using System;
12
using System.Collections.Generic;
23
using Microsoft.Extensions.Logging;
34

@@ -20,6 +21,13 @@ public interface ITestCaptureLogger : ILogger
2021
/// called won't be available in the list, and it will have to be called again.</remarks>
2122
IReadOnlyList<LogEntry> GetLogs();
2223

24+
/// <summary>
25+
/// Gets a read-only list of logs that is a snapshot of this logger filtered by the predicate.
26+
/// </summary>
27+
/// <remarks>Any additional logs added to the logger after this is
28+
/// called won't be available in the list, and it will have to be called again.</remarks>
29+
IReadOnlyList<LogEntry> GetLogs(Func<LogEntry, bool> predicate);
30+
2331
/// <summary>
2432
/// Gets a read-only list of logs that have an exception attached in sequential order.
2533
/// </summary>

src/Stravaig.Extensions.Logging.Diagnostics/TestCaptureLogger(OfTCategoryType).cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ public TestCaptureLogger()
2828
/// </summary>
2929
public TestCaptureLogger(TestCaptureLogger logger)
3030
{
31+
ArgumentNullException.ThrowIfNull(logger, nameof(logger));
3132
var expectedCategoryName = TypeNameHelper.GetTypeDisplayName(typeof(TCategoryType));
3233
if (logger.CategoryName != expectedCategoryName)
3334
throw new InvalidOperationException(
@@ -66,6 +67,10 @@ public void Reset()
6667
public IReadOnlyList<LogEntry> GetLogs()
6768
=> _logger.GetLogs();
6869

70+
/// <inheritdoc />
71+
public IReadOnlyList<LogEntry> GetLogs(Func<LogEntry, bool> predicate)
72+
=> _logger.GetLogs(predicate);
73+
6974
/// <summary>
7075
/// Gets a read-only list of logs that have an exception attached in sequential order.
7176
/// </summary>

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

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -49,21 +49,22 @@ public IReadOnlyList<LogEntry> GetLogs()
4949
}
5050
}
5151

52-
5352
/// <inheritdoc />
54-
public IReadOnlyList<LogEntry> GetLogEntriesWithExceptions()
53+
public IReadOnlyList<LogEntry> GetLogs(Func<LogEntry, bool> predicate)
5554
{
56-
List<LogEntry> result;
5755
lock (_syncRoot)
5856
{
59-
result = _logs
60-
.Where(l => l.Exception != null)
61-
.ToList();
57+
return _logs
58+
.Where(predicate)
59+
.OrderBy(static log => log)
60+
.ToArray();
6261
}
63-
result.Sort();
64-
return result;
6562
}
6663

64+
/// <inheritdoc />
65+
public IReadOnlyList<LogEntry> GetLogEntriesWithExceptions()
66+
=> GetLogs(log => log.Exception != null);
67+
6768
/// <summary>
6869
/// Writes a log entry
6970
/// </summary>

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

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ ILogger ILoggerProvider.CreateLogger(string categoryName)
6666
/// <param name="categoryName">The category name for messages produced by the logger.</param>
6767
/// <returns>The instance of the <see cref="TestCaptureLogger"/> that was created.</returns>
6868
public TestCaptureLogger CreateLogger(string categoryName)
69-
=> _captures.GetOrAdd(categoryName, static (cn) => new TestCaptureLogger(cn));
69+
=> _captures.GetOrAdd(categoryName, static cn => new TestCaptureLogger(cn));
7070

7171
/// <summary>
7272
/// Creates a new <see cref="T:TestCaptureLogger"/> instance.
@@ -76,7 +76,7 @@ public TestCaptureLogger CreateLogger(string categoryName)
7676
public TestCaptureLogger<T> CreateLogger<T>()
7777
=> (TestCaptureLogger<T>)_typedCaptures.GetOrAdd(typeof(T), type =>
7878
{
79-
var categoryName = GetTypeDisplayName(typeof(T));
79+
var categoryName = GetTypeDisplayName(type);
8080
var underlyingLogger = CreateLogger(categoryName);
8181
return new TestCaptureLogger<T>(underlyingLogger);
8282
});
@@ -97,24 +97,33 @@ public IReadOnlyList<string> GetCategories()
9797
public IReadOnlyList<LogEntry> GetAllLogEntries()
9898
{
9999
var loggers = _captures.Values;
100-
var allLogs = loggers.SelectMany(l => l.GetLogs()).ToList();
100+
var allLogs = loggers.SelectMany(static l => l.GetLogs()).ToList();
101101
allLogs.Sort();
102102
return allLogs;
103103
}
104104

105105
/// <summary>
106-
/// Gets all log entries with exceptions attached regardless of the
107-
/// Category they were logged as.
106+
/// Gets all log entries matching the predicate regardless of the Category
107+
/// they were logged as.
108108
/// </summary>
109-
/// <returns>A read only list of <see cref="LogEntry"/></returns>
110-
public IReadOnlyList<LogEntry> GetAllLogEntriesWithExceptions()
109+
/// <returns>A read only list of <see cref="LogEntry"/> objects.</returns>
110+
public IReadOnlyList<LogEntry> GetLogs(Func<LogEntry, bool> predicate)
111111
{
112112
var loggers = _captures.Values;
113-
var allLogs = loggers.SelectMany(l => l.GetLogEntriesWithExceptions()).ToList();
113+
var allLogs = loggers.SelectMany(l => l.GetLogs(predicate)).ToList();
114114
allLogs.Sort();
115115
return allLogs;
116116
}
117117

118+
119+
/// <summary>
120+
/// Gets all log entries with exceptions attached regardless of the
121+
/// Category they were logged as.
122+
/// </summary>
123+
/// <returns>A read only list of <see cref="LogEntry"/> objects.</returns>
124+
public IReadOnlyList<LogEntry> GetAllLogEntriesWithExceptions()
125+
=> GetLogs(static log => log.Exception != null);
126+
118127
/// <summary>
119128
/// Resets the captures to an empty state.
120129
/// </summary>
@@ -131,6 +140,8 @@ public void Reset()
131140
/// </summary>
132141
public void Dispose()
133142
{
134-
Reset();
143+
_captures.Clear();
144+
_typedCaptures.Clear();
145+
GC.SuppressFinalize(this);
135146
}
136147
}

0 commit comments

Comments
 (0)