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..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()
{
@@ -659,6 +660,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 +795,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
}