Skip to content

Commit

Permalink
Merge pull request #246 from 0xced/IMessageSink
Browse files Browse the repository at this point in the history
Add support for IMessageSink
  • Loading branch information
martincostello authored Oct 2, 2021
2 parents bedd07b + 144c55a commit ea86954
Show file tree
Hide file tree
Showing 22 changed files with 985 additions and 87 deletions.
3 changes: 2 additions & 1 deletion Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
</ItemGroup>
<ItemGroup Condition=" '$(IsTestProject)' != 'true' ">
<PackageVersion Include="Microsoft.Extensions.Logging" Version="2.0.0" />
<PackageVersion Include="xunit.abstractions" Version="2.0.1" />
<PackageVersion Include="xunit.abstractions" Version="2.0.2" />
<PackageVersion Include="xunit.extensibility.execution" Version="2.4.0" />
</ItemGroup>
<ItemGroup Condition=" '$(IsTestProject)' == 'true' ">
<PackageVersion Include="Microsoft.Extensions.Logging" Version="5.0.0" />
Expand Down
18 changes: 18 additions & 0 deletions src/Logging.XUnit/IMessageSinkAccessor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Copyright (c) Martin Costello, 2018. All rights reserved.
// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.

using Xunit.Abstractions;

namespace MartinCostello.Logging.XUnit
{
/// <summary>
/// Defines a property for accessing an <see cref="IMessageSink"/>.
/// </summary>
public interface IMessageSinkAccessor
{
/// <summary>
/// Gets or sets the <see cref="IMessageSink"/> to use.
/// </summary>
IMessageSink? MessageSink { get; set; }
}
}
50 changes: 50 additions & 0 deletions src/Logging.XUnit/IMessageSinkExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Copyright (c) Martin Costello, 2018. All rights reserved.
// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.

using System;
using System.ComponentModel;
using Microsoft.Extensions.Logging;

namespace Xunit.Abstractions
{
/// <summary>
/// A class containing extension methods for the <see cref="IMessageSink"/> interface. This class cannot be inherited.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public static class IMessageSinkExtensions
{
/// <summary>
/// Returns an <see cref="ILoggerFactory"/> that logs to the message sink.
/// </summary>
/// <param name="messageSink">The <see cref="IMessageSink"/> to create the logger factory from.</param>
/// <returns>
/// An <see cref="ILoggerFactory"/> that writes messages to the message sink.
/// </returns>
/// <exception cref="ArgumentNullException">
/// <paramref name="messageSink"/> is <see langword="null"/>.
/// </exception>
public static ILoggerFactory ToLoggerFactory(this IMessageSink messageSink)
{
if (messageSink == null)
{
throw new ArgumentNullException(nameof(messageSink));
}

return new LoggerFactory().AddXUnit(messageSink);
}

/// <summary>
/// Returns an <see cref="ILogger{T}"/> that logs to the message sink.
/// </summary>
/// <typeparam name="T">The type of the logger to create.</typeparam>
/// <param name="messageSink">The <see cref="IMessageSink"/> to create the logger from.</param>
/// <returns>
/// An <see cref="ILogger{T}"/> that writes messages to the message sink.
/// </returns>
/// <exception cref="ArgumentNullException">
/// <paramref name="messageSink"/> is <see langword="null"/>.
/// </exception>
public static ILogger<T> ToLogger<T>(this IMessageSink messageSink)
=> messageSink.ToLoggerFactory().CreateLogger<T>();
}
}
1 change: 1 addition & 0 deletions src/Logging.XUnit/MartinCostello.Logging.XUnit.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,6 @@
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging" />
<PackageReference Include="xunit.abstractions" />
<PackageReference Include="xunit.extensibility.execution" />
</ItemGroup>
</Project>
31 changes: 31 additions & 0 deletions src/Logging.XUnit/MessageSinkAccessor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Copyright (c) Martin Costello, 2018. All rights reserved.
// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.

using System;
using Xunit.Abstractions;

namespace MartinCostello.Logging.XUnit
{
/// <summary>
/// A class representing the default implementation of <see cref="IMessageSinkAccessor"/>. This class cannot be inherited.
/// </summary>
internal sealed class MessageSinkAccessor : IMessageSinkAccessor
{
/// <summary>
/// Initializes a new instance of the <see cref="MessageSinkAccessor"/> class.
/// </summary>
/// <param name="messageSink">The <see cref="IMessageSink"/> to use.</param>
/// <exception cref="ArgumentNullException">
/// <paramref name="messageSink"/> is <see langword="null"/>.
/// </exception>
internal MessageSinkAccessor(IMessageSink messageSink)
{
MessageSink = messageSink ?? throw new ArgumentNullException(nameof(messageSink));
}

/// <summary>
/// Gets or sets the current <see cref="IMessageSink"/>.
/// </summary>
public IMessageSink? MessageSink { get; set; }
}
}
66 changes: 66 additions & 0 deletions src/Logging.XUnit/XUnitLogger.IMessageSink.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// Copyright (c) Martin Costello, 2018. All rights reserved.
// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.

