mirror of
https://github.com/velopack/velopack.git
synced 2025-10-25 15:19:22 +00:00
Replace local xunit logging project with new signed nuget package
This commit is contained in:
@@ -37,8 +37,8 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition=" $(MSBuildProjectName.EndsWith('Tests')) ">
|
||||
<ProjectReference Include="..\Divergic.Logging.Xunit\Divergic.Logging.Xunit.csproj" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />
|
||||
<PackageReference Include="Neovolve.Logging.Xunit.Signed" Version="6.1.0" />
|
||||
<PackageReference Include="Microsoft.CSharp" Version="4.7.0" />
|
||||
<PackageReference Include="xunit" Version="2.9.0" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" />
|
||||
|
||||
@@ -1,156 +0,0 @@
|
||||
namespace Divergic.Logging.Xunit
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="CacheLogger" />
|
||||
/// class provides a cache of log entries written to the logger.
|
||||
/// </summary>
|
||||
public class CacheLogger : FilterLogger, ICacheLogger
|
||||
{
|
||||
private static readonly AsyncLocal<ConcurrentStack<CacheScope>> _scopes =
|
||||
new AsyncLocal<ConcurrentStack<CacheScope>>();
|
||||
|
||||
private readonly ILoggerFactory? _factory;
|
||||
private readonly IList<LogEntry> _logEntries = new List<LogEntry>();
|
||||
private readonly ILogger? _logger;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="CacheLogger" /> class.
|
||||
/// </summary>
|
||||
public CacheLogger()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="CacheLogger" /> class.
|
||||
/// </summary>
|
||||
/// <param name="logger">The source logger.</param>
|
||||
/// <exception cref="ArgumentNullException">The <paramref name="logger" /> is <c>null</c>.</exception>
|
||||
public CacheLogger(ILogger logger)
|
||||
{
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
internal CacheLogger(ILogger logger, ILoggerFactory factory)
|
||||
{
|
||||
_logger = logger;
|
||||
_factory = factory;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override IDisposable? BeginScope<TState>(TState state)
|
||||
{
|
||||
var scope = _logger?.BeginScope(state) ?? NoopDisposable.Instance;
|
||||
|
||||
var cacheScope = new CacheScope(scope, state, () => Scopes.TryPop(out _));
|
||||
|
||||
Scopes.Push(cacheScope);
|
||||
|
||||
return cacheScope;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool IsEnabled(LogLevel logLevel)
|
||||
{
|
||||
if (_logger == null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return _logger.IsEnabled(logLevel);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disposes resources held by this instance.
|
||||
/// </summary>
|
||||
/// <param name="disposing"><c>true</c> if disposing unmanaged types; otherwise <c>false</c>.</param>
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
_factory?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void WriteLogEntry<TState>(
|
||||
LogLevel logLevel,
|
||||
EventId eventId,
|
||||
TState state,
|
||||
string message,
|
||||
Exception? exception,
|
||||
Func<TState, Exception?, string> formatter)
|
||||
{
|
||||
var entry = new LogEntry(
|
||||
logLevel,
|
||||
eventId,
|
||||
state,
|
||||
exception,
|
||||
message,
|
||||
Scopes.Select(s => s.State).ToArray());
|
||||
|
||||
_logEntries.Add(entry);
|
||||
|
||||
_logger?.Log(logLevel, eventId, state, exception, formatter);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the count of cached log entries.
|
||||
/// </summary>
|
||||
public int Count => _logEntries.Count;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the cached log entries.
|
||||
/// </summary>
|
||||
public IReadOnlyCollection<LogEntry> Entries => new ReadOnlyCollection<LogEntry>(_logEntries);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the last entry logged.
|
||||
/// </summary>
|
||||
public LogEntry? Last
|
||||
{
|
||||
get
|
||||
{
|
||||
var count = _logEntries.Count;
|
||||
|
||||
if (count == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return _logEntries[count - 1];
|
||||
}
|
||||
}
|
||||
|
||||
private static ConcurrentStack<CacheScope> Scopes
|
||||
{
|
||||
get
|
||||
{
|
||||
var scopes = _scopes.Value;
|
||||
|
||||
if (scopes == null)
|
||||
{
|
||||
scopes = new ConcurrentStack<CacheScope>();
|
||||
|
||||
_scopes.Value = scopes;
|
||||
}
|
||||
|
||||
return scopes;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
namespace Divergic.Logging.Xunit
|
||||
{
|
||||
using System;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="CacheLogger{T}" />
|
||||
/// class provides a cache logger for <see cref="ILogger{TCategoryName}" />.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The generic type of logger.</typeparam>
|
||||
public class CacheLogger<T> : CacheLogger, ICacheLogger<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="CacheLogger{T}" /> class.
|
||||
/// </summary>
|
||||
public CacheLogger()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="CacheLogger{T}" /> class.
|
||||
/// </summary>
|
||||
/// <param name="logger">The source logger.</param>
|
||||
/// <exception cref="ArgumentNullException">The <paramref name="logger" /> is <c>null</c>.</exception>
|
||||
public CacheLogger(ILogger logger)
|
||||
: base(logger)
|
||||
{
|
||||
}
|
||||
|
||||
internal CacheLogger(ILogger logger, ILoggerFactory factory)
|
||||
: base(logger, factory)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
namespace Divergic.Logging.Xunit
|
||||
{
|
||||
using System;
|
||||
|
||||
internal class CacheScope : IDisposable
|
||||
{
|
||||
private readonly Action _onScopeEnd;
|
||||
private readonly IDisposable _scope;
|
||||
|
||||
public CacheScope(IDisposable scope, object? state, Action onScopeEnd)
|
||||
{
|
||||
_scope = scope;
|
||||
State = state;
|
||||
_onScopeEnd = onScopeEnd;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// Pass on the end scope request
|
||||
_scope.Dispose();
|
||||
|
||||
// Clean up the scope in the cache logger
|
||||
_onScopeEnd.Invoke();
|
||||
}
|
||||
|
||||
public object? State { get; }
|
||||
}
|
||||
}
|
||||
@@ -1,93 +0,0 @@
|
||||
namespace Divergic.Logging.Xunit
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="DefaultFormatter" />
|
||||
/// class provides the default formatting of log messages for xUnit test output.
|
||||
/// </summary>
|
||||
public class DefaultFormatter : ILogFormatter
|
||||
{
|
||||
private readonly LoggingConfig _config;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DefaultFormatter" /> class.
|
||||
/// </summary>
|
||||
/// <param name="config">The logging configuration.</param>
|
||||
/// <exception cref="ArgumentNullException">The <paramref name="config" /> value is <c>null</c>.</exception>
|
||||
public DefaultFormatter(LoggingConfig config)
|
||||
{
|
||||
_config = config ?? throw new ArgumentNullException(nameof(config));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual string Format(
|
||||
int scopeLevel,
|
||||
string categoryName,
|
||||
LogLevel logLevel,
|
||||
EventId eventId,
|
||||
string message,
|
||||
Exception? exception)
|
||||
{
|
||||
var padding = new string(' ', scopeLevel * _config.ScopePaddingSpaces);
|
||||
var parts = new List<string>(2);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(message) == false)
|
||||
{
|
||||
var part = string.Format(CultureInfo.InvariantCulture, FormatMask, padding, logLevel, eventId.Id,
|
||||
message);
|
||||
|
||||
part = MaskSensitiveValues(part);
|
||||
|
||||
parts.Add(part);
|
||||
}
|
||||
|
||||
if (exception != null)
|
||||
{
|
||||
var part = string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
FormatMask,
|
||||
padding,
|
||||
logLevel,
|
||||
eventId.Id,
|
||||
exception);
|
||||
|
||||
part = MaskSensitiveValues(part);
|
||||
|
||||
parts.Add(part);
|
||||
}
|
||||
|
||||
return string.Join(Environment.NewLine, parts);
|
||||
}
|
||||
|
||||
private string MaskSensitiveValues(string value)
|
||||
{
|
||||
const string mask = "****";
|
||||
|
||||
for (var index = 0; index < _config.SensitiveValues.Count; index++)
|
||||
{
|
||||
var sensitiveValue = _config.SensitiveValues[index];
|
||||
|
||||
value = value.Replace(sensitiveValue, mask);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the string format mask used to generate a log message.
|
||||
/// </summary>
|
||||
/// <remarks>The format values are:
|
||||
/// <ul>
|
||||
/// <li>0: Padding</li>
|
||||
/// <li>1: Level</li>
|
||||
/// <li>2: Event Id</li>
|
||||
/// <li>3: Message</li>
|
||||
/// </ul>
|
||||
/// </remarks>
|
||||
protected virtual string FormatMask { get; } = "{0}{1} [{2}]: {3}";
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
namespace Divergic.Logging.Xunit;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="DefaultScopeFormatter" />
|
||||
/// class is used to provide log message formatting logic for scope beginning and end messages.
|
||||
/// </summary>
|
||||
public class DefaultScopeFormatter : DefaultFormatter
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DefaultScopeFormatter" /> class.
|
||||
/// </summary>
|
||||
/// <param name="config">The logging configuration.</param>
|
||||
public DefaultScopeFormatter(LoggingConfig config) : base(config)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override string FormatMask { get; } = "{0}{3}";
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net48;net8.0</TargetFrameworks>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<SignAssembly>True</SignAssembly>
|
||||
<AssemblyOriginatorKeyFile>..\..\Velopack.snk</AssemblyOriginatorKeyFile>
|
||||
<NoWarn>$(NoWarn);IDE0161</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="8.0.0" />
|
||||
<PackageReference Include="System.Text.Json" Version="8.0.4" />
|
||||
<PackageReference Include="Xunit.Abstractions" Version="2.0.3" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -1,113 +0,0 @@
|
||||
namespace Divergic.Logging.Xunit
|
||||
{
|
||||
using System;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="FilterLogger" />
|
||||
/// class provides common filtering logic to ensure log records are only written for enabled loggers where there is a
|
||||
/// formatted message and/or exception to log.
|
||||
/// </summary>
|
||||
public abstract class FilterLogger : ILogger
|
||||
{
|
||||
private readonly string _nullFormatted = "[null]";
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract IDisposable? BeginScope<TState>(TState state) where TState : notnull;
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract bool IsEnabled(LogLevel logLevel);
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Log<TState>(
|
||||
LogLevel logLevel,
|
||||
EventId eventId,
|
||||
TState state,
|
||||
Exception? exception,
|
||||
Func<TState, Exception?, string> formatter)
|
||||
{
|
||||
formatter = formatter ?? throw new ArgumentNullException(nameof(formatter));
|
||||
|
||||
if (IsEnabled(logLevel) == false)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var formattedMessage = FormatMessage(state, exception, formatter);
|
||||
|
||||
if (ShouldFilter(formattedMessage, exception))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
WriteLogEntry(logLevel, eventId, state, formattedMessage, exception, formatter);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the formatted log message.
|
||||
/// </summary>
|
||||
/// <typeparam name="TState">The type of state data to log.</typeparam>
|
||||
/// <param name="state">The state data to log.</param>
|
||||
/// <param name="exception">The exception to log.</param>
|
||||
/// <param name="formatter">The formatter that creates the log message.</param>
|
||||
/// <returns>The log message.</returns>
|
||||
protected string FormatMessage<TState>(
|
||||
TState state,
|
||||
Exception? exception,
|
||||
Func<TState, Exception?, string> formatter)
|
||||
{
|
||||
formatter = formatter ?? throw new ArgumentNullException(nameof(formatter));
|
||||
|
||||
#pragma warning disable CA1062 // Validate arguments of public methods
|
||||
var formattedMessage = formatter(state, exception);
|
||||
#pragma warning restore CA1062 // Validate arguments of public methods
|
||||
|
||||
// Clear the message if it looks like a null formatted message
|
||||
if (formattedMessage == _nullFormatted)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
return formattedMessage;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the log message should be filtered and not written.
|
||||
/// </summary>
|
||||
/// <param name="message">The message to log.</param>
|
||||
/// <param name="exception">The exception to log.</param>
|
||||
/// <returns><c>true</c> if the log should not be written; otherwise <c>false</c>.</returns>
|
||||
protected virtual bool ShouldFilter(string message, Exception? exception)
|
||||
{
|
||||
if (exception != null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(message))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes the log entry with the specified values.
|
||||
/// </summary>
|
||||
/// <typeparam name="TState">The type of state data to log.</typeparam>
|
||||
/// <param name="logLevel">The log level.</param>
|
||||
/// <param name="eventId">The event id.</param>
|
||||
/// <param name="state">The state data to log.</param>
|
||||
/// <param name="message">The formatted message.</param>
|
||||
/// <param name="exception">The exception to log.</param>
|
||||
/// <param name="formatter">The formatter that creates the log message.</param>
|
||||
protected abstract void WriteLogEntry<TState>(
|
||||
LogLevel logLevel,
|
||||
EventId eventId,
|
||||
TState state,
|
||||
string message,
|
||||
Exception? exception,
|
||||
Func<TState, Exception?, string> formatter);
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
namespace Divergic.Logging.Xunit
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="ICacheLogger" />
|
||||
/// interface defines the members for recording and accessing log entries.
|
||||
/// </summary>
|
||||
public interface ICacheLogger : ILogger, IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the number of cache entries recorded.
|
||||
/// </summary>
|
||||
int Count { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the recorded cache entries.
|
||||
/// </summary>
|
||||
IReadOnlyCollection<LogEntry> Entries { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the latest cache entry.
|
||||
/// </summary>
|
||||
LogEntry? Last { get; }
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
namespace Divergic.Logging.Xunit
|
||||
{
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="ICacheLogger" />
|
||||
/// interface defines the members for recording and accessing log entries.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of class using the cache.</typeparam>
|
||||
public interface ICacheLogger<out T> : ICacheLogger, ILogger<T>
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
namespace Divergic.Logging.Xunit
|
||||
{
|
||||
using System;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="ILogFormatter" />
|
||||
/// interface defines the members for formatting log messages.
|
||||
/// </summary>
|
||||
public interface ILogFormatter
|
||||
{
|
||||
/// <summary>
|
||||
/// Formats the log message with the specified values.
|
||||
/// </summary>
|
||||
/// <param name="scopeLevel">The number of active logging scopes.</param>
|
||||
/// <param name="categoryName">The logger name.</param>
|
||||
/// <param name="logLevel">The log level.</param>
|
||||
/// <param name="eventId">The event id.</param>
|
||||
/// <param name="message">The log message.</param>
|
||||
/// <param name="exception">The exception to be logged.</param>
|
||||
/// <returns>The formatted log message.</returns>
|
||||
string Format(
|
||||
int scopeLevel,
|
||||
string categoryName,
|
||||
LogLevel logLevel,
|
||||
EventId eventId,
|
||||
string message,
|
||||
Exception? exception);
|
||||
}
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
namespace Divergic.Logging.Xunit
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="LogEntry" />
|
||||
/// class is used to identify the data related to a log entry.
|
||||
/// </summary>
|
||||
public class LogEntry
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="LogEntry" /> class.
|
||||
/// </summary>
|
||||
/// <param name="logLevel">The log level.</param>
|
||||
/// <param name="eventId">The event id.</param>
|
||||
/// <param name="state">The state.</param>
|
||||
/// <param name="exception">The exception.</param>
|
||||
/// <param name="message">The message.</param>
|
||||
/// <param name="scopes">The currently active scopes.</param>
|
||||
public LogEntry(
|
||||
LogLevel logLevel,
|
||||
EventId eventId,
|
||||
object? state,
|
||||
Exception? exception,
|
||||
string message,
|
||||
IReadOnlyCollection<object?> scopes)
|
||||
{
|
||||
LogLevel = logLevel;
|
||||
EventId = eventId;
|
||||
State = state;
|
||||
Exception = exception;
|
||||
Message = message;
|
||||
Scopes = scopes;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the event id of the entry.
|
||||
/// </summary>
|
||||
public EventId EventId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the exception of the entry.
|
||||
/// </summary>
|
||||
public Exception? Exception { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the log level of the entry.
|
||||
/// </summary>
|
||||
public LogLevel LogLevel { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the message of the entry.
|
||||
/// </summary>
|
||||
public string Message { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the scopes active at the time of the call to <see cref="ILogger.Log{TState}" />
|
||||
/// </summary>
|
||||
public IReadOnlyCollection<object?> Scopes { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the state of the entry.
|
||||
/// </summary>
|
||||
public object? State { get; }
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
namespace Divergic.Logging.Xunit
|
||||
{
|
||||
using System;
|
||||
using global::Xunit.Abstractions;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="LogFactory" />
|
||||
/// class is used to create <see cref="ILogger" /> instances.
|
||||
/// </summary>
|
||||
public static class LogFactory
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates an <see cref="ILoggerFactory" /> instance that is configured for xUnit output.
|
||||
/// </summary>
|
||||
/// <param name="output">The test output.</param>
|
||||
/// <param name="config">Optional logging configuration.</param>
|
||||
/// <returns>The logger factory.</returns>
|
||||
/// <exception cref="ArgumentNullException">The <paramref name="output" /> is <c>null</c>.</exception>
|
||||
public static ILoggerFactory Create(
|
||||
ITestOutputHelper output, LoggingConfig? config = null)
|
||||
{
|
||||
output = output ?? throw new ArgumentNullException(nameof(output));
|
||||
|
||||
var factory = new LoggerFactory();
|
||||
|
||||
factory.AddXunit(output, config);
|
||||
|
||||
return factory;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
namespace Divergic.Logging.Xunit
|
||||
{
|
||||
using System;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="LoggerExtensions" />
|
||||
/// class provides extension methods for wrapping <see cref="ILogger" /> instances in <see cref="ICacheLogger" />.
|
||||
/// </summary>
|
||||
public static class LoggerExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns a <see cref="ICacheLogger" /> for the specified logger.
|
||||
/// </summary>
|
||||
/// <param name="logger">The source logger.</param>
|
||||
/// <returns>The cache logger.</returns>
|
||||
/// <exception cref="ArgumentNullException">The <paramref name="logger" /> is <c>null</c>.</exception>
|
||||
public static ICacheLogger WithCache(this ILogger logger)
|
||||
{
|
||||
logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
|
||||
var cacheLogger = new CacheLogger(logger);
|
||||
|
||||
return cacheLogger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a <see cref="ICacheLogger{T}" /> for the specified logger.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of generic logger.</typeparam>
|
||||
/// <param name="logger">The source logger.</param>
|
||||
/// <returns>The cache logger.</returns>
|
||||
/// <exception cref="ArgumentNullException">The <paramref name="logger" /> is <c>null</c>.</exception>
|
||||
public static ICacheLogger<T> WithCache<T>(this ILogger<T> logger)
|
||||
{
|
||||
logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
|
||||
var cacheLogger = new CacheLogger<T>(logger);
|
||||
|
||||
return cacheLogger;
|
||||
}
|
||||
|
||||
internal static ICacheLogger WithCache(this ILogger logger, ILoggerFactory factory)
|
||||
{
|
||||
var cacheLogger = new CacheLogger(logger, factory);
|
||||
|
||||
return cacheLogger;
|
||||
}
|
||||
|
||||
internal static ICacheLogger<T> WithCache<T>(this ILogger<T> logger, ILoggerFactory factory)
|
||||
{
|
||||
var cacheLogger = new CacheLogger<T>(logger, factory);
|
||||
|
||||
return cacheLogger;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
namespace Microsoft.Extensions.Logging
|
||||
{
|
||||
using System;
|
||||
using Divergic.Logging.Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="LoggerFactoryExtensions" />
|
||||
/// class provides extension methods for configuring <see cref="ILoggerFactory" /> with providers.
|
||||
/// </summary>
|
||||
public static class LoggerFactoryExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Registers the <see cref="TestOutputLoggerProvider" /> in the factory using the specified
|
||||
/// <see cref="ITestOutputHelper" />.
|
||||
/// </summary>
|
||||
/// <param name="factory">The factory to add the provider to.</param>
|
||||
/// <param name="output">The test output reference.</param>
|
||||
/// <param name="config">Optional logging configuration.</param>
|
||||
/// <returns>The logger factory.</returns>
|
||||
/// <exception cref="ArgumentNullException">The <paramref name="factory" /> is <c>null</c>.</exception>
|
||||
/// <exception cref="ArgumentNullException">The <paramref name="output" /> is <c>null</c>.</exception>
|
||||
public static ILoggerFactory AddXunit(this ILoggerFactory factory, ITestOutputHelper output,
|
||||
LoggingConfig? config = null)
|
||||
{
|
||||
factory = factory ?? throw new ArgumentNullException(nameof(factory));
|
||||
output = output ?? throw new ArgumentNullException(nameof(output));
|
||||
|
||||
#pragma warning disable CA2000 // Dispose objects before losing scope
|
||||
var provider = new TestOutputLoggerProvider(output, config);
|
||||
#pragma warning restore CA2000 // Dispose objects before losing scope
|
||||
|
||||
#pragma warning disable CA1062 // Validate arguments of public methods
|
||||
factory.AddProvider(provider);
|
||||
#pragma warning restore CA1062 // Validate arguments of public methods
|
||||
|
||||
return factory;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
// ReSharper disable once CheckNamespace
|
||||
namespace Microsoft.Extensions.Logging
|
||||
{
|
||||
using System;
|
||||
using Divergic.Logging.Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="LoggingBuilderExtensions" />
|
||||
/// class provides extension methods for the <see cref="ILoggingBuilder" /> interface.
|
||||
/// </summary>
|
||||
public static class LoggingBuilderExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds a logger to writes to the xUnit test output to the specified logging builder.
|
||||
/// </summary>
|
||||
/// <param name="builder">The logging builder.</param>
|
||||
/// <param name="output">The xUnit test output helper.</param>
|
||||
/// <param name="config">Optional logging configuration.</param>
|
||||
public static void AddXunit(this ILoggingBuilder builder, ITestOutputHelper output,
|
||||
LoggingConfig? config = null)
|
||||
{
|
||||
builder = builder ?? throw new ArgumentNullException(nameof(builder));
|
||||
output = output ?? throw new ArgumentNullException(nameof(output));
|
||||
|
||||
// Object is added as a provider to the builder and cannot be disposed of here
|
||||
#pragma warning disable CA2000 // Dispose objects before losing scope
|
||||
var provider = new TestOutputLoggerProvider(output, config);
|
||||
#pragma warning restore CA2000 // Dispose objects before losing scope
|
||||
|
||||
builder.AddProvider(provider);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
namespace Divergic.Logging.Xunit
|
||||
{
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="LoggingConfig" />
|
||||
/// class is used to configure how logging operates.
|
||||
/// </summary>
|
||||
public class LoggingConfig
|
||||
{
|
||||
private ILogFormatter _formatter;
|
||||
private ILogFormatter _scopeFormatter;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="LoggingConfig" /> class.
|
||||
/// </summary>
|
||||
public LoggingConfig()
|
||||
{
|
||||
_formatter = new DefaultFormatter(this);
|
||||
_scopeFormatter = new DefaultScopeFormatter(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a custom formatting for rendering log messages to xUnit test output.
|
||||
/// </summary>
|
||||
public ILogFormatter Formatter
|
||||
{
|
||||
get => _formatter;
|
||||
set =>
|
||||
_formatter = value ?? throw new ArgumentNullException(nameof(value));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether exceptions thrown while logging outside of the test execution will be ignored.
|
||||
/// </summary>
|
||||
public bool IgnoreTestBoundaryException { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the minimum logging level.
|
||||
/// </summary>
|
||||
public LogLevel LogLevel { get; set; } = LogLevel.Trace;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a custom formatting for rendering scope beginning and end messages to xUnit test output.
|
||||
/// </summary>
|
||||
public ILogFormatter ScopeFormatter
|
||||
{
|
||||
get => _scopeFormatter;
|
||||
set =>
|
||||
_scopeFormatter = value ?? throw new ArgumentNullException(nameof(value));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Identifies the number of spaces to use for indenting scopes.
|
||||
/// </summary>
|
||||
public int ScopePaddingSpaces { get; set; } = 3;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the set of sensitive values that should be filtered out when writing log messages.
|
||||
/// </summary>
|
||||
public Collection<string> SensitiveValues { get; } = new();
|
||||
}
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
namespace Divergic.Logging.Xunit
|
||||
{
|
||||
using System;
|
||||
using global::Xunit.Abstractions;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="LoggingTestsBase" />
|
||||
/// class is used to provide a simple logging bootstrap class for xUnit test classes.
|
||||
/// </summary>
|
||||
public abstract class LoggingTestsBase : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="LoggingTestsBase" /> class.
|
||||
/// </summary>
|
||||
/// <param name="output">The xUnit test output.</param>
|
||||
/// <param name="logLevel">The minimum log level to output.</param>
|
||||
protected LoggingTestsBase(ITestOutputHelper output, LogLevel logLevel)
|
||||
{
|
||||
Output = output ?? throw new ArgumentNullException(nameof(output));
|
||||
Logger = output.BuildLogger(logLevel);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="LoggingTestsBase" /> class.
|
||||
/// </summary>
|
||||
/// <param name="output">The xUnit test output.</param>
|
||||
/// <param name="config">Optional logging configuration.</param>
|
||||
protected LoggingTestsBase(ITestOutputHelper output, LoggingConfig? config = null)
|
||||
{
|
||||
Output = output ?? throw new ArgumentNullException(nameof(output));
|
||||
Logger = output.BuildLogger(config);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disposes resources held by this instance.
|
||||
/// </summary>
|
||||
/// <param name="disposing"><c>true</c> if disposing unmanaged types; otherwise <c>false</c>.</param>
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
Logger.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the logger instance.
|
||||
/// </summary>
|
||||
protected ICacheLogger Logger { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the xUnit test output.
|
||||
/// </summary>
|
||||
protected ITestOutputHelper Output { get; }
|
||||
}
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
namespace Divergic.Logging.Xunit
|
||||
{
|
||||
using System;
|
||||
using global::Xunit.Abstractions;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="LoggingTestsBase{T}" />
|
||||
/// class is used to provide a simple logging bootstrap class for xUnit test classes.
|
||||
/// </summary>
|
||||
public abstract class LoggingTestsBase<T> : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="LoggingTestsBase{T}" /> class.
|
||||
/// </summary>
|
||||
/// <param name="output">The xUnit test output.</param>
|
||||
/// <param name="logLevel">The minimum log level to output.</param>
|
||||
protected LoggingTestsBase(ITestOutputHelper output, LogLevel logLevel)
|
||||
{
|
||||
Output = output ?? throw new ArgumentNullException(nameof(output));
|
||||
Logger = output.BuildLoggerFor<T>(logLevel);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="LoggingTestsBase{T}" /> class.
|
||||
/// </summary>
|
||||
/// <param name="output">The xUnit test output.</param>
|
||||
/// <param name="config">Optional logging configuration.</param>
|
||||
protected LoggingTestsBase(ITestOutputHelper output, LoggingConfig? config = null)
|
||||
{
|
||||
Output = output ?? throw new ArgumentNullException(nameof(output));
|
||||
Logger = output.BuildLoggerFor<T>(config);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disposes resources held by this instance.
|
||||
/// </summary>
|
||||
/// <param name="disposing"><c>true</c> if disposing unmanaged types; otherwise <c>false</c>.</param>
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
Logger.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the logger instance.
|
||||
/// </summary>
|
||||
protected ICacheLogger<T> Logger { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the xUnit test output.
|
||||
/// </summary>
|
||||
protected ITestOutputHelper Output { get; }
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
namespace Divergic.Logging.Xunit
|
||||
{
|
||||
using System;
|
||||
|
||||
internal class NoopDisposable : IDisposable
|
||||
{
|
||||
public static readonly NoopDisposable Instance = new NoopDisposable();
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// No-op
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,129 +0,0 @@
|
||||
namespace Divergic.Logging.Xunit
|
||||
{
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.Text.Json;
|
||||
using global::Xunit.Abstractions;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
internal class ScopeWriter : IDisposable
|
||||
{
|
||||
private readonly string _category;
|
||||
private readonly LoggingConfig _config;
|
||||
private readonly int _depth;
|
||||
private readonly Action _onScopeEnd;
|
||||
private readonly ITestOutputHelper _output;
|
||||
private readonly object? _state;
|
||||
private string _scopeMessage = string.Empty;
|
||||
private string _structuredStateData = string.Empty;
|
||||
|
||||
public ScopeWriter(
|
||||
ITestOutputHelper output,
|
||||
object? state,
|
||||
int depth,
|
||||
string category,
|
||||
Action onScopeEnd,
|
||||
LoggingConfig config)
|
||||
{
|
||||
_output = output;
|
||||
_state = state;
|
||||
_depth = depth;
|
||||
_category = category;
|
||||
_onScopeEnd = onScopeEnd;
|
||||
_config = config;
|
||||
|
||||
DetermineScopeStateMessage();
|
||||
|
||||
var scopeStartMessage = BuildScopeStateMessage(false);
|
||||
|
||||
WriteLog(_depth, scopeStartMessage);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(_structuredStateData) == false)
|
||||
{
|
||||
// Add the padding to the structured data
|
||||
var structuredLines =
|
||||
_structuredStateData.Split(new[] {Environment.NewLine}, StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
WriteLog(_depth + 1, "Scope data: ");
|
||||
|
||||
foreach (var structuredLine in structuredLines)
|
||||
{
|
||||
WriteLog(_depth + 1, structuredLine);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void WriteLog(int depth, string message)
|
||||
{
|
||||
var formattedMessage = _config.ScopeFormatter.Format(depth, _category, LogLevel.Information, 0, message, null);
|
||||
|
||||
_output.WriteLine(formattedMessage);
|
||||
|
||||
// Write the message to the output window
|
||||
Trace.WriteLine(formattedMessage);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
var scopeStartMessage = BuildScopeStateMessage(true);
|
||||
|
||||
_output.WriteLine(scopeStartMessage);
|
||||
|
||||
_onScopeEnd.Invoke();
|
||||
}
|
||||
|
||||
private string BuildScopeStateMessage(bool isScopeEnd)
|
||||
{
|
||||
var endScopeMarker = isScopeEnd ? "/" : string.Empty;
|
||||
const string format = "<{0}{1}>";
|
||||
|
||||
var message = string.Format(CultureInfo.InvariantCulture, format, endScopeMarker, _scopeMessage);
|
||||
|
||||
var formattedMessage =
|
||||
_config.ScopeFormatter.Format(_depth, _category, LogLevel.Information, 0, message, null);
|
||||
|
||||
return formattedMessage;
|
||||
}
|
||||
|
||||
private void DetermineScopeStateMessage()
|
||||
{
|
||||
const string scopeMarker = "Scope: ";
|
||||
var defaultScopeMessage = "Scope " + (_depth + 1);
|
||||
|
||||
if (_state == null)
|
||||
{
|
||||
_scopeMessage = defaultScopeMessage;
|
||||
}
|
||||
else if (_state is string state)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(state))
|
||||
{
|
||||
_scopeMessage = defaultScopeMessage;
|
||||
}
|
||||
else
|
||||
{
|
||||
_scopeMessage = scopeMarker + state;
|
||||
}
|
||||
}
|
||||
else if (_state.GetType().IsValueType)
|
||||
{
|
||||
_scopeMessage = scopeMarker + _state;
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
// The data is probably a complex object or a structured log entry
|
||||
_structuredStateData = JsonSerializer.Serialize(_state, SerializerSettings.Default);
|
||||
}
|
||||
catch (JsonException ex)
|
||||
{
|
||||
_structuredStateData = ex.ToString();
|
||||
}
|
||||
|
||||
_scopeMessage = defaultScopeMessage;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
namespace Divergic.Logging.Xunit
|
||||
{
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="SerializerSettings" />
|
||||
/// class provides access to settings for JSON serialization.
|
||||
/// </summary>
|
||||
public static class SerializerSettings
|
||||
{
|
||||
private static JsonSerializerOptions BuildSerializerSettings()
|
||||
{
|
||||
var settings = new JsonSerializerOptions
|
||||
{
|
||||
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
|
||||
WriteIndented = true
|
||||
};
|
||||
|
||||
return settings;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the default serializer settings.
|
||||
/// </summary>
|
||||
public static JsonSerializerOptions Default { get; } = BuildSerializerSettings();
|
||||
}
|
||||
}
|
||||
@@ -1,183 +0,0 @@
|
||||
// ReSharper disable once CheckNamespace
|
||||
namespace Xunit.Abstractions
|
||||
{
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Divergic.Logging.Xunit;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="TestOutputHelperExtensions" /> class provides extension methods for the
|
||||
/// <see cref="ITestOutputHelper" />.
|
||||
/// </summary>
|
||||
public static class TestOutputHelperExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Builds a logger from the specified test output helper.
|
||||
/// </summary>
|
||||
/// <param name="output">The test output helper.</param>
|
||||
/// <param name="memberName">
|
||||
/// The member to create the logger for. This is automatically populated using <see cref="CallerMemberNameAttribute" />
|
||||
/// .
|
||||
/// </param>
|
||||
/// <returns>The logger.</returns>
|
||||
/// <exception cref="ArgumentNullException">The <paramref name="output" /> is <c>null</c>.</exception>
|
||||
public static ICacheLogger BuildLogger(
|
||||
this ITestOutputHelper output,
|
||||
[CallerMemberName] string memberName = "")
|
||||
{
|
||||
return BuildLogger(output, null, memberName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds a logger from the specified test output helper.
|
||||
/// </summary>
|
||||
/// <param name="output">The test output helper.</param>
|
||||
/// <param name="logLevel">The minimum log level to output.</param>
|
||||
/// <param name="memberName">
|
||||
/// The member to create the logger for. This is automatically populated using <see cref="CallerMemberNameAttribute" />
|
||||
/// .
|
||||
/// </param>
|
||||
/// <returns>The logger.</returns>
|
||||
/// <exception cref="ArgumentNullException">The <paramref name="output" /> is <c>null</c>.</exception>
|
||||
public static ICacheLogger BuildLogger(
|
||||
this ITestOutputHelper output,
|
||||
LogLevel logLevel,
|
||||
[CallerMemberName] string memberName = "")
|
||||
{
|
||||
var config = new LoggingConfig
|
||||
{
|
||||
LogLevel = logLevel
|
||||
};
|
||||
|
||||
return BuildLogger(output, config, memberName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds a logger from the specified test output helper.
|
||||
/// </summary>
|
||||
/// <param name="output">The test output helper.</param>
|
||||
/// <param name="config">Optional logging configuration.</param>
|
||||
/// <param name="memberName">
|
||||
/// The member to create the logger for. This is automatically populated using <see cref="CallerMemberNameAttribute" />
|
||||
/// .
|
||||
/// </param>
|
||||
/// <returns>The logger.</returns>
|
||||
/// <exception cref="ArgumentNullException">The <paramref name="output" /> is <c>null</c>.</exception>
|
||||
public static ICacheLogger BuildLogger(
|
||||
this ITestOutputHelper output,
|
||||
LoggingConfig? config,
|
||||
[CallerMemberName] string memberName = "")
|
||||
{
|
||||
output = output ?? throw new ArgumentNullException(nameof(output));
|
||||
|
||||
// Do not use the using keyword here because the factory will be disposed before the cache logger is finished with it
|
||||
var factory = LogFactory.Create(output, config);
|
||||
|
||||
var logger = factory.CreateLogger(memberName);
|
||||
|
||||
return logger.WithCache(factory);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds a logger from the specified test output helper for the specified type.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type to create the logger for.</typeparam>
|
||||
/// <param name="output">The test output helper.</param>
|
||||
/// <returns>The logger.</returns>
|
||||
/// <exception cref="ArgumentNullException">The <paramref name="output" /> is <c>null</c>.</exception>
|
||||
public static ICacheLogger<T> BuildLoggerFor<T>(this ITestOutputHelper output)
|
||||
{
|
||||
return BuildLoggerFor<T>(output, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds a logger from the specified test output helper for the specified type.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type to create the logger for.</typeparam>
|
||||
/// <param name="output">The test output helper.</param>
|
||||
/// <param name="logLevel">The minimum log level to output.</param>
|
||||
/// <returns>The logger.</returns>
|
||||
/// <exception cref="ArgumentNullException">The <paramref name="output" /> is <c>null</c>.</exception>
|
||||
public static ICacheLogger<T> BuildLoggerFor<T>(this ITestOutputHelper output, LogLevel logLevel)
|
||||
{
|
||||
var config = new LoggingConfig
|
||||
{
|
||||
LogLevel = logLevel
|
||||
};
|
||||
|
||||
return BuildLoggerFor<T>(output, config);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds a logger from the specified test output helper for the specified type.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type to create the logger for.</typeparam>
|
||||
/// <param name="output">The test output helper.</param>
|
||||
/// <param name="config">Optional logging configuration.</param>
|
||||
/// <returns>The logger.</returns>
|
||||
/// <exception cref="ArgumentNullException">The <paramref name="output" /> is <c>null</c>.</exception>
|
||||
public static ICacheLogger<T> BuildLoggerFor<T>(this ITestOutputHelper output, LoggingConfig? config)
|
||||
{
|
||||
output = output ?? throw new ArgumentNullException(nameof(output));
|
||||
|
||||
// Do not use the using keyword here because the factory will be disposed before the cache logger is finished with it
|
||||
var factory = LogFactory.Create(output, config);
|
||||
|
||||
var logger = factory.CreateLogger<T>();
|
||||
|
||||
return logger.WithCache(factory);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds a logger factory from the specified test output helper.
|
||||
/// </summary>
|
||||
/// <param name="output">The test output helper.</param>
|
||||
/// <returns>The logger factory.</returns>
|
||||
/// <exception cref="ArgumentNullException">The <paramref name="output" /> is <c>null</c>.</exception>
|
||||
public static ILoggerFactory BuildLoggerFactory(
|
||||
this ITestOutputHelper output)
|
||||
{
|
||||
return BuildLoggerFactory(output, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds a logger factory from the specified test output helper.
|
||||
/// </summary>
|
||||
/// <param name="output">The test output helper.</param>
|
||||
/// <param name="logLevel">The minimum log level to output.</param>
|
||||
/// <returns>The logger factory.</returns>
|
||||
/// <exception cref="ArgumentNullException">The <paramref name="output" /> is <c>null</c>.</exception>
|
||||
public static ILoggerFactory BuildLoggerFactory(
|
||||
this ITestOutputHelper output,
|
||||
LogLevel logLevel)
|
||||
{
|
||||
var config = new LoggingConfig
|
||||
{
|
||||
LogLevel = logLevel
|
||||
};
|
||||
|
||||
return BuildLoggerFactory(output, config);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds a logger factory from the specified test output helper.
|
||||
/// </summary>
|
||||
/// <param name="output">The test output helper.</param>
|
||||
/// <param name="config">Optional logging configuration.</param>
|
||||
/// <returns>The logger factory.</returns>
|
||||
/// <exception cref="ArgumentNullException">The <paramref name="output" /> is <c>null</c>.</exception>
|
||||
public static ILoggerFactory BuildLoggerFactory(
|
||||
this ITestOutputHelper output,
|
||||
LoggingConfig? config)
|
||||
{
|
||||
output = output ?? throw new ArgumentNullException(nameof(output));
|
||||
|
||||
// Do not use the using keyword here because the factory will be disposed before the cache logger is finished with it
|
||||
var factory = LogFactory.Create(output, config);
|
||||
|
||||
return factory;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,113 +0,0 @@
|
||||
namespace Divergic.Logging.Xunit
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Diagnostics;
|
||||
using System.Threading;
|
||||
using global::Xunit.Abstractions;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="TestOutputLogger" />
|
||||
/// class is used to provide logging implementation for Xunit.
|
||||
/// </summary>
|
||||
public class TestOutputLogger : FilterLogger
|
||||
{
|
||||
private static readonly AsyncLocal<ConcurrentStack<ScopeWriter>> _scopes =
|
||||
new AsyncLocal<ConcurrentStack<ScopeWriter>>();
|
||||
|
||||
private readonly LoggingConfig _config;
|
||||
private readonly string _categoryName;
|
||||
private readonly ITestOutputHelper _output;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="TestOutputLogger" /> class.
|
||||
/// </summary>
|
||||
/// <param name="categoryName">The category name of the logger.</param>
|
||||
/// <param name="output">The test output helper.</param>
|
||||
/// <param name="config">Optional logging configuration.</param>
|
||||
/// <exception cref="ArgumentException">The <paramref name="categoryName" /> is <c>null</c>, empty or whitespace.</exception>
|
||||
/// <exception cref="ArgumentNullException">The <paramref name="output" /> is <c>null</c>.</exception>
|
||||
public TestOutputLogger(string categoryName, ITestOutputHelper output, LoggingConfig? config = null)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(categoryName))
|
||||
{
|
||||
throw new ArgumentException("No name value has been supplied", nameof(categoryName));
|
||||
}
|
||||
|
||||
_categoryName = categoryName;
|
||||
_output = output ?? throw new ArgumentNullException(nameof(output));
|
||||
_config = config ?? new LoggingConfig();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override IDisposable BeginScope<TState>(TState state)
|
||||
{
|
||||
var scopeWriter = new ScopeWriter(_output, state, Scopes.Count, _categoryName, () => Scopes.TryPop(out _), _config);
|
||||
|
||||
Scopes.Push(scopeWriter);
|
||||
|
||||
return scopeWriter;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool IsEnabled(LogLevel logLevel)
|
||||
{
|
||||
if (logLevel == LogLevel.None)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return logLevel >= _config.LogLevel;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void WriteLogEntry<TState>(
|
||||
LogLevel logLevel,
|
||||
EventId eventId,
|
||||
TState state,
|
||||
string message,
|
||||
Exception? exception,
|
||||
Func<TState, Exception?, string> formatter)
|
||||
{
|
||||
try
|
||||
{
|
||||
WriteLog(logLevel, eventId, message, exception);
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
if (_config.IgnoreTestBoundaryException == false)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void WriteLog(LogLevel logLevel, EventId eventId, string message, Exception? exception)
|
||||
{
|
||||
var formattedMessage = _config.Formatter.Format(Scopes.Count, _categoryName, logLevel, eventId, message, exception);
|
||||
|
||||
_output.WriteLine(formattedMessage);
|
||||
|
||||
// Write the message to the output window
|
||||
Trace.WriteLine(formattedMessage);
|
||||
}
|
||||
|
||||
private static ConcurrentStack<ScopeWriter> Scopes
|
||||
{
|
||||
get
|
||||
{
|
||||
var scopes = _scopes.Value;
|
||||
|
||||
if (scopes == null)
|
||||
{
|
||||
scopes = new ConcurrentStack<ScopeWriter>();
|
||||
|
||||
_scopes.Value = scopes;
|
||||
}
|
||||
|
||||
return scopes;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
namespace Divergic.Logging.Xunit
|
||||
{
|
||||
using System;
|
||||
using global::Xunit.Abstractions;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="TestOutputLoggerProvider" /> class is used to provide Xunit logging to <see cref="ILoggerFactory" />
|
||||
/// .
|
||||
/// </summary>
|
||||
public sealed class TestOutputLoggerProvider : ILoggerProvider
|
||||
{
|
||||
private readonly LoggingConfig? _config;
|
||||
private readonly ITestOutputHelper _output;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TestOutputLoggerProvider" /> class.
|
||||
/// </summary>
|
||||
/// <param name="output">The test output helper.</param>
|
||||
/// <param name="config">Optional logging configuration.</param>
|
||||
/// <exception cref="ArgumentNullException">The <paramref name="output" /> is <c>null</c>.</exception>
|
||||
public TestOutputLoggerProvider(ITestOutputHelper output, LoggingConfig? config = null)
|
||||
{
|
||||
_output = output ?? throw new ArgumentNullException(nameof(output));
|
||||
_config = config;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <exception cref="ArgumentException">The <paramref name="categoryName" /> is <c>null</c>, empty or whitespace.</exception>
|
||||
public ILogger CreateLogger(string categoryName)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(categoryName))
|
||||
{
|
||||
throw new ArgumentException("No categoryName value has been supplied", nameof(categoryName));
|
||||
}
|
||||
|
||||
return new TestOutputLogger(categoryName, _output, _config);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
// no-op
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
using Divergic.Logging.Xunit;
|
||||
using Neovolve.Logging.Xunit;
|
||||
using Velopack.Packaging.Exceptions;
|
||||
using Velopack.Packaging.Windows;
|
||||
using Velopack.Vpk;
|
||||
|
||||
Reference in New Issue
Block a user