Skip to content

Commit

Permalink
Merge pull request #8 from martincostello/ITestOutputHandlerAccessor
Browse files Browse the repository at this point in the history
Add ITestOutputHelperAccessor
  • Loading branch information
martincostello committed Aug 20, 2018
2 parents b59a087 + c83d155 commit c959799
Show file tree
Hide file tree
Showing 22 changed files with 660 additions and 15 deletions.
7 changes: 7 additions & 0 deletions Logging.XUnit.sln
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MartinCostello.Logging.XUni
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MartinCostello.Logging.XUnit", "src\Logging.XUnit\MartinCostello.Logging.XUnit.csproj", "{BB443063-F523-474D-83E3-D5FF5B075950}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SampleApp", "tests\SampleApp\SampleApp.csproj", "{B690F271-3B5D-4975-A607-AED1768595B1}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -66,6 +68,10 @@ Global
{BB443063-F523-474D-83E3-D5FF5B075950}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BB443063-F523-474D-83E3-D5FF5B075950}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BB443063-F523-474D-83E3-D5FF5B075950}.Release|Any CPU.Build.0 = Release|Any CPU
{B690F271-3B5D-4975-A607-AED1768595B1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B690F271-3B5D-4975-A607-AED1768595B1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B690F271-3B5D-4975-A607-AED1768595B1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B690F271-3B5D-4975-A607-AED1768595B1}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -76,6 +82,7 @@ Global
{F37263E7-6656-4A2B-B02E-538B5F9970BD} = {D0426D09-1FF8-4E1F-A9AF-38DDEE5D7CCA}
{331BC69B-DFFC-4174-8A97-6335BF6D86CF} = {278BCCB1-39B2-46DB-9395-7F85995A6132}
{BB443063-F523-474D-83E3-D5FF5B075950} = {2684B19D-7D49-4099-8BD2-4D281455EB29}
{B690F271-3B5D-4975-A607-AED1768595B1} = {278BCCB1-39B2-46DB-9395-7F85995A6132}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {3B9E157C-5E92-4357-B233-281B4530EABD}
Expand Down
31 changes: 31 additions & 0 deletions src/Logging.XUnit/AmbientTestOutputHelperAccessor.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.Threading;
using Xunit.Abstractions;

namespace MartinCostello.Logging.XUnit
{
/// <summary>
/// A class representing an implementation of <see cref="ITestOutputHelperAccessor"/> that
/// stores the <see cref="ITestOutputHelper"/> as an asynchronous local value. This class cannot be inherited.
/// </summary>
internal sealed class AmbientTestOutputHelperAccessor : ITestOutputHelperAccessor
{
/// <summary>
/// A backing field for the <see cref="ITestOutputHelper"/> for the current thread.
/// </summary>
private static readonly AsyncLocal<ITestOutputHelper> _current = new AsyncLocal<ITestOutputHelper>();

#pragma warning disable CA1822
/// <summary>
/// Gets or sets the current <see cref="ITestOutputHelper"/>.
/// </summary>
public ITestOutputHelper OutputHelper
{
get { return _current.Value; }
set { _current.Value = value; }
}
#pragma warning restore CA1822
}
}
18 changes: 18 additions & 0 deletions src/Logging.XUnit/ITestOutputHelperAccessor.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="ITestOutputHelper"/>.
/// </summary>
public interface ITestOutputHelperAccessor
{
/// <summary>
/// Gets or sets the <see cref="ITestOutputHelper"/> to use.
/// </summary>
ITestOutputHelper OutputHelper { get; set; }
}
}
31 changes: 31 additions & 0 deletions src/Logging.XUnit/TestOutputHelperAccessor.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="ITestOutputHelperAccessor"/>. This class cannot be inherited.
/// </summary>
internal sealed class TestOutputHelperAccessor : ITestOutputHelperAccessor
{
/// <summary>
/// Initializes a new instance of the <see cref="TestOutputHelperAccessor"/> class.
/// </summary>
/// <param name="outputHelper">The <see cref="ITestOutputHelper"/> to use.</param>
/// <exception cref="ArgumentNullException">
/// <paramref name="outputHelper"/> is <see langword="null"/>.
/// </exception>
internal TestOutputHelperAccessor(ITestOutputHelper outputHelper)
{
OutputHelper = outputHelper ?? throw new ArgumentNullException(nameof(outputHelper));
}

/// <summary>
/// Gets or sets the current <see cref="ITestOutputHelper"/>.
/// </summary>
public ITestOutputHelper OutputHelper { get; set; }
}
}
29 changes: 25 additions & 4 deletions src/Logging.XUnit/XUnitLogger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,9 @@ public class XUnitLogger : ILogger
private static StringBuilder _logBuilder;

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

