Repository: martincostello/xunit-logging
Pull request #912: Add TimeProvider
Author: login: fiotti
Labels: name: enhancement
description: New feature or request
Created: 2025-05-07T06:54:53Z | Merged: 2025-05-21T08:08:50Z

Implements support for the [`TimeProvider`](https://learn.microsoft.com/dotnet/standard/datetime/timeprovider-overview) (closes https://github.com/martincostello/xunit-logging/issues/911).


Diff:
diff --git a/src/Logging.XUnit.v3/PublicAPI/net8.0/PublicAPI.Shipped.txt b/src/Logging.XUnit.v3/PublicAPI/net8.0/PublicAPI.Shipped.txt
new file mode 100644
index 00000000..7dc5c581
--- /dev/null
+++ b/src/Logging.XUnit.v3/PublicAPI/net8.0/PublicAPI.Shipped.txt
@@ -0,0 +1 @@
+#nullable enable
diff --git a/src/Logging.XUnit.v3/PublicAPI/net8.0/PublicAPI.Unshipped.txt b/src/Logging.XUnit.v3/PublicAPI/net8.0/PublicAPI.Unshipped.txt
new file mode 100644
index 00000000..3c8006fb
--- /dev/null
+++ b/src/Logging.XUnit.v3/PublicAPI/net8.0/PublicAPI.Unshipped.txt
@@ -0,0 +1,3 @@
+#nullable enable
+MartinCostello.Logging.XUnit.XUnitLoggerOptions.TimeProvider.get -> System.TimeProvider!
+MartinCostello.Logging.XUnit.XUnitLoggerOptions.TimeProvider.set -> void
diff --git a/src/Logging.XUnit/PublicAPI/net8.0/PublicAPI.Shipped.txt b/src/Logging.XUnit/PublicAPI/net8.0/PublicAPI.Shipped.txt
new file mode 100644
index 00000000..7dc5c581
--- /dev/null
+++ b/src/Logging.XUnit/PublicAPI/net8.0/PublicAPI.Shipped.txt
@@ -0,0 +1 @@
+#nullable enable
diff --git a/src/Logging.XUnit/PublicAPI/net8.0/PublicAPI.Unshipped.txt b/src/Logging.XUnit/PublicAPI/net8.0/PublicAPI.Unshipped.txt
new file mode 100644
index 00000000..3c8006fb
--- /dev/null
+++ b/src/Logging.XUnit/PublicAPI/net8.0/PublicAPI.Unshipped.txt
@@ -0,0 +1,3 @@
+#nullable enable
+MartinCostello.Logging.XUnit.XUnitLoggerOptions.TimeProvider.get -> System.TimeProvider!
+MartinCostello.Logging.XUnit.XUnitLoggerOptions.TimeProvider.set -> void
diff --git a/src/Shared/XUnitLogger.cs b/src/Shared/XUnitLogger.cs
index 90c6df8b..e877a833 100644
--- a/src/Shared/XUnitLogger.cs
+++ b/src/Shared/XUnitLogger.cs
@@ -39,6 +39,15 @@ public partial class XUnitLogger : ILogger
     /// </summary>
     private readonly string _timestampFormat;
 
+#if NET8_0_OR_GREATER
+
+    /// <summary>
+    /// The time provider used in log messages.
+    /// </summary>
+    private readonly TimeProvider _timeProvider;
+
+#endif
+
     /// <summary>
     /// Gets or sets the filter to use.
     /// </summary>
@@ -57,6 +66,12 @@ private XUnitLogger(string name, XUnitLoggerOptions? options)
         _messageSinkMessageFactory = options?.MessageSinkMessageFactory ?? (static (message) => new DiagnosticMessage(message));
         _timestampFormat = options?.TimestampFormat ?? "u";
         IncludeScopes = options?.IncludeScopes ?? false;
+#if NET8_0_OR_GREATER
+        _timeProvider = options?.TimeProvider ?? TimeProvider.System;
+        Clock = () => _timeProvider.GetLocalNow();
+#else
+        Clock = static () => DateTimeOffset.Now;
+#endif
     }
 
     /// <summary>
@@ -84,7 +99,7 @@ private XUnitLogger(string name, XUnitLoggerOptions? options)
     /// <summary>
     /// Gets or sets a delegate representing the system clock.
     /// </summary>
-    internal Func<DateTimeOffset> Clock { get; set; } = static () => DateTimeOffset.Now;
+    internal Func<DateTimeOffset> Clock { get; set; }
 
     /// <inheritdoc />
     public IDisposable? BeginScope<TState>(TState state)
