Skip to content

Commit

Permalink
EventPropertiesLayoutRenderer - Added ObjectPath for extracting neste…
Browse files Browse the repository at this point in the history
…d property
  • Loading branch information
snakefoot committed Apr 23, 2019
1 parent 2fffff4 commit 8580b96
Show file tree
Hide file tree
Showing 3 changed files with 133 additions and 8 deletions.
60 changes: 57 additions & 3 deletions src/NLog/Internal/ObjectReflectionCache.cs
Expand Up @@ -206,9 +206,10 @@ private static FastPropertyLookup[] BuildFastLookup(PropertyInfo[] properties, b

public struct ObjectPropertyList : IEnumerable<ObjectPropertyList.PropertyValue>
{
internal static readonly StringComparer NameComparer = StringComparer.Ordinal;
private readonly object _object;
private readonly PropertyInfo[] _properties;
private readonly ObjectReflectionCache.FastPropertyLookup[] _fastLookup;
private readonly FastPropertyLookup[] _fastLookup;

public struct PropertyValue
{
Expand All @@ -235,6 +236,20 @@ public PropertyValue(string name, object value, TypeCode typeCode)
Value = value;
_typecode = typeCode;
}

public PropertyValue(object owner, PropertyInfo propertyInfo)
{
Name = propertyInfo.Name;
Value = propertyInfo.GetValue(owner, null);
_typecode = TypeCode.Object;
}

public PropertyValue(object owner, FastPropertyLookup fastProperty)
{
Name = fastProperty.Name;
Value = fastProperty.ValueLookup(owner, null);
_typecode = fastProperty.TypeCode;
}
}

public int Count => _fastLookup?.Length ?? _properties?.Length ?? (_object as ICollection)?.Count ?? (_object as ICollection<KeyValuePair<string, object>>)?.Count ?? 0;
Expand All @@ -253,6 +268,43 @@ public ObjectPropertyList(IDictionary<string, object> value)
_fastLookup = null;
}

public bool TryGetPropertyValue(string name, out PropertyValue propertyValue)
{
if (_fastLookup != null)
{
int nameHashCode = NameComparer.GetHashCode(name);
foreach (var fastProperty in _fastLookup)
{
if (fastProperty.NameHashCode==nameHashCode && NameComparer.Equals(fastProperty.Name, name))
{
propertyValue = new PropertyValue(_object, fastProperty);
return true;
}
}
}
else if (_properties != null)
{
foreach (var propInfo in _properties)
{
if (NameComparer.Equals(propInfo.Name, name))
{
propertyValue = new PropertyValue(_object, propInfo);
return true;
}
}
}
else if (_object is IDictionary<string, object> expandoObject)
{
if (expandoObject.TryGetValue(name, out var objectValue))
{
propertyValue = new PropertyValue(name, objectValue, TypeCode.Object);
return true;
}
}
propertyValue = default(PropertyValue);
return false;
}

public override string ToString()
{
return _object?.ToString() ?? "null";
Expand Down Expand Up @@ -302,9 +354,9 @@ public PropertyValue Current
try
{
if (_fastLookup != null)
return new PropertyValue(_fastLookup[_index].Name, _fastLookup[_index].ValueLookup(_owner, null), _fastLookup[_index].TypeCode);
return new PropertyValue(_owner, _fastLookup[_index]);
else if (_properties != null)
return new PropertyValue(_properties[_index].Name, _properties[_index].GetValue(_owner, null), TypeCode.Object);
return new PropertyValue(_owner, _properties[_index]);
else
return new PropertyValue(_enumerator.Current.Key, _enumerator.Current.Value, TypeCode.Object);
}
Expand Down Expand Up @@ -346,12 +398,14 @@ internal struct FastPropertyLookup
public readonly string Name;
public readonly ReflectionHelpers.LateBoundMethod ValueLookup;
public readonly TypeCode TypeCode;
public readonly int NameHashCode;

public FastPropertyLookup(string name, TypeCode typeCode, ReflectionHelpers.LateBoundMethod valueLookup)
{
Name = name;
ValueLookup = valueLookup;
TypeCode = typeCode;
NameHashCode = ObjectPropertyList.NameComparer.GetHashCode(name);
}
}

Expand Down
56 changes: 51 additions & 5 deletions src/NLog/LayoutRenderers/EventPropertiesLayoutRenderer.cs
Expand Up @@ -68,10 +68,24 @@ public class EventPropertiesLayoutRenderer : LayoutRenderer, IRawValue, IStringV
/// <docgen category='Rendering Options' order='100' />
public CultureInfo Culture { get; set; } = CultureInfo.InvariantCulture;