/// <summary>
/// Gets or sets the filter to use.
Expand All @@ -56,9 +56,23 @@ public class XUnitLogger : ILogger
/// <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)
{
Name = name ?? throw new ArgumentNullException(nameof(name));
_outputHelper = outputHelper ?? throw new ArgumentNullException(nameof(outputHelper));
_accessor = accessor ?? throw new ArgumentNullException(nameof(accessor));

Filter = options?.Filter ?? ((category, logLevel) => true);
IncludeScopes = options?.IncludeScopes ?? false;
Expand Down Expand Up @@ -143,6 +157,13 @@ public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Except
/// <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;

if (outputHelper == null)
{
return;
}

StringBuilder logBuilder = _logBuilder;
_logBuilder = null;

Expand Down Expand Up @@ -186,7 +207,7 @@ public virtual void WriteMessage(LogLevel logLevel, int eventId, string message,
}

string formatted = logBuilder.ToString();
_outputHelper.WriteLine($"[{Clock():u}] {logLevelString}{formatted}");
outputHelper.WriteLine($"[{Clock():u}] {logLevelString}{formatted}");

logBuilder.Clear();

Expand Down
86 changes: 86 additions & 0 deletions src/Logging.XUnit/XUnitLoggerExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System;
using System.ComponentModel;
using MartinCostello.Logging.XUnit;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Xunit.Abstractions;

namespace Microsoft.Extensions.Logging
Expand All @@ -14,6 +15,91 @@ namespace Microsoft.Extensions.Logging
[EditorBrowsable(EditorBrowsableState.Never)]
public static class XUnitLoggerExtensions
{
/// <summary>
/// Adds an xunit logger to the logging builder.
/// </summary>
/// <param name="builder">The <see cref="ILoggingBuilder"/> to use.</param>
/// <returns>
/// The instance of <see cref="ILoggingBuilder"/> specified by <paramref name="builder"/>.
/// </returns>
/// <exception cref="ArgumentNullException">
/// <paramref name="builder"/> is <see langword="null"/>.
/// </exception>
public static ILoggingBuilder AddXUnit(this ILoggingBuilder builder)
{
if (builder == null)
{
throw new ArgumentNullException(nameof(builder));
}

return builder.AddXUnit(new AmbientTestOutputHelperAccessor(), (_) => { });
}

/// <summary>
/// Adds an xunit logger to the logging builder.
/// </summary>
/// <param name="builder">The <see cref="ILoggingBuilder"/> to use.</param>
/// <param name="accessor">The <see cref="ITestOutputHelperAccessor"/> to use.</param>
/// <returns>
/// The instance of <see cref="ILoggingBuilder"/> specified by <paramref name="builder"/>.
/// </returns>
/// <exception cref="ArgumentNullException">
/// <paramref name="builder"/> or <paramref name="accessor"/> is <see langword="null"/>.
/// </exception>
public static ILoggingBuilder AddXUnit(this ILoggingBuilder builder, ITestOutputHelperAccessor accessor)
{
if (builder == null)
{
throw new ArgumentNullException(nameof(builder));
}

if (accessor == null)
{
throw new ArgumentNullException(nameof(accessor));
}

return builder.AddXUnit(accessor, (_) => { });
}

/// <summary>
/// Adds an xunit logger to the logging builder.
/// </summary>
/// <param name="builder">The <see cref="ILoggingBuilder"/> to use.</param>
/// <param name="accessor">The <see cref="ITestOutputHelperAccessor"/> to use.</param>
/// <param name="configure">A delegate to a method to use to configure the logging options.</param>
/// <returns>
/// The instance of <see cref="ILoggingBuilder"/> specified by <paramref name="builder"/>.
/// </returns>
/// <exception cref="ArgumentNullException">
/// <paramref name="builder"/>, <paramref name="accessor"/> OR <paramref name="configure"/> is <see langword="null"/>.
/// </exception>
public static ILoggingBuilder AddXUnit(this ILoggingBuilder builder, ITestOutputHelperAccessor accessor, Action<XUnitLoggerOptions> configure)
{
if (builder == null)
{
throw new ArgumentNullException(nameof(builder));
}

if (accessor == null)
{
throw new ArgumentNullException(nameof(accessor));
}

if (configure == null)
{
throw new ArgumentNullException(nameof(configure));
}

var options = new XUnitLoggerOptions();

configure(options);

builder.AddProvider(new XUnitLoggerProvider(accessor, options));
builder.Services.TryAddSingleton(accessor);

return builder;
}

/// <summary>
/// Adds an xunit logger to the logging builder.
/// </summary>
Expand Down
21 changes: 17 additions & 4 deletions src/Logging.XUnit/XUnitLoggerProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ namespace MartinCostello.Logging.XUnit
public class XUnitLoggerProvider : ILoggerProvider
{
/// <summary>
/// The <see cref="ITestOutputHelper"/> to use. This field is readonly.
/// The <see cref="ITestOutputHelperAccessor"/> to use. This field is readonly.
/// </summary>
private readonly ITestOutputHelper _outputHelper;
private readonly ITestOutputHelperAccessor _accessor;

/// <summary>
/// The <see cref="XUnitLoggerOptions"/> to use. This field is readonly.
Expand All @@ -31,8 +31,21 @@ public class XUnitLoggerProvider : ILoggerProvider
/// <paramref name="outputHelper"/> or <paramref name="options"/> is <see langword="null"/>.
/// </exception>
public XUnitLoggerProvider(ITestOutputHelper outputHelper, XUnitLoggerOptions options)
: this(new TestOutputHelperAccessor(outputHelper), options)
{
_outputHelper = outputHelper ?? throw new ArgumentNullException(nameof(outputHelper));
}

/// <summary>
/// Initializes a new instance of the <see cref="XUnitLoggerProvider"/> class.
/// </summary>
/// <param name="accessor">The <see cref="ITestOutputHelperAccessor"/> to use.</param>
/// <param name="options">The options to use for logging to xunit.</param>
/// <exception cref="ArgumentNullException">
/// <paramref name="accessor"/> or <paramref name="options"/> is <see langword="null"/>.
/// </exception>
public XUnitLoggerProvider(ITestOutputHelperAccessor accessor, XUnitLoggerOptions options)
{
_accessor = accessor ?? throw new ArgumentNullException(nameof(accessor));
_options = options ?? throw new ArgumentNullException(nameof(options));
}

Expand All @@ -45,7 +58,7 @@ public XUnitLoggerProvider(ITestOutputHelper outputHelper, XUnitLoggerOptions op
}

/// <inheritdoc />
public virtual ILogger CreateLogger(string categoryName) => new XUnitLogger(categoryName, _outputHelper, _options);
public virtual ILogger CreateLogger(string categoryName) => new XUnitLogger(categoryName, _accessor, _options);

/// <inheritdoc />
public void Dispose()
Expand Down
Loading

0 comments on commit c959799

Please sign in to comment.