diff --git a/src/Shared/XUnitLoggerOptions.cs b/src/Shared/XUnitLoggerOptions.cs
index db397713..1282cbd6 100644
--- a/src/Shared/XUnitLoggerOptions.cs
+++ b/src/Shared/XUnitLoggerOptions.cs
@@ -38,4 +38,13 @@ public XUnitLoggerOptions()
     /// </summary>
     [StringSyntax(StringSyntaxAttribute.DateTimeFormat)]
     public string? TimestampFormat { get; set; }
+
+#if NET8_0_OR_GREATER
+
+    /// <summary>
+    /// Gets or sets the time provider used in log messages. Defaults to <see cref="TimeProvider.System"/>.
+    /// </summary>
+    public TimeProvider TimeProvider { get; set; } = TimeProvider.System;
+
+#endif
 }
diff --git a/tests/Shared/XUnitLoggerTests.cs b/tests/Shared/XUnitLoggerTests.cs
index 165232ec..1efde1e4 100644
--- a/tests/Shared/XUnitLoggerTests.cs
+++ b/tests/Shared/XUnitLoggerTests.cs
@@ -659,6 +659,41 @@ public static void XUnitLogger_Log_Logs_Message_If_Scopes_Included_And_There_Is_
         outputHelper.Received(1).WriteLine(expected);
     }
 
+#if NET8_0_OR_GREATER
+
+    [Fact]
+    public static void XUnitLogger_Log_Logs_Message_With_TimeProvider()
+    {
+        // Arrange
+        var outputHelper = Substitute.For<ITestOutputHelper>();
+        string name = "MyName";
+
+        var timeProvider = Substitute.For<TimeProvider>();
+        timeProvider.LocalTimeZone.Returns(TimeZoneInfo.FindSystemTimeZoneById("Europe/London"));
+        timeProvider.GetUtcNow().Returns(new DateTimeOffset(2019, 05, 12, 13, 47, 41, 123, 456, TimeSpan.Zero));
+
+        var options = new XUnitLoggerOptions()
+        {
+            Filter = FilterTrue,
+            TimeProvider = timeProvider,
+        };
+
+        var logger = new XUnitLogger(name, outputHelper, options);
+
+        string expected = string.Join(
+            Environment.NewLine,
+            "[2019-05-12 13:47:41Z] info: MyName[85]",
+            "      Message|True|False");
+
+        // Act
+        logger.Log(LogLevel.Information, new EventId(85), "Martin", null, Formatter);
+
+        // Assert
+        outputHelper.Received(1).WriteLine(expected);
+    }
+
+#endif
+
     private static DateTimeOffset StaticClock() => new(2018, 08, 19, 17, 12, 16, TimeSpan.FromHours(1));
 
     private static DiagnosticMessage DiagnosticMessageFactory(string message) => new(message);


Reviews:
user: login: martincostello
body: Thanks for the PR!

