Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MicrosoftILoggerTarget - With ILoggerFactory as constructor parameter #468

Merged
merged 1 commit into from
Jan 24, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 69 additions & 30 deletions src/NLog.Extensions.Logging/Targets/MicrosoftILoggerTarget.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ namespace NLog.Extensions.Logging
[Target("MicrosoftILogger")]
public class MicrosoftILoggerTarget : TargetWithContext
{
private readonly Microsoft.Extensions.Logging.ILoggerFactory _loggerFactory;
private readonly Microsoft.Extensions.Logging.ILogger _logger;

/// <summary>
Expand All @@ -29,22 +30,35 @@ public class MicrosoftILoggerTarget : TargetWithContext
/// <summary>
/// Initializes a new instance of the <see cref="MicrosoftILoggerTarget" /> class.
/// </summary>
/// <param name="logger">Microsoft ILogger instance</param>
/// <param name="logger">Microsoft ILogger singleton instance</param>
public MicrosoftILoggerTarget(Microsoft.Extensions.Logging.ILogger logger)
{
_logger = logger;
Layout = "${message}";
OptimizeBufferReuse = true;
}

/// <summary>
/// Initializes a new instance of the <see cref="MicrosoftILoggerTarget" /> class.
/// </summary>
/// <param name="loggerFactory">Microsoft ILoggerFactory instance</param>
public MicrosoftILoggerTarget(Microsoft.Extensions.Logging.ILoggerFactory loggerFactory)
{
_loggerFactory = loggerFactory;
Layout = "${message}";
OptimizeBufferReuse = true;
}

/// <summary>
/// Converts NLog-LogEvent into Microsoft Extension Logging LogState
/// </summary>
/// <param name="logEvent"></param>
protected override void Write(LogEventInfo logEvent)
{
var ilogger = _logger ?? (string.IsNullOrEmpty(logEvent.LoggerName) ? _loggerFactory.CreateLogger("NLog") : _loggerFactory.CreateLogger(logEvent.LoggerName));

var logLevel = ConvertToLogLevel(logEvent.Level);
if (!_logger.IsEnabled(logLevel))
if (!ilogger.IsEnabled(logLevel))
return;

var eventId = default(EventId);
Expand All @@ -70,10 +84,10 @@ protected override void Write(LogEventInfo logEvent)
contextProperties = null;
}

_logger.Log(ConvertToLogLevel(logEvent.Level), eventId, new LogState(logEvent, layoutMessage, contextProperties), logEvent.Exception, LogStateFormatter);
ilogger.Log(ConvertToLogLevel(logEvent.Level), eventId, new LogState(logEvent, layoutMessage, contextProperties), logEvent.Exception, (s,ex) => LogStateFormatter(s));
}

