Skip to content

Commit

Permalink
Added MicrosoftILoggerTarget for Azure Lambda Logging to ILogger (#260)
Browse files Browse the repository at this point in the history
* MicrosoftLoggerTarget for Azure Lambda Logging to ILogger

* ExtensionILoggerTarget for Azure Lambda Logging to ILogger

* ExtensionILoggerTarget for Azure Lambda Logging to ILogger (TargetWithContext)

* ExtensionILoggerTarget for Azure Lambda Logging to ILogger (Code review)

* ExtensionILoggerTarget for Azure Lambda Logging to ILogger (Code review)

* ExtensionILoggerTarget for Azure Lambda Logging to ILogger (Code review)

* ExtensionILoggerTarget for Azure Lambda Logging to ILogger (rename)

* ExtensionILoggerTarget for Azure Lambda Logging to ILogger (rename)
  • Loading branch information
snakefoot authored and 304NotModified committed Mar 31, 2019
1 parent 96e6756 commit 2460b35
Show file tree
Hide file tree
Showing 3 changed files with 318 additions and 22 deletions.
39 changes: 17 additions & 22 deletions examples/NetCore2/ConsoleExample/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,12 @@ private static void Main()
var logger = LogManager.GetCurrentClassLogger();
try
{
var servicesProvider = BuildDi();
var config = new ConfigurationBuilder()
.SetBasePath(System.IO.Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.Build();

var servicesProvider = BuildDi(config);
using (servicesProvider as IDisposable)
{
var runner = servicesProvider.GetRequiredService<Runner>();
Expand All @@ -37,28 +42,18 @@ private static void Main()
}
}

private static IServiceProvider BuildDi()
private static IServiceProvider BuildDi(IConfiguration config)
{
var services = new ServiceCollection();

// Runner is the custom class
services.AddTransient<Runner>();

var config = new ConfigurationBuilder()
.SetBasePath(System.IO.Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.Build();

// configure Logging with NLog
services.AddLogging(loggingBuilder =>
{
loggingBuilder.ClearProviders();
loggingBuilder.SetMinimumLevel(Microsoft.Extensions.Logging.LogLevel.Trace);
loggingBuilder.AddNLog(config);
});

var serviceProvider = services.BuildServiceProvider();
return serviceProvider;
return new ServiceCollection()
.AddTransient<Runner>() // Runner is the custom class
.AddLogging(loggingBuilder =>
{
// configure Logging with NLog
loggingBuilder.ClearProviders();
loggingBuilder.SetMinimumLevel(Microsoft.Extensions.Logging.LogLevel.Trace);
loggingBuilder.AddNLog(config);
})
.BuildServiceProvider();
}
}

Expand Down
181 changes: 181 additions & 0 deletions src/NLog.Extensions.Logging/Targets/MicrosoftILoggerTarget.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using NLog.Layouts;
using NLog.Targets;
using EventId = Microsoft.Extensions.Logging.EventId;

namespace NLog.Extensions.Logging
{
/// <summary>
/// Forwards NLog LogEvents to Microsoft ILogger-interface with support for NLog Layout-features
/// </summary>
[Target("MicrosoftILogger")]
public class MicrosoftILoggerTarget : TargetWithContext
{
private readonly Microsoft.Extensions.Logging.ILogger _logger;

/// <summary>
/// EventId forwarded to ILogger
/// </summary>
public Layout EventId { get; set; }

/// <summary>
/// EventId-Name forwarded to ILogger
/// </summary>
public Layout EventName { get; set; }

/// <summary>
/// Initializes a new instance of the <see cref="MicrosoftILoggerTarget" /> class.
/// </summary>
/// <param name="logger">Microsoft ILogger instance</param>
public MicrosoftILoggerTarget(Microsoft.Extensions.Logging.ILogger logger)
{
_logger = logger;
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 logLevel = ConvertToLogLevel(logEvent.Level);
if (!_logger.IsEnabled(logLevel))
return;

var eventId = default(EventId);
if (EventId != null)
{
var eventIdValue = RenderLogEvent(EventId, logEvent);
if (!string.IsNullOrEmpty(eventIdValue) && int.TryParse(eventIdValue, out int eventIdParsed) && eventIdParsed != 0)
eventId = new EventId(eventIdParsed);
}
if (EventName != null)
{
var eventNameValue = RenderLogEvent(EventName, logEvent);
if (!string.IsNullOrEmpty(eventNameValue))
eventId = new EventId(eventId.Id, eventNameValue);
}

var layoutMessage = RenderLogEvent(Layout, logEvent);
IDictionary<string, object> contextProperties = null;
if (ContextProperties.Count > 0 || IncludeMdlc || IncludeMdc || IncludeGdc)
{
contextProperties = GetContextProperties(logEvent);
if (contextProperties?.Count == 0)
contextProperties = null;
}

_logger.Log(ConvertToLogLevel(logEvent.Level), eventId, new LogState(logEvent, layoutMessage, contextProperties), logEvent.Exception, LogStateFormatter);
}

struct LogState : IReadOnlyList<KeyValuePair<string, object>>
{
public readonly LogEventInfo LogEvent;
public readonly string LayoutMessage;
public readonly IDictionary<string, object> ContextProperties;

public int Count => (LogEvent.HasProperties ? LogEvent.Properties.Count : 0) + (ContextProperties?.Count ?? 0) + 1;

public KeyValuePair<string, object> this[int index]
{
get
{
if (LogEvent.HasProperties)
{
if (TryGetPropertyFromIndex(LogEvent.Properties, CreateLogEventProperty, ref index, out var property))
return property;
}
if (ContextProperties != null)
{
if (TryGetPropertyFromIndex(ContextProperties, p => p, ref index, out var property))
return property;
}
if (index != 0)
throw new ArgumentOutOfRangeException(nameof(index));
return CreateOriginalFormatProperty();
}
}

public LogState(LogEventInfo logEvent, string layoutMessage, IDictionary<string, object> contextProperties)
{
LogEvent = logEvent;
LayoutMessage = layoutMessage;
ContextProperties = contextProperties;
}

public IEnumerator<KeyValuePair<string, object>> GetEnumerator()
{
IList<KeyValuePair<string, object>> originalMessage = new[] { CreateOriginalFormatProperty() };
IEnumerable<KeyValuePair<string, object>> allProperties = ContextProperties?.Concat(originalMessage) ?? originalMessage;
if (LogEvent.HasProperties)
{
allProperties = LogEvent.Properties.Select(prop => CreateLogEventProperty(prop)).Concat(allProperties);
}
return allProperties.GetEnumerator();
}

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)
{
if (index < properties.Count)
{
foreach (var prop in properties)
{
if (index-- == 0)
{
property = converter(prop);
return true;
}
}
}
else
{
index -= properties.Count;
}

property = default(KeyValuePair<string, object>);
return false;
}

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

private KeyValuePair<string, object> CreateOriginalFormatProperty()
{
return new KeyValuePair<string, object>(NLogLogger.OriginalFormatPropertyName, LogEvent.Message);
}
}

static string LogStateFormatter(LogState logState, Exception _)
{
return logState.LayoutMessage;
}

static Microsoft.Extensions.Logging.LogLevel ConvertToLogLevel(NLog.LogLevel logLevel)
{
if (logLevel == NLog.LogLevel.Trace)
return Microsoft.Extensions.Logging.LogLevel.Trace;
else if (logLevel == NLog.LogLevel.Debug)
return Microsoft.Extensions.Logging.LogLevel.Debug;
else if (logLevel == NLog.LogLevel.Info)
return Microsoft.Extensions.Logging.LogLevel.Information;
else if (logLevel == NLog.LogLevel.Warn)
return Microsoft.Extensions.Logging.LogLevel.Warning;
else if (logLevel == NLog.LogLevel.Error)
return Microsoft.Extensions.Logging.LogLevel.Error;
else // if (logLevel == NLog.LogLevel.Fatal)
return Microsoft.Extensions.Logging.LogLevel.Critical;
}
}
}
120 changes: 120 additions & 0 deletions test/NLog.Extensions.Logging.Tests/MicrosoftILoggerTargetTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Extensions.Logging;
using Xunit;

namespace NLog.Extensions.Logging.Tests
{
public class MicrosoftILoggerTargetTests
{
[Fact]
public void SimpleILoggerMessageTest()
{
var logFactory = new NLog.LogFactory();
var logConfig = new NLog.Config.LoggingConfiguration();
var ilogger = new TestLogger();
logConfig.AddRuleForAllLevels(new MicrosoftILoggerTarget(ilogger) { Layout = "${message}" });
logFactory.Configuration = logConfig;
var logger = logFactory.GetCurrentClassLogger();
logger.Info("Hello World");
Assert.Equal("Hello World", ilogger.LastLogMessage);
Assert.Single(ilogger.LastLogProperties);
Assert.Equal("Hello World", ilogger.LastLogProperties[0].Value);
}

[Fact]
public void FilterILoggerMessageTest()
{
var logFactory = new NLog.LogFactory();
var logConfig = new NLog.Config.LoggingConfiguration();
var ilogger = new TestLogger();
logConfig.AddRuleForAllLevels(new MicrosoftILoggerTarget(ilogger) { Layout = "${message}" });
logFactory.Configuration = logConfig;
var logger = logFactory.GetCurrentClassLogger();
logger.Debug("Hello World");
Assert.Equal(null, ilogger.LastLogMessage);
}

[Fact]
public void StructuredILoggerMessageTest()
{
var logFactory = new NLog.LogFactory();
var logConfig = new NLog.Config.LoggingConfiguration();
var ilogger = new TestLogger();
logConfig.AddRuleForAllLevels(new MicrosoftILoggerTarget(ilogger) { Layout = "${message}" });
logFactory.Configuration = logConfig;
var logger = logFactory.GetCurrentClassLogger();
logger.Info("Hello {Planet}", "Earth");
Assert.Equal("Hello \"Earth\"", ilogger.LastLogMessage);
Assert.Equal(2, ilogger.LastLogProperties.Count);
Assert.Equal("Planet", ilogger.LastLogProperties[0].Key);
Assert.Equal("Earth", ilogger.LastLogProperties[0].Value);
Assert.Equal("Hello {Planet}", ilogger.LastLogProperties[1].Value);
}

[Fact]
public void ContextPropertiesILoggerTest()
{
var logFactory = new NLog.LogFactory();
var logConfig = new NLog.Config.LoggingConfiguration();
var ilogger = new TestLogger();
var target = new MicrosoftILoggerTarget(ilogger) { Layout = "${message}" };
target.ContextProperties.Add(new Targets.TargetPropertyWithContext() { Name = "ThreadId", Layout = "${threadid}" });
logConfig.AddRuleForAllLevels(target);
logFactory.Configuration = logConfig;
var logger = logFactory.GetCurrentClassLogger();
logger.Info("Hello {Planet}", "Earth");
Assert.Equal("Hello \"Earth\"", ilogger.LastLogMessage);
Assert.Equal(3, ilogger.LastLogProperties.Count);
Assert.Equal("Planet", ilogger.LastLogProperties[0].Key);
Assert.Equal("Earth", ilogger.LastLogProperties[0].Value);
Assert.Equal("ThreadId", ilogger.LastLogProperties[1].Key);
Assert.Equal(System.Threading.Thread.CurrentThread.ManagedThreadId.ToString(), ilogger.LastLogProperties[1].Value);
Assert.Equal("Hello {Planet}", ilogger.LastLogProperties[2].Value);
}

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

public IDisposable BeginScope<TState>(TState state)
{
return null;
}

public bool IsEnabled(Microsoft.Extensions.Logging.LogLevel logLevel)
{
switch (logLevel)
{
case Microsoft.Extensions.Logging.LogLevel.Trace:
case Microsoft.Extensions.Logging.LogLevel.Debug:
return false;
}
return true;
}

public void Log<TState>(Microsoft.Extensions.Logging.LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
{
LastLogLevel = logLevel;
LastLogEventId = eventId;
LastLogMessage = formatter(state, exception);
LastLogException = exception;
var propertiesList = (state as IReadOnlyList<KeyValuePair<string, object>>);
LastLogProperties = propertiesList?.ToList();
for (int i = 0; i < propertiesList?.Count; ++i)
{
var property = propertiesList[i];
if (property.Key != LastLogProperties[i].Key)
throw new ArgumentException($"Property key mismatch {LastLogProperties[i].Key} <-> {property.Key}");
if (property.Value != LastLogProperties[i].Value)
throw new ArgumentException($"Property Value mismatch {LastLogProperties[i].Value} <-> {property.Value}");
}
}
}
}
}

0 comments on commit 2460b35

Please sign in to comment.