Could you add a test [like this one](https://github.com/martincostello/xunit-logging/blob/6553ea3dc37659d3adeca5d61fde28c0ab3f5bf7/tests/Shared/XUnitLoggerTests.cs#L270-L299) that validates the use of the user-specified `TimeProvider`?

Let me know what difficulties you're having building things, and I can see if there's anything I need to fix, otherwise I should be able to give some pointers.

state: COMMENTED
author_association: OWNER
submitted_at: 2025-05-07T08:45:39Z
user: login: martincostello
state: COMMENTED
author_association: OWNER
submitted_at: 2025-05-16T13:59:19Z
user: login: fiotti
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-19T11:43:12Z
user: login: fiotti
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-19T11:43:44Z
user: login: martincostello
state: COMMENTED
author_association: OWNER
submitted_at: 2025-05-19T11:48:18Z
user: login: fiotti
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-19T11:50:58Z
user: login: martincostello
state: COMMENTED
author_association: OWNER
submitted_at: 2025-05-19T13:02:11Z
user: login: fiotti
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-20T08:06:10Z
user: login: fiotti
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-20T08:10:27Z
user: login: martincostello
state: COMMENTED
author_association: OWNER
submitted_at: 2025-05-20T09:55:30Z
user: login: fiotti
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-21T07:43:37Z
user: login: martincostello
state: COMMENTED
author_association: OWNER
submitted_at: 2025-05-21T07:50:26Z
user: login: martincostello
state: APPROVED
author_association: OWNER
submitted_at: 2025-05-21T08:07:10Z

Review comments:
diff_hunk: @@ -84,7 +97,14 @@ private XUnitLogger(string name, XUnitLoggerOptions? options)
     /// <summary>
     /// Gets or sets a delegate representing the system clock.
     /// </summary>
-    internal Func<DateTimeOffset> Clock { get; set; } = static () => DateTimeOffset.Now;
+    internal Func<DateTimeOffset> Clock { get; set; } = static () =>
path: src/Shared/XUnitLogger.cs
user: login: martincostello
body: I'm fairly sure this won't compile in the .NET 8 case as the `static` will prevent `_timeProvider` from being accessible.
created_at: 2025-05-07T08:44:50Z
author_association: OWNER
path: src/Logging.XUnit.v3/PublicAPI/net8.0/PublicAPI.Shipped.txt
user: login: martincostello
body: These entries should go in the `src/Logging.XUnit.v3/PublicAPI/PublicAPI.Unshipped.txt` file and this file should be deleted.
created_at: 2025-05-16T13:49:48Z
author_association: OWNER
path: src/Logging.XUnit.v3/PublicAPI/net8.0/PublicAPI.Unshipped.txt
user: login: martincostello
body: Remove this file.
created_at: 2025-05-16T13:50:06Z
author_association: OWNER
path: src/Logging.XUnit/PublicAPI/net8.0/PublicAPI.Shipped.txt
user: login: martincostello
body: These entries should go in the `src/Logging.XUnit/PublicAPI/PublicAPI.Shipped.txt` file and this file should be deleted.
created_at: 2025-05-16T13:50:34Z
author_association: OWNER
path: src/Logging.XUnit/PublicAPI/net8.0/PublicAPI.Unshipped.txt
user: login: martincostello
body: Remove this file.
created_at: 2025-05-16T13:50:42Z
author_association: OWNER
diff_hunk: @@ -659,6 +659,41 @@ public static void XUnitLogger_Log_Logs_Message_If_Scopes_Included_And_There_Is_
         outputHelper.Received(1).WriteLine(expected);
     }
 