private struct LogState : IReadOnlyList<KeyValuePair<string, object>>
private struct LogState : IReadOnlyList<KeyValuePair<string, object>>, IEquatable<LogState>
{
private readonly LogEventInfo _logEvent;
public readonly string LayoutMessage;
Expand All @@ -85,11 +99,11 @@ private struct LogState : IReadOnlyList<KeyValuePair<string, object>>
{
get
{
if (_logEvent.HasProperties && TryGetPropertyFromIndex(_logEvent.Properties, CreateLogEventProperty, ref index, out var property))
if (_logEvent.HasProperties && TryGetLogEventProperty(_logEvent.Properties, ref index, out var property))
{
return property;
}
if (_contextProperties != null && TryGetPropertyFromIndex(_contextProperties, p => p, ref index, out var contextProperty))
if (_contextProperties != null && TryGetContextProperty(_contextProperties, ref index, out var contextProperty))
{
return contextProperty;
}
Expand All @@ -112,7 +126,7 @@ public LogState(LogEventInfo logEvent, string layoutMessage, IDictionary<string,
IEnumerable<KeyValuePair<string, object>> allProperties = _contextProperties?.Concat(originalMessage) ?? originalMessage;
if (_logEvent.HasProperties)
{
allProperties = _logEvent.Properties.Select(CreateLogEventProperty).Concat(allProperties);
allProperties = _logEvent.Properties.Select(p => CreateLogEventProperty(p)).Concat(allProperties);
}
return allProperties.GetEnumerator();
}
Expand All @@ -122,40 +136,65 @@ IEnumerator IEnumerable.GetEnumerator()
return GetEnumerator();
}

private static bool TryGetPropertyFromIndex<TKey, TValue>(ICollection<KeyValuePair<TKey, TValue>> properties, Func<KeyValuePair<TKey, TValue>, KeyValuePair<string, object>> converter, ref int index, out KeyValuePair<string, object> property)
private KeyValuePair<string, object> CreateOriginalFormatProperty()
{
if (index < properties.Count)
{
foreach (var prop in properties)
{
if (index-- == 0)
{
property = converter(prop);
return true;
}
}
}
else
{
index -= properties.Count;
}
return new KeyValuePair<string, object>(NLogLogger.OriginalFormatPropertyName, _logEvent.Message);
}

property = default;
return false;
public bool Equals(LogState other)
{
return ReferenceEquals(_logEvent, other._logEvent);
}

private static KeyValuePair<string, object> CreateLogEventProperty(KeyValuePair<object, object> prop)
public override bool Equals(object obj)
{
return new KeyValuePair<string, object>(prop.Key.ToString(), prop.Value);
return obj is LogState other && Equals(other);
}

private KeyValuePair<string, object> CreateOriginalFormatProperty()
public override int GetHashCode()
{
return new KeyValuePair<string, object>(NLogLogger.OriginalFormatPropertyName, _logEvent.Message);
return _logEvent.GetHashCode();
}
}

private static string LogStateFormatter(LogState logState, Exception _)
private static bool TryGetContextProperty(IDictionary<string, object> contextProperties, ref int index, out KeyValuePair<string, object> contextProperty)
{
return TryGetPropertyFromIndex(contextProperties, p => p, ref index, out contextProperty);
}

private static bool TryGetLogEventProperty(IDictionary<object, object> logEventProperties, ref int index, out KeyValuePair<string, object> logEventProperty)
{
return TryGetPropertyFromIndex(logEventProperties, p => CreateLogEventProperty(p), ref index, out logEventProperty);
}

private static bool TryGetPropertyFromIndex<TKey, TValue>(ICollection<KeyValuePair<TKey, TValue>> properties, Func<KeyValuePair<TKey, TValue>, KeyValuePair<string, object>> converter, ref int index, out KeyValuePair<string, object> property) where TKey : class
{
if (index < properties.Count)
{
foreach (var prop in properties)
{
if (index-- == 0)
{
property = converter(prop);
return true;
}
}
}
else
{
index -= properties.Count;
}

property = default;
return false;
}

private static KeyValuePair<string, object> CreateLogEventProperty(KeyValuePair<object, object> prop)
{
return new KeyValuePair<string, object>(prop.Key.ToString(), prop.Value);
}

private static string LogStateFormatter(LogState logState)
{
return logState.LayoutMessage;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,22 @@ public void SimpleILoggerMessageTest()
Assert.Equal("Hello World", mock.LastLogProperties[0].Value);
}

[Fact]
public void SimpleILoggerFactoryMessageTest()
{
// Arrange
var (logger, mock) = CreateLoggerFactoryMock(out _);

// Act
logger.Info("Hello World");

// Assert
Assert.Single(mock.Loggers);
Assert.Equal("Hello World", mock.Loggers.First().Value.LastLogMessage);
Assert.Single(mock.Loggers.First().Value.LastLogProperties);
Assert.Equal("Hello World", mock.Loggers.First().Value.LastLogProperties[0].Value);
}

[Fact]
public void FilterILoggerMessageTest()
{
Expand Down Expand Up @@ -165,6 +181,7 @@ public void TestOnLogLevel(string levelText, string expectedILoggerLogLevelText)
Assert.Equal("message1", mock.LastLogMessage);
Assert.Equal(expectedLogLevel, mock.LastLogLevel);
}

[Fact]
public void LogWitException()
{
Expand Down Expand Up @@ -192,23 +209,66 @@ private static (Logger, LoggerMock) CreateLoggerMock(out MicrosoftILoggerTarget
{
var logFactory = new LogFactory();
var logConfig = new Config.LoggingConfiguration();
var loggerMock = new LoggerMock();
var loggerMock = new LoggerMock("NLog");
target = new MicrosoftILoggerTarget(loggerMock) { Layout = "${message}" };
logConfig.AddRuleForAllLevels(target);
logFactory.Configuration = logConfig;
var logger = logFactory.GetCurrentClassLogger();
return (logger, loggerMock);
}

private static (Logger, LoggerFactoryMock) CreateLoggerFactoryMock(out MicrosoftILoggerTarget target)
{
var logFactory = new LogFactory();
var logConfig = new Config.LoggingConfiguration();
var loggerFactoryMock = new LoggerFactoryMock();
target = new MicrosoftILoggerTarget(loggerFactoryMock) { Layout = "${message}" };
logConfig.AddRuleForAllLevels(target);
logFactory.Configuration = logConfig;
var logger = logFactory.GetCurrentClassLogger();
return (logger, loggerFactoryMock);
}

class LoggerFactoryMock : Microsoft.Extensions.Logging.ILoggerFactory
{
public readonly Dictionary<string, LoggerMock> Loggers = new Dictionary<string, LoggerMock>();

public void AddProvider(ILoggerProvider provider)
{
// Nothing to do
}

public Microsoft.Extensions.Logging.ILogger CreateLogger(string categoryName)
{
if (!Loggers.TryGetValue(categoryName, out var logger))
{
logger = new LoggerMock(categoryName);
Loggers[categoryName] = logger;
}
return logger;
}

public void Dispose()
{
// Nothing to do
}
}

class LoggerMock : Microsoft.Extensions.Logging.ILogger
{
public readonly string CategoryName;
public Microsoft.Extensions.Logging.LogLevel LastLogLevel;
public string LastLogMessage;
public Exception LastLogException;
public IList<KeyValuePair<string, object>> LastLogProperties;
public EventId LastLogEventId;
public bool EnableAllLevels;

public LoggerMock(string categoryName)
{
CategoryName = categoryName;
}

public IDisposable BeginScope<TState>(TState state)
{
return null;
Expand Down Expand Up @@ -247,6 +307,11 @@ public void Log<TState>(Microsoft.Extensions.Logging.LogLevel logLevel, EventId
throw new ArgumentException($"Property Value mismatch {LastLogProperties[i].Value} <-> {property.Value}");
}
}

public override string ToString()
{
return CategoryName;
}
}
}
}