Skip to content

Commit 837239b

Browse files
committed
NLog ApplicationInsightsTarget with support for IncludeMldc
1 parent 0d7d2fd commit 837239b

File tree

4 files changed

+145
-53
lines changed

4 files changed

+145
-53
lines changed

LOGGING/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ If your application does not have web.config then it can also be configured manu
4545
<add assembly="Microsoft.ApplicationInsights.NLogTarget" />
4646
</extensions>
4747
<targets>
48-
<target xsi:type="ApplicationInsightsTarget" name="aiTarget">
48+
<target xsi:type="ApplicationInsightsTarget" name="aiTarget" includeEventProperties="true" includeMdlc="false">
4949
<instrumentationKey>Your_Resource_Key</instrumentationKey> <!-- Only required if not using ApplicationInsights.config -->
5050
<contextproperty name="threadid" layout="${threadid}" /> <!-- Can be repeated with more context -->
5151
</target>

LOGGING/src/NLogTarget/ApplicationInsightsTarget.cs

Lines changed: 17 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ namespace Microsoft.ApplicationInsights.NLogTarget
2323
/// The messages will be uploaded to the Application Insights cloud service.
2424
/// </summary>
2525
[Target("ApplicationInsightsTarget")]
26-
public sealed class ApplicationInsightsTarget : TargetWithLayout
26+
public sealed class ApplicationInsightsTarget : TargetWithContext
2727
{
2828
private const string ConnectionStringRequiredMessage = "Azure Monitor connection string is required. Please provide a valid connection string.";
2929

@@ -39,6 +39,7 @@ public ApplicationInsightsTarget()
3939
{
4040
this.Layout = @"${message}";
4141
this.OptimizeBufferReuse = true;
42+
this.IncludeEventProperties = true;
4243
}
4344

4445
/// <summary>
@@ -50,12 +51,6 @@ public string ConnectionString
5051
set => this.connectionStringLayout = value ?? string.Empty;
5152
}
5253

53-
/// <summary>
54-
/// Gets the array of custom attributes to be passed into the logevent context.
55-
/// </summary>
56-
[ArrayParameter(typeof(TargetPropertyWithContext), "contextproperty")]
57-
public IList<TargetPropertyWithContext> ContextProperties { get; } = new List<TargetPropertyWithContext>();
58-
5954
/// <summary>
6055
/// Gets the logging controller we will be using.
6156
/// </summary>
@@ -89,20 +84,25 @@ internal void BuildPropertyBag(LogEventInfo logEvent, ITelemetry trace)
8984
propertyBag.Add("UserStackFrame", logEvent.UserStackFrame.ToString());
9085
propertyBag.Add("UserStackFrameNumber", logEvent.UserStackFrameNumber.ToString(CultureInfo.InvariantCulture));
9186
}
92-
93-
for (int i = 0; i < this.ContextProperties.Count; ++i)
87+
else
9488
{
95-
var contextProperty = this.ContextProperties[i];
96-
if (!string.IsNullOrEmpty(contextProperty.Name) && contextProperty.Layout != null)
97-
{
98-
string propertyValue = this.RenderLogEvent(contextProperty.Layout, logEvent);
99-
PopulatePropertyBag(propertyBag, contextProperty.Name, propertyValue);
100-
}
89+
var callsiteClassName = logEvent.CallerClassName;
90+
if (!string.IsNullOrEmpty(callsiteClassName))
91+
propertyBag.Add("UserStackClassName", callsiteClassName);
92+
var callsiteMemberName = logEvent.CallerMemberName;
93+
if (!string.IsNullOrEmpty(callsiteMemberName))
94+
propertyBag.Add("UserStackMemberName", callsiteMemberName);
95+
var callsiteSourceFilePath = logEvent.CallerFilePath;
96+
if (!string.IsNullOrEmpty(callsiteSourceFilePath))
97+
propertyBag.Add("UserStackSourceFile", callsiteSourceFilePath);
98+
var callsiteSourceLineNumber = logEvent.CallerLineNumber;
99+
if (callsiteSourceLineNumber != 0)
100+
propertyBag.Add("UserStackSourceLine", callsiteSourceLineNumber.ToString());
101101
}
102102

103-
if (logEvent.HasProperties)
103+
if (ShouldIncludeProperties(logEvent) || ContextProperties.Count > 0)
104104
{
105-
LoadLogEventProperties(logEvent, propertyBag);
105+
this.GetAllProperties(logEvent, new StringDictionaryConverter(propertyBag));
106106
}
107107
}
108108

@@ -222,40 +222,6 @@ protected override void FlushAsync(AsyncContinuation asyncContinuation)
222222
}
223223
}
224224