/// <summary>
/// Gets or sets the object-property-navigation-path for lookup of nested property
/// </summary>
/// <docgen category='Rendering Options' order='20' />
public string ObjectPath
{
get => _objectPropertyPath?.Length > 0 ? string.Join(".", _objectPropertyPath) : null;
set => _objectPropertyPath = StringHelpers.IsNullOrWhiteSpace(value) ? null : value.SplitAndTrimTokens('.');
}
private string[] _objectPropertyPath;

private ObjectReflectionCache ObjectReflectionCache => _objectReflectionCache ?? (_objectReflectionCache = new ObjectReflectionCache());
private ObjectReflectionCache _objectReflectionCache;

/// <inheritdoc/>
protected override void Append(StringBuilder builder, LogEventInfo logEvent)
{
if (GetValue(logEvent, out var value))
if (TryGetValue(logEvent, out var value))
{
var formatProvider = GetFormatProvider(logEvent, Culture);
builder.AppendFormattedValue(value, Format, formatProvider);
Expand All @@ -81,24 +95,56 @@ protected override void Append(StringBuilder builder, LogEventInfo logEvent)
/// <inheritdoc/>
bool IRawValue.TryGetRawValue(LogEventInfo logEvent, out object value)
{
GetValue(logEvent, out value);
TryGetValue(logEvent, out value);
return true;
}

/// <inheritdoc/>
string IStringValueRenderer.GetFormattedString(LogEventInfo logEvent) => GetStringValue(logEvent);

private bool GetValue(LogEventInfo logEvent, out object value)
private bool TryGetValue(LogEventInfo logEvent, out object value)
{
value = null;
return logEvent.HasProperties && logEvent.Properties.TryGetValue(Item, out value);

if (!logEvent.HasProperties)
return false;

if (!logEvent.Properties.TryGetValue(Item, out value))
return false;

if (_objectPropertyPath != null && !TryGetObjectProperty(ref value))
return false;

return true;
}

private bool TryGetObjectProperty(ref object value)
{
var objectReflectionCache = ObjectReflectionCache;
for (int i = 0; i < _objectPropertyPath.Length; ++i)
{
if (value == null)
return false;

var eventProperties = objectReflectionCache.LookupObjectProperties(value);
if (eventProperties.TryGetPropertyValue(_objectPropertyPath[i], out var propertyValue))
{
value = propertyValue.Value;
}
else
{
return false;
}
}

return true;
}

private string GetStringValue(LogEventInfo logEvent)
{
if (Format != MessageTemplates.ValueFormatter.FormatAsJson)
{
if (GetValue(logEvent, out var value))
if (TryGetValue(logEvent, out var value))
{
string stringValue = FormatHelper.TryFormatToString(value, Format, GetFormatProvider(logEvent, Culture));
return stringValue;
Expand Down
25 changes: 25 additions & 0 deletions tests/NLog.UnitTests/LayoutRenderers/EventPropertiesTests.cs
Expand Up @@ -127,5 +127,30 @@ public void JsonFormat()
logEvent.Properties["prop1"] = new string[] { "Hello", "World" };
Assert.Equal("[\"Hello\",\"World\"]", layout.Render(logEvent));
}

[Theory]
[InlineData("prop1", "", "{ Id = 1, id = 2, Name = test, Nested = { Id = 3 } }")]
[InlineData("prop1", "Id", "1")]
[InlineData("prop1", "id", "2")] //correct casing
[InlineData("prop1", "Nested.Id", "3")]
[InlineData("prop1", "Id.Wrong", "")] //don't crash on nesting
[InlineData("prop1", "Name", "test")]
[InlineData("prop1", "Name ", "test")] // trimend
[InlineData("prop1", "NameWrong", "")]
public void ObjectPathNestedProperty(string item, string objectPath, string expectedValue)
{
Layout layout = "${event-properties:" + item + ":objectpath=" + objectPath + "}";
layout.Initialize(null);

// Slow Uncached Lookup
LogEventInfo logEvent = LogEventInfo.Create(LogLevel.Info, "logger1", "message1");
logEvent.Properties["prop1"] = new { Id = 1, id = 2, Name = "test", Nested = new { Id = 3 } };
Assert.Equal(expectedValue, layout.Render(logEvent));

// Fast Cached Lookup
LogEventInfo logEvent2 = LogEventInfo.Create(LogLevel.Info, "logger1", "message1");
logEvent2.Properties["prop1"] = new { Id = 1, id = 2, Name = "test", Nested = new { Id = 3 } };
Assert.Equal(expectedValue, layout.Render(logEvent2));
}
}
}

0 comments on commit 8580b96

Please sign in to comment.