Skip to content

Commit

Permalink
Introduced NLogBeginScopeParser that provides caching of property-ref…
Browse files Browse the repository at this point in the history
…lection across all NLogLogger-objects (Reduce allocation and better cache reuse).
  • Loading branch information
snakefoot committed Jul 25, 2018
1 parent cd85e8d commit 913bd9e
Show file tree
Hide file tree
Showing 5 changed files with 248 additions and 221 deletions.
15 changes: 8 additions & 7 deletions examples/NetCore2/ConsoleExample/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,14 @@ static void Main(string[] args)
var logger = NLog.LogManager.LoadConfiguration("nlog.config").GetCurrentClassLogger();
try
{
var servicesProvider = BuildDi();
var runner = servicesProvider.GetRequiredService<Runner>();
using (var servicesProvider = BuildDi())
{
var runner = servicesProvider.GetRequiredService<Runner>();
runner.DoAction("Action1");

runner.DoAction("Action1");

Console.WriteLine("Press ANY key to exit");
Console.ReadLine();
Console.WriteLine("Press ANY key to exit");
Console.ReadLine();
}
}
catch (Exception ex)
{
Expand All @@ -34,7 +35,7 @@ static void Main(string[] args)
}


private static IServiceProvider BuildDi()
private static ServiceProvider BuildDi()
{
var services = new ServiceCollection();

Expand Down
10 changes: 2 additions & 8 deletions src/NLog.Extensions.Logging/Extensions/ConfigureExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,7 @@ public static ILoggerFactory AddNLog(this ILoggerFactory factory)
/// <returns>ILoggerFactory for chaining</returns>
public static ILoggerFactory AddNLog(this ILoggerFactory factory, NLogProviderOptions options)
{
using (var provider = new NLogLoggerProvider(options))
{
factory.AddProvider(provider);
}
factory.AddProvider(new NLogLoggerProvider(options));
return factory;
}

Expand All @@ -55,10 +52,7 @@ public static ILoggingBuilder AddNLog(this ILoggingBuilder factory)
/// <returns>ILoggerFactory for chaining</returns>
public static ILoggingBuilder AddNLog(this ILoggingBuilder factory, NLogProviderOptions options)
{
using (var provider = new NLogLoggerProvider(options))
{
factory.AddProvider(provider);
}
factory.AddProvider(new NLogLoggerProvider(options));
return factory;
}
#endif
Expand Down
227 changes: 227 additions & 0 deletions src/NLog.Extensions.Logging/Logging/NLogBeginScopeParser.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Reflection;
using NLog.Common;

namespace NLog.Extensions.Logging
{
internal class NLogBeginScopeParser
{
private readonly NLogProviderOptions _options;
private readonly ConcurrentDictionary<Type, KeyValuePair<Func<object, object>, Func<object, object>>> _scopeStateExtractors = new ConcurrentDictionary<Type, KeyValuePair<Func<object, object>, Func<object, object>>>();

public NLogBeginScopeParser(NLogProviderOptions options)
{
_options = options ?? NLogProviderOptions.Default;
}

public IDisposable ParseBeginScope<T>(T state)
{
if (_options.CaptureMessageProperties)
{
if (state is IReadOnlyList<KeyValuePair<string, object>> contextProperties)
{
return ScopeProperties.CreateFromState(contextProperties);
}

if (!(state is string))
{
var scope = ScopeProperties.CreateFromStateExtractor(state, _scopeStateExtractors);
if (scope != null)
return scope;
}
else
{
return NestedDiagnosticsLogicalContext.Push(state);
}
}

return NestedDiagnosticsLogicalContext.Push(CreateDiagnosticLogicalContext(state));
}

public static IDisposable CreateDiagnosticLogicalContext<T>(T state)
{
try
{
#if NETSTANDARD
return NestedDiagnosticsLogicalContext.Push(state); // AsyncLocal has no requirement to be Serializable
#else
// TODO Add support for Net46 in NLog (AsyncLocal), then we only have to do this check for legacy Net451 (CallContext)
if (state?.GetType().IsSerializable ?? true)
return NestedDiagnosticsLogicalContext.Push(state);
else
return NestedDiagnosticsLogicalContext.Push(state.ToString()); // Support ViewComponentLogScope, ActionLogScope and others
#endif
}
catch (Exception ex)
{
InternalLogger.Debug(ex, "Exception in BeginScope push NestedDiagnosticsLogicalContext");
return null;
}
}

private class ScopeProperties : IDisposable
{
List<IDisposable> _properties;

/// <summary>
/// Properties, never null and lazy init
/// </summary>
List<IDisposable> Properties => _properties ?? (_properties = new List<IDisposable>());

public static ScopeProperties CreateFromState(IReadOnlyList<KeyValuePair<string, object>> messageProperties)
{
ScopeProperties scope = new ScopeProperties();

for (int i = 0; i < messageProperties.Count; ++i)
{
var property = messageProperties[i];
scope.AddProperty(property.Key, property.Value);
}

scope.AddDispose(CreateDiagnosticLogicalContext(messageProperties));
return scope;
}

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)
{
Type propertyType = property.GetType();

if (!stateExractor.TryGetValue(propertyType, out keyValueExtractor))
{
try
{
var itemType = propertyType.GetTypeInfo();
if (!itemType.IsGenericType || itemType.GetGenericTypeDefinition() != typeof(KeyValuePair<,>))
return false;

var keyPropertyInfo = itemType.GetDeclaredProperty("Key");
var valuePropertyInfo = itemType.GetDeclaredProperty("Value");
if (valuePropertyInfo == null || keyPropertyInfo == null)
return false;

var keyValuePairObjParam = System.Linq.Expressions.Expression.Parameter(typeof(object), "pair");
var keyValuePairTypeParam = System.Linq.Expressions.Expression.Convert(keyValuePairObjParam, propertyType);

var propertyKeyAccess = System.Linq.Expressions.Expression.Property(keyValuePairTypeParam, keyPropertyInfo);
var propertyKeyAccessObj = System.Linq.Expressions.Expression.Convert(propertyKeyAccess, typeof(object));
var propertyKeyLambda = System.Linq.Expressions.Expression.Lambda<Func<object, object>>(propertyKeyAccessObj, keyValuePairObjParam).Compile();

var propertyValueAccess = System.Linq.Expressions.Expression.Property(keyValuePairTypeParam, valuePropertyInfo);
var propertyValueLambda = System.Linq.Expressions.Expression.Lambda<Func<object, object>>(propertyValueAccess, keyValuePairObjParam).Compile();

keyValueExtractor = new KeyValuePair<Func<object, object>, Func<object, object>>(propertyKeyLambda, propertyValueLambda);
}
catch (Exception ex)
{
InternalLogger.Debug(ex, "Exception in BeginScope create property extractor");
}
finally
{
stateExractor[propertyType] = keyValueExtractor;
}
}

return keyValueExtractor.Key != null;
}

public static IDisposable CreateFromStateExtractor<TState>(TState state, ConcurrentDictionary<Type, KeyValuePair<Func<object, object>, Func<object, object>>> stateExractor)
{
ScopeProperties scope = null;
var keyValueExtractor = default(KeyValuePair<Func<object, object>, Func<object, object>>);
if (state is System.Collections.IEnumerable messageProperties)
{
foreach (var property in messageProperties)
{
if (property == null)
return null;

if (scope == null)
{
if (!TryCreateExtractor<object>(stateExractor, property, out keyValueExtractor))
return null;

scope = new ScopeProperties();
}

AddKeyValueProperty(scope, keyValueExtractor, property);
}
}
else
{
if (!TryCreateExtractor(stateExractor, state, out keyValueExtractor))
return null;

scope = new ScopeProperties();
AddKeyValueProperty(scope, keyValueExtractor, state);
}

if (scope != null)
scope.AddDispose(CreateDiagnosticLogicalContext(state));
return scope;
}

private static void AddKeyValueProperty(ScopeProperties scope, KeyValuePair<Func<object, object>, Func<object, object>> keyValueExtractor, object property)
{
try
{
var propertyKey = keyValueExtractor.Key.Invoke(property);
var propertyValue = keyValueExtractor.Value.Invoke(property);
scope.AddProperty(propertyKey?.ToString() ?? string.Empty, propertyValue);
}
catch (Exception ex)
{
InternalLogger.Debug(ex, "Exception in BeginScope add property");
}
}

public void AddDispose(IDisposable disposable)
{
if (disposable != null)
Properties.Add(disposable);
}

public void AddProperty(string key, object value)
{
AddDispose(new ScopeProperty(key, value));
}

public void Dispose()
{
var properties = _properties;
if (properties != null)
{
_properties = null;
foreach (var property in properties)
{
try
{
property.Dispose();
}
catch (Exception ex)
{
InternalLogger.Debug(ex, "Exception in BeginScope dispose property {0}", property);
}
}
}
}

class ScopeProperty : IDisposable
{
readonly string _key;

public ScopeProperty(string key, object value)
{
_key = key;
MappedDiagnosticsLogicalContext.Set(key, value);
}

public void Dispose()
{
MappedDiagnosticsLogicalContext.Remove(_key);
}
}
}
}
}
Loading

0 comments on commit 913bd9e

Please sign in to comment.