using System;
using Microsoft.Extensions.Logging;
using Xunit.Abstractions;

namespace MartinCostello.Logging.XUnit
{
/// <summary>
/// A class representing an <see cref="ILogger"/> to use with xunit.
/// </summary>
public partial class XUnitLogger
{
/// <summary>
/// The <see cref="IMessageSinkAccessor"/> to use. This field is read-only.
/// </summary>
private readonly IMessageSinkAccessor? _messageSinkAccessor;

/// <summary>
/// Gets or sets the message sink message factory to use when writing to an <see cref="IMessageSink"/>.
/// </summary>
private Func<string, IMessageSinkMessage> _messageSinkMessageFactory;

/// <summary>
/// Initializes a new instance of the <see cref="XUnitLogger"/> class.
/// </summary>
/// <param name="name">The name for messages produced by the logger.</param>
/// <param name="messageSink">The <see cref="IMessageSink"/> to use.</param>
/// <param name="options">The <see cref="XUnitLoggerOptions"/> to use.</param>
/// <exception cref="ArgumentNullException">
/// <paramref name="name"/> or <paramref name="messageSink"/> is <see langword="null"/>.
/// </exception>
public XUnitLogger(string name, IMessageSink messageSink, XUnitLoggerOptions? options)
: this(name, new MessageSinkAccessor(messageSink), options)
{
}

/// <summary>
/// Initializes a new instance of the <see cref="XUnitLogger"/> class.
/// </summary>
/// <param name="name">The name for messages produced by the logger.</param>
/// <param name="accessor">The <see cref="IMessageSinkAccessor"/> to use.</param>
/// <param name="options">The <see cref="XUnitLoggerOptions"/> to use.</param>
/// <exception cref="ArgumentNullException">
/// <paramref name="name"/> or <paramref name="accessor"/> is <see langword="null"/>.
/// </exception>
public XUnitLogger(string name, IMessageSinkAccessor accessor, XUnitLoggerOptions? options)
: this(name, options)
{
_messageSinkAccessor = accessor ?? throw new ArgumentNullException(nameof(accessor));
}

/// <summary>
/// Gets or sets the message sink message factory to use when writing to an <see cref="IMessageSink"/>.
/// </summary>
/// <exception cref="ArgumentNullException">
/// <paramref name="value"/> is <see langword="null"/>.
/// </exception>
public Func<string, IMessageSinkMessage> MessageSinkMessageFactory
{
get { return _messageSinkMessageFactory; }
set { _messageSinkMessageFactory = value ?? throw new ArgumentNullException(nameof(value)); }
}
}
}
49 changes: 49 additions & 0 deletions src/Logging.XUnit/XUnitLogger.ITestOutputHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// Copyright (c) Martin Costello, 2018. All rights reserved.
// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.

using System;
using Microsoft.Extensions.Logging;
using Xunit.Abstractions;

namespace MartinCostello.Logging.XUnit
{
/// <summary>
/// A class representing an <see cref="ILogger"/> to use with xunit.
/// </summary>
public partial class XUnitLogger
{
/// <summary>
/// The <see cref="ITestOutputHelperAccessor"/> to use. This field is read-only.
/// </summary>
private readonly ITestOutputHelperAccessor? _outputHelperAccessor;

/// <summary>
/// Initializes a new instance of the <see cref="XUnitLogger"/> class.
/// </summary>
/// <param name="name">The name for messages produced by the logger.</param>
/// <param name="outputHelper">The <see cref="ITestOutputHelper"/> to use.</param>
/// <param name="options">The <see cref="XUnitLoggerOptions"/> to use.</param>
/// <exception cref="ArgumentNullException">
/// <paramref name="name"/> or <paramref name="outputHelper"/> is <see langword="null"/>.
/// </exception>
public XUnitLogger(string name, ITestOutputHelper outputHelper, XUnitLoggerOptions? options)
: this(name, new TestOutputHelperAccessor(outputHelper), options)
{
}

/// <summary>
/// Initializes a new instance of the <see cref="XUnitLogger"/> class.
/// </summary>
/// <param name="name">The name for messages produced by the logger.</param>
/// <param name="accessor">The <see cref="ITestOutputHelperAccessor"/> to use.</param>
/// <param name="options">The <see cref="XUnitLoggerOptions"/> to use.</param>
/// <exception cref="ArgumentNullException">
/// <paramref name="name"/> or <paramref name="accessor"/> is <see langword="null"/>.
/// </exception>
public XUnitLogger(string name, ITestOutputHelperAccessor accessor, XUnitLoggerOptions? options)
: this(name, options)
{
_outputHelperAccessor = accessor ?? throw new ArgumentNullException(nameof(accessor));
}
}
}
53 changes: 20 additions & 33 deletions src/Logging.XUnit/XUnitLogger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,18 @@
// 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.Linq;
using System.Text;
using Microsoft.Extensions.Logging;
using Xunit.Abstractions;
using Xunit.Sdk;

