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

NLog GlobalDiagnosticContext should be explictly added using ContextProperties #183

Merged
merged 1 commit into from
Apr 30, 2018
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
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 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 All @@ -204,7 +214,7 @@ private void PopulatePropertyBag(IDictionary<string, string> propertyBag, string
return;
}

string value = valueObj.ToString();
string value = Convert.ToString(valueObj, CultureInfo.InvariantCulture);
if (propertyBag.ContainsKey(key))
{
if (string.Equals(value, propertyBag[key], StringComparison.Ordinal))
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()
Copy link
Contributor Author

@snakefoot snakefoot Apr 30, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Think it is a little strange to have unit-test that verifies functionality is not there. Have no opinion about the unit test names. Just happy that they include Context in the name.

{
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 @@ -254,7 +258,9 @@ public void GlobalDiagnosticContextPropertiesSupplementEventProperties()
public void EventPropertyKeyNameIsAppendedWith_1_IfSameAsGlobalDiagnosticContextKeyName()
#pragma warning restore CA1707 // Identifiers should not contain underscores
{
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 @@ -294,7 +300,7 @@ public void TraceAreEnqueuedInChannelAndContainExceptionMessage()
public void CustomMessageIsAddedToExceptionTelemetryCustomProperties()
{
Logger aiLogger = this.CreateTargetWithGivenInstrumentationKey();

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