Skip to content

Commit 987a773

Browse files
Merge pull request #223 from snakefoot/master
BeginScope accepts any type of KeyValuePair and Dictionary
2 parents 3d0d78f + b340362 commit 987a773

File tree

2 files changed

+150
-32
lines changed

2 files changed

+150
-32
lines changed

src/NLog.Extensions.Logging/Logging/NLogLogger.cs

Lines changed: 95 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
using System;
2+
using System.Collections.Concurrent;
23
using System.Collections.Generic;
4+
using System.Reflection;
35
using Microsoft.Extensions.Logging;
46
using NLog.Common;
57
using NLog.MessageTemplates;
@@ -11,6 +13,8 @@ namespace NLog.Extensions.Logging
1113
/// </summary>
1214
internal class NLogLogger : Microsoft.Extensions.Logging.ILogger
1315
{
16+
private readonly ConcurrentDictionary<Type, KeyValuePair<Func<object, object>, Func<object, object>>> _scopeStateExtractors = new ConcurrentDictionary<Type, KeyValuePair<Func<object, object>, Func<object, object>>>();
17+
1418
private readonly Logger _logger;
1519
private readonly NLogProviderOptions _options;
1620

@@ -42,7 +46,7 @@ public void Log<TState>(Microsoft.Extensions.Logging.LogLevel logLevel, EventId
4246
LogEventInfo eventInfo =
4347
TryParseLogEventInfo(nLogLogLevel, messageParameters) ??
4448
CreateLogEventInfo(nLogLogLevel, formatter(state, exception), messageParameters);
45-
49+
4650
if (exception != null)
4751
{
4852
eventInfo.Exception = exception;
@@ -359,7 +363,7 @@ private static LogLevel ConvertLogLevel(Microsoft.Extensions.Logging.LogLevel lo
359363
}
360364
}
361365

362-
class ScopeProperties : IDisposable
366+
private class ScopeProperties : IDisposable
363367
{
364368
List<IDisposable> _properties;
365369

@@ -368,20 +372,91 @@ class ScopeProperties : IDisposable
368372
/// </summary>
369373
List<IDisposable> Properties => _properties ?? (_properties = new List<IDisposable>());
370374

371-
372-
public static IDisposable CreateFromState<TState>(TState state, IEnumerable<KeyValuePair<string, object>> messageProperties)
375+
public static ScopeProperties CreateFromState(IList<KeyValuePair<string, object>> messageProperties)
373376
{
374377
ScopeProperties scope = new ScopeProperties();
375378

376-
foreach (var property in messageProperties)
379+
for (int i = 0; i < messageProperties.Count; ++i)
377380
{
378-
if (String.IsNullOrEmpty(property.Key))
379-
continue;
380-
381+
var property = messageProperties[i];
381382
scope.AddProperty(property.Key, property.Value);
382383
}
383384

384-
scope.AddDispose(NestedDiagnosticsLogicalContext.Push(state));
385+
scope.AddDispose(NestedDiagnosticsLogicalContext.Push(messageProperties));
386+
return scope;
387+
}
388+
389+
public static bool TryCreateExtractor<T>(ConcurrentDictionary<Type, KeyValuePair<Func<object, object>, Func<object, object>>> stateExractor, T property, out KeyValuePair<Func<object, object>, Func<object, object>> keyValueExtractor)
390+
{
391+
Type propertyType = property.GetType();
392+
393+
if (!stateExractor.TryGetValue(propertyType, out keyValueExtractor))
394+
{
395+
var itemType = propertyType.GetTypeInfo();
396+
if (!itemType.IsGenericType || itemType.GetGenericTypeDefinition() != typeof(KeyValuePair<,>))
397+
return false;
398+
399+
var keyPropertyInfo = itemType.GetDeclaredProperty("Key");
400+
var valuePropertyInfo = itemType.GetDeclaredProperty("Value");
401+
if (valuePropertyInfo == null || keyPropertyInfo == null)
402+
return false;
403+
404+
var keyValuePairObjParam = System.Linq.Expressions.Expression.Parameter(typeof(object), "pair");
405+
var keyValuePairTypeParam = System.Linq.Expressions.Expression.Convert(keyValuePairObjParam, propertyType);
406+
407+
var propertyKeyAccess = System.Linq.Expressions.Expression.Property(keyValuePairTypeParam, keyPropertyInfo);
408+
var propertyKeyAccessObj = System.Linq.Expressions.Expression.Convert(propertyKeyAccess, typeof(object));
409+
var propertyKeyLambda = System.Linq.Expressions.Expression.Lambda<Func<object, object>>(propertyKeyAccessObj, keyValuePairObjParam).Compile();
410+
411+
var propertyValueAccess = System.Linq.Expressions.Expression.Property(keyValuePairTypeParam, valuePropertyInfo);
412+
var propertyValueLambda = System.Linq.Expressions.Expression.Lambda<Func<object, object>>(propertyValueAccess, keyValuePairObjParam).Compile();
413+
414+
keyValueExtractor = new KeyValuePair<Func<object, object>, Func<object, object>>(propertyKeyLambda, propertyValueLambda);
415+
416+
stateExractor[propertyType] = keyValueExtractor;
417+
}
418+
419+
return true;
420+
}
421+
422+
public static IDisposable CreateFromStateExtractor<TState>(TState state, ConcurrentDictionary<Type, KeyValuePair<Func<object, object>, Func<object, object>>> stateExractor)
423+
{
424+
ScopeProperties scope = null;
425+
var keyValueExtractor = default(KeyValuePair<Func<object, object>, Func<object, object>>);
426+
if (state is System.Collections.IEnumerable messageProperties)
427+
{
428+
foreach (var property in messageProperties)
429+
{
430+
if (property == null)
431+
return null;
432+
433+
if (scope == null)
434+
{
435+
if (!TryCreateExtractor<object>(stateExractor, property, out keyValueExtractor))
436+
return null;
437+
438+
scope = new ScopeProperties();
439+
}
440+
441+
var propertyKey = keyValueExtractor.Key.Invoke(property);
442+
var propertyValue = keyValueExtractor.Value.Invoke(property);
443+
scope.AddProperty(propertyKey?.ToString() ?? string.Empty, propertyValue);
444+
}
445+
}
446+
else
447+
{
448+
if (!TryCreateExtractor(stateExractor, state, out keyValueExtractor))
449+
return null;
450+
451+
scope = new ScopeProperties();
452+
object property = state; // Single boxing
453+
var propertyKey = keyValueExtractor.Key.Invoke(property);
454+
var propertyValue = keyValueExtractor.Value.Invoke(property);
455+
scope.AddProperty(propertyKey?.ToString() ?? string.Empty, propertyValue);
456+
}
457+
458+
if (scope != null)
459+
scope.AddDispose(NestedDiagnosticsLogicalContext.Push(state));
385460
return scope;
386461
}
387462

@@ -445,9 +520,18 @@ public IDisposable BeginScope<TState>(TState state)
445520
throw new ArgumentNullException(nameof(state));
446521
}
447522

448-
if (_options.CaptureMessageProperties && state is IEnumerable<KeyValuePair<string, object>> messageProperties)
523+
if (_options.CaptureMessageProperties)
449524
{
450-
return ScopeProperties.CreateFromState(state, messageProperties);
525+
if (state is IList<KeyValuePair<string, object>> contextProperties)
526+
{
527+
return ScopeProperties.CreateFromState(contextProperties);
528+
}
529+
else if (!(state is string))
530+
{
531+
var scope = ScopeProperties.CreateFromStateExtractor(state, _scopeStateExtractors);
532+
if (scope != null)
533+
return scope;
534+
}
451535
}
452536

453537
return NestedDiagnosticsLogicalContext.Push(state);

test/LoggerTests.cs

Lines changed: 55 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -89,12 +89,30 @@ public void TestMessagePropertiesList()
8989
}
9090

9191
[Fact]
92-
public void TestScopeProperties()
92+
public void TestScopeProperty()
9393
{
94-
GetRunner().LogWithScopeParameters();
94+
GetRunner().LogWithScopeParameter();
9595

9696
var target = GetTarget();
97-
Assert.Equal("NLog.Extensions.Logging.Tests.LoggerTests.Runner|DEBUG|message with id and 1 parameters |Hello", target.Logs.FirstOrDefault());
97+
Assert.Equal("NLog.Extensions.Logging.Tests.LoggerTests.Runner|DEBUG|message with id and 1 parameters |Hello", target.Logs.LastOrDefault());
98+
}
99+
100+
[Fact]
101+
public void TestScopePropertyList()
102+
{
103+
GetRunner().LogWithScopeParameterList();
104+
105+
var target = GetTarget();
106+
Assert.Equal("NLog.Extensions.Logging.Tests.LoggerTests.Runner|DEBUG|message with id and 1 parameters |Hello", target.Logs.LastOrDefault());
107+
}
108+
109+
[Fact]
110+
public void TestScopePropertyDictionary()
111+
{
112+
GetRunner().LogWithScopeParameterDictionary();
113+
114+
var target = GetTarget();
115+
Assert.Equal("NLog.Extensions.Logging.Tests.LoggerTests.Runner|DEBUG|message with id and 1 parameters |Hello", target.Logs.LastOrDefault());
98116
}
99117

100118
[Theory]
@@ -107,7 +125,7 @@ public void TestScopeProperties()
107125
public void TestExceptionWithMessage(Microsoft.Extensions.Logging.LogLevel logLevel, string expectedLogMessage)
108126
{
109127
GetRunner().Log(logLevel, 20, new Exception(), "message");
110-
128+
111129
var target = GetTarget();
112130
Assert.Equal(expectedLogMessage, target.Logs.FirstOrDefault());
113131
}
@@ -122,11 +140,11 @@ public void TestExceptionWithMessage(Microsoft.Extensions.Logging.LogLevel logLe
122140
public void TestExceptionWithEmptyMessage(Microsoft.Extensions.Logging.LogLevel logLevel, string expectedLogMessage)
123141
{
124142
GetRunner().Log(logLevel, 20, new Exception(), string.Empty);
125-
143+
126144
var target = GetTarget();
127145
Assert.Equal(expectedLogMessage, target.Logs.FirstOrDefault());
128146
}
129-
147+
130148
[Theory]
131149
[InlineData(Microsoft.Extensions.Logging.LogLevel.Critical, "NLog.Extensions.Logging.Tests.LoggerTests.Runner|FATAL|[null] Exception of type 'System.Exception' was thrown.|20")]
132150
[InlineData(Microsoft.Extensions.Logging.LogLevel.Debug, "NLog.Extensions.Logging.Tests.LoggerTests.Runner|DEBUG|[null] Exception of type 'System.Exception' was thrown.|20")]
@@ -137,11 +155,11 @@ public void TestExceptionWithEmptyMessage(Microsoft.Extensions.Logging.LogLevel
137155
public void TestExceptionWithNullMessage(Microsoft.Extensions.Logging.LogLevel logLevel, string expectedLogMessage)
138156
{
139157
GetRunner().Log(logLevel, 20, new Exception(), null);
140-
158+
141159
var target = GetTarget();
142160
Assert.Equal(expectedLogMessage, target.Logs.FirstOrDefault());
143161
}
144-
162+
145163
[Theory]
146164
[InlineData(Microsoft.Extensions.Logging.LogLevel.Critical, "NLog.Extensions.Logging.Tests.LoggerTests.Runner|FATAL|message |20")]
147165
[InlineData(Microsoft.Extensions.Logging.LogLevel.Debug, "NLog.Extensions.Logging.Tests.LoggerTests.Runner|DEBUG|message |20")]
@@ -152,11 +170,11 @@ public void TestExceptionWithNullMessage(Microsoft.Extensions.Logging.LogLevel l
152170
public void TestMessageWithNullException(Microsoft.Extensions.Logging.LogLevel logLevel, string expectedLogMessage)
153171
{
154172
GetRunner<Runner>().Log(logLevel, 20, null, "message");
155-
173+
156174
var target = GetTarget();
157-
Assert.Equal(expectedLogMessage, target.Logs.FirstOrDefault());
175+
Assert.Equal(expectedLogMessage, target.Logs.FirstOrDefault());
158176
}
159-
177+
160178
[Theory]
161179
[InlineData(Microsoft.Extensions.Logging.LogLevel.Critical, "NLog.Extensions.Logging.Tests.LoggerTests.Runner|FATAL|[null] |20")]
162180
[InlineData(Microsoft.Extensions.Logging.LogLevel.Debug, "NLog.Extensions.Logging.Tests.LoggerTests.Runner|DEBUG|[null] |20")]
@@ -167,11 +185,11 @@ public void TestMessageWithNullException(Microsoft.Extensions.Logging.LogLevel l
167185
public void TestWithNullMessageAndNullException(Microsoft.Extensions.Logging.LogLevel logLevel, string expectedLogMessage)
168186
{
169187
GetRunner().Log(logLevel, 20, null, null);
170-
188+
171189
var target = GetTarget();
172-
Assert.Equal(expectedLogMessage, target.Logs.FirstOrDefault());
190+
Assert.Equal(expectedLogMessage, target.Logs.FirstOrDefault());
173191
}
174-
192+
175193
[Theory]
176194
[InlineData(Microsoft.Extensions.Logging.LogLevel.Critical, "NLog.Extensions.Logging.Tests.LoggerTests.Runner|FATAL| |20")]
177195
[InlineData(Microsoft.Extensions.Logging.LogLevel.Debug, "NLog.Extensions.Logging.Tests.LoggerTests.Runner|DEBUG| |20")]
@@ -182,11 +200,11 @@ public void TestWithNullMessageAndNullException(Microsoft.Extensions.Logging.Log
182200
public void TestWithEmptyMessageAndNullException(Microsoft.Extensions.Logging.LogLevel logLevel, string expectedLogMessage)
183201
{
184202
GetRunner().Log(logLevel, 20, null, string.Empty);
185-
203+
186204
var target = GetTarget();
187-
Assert.Equal(expectedLogMessage, target.Logs.FirstOrDefault());
205+
Assert.Equal(expectedLogMessage, target.Logs.FirstOrDefault());
188206
}
189-
207+
190208
private static MemoryTarget GetTarget()
191209
{
192210
var target = LogManager.Configuration.FindTargetByName<MemoryTarget>("target1");
@@ -212,7 +230,7 @@ public void LogDebugWithId()
212230
{
213231
_logger.LogDebug(20, "message with id");
214232
}
215-
233+
216234
public void Log(Microsoft.Extensions.Logging.LogLevel logLevel, int eventId, Exception exception, string message)
217235
{
218236
switch (logLevel)
@@ -257,12 +275,12 @@ public void LogDebugWithStructuredParameterFormater()
257275

258276
public void LogDebugWithSimulatedStructuredParameters()
259277
{
260-
_logger.Log(Microsoft.Extensions.Logging.LogLevel.Debug, default(EventId), new List<KeyValuePair<string, object>>(new [] { new KeyValuePair<string,object>("{OriginalFormat}", "message with id and {ParameterCount} property"), new KeyValuePair<string, object>("ParameterCount", 1) }), null, (s, ex) => "message with id and 1 property");
278+
_logger.Log(Microsoft.Extensions.Logging.LogLevel.Debug, default(EventId), new List<KeyValuePair<string, object>>(new[] { new KeyValuePair<string, object>("{OriginalFormat}", "message with id and {ParameterCount} property"), new KeyValuePair<string, object>("ParameterCount", 1) }), null, (s, ex) => "message with id and 1 property");
261279
}
262280

263281
public void LogDebugWithMessageProperties()
264282
{
265-
_logger.Log(Microsoft.Extensions.Logging.LogLevel.Debug, default(EventId), new Dictionary<string, object> { { "ParameterCount", "1" } }, null, (s,ex) => "message with id and 1 property");
283+
_logger.Log(Microsoft.Extensions.Logging.LogLevel.Debug, default(EventId), new Dictionary<string, object> { { "ParameterCount", "1" } }, null, (s, ex) => "message with id and 1 property");
266284
}
267285

268286
public void LogDebugWithMessagePropertiesList()
@@ -278,14 +296,30 @@ public void LogWithScope()
278296
}
279297
}
280298

281-
public void LogWithScopeParameters()
299+
public void LogWithScopeParameter()
300+
{
301+
using (_logger.BeginScope(new KeyValuePair<string, string>("scope1", "Hello")))
302+
{
303+
_logger.LogDebug("message with id and {0} parameters", 1);
304+
}
305+
}
306+
307+
public void LogWithScopeParameterList()
282308
{
283309
using (_logger.BeginScope(new[] { new KeyValuePair<string, object>("scope1", "Hello") }))
284310
{
285311
_logger.LogDebug("message with id and {0} parameters", 1);
286312
}
287313
}
288314

315+
public void LogWithScopeParameterDictionary()
316+
{
317+
using (_logger.BeginScope(new Dictionary<string, string>() { ["scope1"] = "Hello" }))
318+
{
319+
_logger.LogDebug("message with id and {0} parameters", 1);
320+
}
321+
}
322+
289323
public void Init()
290324
{
291325
_logger.LogDebug("init runner");

0 commit comments

Comments
 (0)