namespace MartinCostello.Logging.XUnit
{
/// <summary>
/// A class representing an <see cref="ILogger"/> to use with xunit.
/// </summary>
public class XUnitLogger : ILogger
public partial class XUnitLogger : ILogger
{
//// Based on https://github.com/aspnet/Logging/blob/master/src/Microsoft.Extensions.Logging.Console/ConsoleLogger.cs

Expand All @@ -39,11 +38,6 @@ public class XUnitLogger : ILogger
[ThreadStatic]
private static StringBuilder? _logBuilder;

/// <summary>
/// The <see cref="ITestOutputHelperAccessor"/> to use. This field is read-only.
/// </summary>
private readonly ITestOutputHelperAccessor _accessor;

/// <summary>
/// Gets or sets the filter to use.
/// </summary>
Expand All @@ -53,31 +47,13 @@ public class XUnitLogger : ILogger
/// Initializes a new instance of the <see cref="XUnitLogger"/> class.
/// </summary>
/// <param name="name">The name for messages produced by the logger.</param>
/// <param name="outputHelper">The <see cref="ITestOutputHelper"/> to use.</param>
/// <param name="options">The <see cref="XUnitLoggerOptions"/> to use.</param>
/// <exception cref="ArgumentNullException">
/// <paramref name="name"/> or <paramref name="outputHelper"/> is <see langword="null"/>.
/// </exception>
public XUnitLogger(string name, ITestOutputHelper outputHelper, XUnitLoggerOptions? options)
: this(name, new TestOutputHelperAccessor(outputHelper), options)
{
}

/// <summary>
/// Initializes a new instance of the <see cref="XUnitLogger"/> class.
/// </summary>
/// <param name="name">The name for messages produced by the logger.</param>
/// <param name="accessor">The <see cref="ITestOutputHelperAccessor"/> to use.</param>
/// <param name="options">The <see cref="XUnitLoggerOptions"/> to use.</param>
/// <exception cref="ArgumentNullException">
/// <paramref name="name"/> or <paramref name="accessor"/> is <see langword="null"/>.
/// </exception>
public XUnitLogger(string name, ITestOutputHelperAccessor accessor, XUnitLoggerOptions? options)
private XUnitLogger(string name, XUnitLoggerOptions? options)
{
Name = name ?? throw new ArgumentNullException(nameof(name));
_accessor = accessor ?? throw new ArgumentNullException(nameof(accessor));

_filter = options?.Filter ?? ((category, logLevel) => true);
_filter = options?.Filter ?? ((_, _) => true);
_messageSinkMessageFactory = options?.MessageSinkMessageFactory ?? (message => new DiagnosticMessage(message));
IncludeScopes = options?.IncludeScopes ?? false;
}

Expand Down Expand Up @@ -152,17 +128,18 @@ public void Log<TState>(LogLevel logLevel, EventId eventId, TState? state, Excep
}

/// <summary>
/// Writes a message to the <see cref="ITestOutputHelper"/> associated with the instance.
/// 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 = _accessor.OutputHelper;
ITestOutputHelper? outputHelper = _outputHelperAccessor?.OutputHelper;
IMessageSink? messageSink = _messageSinkAccessor?.MessageSink;

if (outputHelper == null)
if (outputHelper is null && messageSink is null)
{
return;
}
Expand Down Expand Up @@ -213,7 +190,17 @@ public virtual void WriteMessage(LogLevel logLevel, int eventId, string? message

try
{
outputHelper.WriteLine($"[{Clock():u}] {logLevelString}{formatted}");
var line = $"[{Clock():u}] {logLevelString}{formatted}";
if (outputHelper != null)
{
outputHelper.WriteLine(line);
}

if (messageSink != null)
{
var sinkMessage = _messageSinkMessageFactory(line);
messageSink.OnMessage(sinkMessage);
}
}
#pragma warning disable CA1031
catch (InvalidOperationException)
Expand Down
Loading

0 comments on commit ea86954

Please sign in to comment.