+#if NET8_0_OR_GREATER
+
+    [Fact]
+    public static void XUnitLogger_Log_Logs_Message_With_TimeProvider()
+    {
+        // Arrange
+        var outputHelper = Substitute.For<ITestOutputHelper>();
+        string name = "MyName";
+
+        var timeProvider = Substitute.For<TimeProvider>();
+        timeProvider.LocalTimeZone.Returns(TimeZoneInfo.Utc);
+        timeProvider.GetUtcNow().Returns(new DateTimeOffset(2019, 05, 12, 13, 47, 41, 123, 456, TimeSpan.Zero));
path: tests/Shared/XUnitLoggerTests.cs
user: login: martincostello
body: Shouldn't this be setting up `GetLocalNow()` as that's what's being used in the logger? If it's because `GetLocalNow()` is implemented in terms of `GetUtcNow()`, then we should just use that in the logger instead as then it's less to set up.
created_at: 2025-05-16T13:59:09Z
author_association: OWNER
path: src/Logging.XUnit.v3/PublicAPI/net8.0/PublicAPI.Shipped.txt
user: login: fiotti
body: I put them in `…/PublicAPI/net8.0/*.txt` instead of `…/PublicAPI/*.txt` because of this: https://github.com/martincostello/xunit-logging/blob/6e828169899dcb5447bc991f9c58638772eb465e/src/Shared/XUnitLoggerOptions.cs#L42 If I put them in `…/PublicAPI/*.txt`, the netstandard2.0 build would fail as these properties are not available there.

I could move it to `…/PublicAPI/net8.0/PublicAPI.Unshipped.txt`, would that be appropriate?

The alternative would be to add a dependency to the [Microsoft.Bcl.TimeProvider](https://www.nuget.org/packages/Microsoft.Bcl.TimeProvider) package in the netstandard2.0 build.
created_at: 2025-05-19T11:43:12Z
author_association: CONTRIBUTOR
path: src/Logging.XUnit/PublicAPI/net8.0/PublicAPI.Shipped.txt
user: login: fiotti
body: (See [here](https://github.com/martincostello/xunit-logging/pull/912/files/6e828169899dcb5447bc991f9c58638772eb465e#r2095520732))
created_at: 2025-05-19T11:43:44Z
author_association: CONTRIBUTOR
path: src/Logging.XUnit.v3/PublicAPI/net8.0/PublicAPI.Shipped.txt
user: login: martincostello
body: Ah of course, sorry. In that case the fix is just to use the `Unshipped` files for these entries. The `Shipped` files are for me to update after a new release is published to NuGet.org.
created_at: 2025-05-19T11:48:18Z
author_association: OWNER
diff_hunk: @@ -659,6 +659,41 @@ public static void XUnitLogger_Log_Logs_Message_If_Scopes_Included_And_There_Is_
         outputHelper.Received(1).WriteLine(expected);
     }
 
+#if NET8_0_OR_GREATER
+
+    [Fact]
+    public static void XUnitLogger_Log_Logs_Message_With_TimeProvider()
+    {
+        // Arrange
+        var outputHelper = Substitute.For<ITestOutputHelper>();
+        string name = "MyName";
+
+        var timeProvider = Substitute.For<TimeProvider>();
+        timeProvider.LocalTimeZone.Returns(TimeZoneInfo.Utc);
+        timeProvider.GetUtcNow().Returns(new DateTimeOffset(2019, 05, 12, 13, 47, 41, 123, 456, TimeSpan.Zero));
path: tests/Shared/XUnitLoggerTests.cs
user: login: fiotti
body: In `TimeProvider`, `GetLocalNow()` is indeed [internally implemented](https://github.com/dotnet/runtime/blob/a50dedf326cf31e2320cbc97d3ae721b26a1f2b2/src/libraries/Common/src/System/TimeProvider.cs#L47-L72) as `GetUtcNow()` for the date, and `LocalTimeZone` for the offset. It is not virtual, so it could normally not be overridden.

Since `GetUtcNot()` and `LocalTimeZone` are virtual I opted to mock those two instead. I now realize that this doesn't really make much sense, my apologies.

> If it's because `GetLocalNow()` is implemented in terms of `GetUtcNow()`, then we should just use that in the logger instead as then it's less to set up.

That would make the logger less flexible, as it would force the time-zone to always be UTC instead of relying on `LocalTimeZone`. But if you prefer to use `GetUtcNow()` in the logger, we certainly can. Let me know which way you prefer.
created_at: 2025-05-19T11:50:58Z
author_association: CONTRIBUTOR
diff_hunk: @@ -659,6 +659,41 @@ public static void XUnitLogger_Log_Logs_Message_If_Scopes_Included_And_There_Is_
         outputHelper.Received(1).WriteLine(expected);
     }
 
+#if NET8_0_OR_GREATER
+
+    [Fact]
+    public static void XUnitLogger_Log_Logs_Message_With_TimeProvider()
+    {
+        // Arrange
+        var outputHelper = Substitute.For<ITestOutputHelper>();
+        string name = "MyName";
+
+        var timeProvider = Substitute.For<TimeProvider>();
+        timeProvider.LocalTimeZone.Returns(TimeZoneInfo.Utc);
+        timeProvider.GetUtcNow().Returns(new DateTimeOffset(2019, 05, 12, 13, 47, 41, 123, 456, TimeSpan.Zero));
path: tests/Shared/XUnitLoggerTests.cs
user: login: martincostello
body: Sorry yes you're right, it's been a while since I've looked at the code.

Local time is used, but then the default is to format it in UTC as with `"u"` as the format string, but that can be customised to give a local result if desired.

Based on that, then using `GetLocalNow()` is correct 😅

created_at: 2025-05-19T13:02:11Z
author_association: OWNER
diff_hunk: @@ -659,6 +659,41 @@ public static void XUnitLogger_Log_Logs_Message_If_Scopes_Included_And_There_Is_
         outputHelper.Received(1).WriteLine(expected);
     }
 
+#if NET8_0_OR_GREATER
+
+    [Fact]
+    public static void XUnitLogger_Log_Logs_Message_With_TimeProvider()
+    {
+        // Arrange
+        var outputHelper = Substitute.For<ITestOutputHelper>();
+        string name = "MyName";
+
+        var timeProvider = Substitute.For<TimeProvider>();
+        timeProvider.LocalTimeZone.Returns(TimeZoneInfo.Utc);
+        timeProvider.GetUtcNow().Returns(new DateTimeOffset(2019, 05, 12, 13, 47, 41, 123, 456, TimeSpan.Zero));
path: tests/Shared/XUnitLoggerTests.cs
user: login: fiotti
body: > In `TimeProvider`, `GetLocalNow()` is indeed [internally implemented](https://github.com/dotnet/runtime/blob/a50dedf326cf31e2320cbc97d3ae721b26a1f2b2/src/libraries/Common/src/System/TimeProvider.cs#L47-L72) as `GetUtcNow()` for the date, and `LocalTimeZone` for the offset. It is not virtual, so it could normally not be overridden.
>
> Since `GetUtcNot()` and `LocalTimeZone` are virtual I opted to mock those two instead. I now realize that this doesn't really make much sense, my apologies.

Sorry to bother you again, I am not familiar with NSubstitute. According to the documentation _«Only virtual members can be recorded or have return values specified»_. I guess leaving it like this is the only option, as `GetLocalNow()` is not virtual. What do you think?

In the meantime I will proceed updating the PR.
created_at: 2025-05-20T08:06:10Z
author_association: CONTRIBUTOR
path: src/Logging.XUnit/PublicAPI/net8.0/PublicAPI.Shipped.txt
user: login: fiotti
body: I assume you meant `src/Logging.XUnit/PublicAPI/PublicAPI.Unshipped.txt`.

I will move them to `src/Logging.XUnit/PublicAPI/net8.0/PublicAPI.Unshipped.txt`. Let me know if this is not correct.
created_at: 2025-05-20T08:10:27Z
author_association: CONTRIBUTOR
diff_hunk: @@ -659,6 +659,41 @@ public static void XUnitLogger_Log_Logs_Message_If_Scopes_Included_And_There_Is_
         outputHelper.Received(1).WriteLine(expected);
     }
 
+#if NET8_0_OR_GREATER
+
+    [Fact]
+    public static void XUnitLogger_Log_Logs_Message_With_TimeProvider()
+    {
+        // Arrange
+        var outputHelper = Substitute.For<ITestOutputHelper>();
+        string name = "MyName";
+
+        var timeProvider = Substitute.For<TimeProvider>();
+        timeProvider.LocalTimeZone.Returns(TimeZoneInfo.Utc);
+        timeProvider.GetUtcNow().Returns(new DateTimeOffset(2019, 05, 12, 13, 47, 41, 123, 456, TimeSpan.Zero));
path: tests/Shared/XUnitLoggerTests.cs
user: login: martincostello
body: One last change and then I think this is all done and good to go.

Please change the time zone to whatever maps to `Europe/London` (which should be `+01:00` on the date on the test, because it's in late Spring).

That will then validate that local time was definitely used, as the test as-is doesn't definitively _prove_ that as the local time is the same as the UTC time.

You can see the same concept here. The local time is 17:12 in August in the summer:

https://github.com/martincostello/xunit-logging/blob/de2b654fe80a5e4145556babbae542a8e6f435d3/tests/Shared/XUnitLoggerTests.cs#L697

But the time in the log message is an hour earlier as it's UTC:

https://github.com/martincostello/xunit-logging/blob/de2b654fe80a5e4145556babbae542a8e6f435d3/tests/Shared/XUnitLoggerTests.cs#L322
created_at: 2025-05-20T09:55:18Z
author_association: OWNER
diff_hunk: @@ -659,6 +659,41 @@ public static void XUnitLogger_Log_Logs_Message_If_Scopes_Included_And_There_Is_
         outputHelper.Received(1).WriteLine(expected);
     }
 
+#if NET8_0_OR_GREATER
+
+    [Fact]
+    public static void XUnitLogger_Log_Logs_Message_With_TimeProvider()
+    {
+        // Arrange
+        var outputHelper = Substitute.For<ITestOutputHelper>();
+        string name = "MyName";
+
+        var timeProvider = Substitute.For<TimeProvider>();
+        timeProvider.LocalTimeZone.Returns(TimeZoneInfo.Utc);
+        timeProvider.GetUtcNow().Returns(new DateTimeOffset(2019, 05, 12, 13, 47, 41, 123, 456, TimeSpan.Zero));
path: tests/Shared/XUnitLoggerTests.cs
user: login: fiotti
body: I thought about it, it wouldn't be allowed to change it to this:
```csharp
timeProvider.LocalTimeZone.Returns(TimeZoneInfo.Utc);
timeProvider.GetUtcNow().Returns(new DateTimeOffset(2019, 05, 12, 13, 47, 41, 123, 456, TimeSpan.FromHours(1)));
```
because `GetUtcNow()` is expected to return a _«value whose date and time are set to the current Coordinated Universal Time (UTC) date and time and whose offset is Zero»_.

If you prefer I could instead change it to the following, which would produce the same result:
```csharp
timeProvider.LocalTimeZone.Returns(TimeZoneInfo.FindSystemTimeZoneById("Europe/London"));
timeProvider.GetUtcNow().Returns(new DateTimeOffset(2019, 05, 12, 13, 47, 41, 123, 456, TimeSpan.Zero));
```

Would you like me to test for both the Europe/London time-zone and for UTC. I could use `[InlineData(…)]`.
created_at: 2025-05-21T07:43:37Z
author_association: CONTRIBUTOR
diff_hunk: @@ -659,6 +659,41 @@ public static void XUnitLogger_Log_Logs_Message_If_Scopes_Included_And_There_Is_
         outputHelper.Received(1).WriteLine(expected);
     }
 
+#if NET8_0_OR_GREATER
+
+    [Fact]
+    public static void XUnitLogger_Log_Logs_Message_With_TimeProvider()
+    {
+        // Arrange
+        var outputHelper = Substitute.For<ITestOutputHelper>();
+        string name = "MyName";
+
+        var timeProvider = Substitute.For<TimeProvider>();
+        timeProvider.LocalTimeZone.Returns(TimeZoneInfo.Utc);
+        timeProvider.GetUtcNow().Returns(new DateTimeOffset(2019, 05, 12, 13, 47, 41, 123, 456, TimeSpan.Zero));
path: tests/Shared/XUnitLoggerTests.cs
user: login: martincostello
body: The change I was getting at (without having a debugger/compiler to hand) was the latter one to change the local time zone.

I don't think there's a need for a data-driven test, as I'm fairly sure the existing tests already cover that.
created_at: 2025-05-21T07:50:26Z
author_association: OWNER

PR comments:
user: login: fiotti
created_at: 2025-05-08T06:48:59Z
body: > Let me know what difficulties you're having building things, and I can see if there's anything I need to fix, otherwise I should be able to give some pointers.

I managed to get everything running properly and I should be able to do everything on my own now, but something urgent came up, I might need a few days before I can get back on this PR. Thank you, Martin 🙏
author_association: CONTRIBUTOR
user: login: codecov[bot]
created_at: 2025-05-20T09:49:40Z
body: ## [Codecov](https://app.codecov.io/gh/martincostello/xunit-logging/pull/912?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=martincostello) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 98.59%. Comparing base [(`ddb9211`)](https://app.codecov.io/gh/martincostello/xunit-logging/commit/ddb92110d5b14273196e000fc51a21e531bd8675?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=martincostello) to head [(`c47590e`)](https://app.codecov.io/gh/martincostello/xunit-logging/commit/c47590ee5fbdd96a7b17756a082256a394522038?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=martincostello).
> Report is 1 commits behind head on main.

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main     #912      +/-   ##
==========================================
+ Coverage   98.57%   98.59%   +0.01%     
==========================================
  Files          16       16              
  Lines         281      284       +3     
  Branches       36       37       +1     
==========================================
+ Hits          277      280       +3     
  Misses          2        2              
  Partials        2        2              
```

| [Flag](https://app.codecov.io/gh/martincostello/xunit-logging/pull/912/flags?src=pr&el=flags&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=martincostello) | Coverage Δ | |
|---|---|---|
| [linux](https://app.codecov.io/gh/martincostello/xunit-logging/pull/912/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=martincostello) | `98.59% <100.00%> (+0.01%)` | :arrow_up: |
| [macos](https://app.codecov.io/gh/martincostello/xunit-logging/pull/912/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=martincostello) | `97.88% <100.00%> (-0.69%)` | :arrow_down: |
| [windows](https://app.codecov.io/gh/martincostello/xunit-logging/pull/912/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=martincostello) | `98.59% <100.00%> (+0.01%)` | :arrow_up: |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=martincostello#carryforward-flags-in-the-pull-request-comment) to find out more.

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/martincostello/xunit-logging/pull/912?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=martincostello).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=martincostello).

author_association: NONE
user: login: martincostello
created_at: 2025-05-21T08:08:28Z
body: Thanks for persevering with this PR. I'll look to get a new release out with this change either today or later this week.

author_association: OWNER
user: login: fiotti
created_at: 2025-05-21T08:13:53Z
body: Since working on a PR like this was kind of a new experience for me, thank you very much for your patience and kindness.
author_association: CONTRIBUTOR
user: login: costellobot
created_at: 2025-05-21T09:11:36Z
body: Thanks for your contribution @fiotti - the changes from this pull request have been published as part of version 0.6.0 :package:, which is now available from NuGet.org :rocket:
<!-- martincostello/xunit-logging/nuget-packages-published#912 -->
author_association: COLLABORATOR