Skip to content

[Bug]: scopes are not including the state passed Log<TState>() method #982

@goldsam

Description

@goldsam

NuGet Package(s)

MartinCostello.Logging.XUnit

Version

0.6.0

Describe the bug

#214 adds support for capturing states added via BeginScope(), bug neglects the state passed to public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter) in XUnitLogger. This inadvertently omits state from many sourced, notably logging code generated with LogPropertiesAtrribute (see Behind LogProperties and the new telemetry logging source generator for usage/examples.

Expected behaviour

The state passed to public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter) should be passed to public virtual void WriteMessage(LogLevel logLevel, int eventId, string? message, Exception? exception) and in turn private static void GetScopeInformation(StringBuilder builder) when IncludeScopes is true. This state should be included in the formatted output in addition to all state added through scopes. below is the relevant code in XUnitLogger:

if (!string.IsNullOrEmpty(message) || exception != null)
{
WriteMessage(logLevel, eventId.Id, message, exception);
}
}
/// <summary>
/// Writes a message to the <see cref="ITestOutputHelper"/> or <see cref="IMessageSink"/> associated with the instance.
/// </summary>
/// <param name="logLevel">The message to write will be written on this level.</param>
/// <param name="eventId">The Id of the event.</param>
/// <param name="message">The message to write.</param>
/// <param name="exception">The exception related to this message.</param>
public virtual void WriteMessage(LogLevel logLevel, int eventId, string? message, Exception? exception)
{
ITestOutputHelper? outputHelper = _outputHelperAccessor?.OutputHelper;
IMessageSink? messageSink = _messageSinkAccessor?.MessageSink;
if (outputHelper is null && messageSink is null)
{
return;
}
StringBuilder? logBuilder = _logBuilder;
_logBuilder = null;
logBuilder ??= new StringBuilder();
string logLevelString = GetLogLevelString(logLevel);
logBuilder.Append(LogLevelPadding);
logBuilder.Append(Name);
logBuilder.Append('[');
logBuilder.Append(eventId);
logBuilder.Append(']');
logBuilder.AppendLine();
if (IncludeScopes)
{
GetScopeInformation(logBuilder);
}
bool hasMessage = !string.IsNullOrEmpty(message);
if (hasMessage)
{
logBuilder.Append(MessagePadding);
int length = logBuilder.Length;
logBuilder.Append(message);
logBuilder.Replace(Environment.NewLine, NewLineWithMessagePadding, length, message!.Length);
}
if (exception != null)
{
if (hasMessage)
{
logBuilder.AppendLine();
}
logBuilder.Append(exception);
}
// Prefix the formatted message so it renders like this:
// [{timestamp}] {logLevelString}{message}
logBuilder.Insert(0, logLevelString);
logBuilder.Insert(0, "] ");
logBuilder.Insert(0, Clock().ToString(_timestampFormat, CultureInfo.CurrentCulture));
logBuilder.Insert(0, '[');
string line = logBuilder.ToString();
try
{
outputHelper?.WriteLine(line);
if (messageSink != null)
{
var sinkMessage = _messageSinkMessageFactory(line);
messageSink.OnMessage(sinkMessage);
}
}
catch (InvalidOperationException)
{
// Ignore exception if the application tries to log after the test ends
// but before the ITestOutputHelper is detached, e.g. "There is no currently active test."
}
logBuilder.Clear();
if (logBuilder.Capacity > 1024)
{
logBuilder.Capacity = 1024;
}
_logBuilder = logBuilder;
}

Actual behaviour

The state variable is completely ignored when formatting the output message when IncludeScopes is true.

Steps to reproduce

I think the description is sufficient.

Exception(s) (if any)


.NET Version

9.0.301

Anything else?

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workinggood first issueGood for newcomershelp wantedExtra attention is needed

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions