diff --git a/CHANGELOG.md b/CHANGELOG.md
index 550074df..ba70d57d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,7 +1,8 @@
# Changelog
-### Version 2.6.0
+### Version 2.6.0-vNext
- [NLog Flush should include async delay](https://github.com/Microsoft/ApplicationInsights-dotnet-logging/pull/176)
+- [NLog can include additional ContextProperties](https://github.com/Microsoft/ApplicationInsights-dotnet-logging/pull/183)
### Version 2.6.0-beta3
- [NetStandard Support for TraceListener](https://github.com/Microsoft/ApplicationInsights-dotnet-logging/pull/166)
diff --git a/src/NLogTarget/ApplicationInsightsTarget.cs b/src/NLogTarget/ApplicationInsightsTarget.cs
index 7233d815..cbcbc08c 100644
--- a/src/NLogTarget/ApplicationInsightsTarget.cs
+++ b/src/NLogTarget/ApplicationInsightsTarget.cs
@@ -18,13 +18,14 @@ namespace Microsoft.ApplicationInsights.NLogTarget
using NLog;
using NLog.Common;
+ using NLog.Config;
using NLog.Targets;
///
/// NLog Target that routes all logging output to the Application Insights logging framework.
/// The messages will be uploaded to the Application Insights cloud service.
///
- [Target("ApplicationInsightsTarget")]
+ [Target("ApplicationInsightsTarget")]
public sealed class ApplicationInsightsTarget : TargetWithLayout
{
private TelemetryClient telemetryClient;
@@ -43,6 +44,12 @@ public ApplicationInsightsTarget()
///
public string InstrumentationKey { get; set; }
+ ///
+ /// Gets the array of custom attributes to be passed into the logevent context
+ ///
+ [ArrayParameter(typeof(TargetPropertyWithContext), "contextproperty")]
+ public IList ContextProperties { get; } = new List();
+
///
/// Gets the logging controller we will be using.
///
@@ -78,8 +85,20 @@ internal void BuildPropertyBag(LogEventInfo logEvent, ITelemetry trace)
propertyBag.Add("UserStackFrameNumber", logEvent.UserStackFrameNumber.ToString(CultureInfo.InvariantCulture));
}
- this.LoadGlobalDiagnosticsContextProperties(propertyBag);
- this.LoadLogEventProperties(logEvent, propertyBag);
+ for (int i = 0; i < this.ContextProperties.Count; ++i)
+ {
+ var contextProperty = this.ContextProperties[i];
+ if (!string.IsNullOrEmpty(contextProperty.Name))
+ {
+ string propertyValue = contextProperty.Layout?.Render(logEvent);
+ this.PopulatePropertyBag(propertyBag, contextProperty.Name, propertyValue);
+ }
+ }
+
+ if (logEvent.HasProperties)
+ {
+ this.LoadLogEventProperties(logEvent, propertyBag);
+ }
}
///
@@ -106,7 +125,7 @@ protected override void Write(LogEventInfo logEvent)
{
if (logEvent == null)
{
- throw new ArgumentNullException("logEvent");
+ throw new ArgumentNullException(nameof(logEvent));
}
this.lastLogEventTime = DateTime.UtcNow;
@@ -165,7 +184,7 @@ private void SendException(LogEventInfo logEvent)
private void SendTrace(LogEventInfo logEvent)
{
string logMessage = this.Layout.Render(logEvent);
-
+
var trace = new TraceTelemetry(logMessage)
{
SeverityLevel = this.GetSeverityLevel(logEvent.Level)
@@ -175,20 +194,11 @@ private void SendTrace(LogEventInfo logEvent)
this.telemetryClient.Track(trace);
}
- private void LoadGlobalDiagnosticsContextProperties(IDictionary propertyBag)
- {
- foreach (string key in GlobalDiagnosticsContext.GetNames())
- {
- this.PopulatePropertyBag(propertyBag, key, GlobalDiagnosticsContext.GetObject(key));
- }
- }
-
private void LoadLogEventProperties(LogEventInfo logEvent, IDictionary propertyBag)
{
- var properties = logEvent.Properties;
- if (properties != null)
+ if (logEvent.Properties?.Count > 0)
{
- foreach (var keyValuePair in properties)
+ foreach (var keyValuePair in logEvent.Properties)
{
string key = keyValuePair.Key.ToString();
object valueObj = keyValuePair.Value;
diff --git a/src/NLogTarget/TargetPropertyWithContext.cs b/src/NLogTarget/TargetPropertyWithContext.cs
new file mode 100644
index 00000000..2616a59f
--- /dev/null
+++ b/src/NLogTarget/TargetPropertyWithContext.cs
@@ -0,0 +1,52 @@
+// -----------------------------------------------------------------------
+//
+// Copyright (c) Microsoft Corporation.
+// All rights reserved. 2013
+//
+// -----------------------------------------------------------------------
+
+namespace Microsoft.ApplicationInsights.NLogTarget
+{
+ using NLog.Config;
+ using NLog.Layouts;
+
+ ///
+ /// NLog Target Context Property that allows capture of context information for all logevents (Ex. Layout=${threadid})
+ ///
+ [NLogConfigurationItem]
+ [ThreadAgnostic]
+ public class TargetPropertyWithContext
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public TargetPropertyWithContext() : this(null, null)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The name of the attribute.
+ /// The layout of the attribute's value.
+ public TargetPropertyWithContext(string name, Layout layout)
+ {
+ this.Name = name;
+ this.Layout = layout;
+ }
+
+ ///
+ /// Gets or sets the name of the attribute.
+ ///
+ ///
+ [RequiredParameter]
+ public string Name { get; set; }
+
+ ///
+ /// Gets or sets the layout that will be rendered as the attribute's value.
+ ///
+ ///
+ [RequiredParameter]
+ public Layout Layout { get; set; }
+ }
+}
diff --git a/test/NLogTarget.Net45.Tests/NLogTargetTests.cs b/test/NLogTarget.Net45.Tests/NLogTargetTests.cs
index efde69d0..2f58bddc 100644
--- a/test/NLogTarget.Net45.Tests/NLogTargetTests.cs
+++ b/test/NLogTarget.Net45.Tests/NLogTargetTests.cs
@@ -62,7 +62,7 @@ public void InitializeTargetNotThrowsWhenInstrumentationKeyIsEmptyString()
Assert.Fail("Expected NLogConfigurationException but none was thrown with message:{0}", ex.Message);
}
}
-
+
[TestMethod]
[TestCategory("NLogTarget")]
public void ExceptionsDoNotEscapeNLog()
@@ -164,7 +164,7 @@ public void TraceMessageCanBeFormedUsingLayout()
target.Layout = @"${uppercase:${level}} ${message}";
Logger aiLogger = this.CreateTargetWithGivenInstrumentationKey("test", null, target);
-
+
aiLogger.Debug("Message");
var telemetry = (TraceTelemetry)this.adapterHelper.Channel.SentItems.FirstOrDefault();
@@ -222,7 +222,9 @@ public void TraceHasCustomProperties()
[TestCategory("NLogTarget")]
public void GlobalDiagnosticContextPropertiesAreAddedToProperties()
{
- Logger aiLogger = this.CreateTargetWithGivenInstrumentationKey();
+ ApplicationInsightsTarget target = new ApplicationInsightsTarget();
+ target.ContextProperties.Add(new TargetPropertyWithContext("global_prop", "${gdc:item=global_prop}"));
+ Logger aiLogger = this.CreateTargetWithGivenInstrumentationKey(target: target);
NLog.GlobalDiagnosticsContext.Set("global_prop", "global_value");
aiLogger.Debug("Message");
@@ -235,7 +237,9 @@ public void GlobalDiagnosticContextPropertiesAreAddedToProperties()
[TestCategory("NLogTarget")]
public void GlobalDiagnosticContextPropertiesSupplementEventProperties()
{
- Logger aiLogger = this.CreateTargetWithGivenInstrumentationKey();
+ ApplicationInsightsTarget target = new ApplicationInsightsTarget();
+ target.ContextProperties.Add(new TargetPropertyWithContext("global_prop", "${gdc:item=global_prop}"));
+ Logger aiLogger = this.CreateTargetWithGivenInstrumentationKey(target: target);
NLog.GlobalDiagnosticsContext.Set("global_prop", "global_value");
@@ -252,7 +256,9 @@ public void GlobalDiagnosticContextPropertiesSupplementEventProperties()
[TestCategory("NLogTarget")]
public void EventPropertyKeyNameIsAppendedWith_1_IfSameAsGlobalDiagnosticContextKeyName()
{
- Logger aiLogger = this.CreateTargetWithGivenInstrumentationKey();
+ ApplicationInsightsTarget target = new ApplicationInsightsTarget();
+ target.ContextProperties.Add(new TargetPropertyWithContext("Name", "${gdc:item=Name}"));
+ Logger aiLogger = this.CreateTargetWithGivenInstrumentationKey(target: target);
NLog.GlobalDiagnosticsContext.Set("Name", "Global Value");
var eventInfo = new LogEventInfo(LogLevel.Trace, "TestLogger", "Hello!");
@@ -292,7 +298,7 @@ public void TraceAreEnqueuedInChannelAndContainExceptionMessage()
public void CustomMessageIsAddedToExceptionTelemetryCustomProperties()
{
Logger aiLogger = this.CreateTargetWithGivenInstrumentationKey();
-
+
try
{
throw new Exception("Test logging exception");
@@ -311,7 +317,7 @@ public void CustomMessageIsAddedToExceptionTelemetryCustomProperties()
public void NLogTraceIsSentAsVerboseTraceItem()
{
var aiLogger = this.CreateTargetWithGivenInstrumentationKey("F8474271-D231-45B6-8DD4-D344C309AE69");
-
+
aiLogger.Trace("trace");
var telemetry = (TraceTelemetry)this.adapterHelper.Channel.SentItems.FirstOrDefault();