From 78f88fb190f879c2577cb6323f3274e883b803e5 Mon Sep 17 00:00:00 2001 From: Tom Scott Date: Tue, 18 Feb 2025 21:54:58 +0000 Subject: [PATCH 1/2] Issue-849 Add ability to provide method to override WriteMessage pass XUnitLogger into WriteMethodOverride add more tests change formatter name --- src/Shared/XUnitLogger.cs | 22 +++++- src/Shared/XUnitLoggerOptions.cs | 5 ++ tests/Shared/XUnitLoggerTests.cs | 120 +++++++++++++++++++++++++++++++ 3 files changed, 146 insertions(+), 1 deletion(-) diff --git a/src/Shared/XUnitLogger.cs b/src/Shared/XUnitLogger.cs index 90c6df8b..50769417 100644 --- a/src/Shared/XUnitLogger.cs +++ b/src/Shared/XUnitLogger.cs @@ -39,6 +39,11 @@ public partial class XUnitLogger : ILogger /// private readonly string _timestampFormat; + /// + /// Gets or sets an optional method to override the writing of log messages. + /// + private readonly Action? _writeMessageOverride; + /// /// Gets or sets the filter to use. /// @@ -57,6 +62,7 @@ private XUnitLogger(string name, XUnitLoggerOptions? options) _messageSinkMessageFactory = options?.MessageSinkMessageFactory ?? (static (message) => new DiagnosticMessage(message)); _timestampFormat = options?.TimestampFormat ?? "u"; IncludeScopes = options?.IncludeScopes ?? false; + _writeMessageOverride = options?.WriteMessageOverride; } /// @@ -134,7 +140,21 @@ public void Log(LogLevel logLevel, EventId eventId, TState state, Except if (!string.IsNullOrEmpty(message) || exception != null) { - WriteMessage(logLevel, eventId.Id, message, exception); + if (_writeMessageOverride != null) + { + _writeMessageOverride( + this, + _outputHelperAccessor?.OutputHelper, + _messageSinkAccessor?.MessageSink, + logLevel, + eventId.Id, + message, + exception); + } + else + { + WriteMessage(logLevel, eventId.Id, message, exception); + } } } diff --git a/src/Shared/XUnitLoggerOptions.cs b/src/Shared/XUnitLoggerOptions.cs index db397713..16c4a417 100644 --- a/src/Shared/XUnitLoggerOptions.cs +++ b/src/Shared/XUnitLoggerOptions.cs @@ -38,4 +38,9 @@ public XUnitLoggerOptions() /// [StringSyntax(StringSyntaxAttribute.DateTimeFormat)] public string? TimestampFormat { get; set; } + + /// + /// Gets or sets an optional method to override the writing of log messages. + /// + public Action? WriteMessageOverride { get; set; } } diff --git a/tests/Shared/XUnitLoggerTests.cs b/tests/Shared/XUnitLoggerTests.cs index 165232ec..1aae87bb 100644 --- a/tests/Shared/XUnitLoggerTests.cs +++ b/tests/Shared/XUnitLoggerTests.cs @@ -659,6 +659,122 @@ public static void XUnitLogger_Log_Logs_Message_If_Scopes_Included_And_There_Is_ outputHelper.Received(1).WriteLine(expected); } + [Fact] + public static void XUnitLogger_Log_Emits_Output_Helper_Message_If_Override_Method_Provided() + { + // Arrange + var outputHelper = Substitute.For(); + string name = "MyName"; + LogLevel inputLogLevel = LogLevel.Information; + int inputEventId = 1; + string? inputMessage = "log message"; + + string expectedOutput = $"[{inputLogLevel}] {name} {inputEventId} {inputMessage}"; + + static void WriteMessageOverride(XUnitLogger xUnitLogger, ITestOutputHelper? helper, IMessageSink? sink, LogLevel level, int eventId, string? message, Exception? exception) + { + string formattedMessage = $"[{level}] {xUnitLogger.Name} {eventId} {message}"; + helper?.WriteLine(formattedMessage); + } + + var options = new XUnitLoggerOptions() + { + Filter = FilterTrue, + WriteMessageOverride = WriteMessageOverride, + }; + + var logger = new XUnitLogger(name, outputHelper, options); + + logger.Log(inputLogLevel, inputEventId, inputMessage, null, FormatterStateAsString); + + // Assert + outputHelper.Received(1).WriteLine(expectedOutput); + } + + [Fact] + public static void XUnitLogger_Log_Emits_Message_Sink_Message_If_Override_Method_Provided() + { + // Arrange + var messageSink = Substitute.For(); + string name = "MyName"; + LogLevel inputLogLevel = LogLevel.Information; + int inputEventId = 1; + string? inputMessage = "message"; + + string expectedOutput = $"[{inputLogLevel}] {name} {inputEventId} {inputMessage}"; + + static void WriteMessageOverride(XUnitLogger xUnitLogger, ITestOutputHelper? helper, IMessageSink? sink, LogLevel level, int eventId, string? message, Exception? exception) + { + string formattedMessage = $"[{level}] {xUnitLogger.Name} {eventId} {message}"; + sink?.OnMessage(new DiagnosticMessage(formattedMessage)); + } + + var options = new XUnitLoggerOptions() + { + Filter = FilterTrue, + WriteMessageOverride = WriteMessageOverride, + }; + + var logger = new XUnitLogger(name, messageSink, options); + + logger.Log(inputLogLevel, inputEventId, inputMessage, null, FormatterStateAsString); + + // Assert + messageSink.Received(1).OnMessage(Arg.Is(message => string.Equals(message.Message, expectedOutput, StringComparison.Ordinal))); + } + + [Fact] + public static void XUnitLogger_Does_Nothing_If_Noop_Override_Method_Provided() + { + // Arrange + var outputHelper = Substitute.For(); + + static void WriteMessageOverride(XUnitLogger xUnitLogger, ITestOutputHelper? helper, IMessageSink? sink, LogLevel level, int eventId, string? message, Exception? exception) + { + } + + var options = new XUnitLoggerOptions() + { + WriteMessageOverride = WriteMessageOverride, + }; + + var logger = new XUnitLogger("MyName", outputHelper, options); + + logger.Log(LogLevel.Information, 1, string.Empty, null, FormatterStateAsString); + + // Assert + outputHelper.DidNotReceiveWithAnyArgs().WriteLine(string.Empty); + } + + [Fact] + public static void XUnitLogger_Logs_Message_If_Null_Override_Message_Provided() + { + // Arrange + var outputHelper = Substitute.For(); + string name = "MyName"; + + var options = new XUnitLoggerOptions() + { + WriteMessageOverride = null, + }; + + string expected = string.Join( + Environment.NewLine, + $"[2018-08-19 16:12:16Z] info: MyName[1]", + " state"); + + var logger = new XUnitLogger(name, outputHelper, options) + { + Clock = StaticClock, + }; + + // Act + logger.Log(LogLevel.Information, 1, "state", null, FormatterStateAsString); + + // Assert + outputHelper.Received(1).WriteLine(Arg.Is(expected)); + } + private static DateTimeOffset StaticClock() => new(2018, 08, 19, 17, 12, 16, TimeSpan.FromHours(1)); private static DiagnosticMessage DiagnosticMessageFactory(string message) => new(message); @@ -678,4 +794,8 @@ private static string Formatter(TState? state, Exception? exception) private static string FormatterLong(TState? state, Exception? exception) => new('a', 2048); private static string FormatterNull(TState? state, Exception? exception) => null!; + +#pragma warning disable IDE0060 // Remove unused parameter + private static string FormatterStateAsString(TState? state, Exception? exception) => state?.ToString() ?? string.Empty; +#pragma warning restore IDE0060 // Remove unused parameter } From 2130ad61d9bfe12527956dcb3283fc6ad41ef46d Mon Sep 17 00:00:00 2001 From: Tom Scott Date: Wed, 19 Feb 2025 15:07:45 +0000 Subject: [PATCH 2/2] fix existing unit test --- tests/Shared/XUnitLoggerTests.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/Shared/XUnitLoggerTests.cs b/tests/Shared/XUnitLoggerTests.cs index 1aae87bb..a8f3baf7 100644 --- a/tests/Shared/XUnitLoggerTests.cs +++ b/tests/Shared/XUnitLoggerTests.cs @@ -255,6 +255,7 @@ public static void XUnitLogger_Log_Does_Nothing_If_No_OutputHelper() // Arrange string name = "MyName"; var accessor = Substitute.For(); + accessor.OutputHelper.Returns(default(ITestOutputHelper)); var options = new XUnitLoggerOptions() {