diff --git a/src/NLog.Extensions.Logging/NLog.Extensions.Logging.csproj b/src/NLog.Extensions.Logging/NLog.Extensions.Logging.csproj
index 12325d29..9983108e 100644
--- a/src/NLog.Extensions.Logging/NLog.Extensions.Logging.csproj
+++ b/src/NLog.Extensions.Logging/NLog.Extensions.Logging.csproj
@@ -56,7 +56,7 @@ For ASP.NET Core, use NLog.Web.AspNetCore: https://www.nuget.org/packages/NLog.W
-
+
\ No newline at end of file
diff --git a/src/NLog.Extensions.Logging/NLogLogger.cs b/src/NLog.Extensions.Logging/NLogLogger.cs
index a38f192e..77fc5588 100644
--- a/src/NLog.Extensions.Logging/NLogLogger.cs
+++ b/src/NLog.Extensions.Logging/NLogLogger.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections.Generic;
using Microsoft.Extensions.Logging;
namespace NLog.Extensions.Logging
@@ -11,6 +12,7 @@ internal class NLogLogger : Microsoft.Extensions.Logging.ILogger
private readonly Logger _logger;
private readonly NLogProviderOptions _options;
+ internal const string OriginalFormatPropertyName = "{OriginalFormat}";
private static readonly object EmptyEventId = default(EventId); // Cache boxing of empty EventId-struct
private static readonly object ZeroEventId = default(EventId).Id; // Cache boxing of zero EventId-Value
private Tuple _eventIdPropertyNames;
@@ -35,8 +37,8 @@ public void Log(Microsoft.Extensions.Logging.LogLevel logLevel, EventId
}
var message = formatter(state, exception);
- //message arguments are not needed as it is already checked that the loglevel is enabled.
- var eventInfo = LogEventInfo.Create(nLogLogLevel, _logger.Name, message);
+ var messageTemplate = _options.EnableStructuredLogging ? state as IReadOnlyList> : null;
+ LogEventInfo eventInfo = CreateLogEventInfo(nLogLogLevel, message, messageTemplate);
eventInfo.Exception = exception;
if (!_options.IgnoreEmptyEventId || eventId.Id != 0 || !string.IsNullOrEmpty(eventId.Name))
{
@@ -58,9 +60,64 @@ public void Log(Microsoft.Extensions.Logging.LogLevel logLevel, EventId
eventInfo.Properties[eventIdPropertyNames.Item3] = eventId.Name;
eventInfo.Properties["EventId"] = idIsZero && eventId.Name == null ? EmptyEventId : eventId;
}
+
_logger.Log(eventInfo);
}
+ private LogEventInfo CreateLogEventInfo(LogLevel nLogLogLevel, string message, IReadOnlyList> parameterList)
+ {
+ if (parameterList != null && parameterList.Count > 1)
+ {
+ // More than a single parameter (last parameter is the {OriginalFormat})
+ var firstParameterName = parameterList[0].Key;
+ if (!string.IsNullOrEmpty(firstParameterName))
+ {
+ if (firstParameterName.Length != 1 || !char.IsDigit(firstParameterName[0]))
+ {
+#if NETSTANDARD2_0
+ var originalFormat = parameterList[parameterList.Count - 1];
+ string originalMessage = null;
+ if (originalFormat.Key == OriginalFormatPropertyName)
+ {
+ // Attempt to capture original message with placeholders
+ originalMessage = originalFormat.Value as string;
+ }
+
+ var messageTemplateParameters = new NLogMessageParameterList(parameterList, originalMessage != null);
+ var eventInfo = new LogEventInfo(nLogLogLevel, _logger.Name, originalMessage ?? message, messageTemplateParameters);
+ if (originalMessage != null)
+ {
+ eventInfo.Parameters = new object[messageTemplateParameters.Count + 1];
+ for (int i = 0; i < messageTemplateParameters.Count; ++i)
+ eventInfo.Parameters[i] = messageTemplateParameters[i].Value;
+ eventInfo.Parameters[messageTemplateParameters.Count] = message;
+ eventInfo.MessageFormatter = (l) => (string)l.Parameters[l.Parameters.Length - 1];
+ }
+ return eventInfo;
+#else
+ var eventInfo = LogEventInfo.Create(nLogLogLevel, _logger.Name, message);
+ for (int i = 0; i < parameterList.Count; ++i)
+ {
+ var parameter = parameterList[i];
+ if (string.IsNullOrEmpty(parameter.Key))
+ break; // Skip capture of invalid parameters
+
+ var parameterName = parameter.Key;
+ switch (parameterName[0])
+ {
+ case '@': parameterName = parameterName.Substring(1); break;
+ case '$': parameterName = parameterName.Substring(1); break;
+ }
+ eventInfo.Properties[parameterName] = parameter.Value;
+ }
+ return eventInfo;
+#endif
+ }
+ }
+ }
+ return LogEventInfo.Create(nLogLogLevel, _logger.Name, message);
+ }
+
///
/// Is logging enabled for this logger at this ?
///
@@ -119,7 +176,7 @@ public IDisposable BeginScope(TState state)
{
throw new ArgumentNullException(nameof(state));
}
-
+
return NestedDiagnosticsLogicalContext.Push(state);
}
}
diff --git a/src/NLog.Extensions.Logging/NLogMessageParameterList.cs b/src/NLog.Extensions.Logging/NLogMessageParameterList.cs
new file mode 100644
index 00000000..44de54d6
--- /dev/null
+++ b/src/NLog.Extensions.Logging/NLogMessageParameterList.cs
@@ -0,0 +1,126 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace NLog.Extensions.Logging
+{
+#if NETSTANDARD2_0
+ ///
+ /// Converts Microsoft Extension Logging ParameterList into NLog MessageTemplate ParameterList
+ ///
+ internal class NLogMessageParameterList : IList
+ {
+ private IReadOnlyList> _parameterList;
+
+ public NLogMessageParameterList(IReadOnlyList> parameterList, bool includesOriginalMessage)
+ {
+ List> validParameterList = includesOriginalMessage ? null : new List>();
+ for (int i = 0; i < parameterList.Count; ++i)
+ {
+ if (!string.IsNullOrEmpty(parameterList[i].Key) && (parameterList[i].Key != NLogLogger.OriginalFormatPropertyName || i == parameterList.Count - 1))
+ {
+ if (validParameterList != null)
+ {
+ if (parameterList[i].Key != NLogLogger.OriginalFormatPropertyName)
+ validParameterList.Add(parameterList[i]);
+ }
+ }
+ else
+ {
+ if (validParameterList == null)
+ {
+ validParameterList = new List>();
+ for (int j = 0; j < i; ++i)
+ validParameterList.Add(parameterList[j]);
+ }
+ }
+ }
+ if (validParameterList != null)
+ {
+ validParameterList.Add(new KeyValuePair());
+ }
+ _parameterList = validParameterList ?? parameterList;
+ }
+
+ public NLog.MessageTemplates.MessageTemplateParameter this[int index]
+ {
+ get
+ {
+ var parameter = _parameterList[index];
+ var parameterName = parameter.Key;
+ NLog.MessageTemplates.CaptureType captureType = NLog.MessageTemplates.CaptureType.Normal;
+ switch (parameterName[0])
+ {
+ case '@':
+ parameterName = parameterName.Substring(1);
+ captureType = NLog.MessageTemplates.CaptureType.Serialize;
+ break;
+ case '$':
+ parameterName = parameterName.Substring(1);
+ captureType = NLog.MessageTemplates.CaptureType.Stringify;
+ break;
+ }
+ return new NLog.MessageTemplates.MessageTemplateParameter(parameter.Key, parameter.Value, null, captureType);
+ }
+ set => throw new NotImplementedException();
+ }
+
+ public int Count => _parameterList.Count - 1;
+
+ public bool IsReadOnly => true;
+
+ public void Add(NLog.MessageTemplates.MessageTemplateParameter item)
+ {
+ throw new NotImplementedException();
+ }
+
+ public void Clear()
+ {
+ throw new NotImplementedException();
+ }
+
+ public bool Contains(NLog.MessageTemplates.MessageTemplateParameter item)
+ {
+ throw new NotImplementedException();
+ }
+
+ public void CopyTo(NLog.MessageTemplates.MessageTemplateParameter[] array, int arrayIndex)
+ {
+ throw new NotImplementedException();
+ }
+
+ public IEnumerator GetEnumerator()
+ {
+ return _parameterList.Take(_parameterList.Count - 1).Select(p => new NLog.MessageTemplates.MessageTemplateParameter(p.Key, p.Value, null)).GetEnumerator();
+ }
+
+ public int IndexOf(NLog.MessageTemplates.MessageTemplateParameter item)
+ {
+ throw new NotImplementedException();
+ }
+
+ public void Insert(int index, NLog.MessageTemplates.MessageTemplateParameter item)
+ {
+ throw new NotImplementedException();
+ }
+
+ public bool Remove(NLog.MessageTemplates.MessageTemplateParameter item)
+ {
+ throw new NotImplementedException();
+ }
+
+ public void RemoveAt(int index)
+ {
+ throw new NotImplementedException();
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+ }
+#endif
+}
diff --git a/src/NLog.Extensions.Logging/NLogProviderOptions.cs b/src/NLog.Extensions.Logging/NLogProviderOptions.cs
index 3d3c780d..ea963457 100644
--- a/src/NLog.Extensions.Logging/NLogProviderOptions.cs
+++ b/src/NLog.Extensions.Logging/NLogProviderOptions.cs
@@ -22,6 +22,11 @@ public class NLogProviderOptions
/// default(EventId)
public bool IgnoreEmptyEventId { get; set; }
+ ///
+ /// Attempt to capture parameter names and values and insert into -dictionary
+ ///
+ public bool EnableStructuredLogging { get; set; }
+
/// Initializes a new instance of the class.
public NLogProviderOptions()
{
diff --git a/test/LoggerTests.cs b/test/LoggerTests.cs
index 7864305f..eb30d13e 100644
--- a/test/LoggerTests.cs
+++ b/test/LoggerTests.cs
@@ -24,7 +24,6 @@ public void TestInit()
var target = GetTarget();
Assert.Equal("NLog.Extensions.Logging.Tests.LoggerTests.Runner|DEBUG|init runner |0", target.Logs.FirstOrDefault());
-
}
[Fact]
@@ -34,7 +33,24 @@ public void TestEventId()
var target = GetTarget();
Assert.Equal("NLog.Extensions.Logging.Tests.LoggerTests.Runner|DEBUG|message with id |20", target.Logs.FirstOrDefault());
-
+ }
+
+ [Fact]
+ public void TestParameters()
+ {
+ GetRunner().LogDebugWithParameters();
+
+ var target = GetTarget();
+ Assert.Equal("NLog.Extensions.Logging.Tests.LoggerTests.Runner|DEBUG|message with id and 1 parameters |0", target.Logs.FirstOrDefault());
+ }
+
+ [Fact]
+ public void TestStructuredLogging()
+ {
+ GetRunner().LogDebugWithStructuredParameters();
+
+ var target = GetTarget();
+ Assert.Equal("NLog.Extensions.Logging.Tests.LoggerTests.Runner|DEBUG|message with id and 1 parameters |01", target.Logs.FirstOrDefault());
}
[Theory]
@@ -152,12 +168,11 @@ private static IServiceProvider BuildDi()
var serviceProvider = services.BuildServiceProvider();
var loggerFactory = serviceProvider.GetRequiredService();
- loggerFactory.AddNLog();
+ loggerFactory.AddNLog(new NLogProviderOptions() { EnableStructuredLogging = true });
loggerFactory.ConfigureNLog("nlog.config");
return serviceProvider;
}
-
public class Runner
{
private readonly ILogger _logger;
@@ -167,7 +182,6 @@ public Runner(ILoggerFactory fac)
_logger = fac.CreateLogger();
}
-
public void LogDebugWithId()
{
_logger.LogDebug(20, "message with id");
@@ -200,6 +214,16 @@ public void Log(Microsoft.Extensions.Logging.LogLevel logLevel, int eventId, Exc
}
}
+ public void LogDebugWithParameters()
+ {
+ _logger.LogDebug("message with id and {0} parameters", "1");
+ }
+
+ public void LogDebugWithStructuredParameters()
+ {
+ _logger.LogDebug("message with id and {ParameterCount} parameters", "1");
+ }
+
public void LogWithScope()
{
using (_logger.BeginScope("scope1"))
@@ -211,7 +235,6 @@ public void LogWithScope()
public void Init()
{
_logger.LogDebug("init runner");
-
}
}
}
diff --git a/test/NLog.Extensions.Logging.Tests.csproj b/test/NLog.Extensions.Logging.Tests.csproj
index d4a14c95..c7991f7a 100644
--- a/test/NLog.Extensions.Logging.Tests.csproj
+++ b/test/NLog.Extensions.Logging.Tests.csproj
@@ -22,7 +22,7 @@
-
+
diff --git a/test/nlog.config b/test/nlog.config
index 6ef7b8bf..a5d9aeff 100644
--- a/test/nlog.config
+++ b/test/nlog.config
@@ -8,7 +8,7 @@
+ layout="${logger}|${uppercase:${level}}|${message} ${exception}|${event-properties:EventId}${event-properties:ParameterCount}" />