-
-
Notifications
You must be signed in to change notification settings - Fork 244
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
17 changed files
with
370 additions
and
171 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
using System; | ||
using Microsoft.Extensions.DependencyInjection; | ||
using Microsoft.Extensions.DependencyInjection.Extensions; | ||
using Microsoft.Extensions.Logging; | ||
using Xunit.Abstractions; | ||
|
||
namespace Foundatio.Xunit; | ||
|
||
public static class LoggingExtensions | ||
{ | ||
public static TestLogger GetTestLogger(this IServiceProvider serviceProvider) | ||
{ | ||
return serviceProvider.GetRequiredService<TestLogger>(); | ||
} | ||
|
||
public static ILoggingBuilder AddXUnit(this ILoggingBuilder builder, ITestOutputHelper outputHelper, | ||
Action<TestLoggerOptions> configure = null) | ||
{ | ||
|
||
var options = new TestLoggerOptions { | ||
WriteLogEntryFunc = logEntry => | ||
{ | ||
outputHelper.WriteLine(logEntry.ToString(false)); | ||
} | ||
}; | ||
|
||
configure?.Invoke(options); | ||
|
||
return builder.AddXUnit(options); | ||
} | ||
|
||
public static ILoggingBuilder AddXUnit(this ILoggingBuilder builder, Action<TestLoggerOptions> configure) | ||
{ | ||
var options = new TestLoggerOptions(); | ||
configure?.Invoke(options); | ||
return builder.AddXUnit(options); | ||
} | ||
|
||
public static ILoggingBuilder AddXUnit(this ILoggingBuilder builder, TestLoggerOptions options = null) | ||
{ | ||
if (builder == null) | ||
throw new ArgumentNullException(nameof(builder)); | ||
|
||
var loggerProvider = new TestLoggerProvider(options); | ||
builder.AddProvider(loggerProvider); | ||
builder.Services.TryAddSingleton(loggerProvider.Log); | ||
|
||
return builder; | ||
} | ||
|
||
public static ILoggerFactory AddXUnit(this ILoggerFactory factory, Action<TestLoggerOptions> configure = null) | ||
{ | ||
if (factory == null) | ||
throw new ArgumentNullException(nameof(factory)); | ||
|
||
var options = new TestLoggerOptions(); | ||
configure?.Invoke(options); | ||
|
||
factory.AddProvider(new TestLoggerProvider(options)); | ||
|
||
return factory; | ||
} | ||
|
||
public static TestLogger ToTestLogger(this ITestOutputHelper outputHelper, Action<TestLoggerOptions> configure = null) | ||
{ | ||
if (outputHelper == null) | ||
throw new ArgumentNullException(nameof(outputHelper)); | ||
|
||
var options = new TestLoggerOptions(); | ||
options.WriteLogEntryFunc = logEntry => | ||
{ | ||
outputHelper.WriteLine(logEntry.ToString()); | ||
}; | ||
|
||
configure?.Invoke(options); | ||
|
||
var testLogger = new TestLogger(options); | ||
|
||
return testLogger; | ||
} | ||
|
||
public static ILogger<T> ToLogger<T>(this ITestOutputHelper outputHelper, Action<TestLoggerOptions> configure = null) | ||
=> outputHelper.ToTestLogger(configure).CreateLogger<T>(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,112 +1,81 @@ | ||
using System; | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Collections.Immutable; | ||
using System.Linq; | ||
using System.Threading; | ||
using Foundatio.Utility; | ||
using Microsoft.Extensions.Logging; | ||
|
||
namespace Foundatio.Xunit; | ||
|
||
internal class TestLogger : ILogger | ||
public class TestLogger : ILoggerFactory | ||
{ | ||
private readonly TestLoggerFactory _loggerFactory; | ||
private readonly string _categoryName; | ||
private readonly Dictionary<string, LogLevel> _logLevels = new(); | ||
private readonly Queue<LogEntry> _logEntries = new(); | ||
|
||
public TestLogger(string categoryName, TestLoggerFactory loggerFactory) | ||
public TestLogger() | ||
{ | ||
_loggerFactory = loggerFactory; | ||
_categoryName = categoryName; | ||
Options = new TestLoggerOptions(); | ||
} | ||
|
||
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter) | ||
public TestLogger(TestLoggerOptions options) | ||
{ | ||
if (!_loggerFactory.IsEnabled(_categoryName, logLevel)) | ||
return; | ||
Options = options ?? new TestLoggerOptions(); | ||
} | ||
|
||
object[] scopes = CurrentScopeStack.Reverse().ToArray(); | ||
var logEntry = new LogEntry | ||
{ | ||
Date = SystemClock.UtcNow, | ||
LogLevel = logLevel, | ||
EventId = eventId, | ||
State = state, | ||
Exception = exception, | ||
Formatter = (s, e) => formatter((TState)s, e), | ||
CategoryName = _categoryName, | ||
Scopes = scopes | ||
}; | ||
|
||
switch (state) | ||
{ | ||
//case LogData logData: | ||
// logEntry.Properties["CallerMemberName"] = logData.MemberName; | ||
// logEntry.Properties["CallerFilePath"] = logData.FilePath; | ||
// logEntry.Properties["CallerLineNumber"] = logData.LineNumber; | ||
|
||
// foreach (var property in logData.Properties) | ||
// logEntry.Properties[property.Key] = property.Value; | ||
// break; | ||
case IDictionary<string, object> logDictionary: | ||
foreach (var property in logDictionary) | ||
logEntry.Properties[property.Key] = property.Value; | ||
break; | ||
} | ||
public TestLoggerOptions Options { get; } | ||
public IReadOnlyList<LogEntry> LogEntries => _logEntries.ToArray(); | ||
|
||
public void Clear() => _logEntries.Clear(); | ||
|
||
foreach (object scope in scopes) | ||
internal void AddLogEntry(LogEntry logEntry) | ||
{ | ||
lock (_logEntries) | ||
{ | ||
if (!(scope is IDictionary<string, object> scopeData)) | ||
continue; | ||
_logEntries.Enqueue(logEntry); | ||
|
||
foreach (var property in scopeData) | ||
logEntry.Properties[property.Key] = property.Value; | ||
if (_logEntries.Count > Options.MaxLogEntriesToStore) | ||
_logEntries.Dequeue(); | ||
} | ||
|
||
_loggerFactory.AddLogEntry(logEntry); | ||
} | ||
if (Options.WriteLogEntryFunc == null || _logEntriesWritten >= Options.MaxLogEntriesToWrite) | ||
return; | ||
|
||
public bool IsEnabled(LogLevel logLevel) | ||
{ | ||
return _loggerFactory.IsEnabled(_categoryName, logLevel); | ||
try | ||
{ | ||
Options.WriteLogEntry(logEntry); | ||
Interlocked.Increment(ref _logEntriesWritten); | ||
} | ||
catch (Exception) | ||
{ | ||
// ignored | ||
} | ||
} | ||
|
||
public IDisposable BeginScope<TState>(TState state) | ||
{ | ||
if (state == null) | ||
throw new ArgumentNullException(nameof(state)); | ||
|
||
return Push(state); | ||
} | ||
private int _logEntriesWritten = 0; | ||
|
||
public IDisposable BeginScope<TState, TScope>(Func<TState, TScope> scopeFactory, TState state) | ||
public ILogger CreateLogger(string categoryName) | ||
{ | ||
if (state == null) | ||
throw new ArgumentNullException(nameof(state)); | ||
|
||
return Push(scopeFactory(state)); | ||
return new TestLoggerLogger(categoryName, this); | ||
} | ||
|
||
private static readonly AsyncLocal<Wrapper> _currentScopeStack = new(); | ||
public void AddProvider(ILoggerProvider loggerProvider) { } | ||
|
||
private sealed class Wrapper | ||
public bool IsEnabled(string category, LogLevel logLevel) | ||
{ | ||
public ImmutableStack<object> Value { get; set; } | ||
} | ||
if (_logLevels.TryGetValue(category, out var categoryLevel)) | ||
return logLevel >= categoryLevel; | ||
|
||
private static ImmutableStack<object> CurrentScopeStack | ||
{ | ||
get => _currentScopeStack.Value?.Value ?? ImmutableStack.Create<object>(); | ||
set => _currentScopeStack.Value = new Wrapper { Value = value }; | ||
return logLevel >= Options.DefaultMinimumLevel; | ||
} | ||
|
||
private static IDisposable Push(object state) | ||
public void SetLogLevel(string category, LogLevel minLogLevel) | ||
{ | ||
CurrentScopeStack = CurrentScopeStack.Push(state); | ||
return new DisposableAction(Pop); | ||
_logLevels[category] = minLogLevel; | ||
} | ||
|
||
private static void Pop() | ||
public void SetLogLevel<T>(LogLevel minLogLevel) | ||
{ | ||
CurrentScopeStack = CurrentScopeStack.Pop(); | ||
SetLogLevel(TypeHelper.GetTypeDisplayName(typeof(T)), minLogLevel); | ||
} | ||
|
||
public void Dispose() { } | ||
} |
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.