Skip to content
This repository has been archived by the owner on Oct 12, 2022. It is now read-only.

Commit

Permalink
NLog ApplicationInsightsTarget - GlobalDiagnosticContext must be expl…
Browse files Browse the repository at this point in the history
…ictly added using ContextProperties
  • Loading branch information
snakefoot committed Apr 29, 2018
1 parent 388ff8e commit 6bff99f
Show file tree
Hide file tree
Showing 4 changed files with 93 additions and 24 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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)
Expand Down
42 changes: 26 additions & 16 deletions src/NLogTarget/ApplicationInsightsTarget.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,14 @@ namespace Microsoft.ApplicationInsights.NLogTarget

using NLog;
using NLog.Common;
using NLog.Config;
using NLog.Targets;

/// <summary>
/// NLog Target that routes all logging output to the Application Insights logging framework.
/// The messages will be uploaded to the Application Insights cloud service.
/// </summary>
[Target("ApplicationInsightsTarget")]
[Target("ApplicationInsightsTarget")]
public sealed class ApplicationInsightsTarget : TargetWithLayout
{
private TelemetryClient telemetryClient;
Expand All @@ -43,6 +44,12 @@ public ApplicationInsightsTarget()
/// </summary>
public string InstrumentationKey { get; set; }

/// <summary>
/// Gets the array of custom attributes to be passed into the logevent context
/// </summary>
[ArrayParameter(typeof(TargetPropertyWithContext), "contextproperty")]
public IList<TargetPropertyWithContext> ContextProperties { get; } = new List<TargetPropertyWithContext>();

/// <summary>
/// Gets the logging controller we will be using.
/// </summary>
Expand Down Expand Up @@ -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);
}
}

/// <summary>
Expand All @@ -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;
Expand Down Expand Up @@ -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)
Expand All @@ -175,20 +194,11 @@ private void SendTrace(LogEventInfo logEvent)
this.telemetryClient.Track(trace);
}

private void LoadGlobalDiagnosticsContextProperties(IDictionary<string, string> propertyBag)
{
foreach (string key in GlobalDiagnosticsContext.GetNames())
{
this.PopulatePropertyBag(propertyBag, key, GlobalDiagnosticsContext.GetObject(key));
}
}

private void LoadLogEventProperties(LogEventInfo logEvent, IDictionary<string, string> 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;
Expand Down
52 changes: 52 additions & 0 deletions src/NLogTarget/TargetPropertyWithContext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// -----------------------------------------------------------------------
// <copyright file="ApplicationInsightsTarget.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation.
// All rights reserved. 2013
// </copyright>
// -----------------------------------------------------------------------

namespace Microsoft.ApplicationInsights.NLogTarget
{
using NLog.Config;
using NLog.Layouts;

/// <summary>
/// NLog Target Context Property that allows capture of context information for all logevents (Ex. Layout=${threadid})
/// </summary>
[NLogConfigurationItem]
[ThreadAgnostic]
public class TargetPropertyWithContext
{
/// <summary>
/// Initializes a new instance of the <see cref="TargetPropertyWithContext" /> class.
/// </summary>
public TargetPropertyWithContext() : this(null, null)
{
}

/// <summary>
/// Initializes a new instance of the <see cref="TargetPropertyWithContext" /> class.
/// </summary>
/// <param name="name">The name of the attribute.</param>
/// <param name="layout">The layout of the attribute's value.</param>
public TargetPropertyWithContext(string name, Layout layout)
{
this.Name = name;
this.Layout = layout;
}

/// <summary>
/// Gets or sets the name of the attribute.
/// </summary>
/// <docgen category='Property Options' order='10' />
[RequiredParameter]
public string Name { get; set; }

/// <summary>
/// Gets or sets the layout that will be rendered as the attribute's value.
/// </summary>
/// <docgen category='Property Options' order='10' />
[RequiredParameter]
public Layout Layout { get; set; }
}
}
20 changes: 13 additions & 7 deletions test/NLogTarget.Net45.Tests/NLogTargetTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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");
Expand All @@ -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");

Expand All @@ -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!");
Expand Down Expand Up @@ -292,7 +298,7 @@ public void TraceAreEnqueuedInChannelAndContainExceptionMessage()
public void CustomMessageIsAddedToExceptionTelemetryCustomProperties()
{
Logger aiLogger = this.CreateTargetWithGivenInstrumentationKey();

try
{
throw new Exception("Test logging exception");
Expand All @@ -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();
Expand Down

0 comments on commit 6bff99f

Please sign in to comment.