Skip to content

Commit

Permalink
- Detect IEnumerable<KeyValuePair<string, object>> in log scopes, and…
Browse files Browse the repository at this point in the history
… write them out instead of the default
  • Loading branch information
gkinsman committed Mar 25, 2021
1 parent 6a2f6c5 commit 39e53da
Show file tree
Hide file tree
Showing 3 changed files with 124 additions and 14 deletions.
14 changes: 7 additions & 7 deletions src/Logging.XUnit/XUnitLogScope.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,20 @@ internal sealed class XUnitLogScope
/// </summary>
private static readonly AsyncLocal<XUnitLogScope> _value = new AsyncLocal<XUnitLogScope>();

/// <summary>
/// The state object for the scope.
/// </summary>
private readonly object _state;

/// <summary>
/// Initializes a new instance of the <see cref="XUnitLogScope"/> class.
/// </summary>
/// <param name="state">The state object for the scope.</param>
internal XUnitLogScope(object state)
{
_state = state;
State = state;
}

/// <summary>
/// Gets the state object for the scope.
/// </summary>
public object State { get; }

/// <summary>
/// Gets or sets the current scope.
/// </summary>
Expand All @@ -46,7 +46,7 @@ internal static XUnitLogScope Current

/// <inheritdoc />
public override string ToString()
=> _state.ToString();
=> State.ToString();

/// <summary>
/// Pushes a new value into the scope.
Expand Down
47 changes: 40 additions & 7 deletions src/Logging.XUnit/XUnitLogger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.

using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using Microsoft.Extensions.Logging;
using Xunit.Abstractions;
Expand Down Expand Up @@ -269,16 +271,20 @@ private static void GetScopeInformation(StringBuilder builder)

while (current != null)
{
if (length == builder.Length)
foreach (var property in StringifyScope(current))
{
scopeLog = $"=> {current}";
}
else
{
scopeLog = $"=> {current} ";
if (length == builder.Length)
{
scopeLog = $"=> {property}";
}
else
{
scopeLog = $"=> {property} ";
}

builder.Insert(length, scopeLog);
}

builder.Insert(length, scopeLog);
current = current.Parent;
}

Expand All @@ -288,5 +294,32 @@ private static void GetScopeInformation(StringBuilder builder)
builder.AppendLine();
}
}

/// <summary>
/// Returns one or more stringified properties from the log scope.
/// </summary>
/// <param name="scope">The <see cref="XUnitLogScope"/> to stringify.</param>
/// <returns>An enumeration of scope properties from the current scope.</returns>
private static IEnumerable<string> StringifyScope(XUnitLogScope scope)
{
if (scope.State is IEnumerable<KeyValuePair<string, object>> pairs)
{
foreach (var pair in pairs)
{
yield return $"{pair.Key}: {pair.Value}";
}
}
else if (scope.State is IEnumerable<string> entries)
{
foreach (var entry in entries)
{
yield return entry;
}
}
else
{
yield return scope.ToString();
}
}
}
}
77 changes: 77 additions & 0 deletions tests/Logging.XUnit.Tests/XUnitLoggerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.

using System;
using System.Collections.Generic;
using Microsoft.Extensions.Logging;
using Moq;
using Shouldly;
Expand Down Expand Up @@ -473,6 +474,82 @@ public static void XUnitLogger_Log_Logs_Message_If_Scopes_Included_And_There_Are
mock.Verify((p) => p.WriteLine(expected), Times.Once());
}

[Fact]
public static void XUnitLogger_Log_Logs_Message_If_Scopes_Included_And_There_Is_Scope_Of_KeyValuePair()
{
// Arrange
var mock = new Mock<ITestOutputHelper>();

string name = "MyName";
var outputHelper = mock.Object;

var options = new XUnitLoggerOptions()
{
Filter = FilterTrue,
IncludeScopes = true,
};

var logger = new XUnitLogger(name, outputHelper, options)
{
Clock = StaticClock,
};

string expected = string.Join(
Environment.NewLine,
new[] { "[2018-08-19 16:12:16Z] info: MyName[0]", " => ScopeKey: ScopeValue", " Message|False|False" });

// Act
using (logger.BeginScope(new[]
{
new KeyValuePair<string, object>("ScopeKey", "ScopeValue"),
}))
{
logger.Log<string>(LogLevel.Information, 0, null, null, Formatter);
}

// Assert
mock.Verify((p) => p.WriteLine(expected), Times.Once());
}

[Fact]
public static void XUnitLogger_Log_Logs_Message_If_Scopes_Included_And_There_Is_Scope_Of_KeyValuePairs()
{
// Arrange
var mock = new Mock<ITestOutputHelper>();

string name = "MyName";
var outputHelper = mock.Object;

var options = new XUnitLoggerOptions()
{
Filter = FilterTrue,
IncludeScopes = true,
};

var logger = new XUnitLogger(name, outputHelper, options)
{
Clock = StaticClock,
};

string expected = string.Join(
Environment.NewLine,
new[] { "[2018-08-19 16:12:16Z] info: MyName[0]", " => ScopeKeyThree: ScopeValueThree => ScopeKeyTwo: ScopeValueTwo => ScopeKeyOne: ScopeValueOne", " Message|False|False" });

// Act
using (logger.BeginScope(new[]
{
new KeyValuePair<string, object>("ScopeKeyOne", "ScopeValueOne"),
new KeyValuePair<string, object>("ScopeKeyTwo", "ScopeValueTwo"),
new KeyValuePair<string, object>("ScopeKeyThree", "ScopeValueThree"),
}))
{
logger.Log<string>(LogLevel.Information, 0, null, null, Formatter);
}

// Assert
mock.Verify((p) => p.WriteLine(expected), Times.Once());
}

private static DateTimeOffset StaticClock() => new DateTimeOffset(2018, 08, 19, 17, 12, 16, TimeSpan.FromHours(1));

private static bool FilterTrue(string categoryName, LogLevel level) => true;
Expand Down

0 comments on commit 39e53da

Please sign in to comment.