225-
private static void LoadLogEventProperties(LogEventInfo logEvent, IDictionary<string, string> propertyBag)
226-
{
227-
if (logEvent.Properties?.Count > 0)
228-
{
229-
foreach (var keyValuePair in logEvent.Properties)
230-
{
231-
string key = keyValuePair.Key.ToString();
232-
object valueObj = keyValuePair.Value;
233-
PopulatePropertyBag(propertyBag, key, valueObj);
234-
}
235-
}
236-
}
237-
238-
private static void PopulatePropertyBag(IDictionary<string, string> propertyBag, string key, object valueObj)
239-
{
240-
if (valueObj == null)
241-
{
242-
return;
243-
}
244-
245-
string value = Convert.ToString(valueObj, CultureInfo.InvariantCulture);
246-
if (propertyBag.ContainsKey(key))
247-
{
248-
if (string.Equals(value, propertyBag[key], StringComparison.Ordinal))
249-
{
250-
return;
251-
}
252-
253-
key += "_1";
254-
}
255-
256-
propertyBag.Add(key, value);
257-
}
258-
259225
private static SeverityLevel? GetSeverityLevel(LogLevel logEventLevel)
260226
{
261227
if (logEventLevel == null)

LOGGING/src/NLogTarget/NLogTarget.csproj

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,10 @@
1616
</PropertyGroup>
1717

1818
<ItemGroup>
19-
<PackageReference Include="NLog" Version="4.5.11" />
19+
<PackageReference Include="NLog" Version="4.7.15" />
20+
<PackageReference Include="Microsoft.Diagnostics.Tracing.EventRegister" Version="1.1.28">
21+
<PrivateAssets>All</PrivateAssets>
22+
</PackageReference>
2023
<ProjectReference Include="..\..\..\BASE\src\Microsoft.ApplicationInsights\Microsoft.ApplicationInsights.csproj" />
2124
</ItemGroup>
2225

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
// -----------------------------------------------------------------------
2+
// <copyright file="ApplicationInsightsTarget.cs" company="Microsoft">
3+
// Copyright (c) Microsoft Corporation.
4+
// All rights reserved. 2013
5+
// </copyright>
6+
// -----------------------------------------------------------------------
7+
8+
using System;
9+
using System.Collections;
10+
using System.Collections.Generic;
11+
using System.Linq;
12+
using System.Runtime.InteropServices;
13+
using System.Text;
14+
using System.Threading.Tasks;
15+
16+
namespace Microsoft.ApplicationInsights.NLogTarget
17+
{
18+
/// <summary>
19+
/// Converts from NLog Object-properties to ApplicationInsight String-properties
20+
/// </summary>
21+
class StringDictionaryConverter : IDictionary<string, object>
22+
{
23+
private readonly IDictionary<string, string> _wrapped;
24+
25+
public StringDictionaryConverter(IDictionary<string, string> wrapped)
26+
{
27+
_wrapped = wrapped;
28+
}
29+
30+
public object this[string key] { get => _wrapped[key]; set => _wrapped[key] = SafeValueConverter(value); }
31+
32+
public ICollection<string> Keys => _wrapped.Keys;
33+
34+
public ICollection<object> Values => new List<object>(_wrapped.Values);
35+
36+
public int Count => _wrapped.Count;
37+
38+
public bool IsReadOnly => _wrapped.IsReadOnly;
39+
40+
public void Add(string key, object value)
41+
{
42+
_wrapped.Add(key, SafeValueConverter(value));
43+
}
44+
45+
public void Add(KeyValuePair<string, object> item)
46+
{
47+
_wrapped.Add(new KeyValuePair<string, string>(item.Key, SafeValueConverter(item.Value)));
48+
}
49+
50+
public void Clear()
51+
{
52+
_wrapped.Clear();
53+
}
54+
55+
public bool Contains(KeyValuePair<string, object> item)
56+
{
57+
return _wrapped.Contains(new KeyValuePair<string, string>(item.Key, SafeValueConverter(item.Value)));
58+
}
59+
60+
public bool ContainsKey(string key)
61+
{
62+
return _wrapped.ContainsKey(key);
63+
}
64+
65+
public void CopyTo(KeyValuePair<string, object>[] array, int arrayIndex)
66+
{
67+
foreach (var item in _wrapped)
68+
{
69+
array[arrayIndex++] = new KeyValuePair<string, object>(item.Key, item.Value);
70+
}
71+
}
72+
73+
public IEnumerator<KeyValuePair<string, object>> GetEnumerator()
74+
{
75+
return AsEnumerable().GetEnumerator();
76+
}
77+
78+
public bool Remove(string key)
79+
{
80+
return _wrapped.Remove(key);
81+
}
82+
83+
public bool Remove(KeyValuePair<string, object> item)
84+
{
85+
return _wrapped.Remove(new KeyValuePair<string, string>(item.Key, SafeValueConverter(item.Value)));
86+
}
87+
88+
public bool TryGetValue(string key, out object value)
89+
{
90+
if (_wrapped.TryGetValue(key, out var stringValue))
91+
{
92+
value = stringValue;
93+
return true;
94+
}
95+
96+
value = null;
97+
return false;
98+
}
99+
100+
IEnumerator IEnumerable.GetEnumerator()
101+
{
102+
return ((IEnumerable)_wrapped).GetEnumerator();
103+
}
104+
105+
private IEnumerable<KeyValuePair<string, object>> AsEnumerable()
106+
{
107+
foreach (var item in _wrapped)
108+
yield return new KeyValuePair<string, object>(item.Key, item.Value);
109+
}
110+
111+
private static string SafeValueConverter(object value)
112+
{
113+
try
114+
{
115+
return Convert.ToString(value, System.Globalization.CultureInfo.InvariantCulture);
116+
}
117+
catch
118+
{
119+
return string.Empty;
120+
}
121+
}
122+
}
123+
}

0 commit comments

Comments
 (0)