Skip to content

Commit

Permalink
ObjectEventPropertyLayoutRenderer - Extract nested property from LogE…
Browse files Browse the repository at this point in the history
…vent Properties
  • Loading branch information
snakefoot committed Apr 22, 2019
1 parent 2fffff4 commit c52ac48
Show file tree
Hide file tree
Showing 4 changed files with 173 additions and 7 deletions.
60 changes: 57 additions & 3 deletions src/NLog/Internal/ObjectReflectionCache.cs
Original file line number Diff line number Diff line change
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
8 changes: 4 additions & 4 deletions src/NLog/LayoutRenderers/EventPropertiesLayoutRenderer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ public class EventPropertiesLayoutRenderer : LayoutRenderer, IRawValue, IStringV
/// <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,14 +81,14 @@ 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)
internal virtual bool TryGetValue(LogEventInfo logEvent, out object value)
{
value = null;
return logEvent.HasProperties && logEvent.Properties.TryGetValue(Item, out value);
Expand All @@ -98,7 +98,7 @@ 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
94 changes: 94 additions & 0 deletions src/NLog/LayoutRenderers/ObjectEventPropertyLayoutRenderer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
//
// Copyright (c) 2004-2019 Jaroslaw Kowalski <jaak@jkowalski.net>, Kim Christensen, Julian Verdurmen
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// * Neither the name of Jaroslaw Kowalski nor the names of its
// contributors may be used to endorse or promote products derived from this
// software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
// THE POSSIBILITY OF SUCH DAMAGE.
//

namespace NLog.LayoutRenderers
{
using System;
using NLog.Config;
using NLog.Internal;

/// <summary>
/// Log individual property for event context data. See <see cref="LogEventInfo.Properties"/>.
/// </summary>
/// <example>
/// ${objecteventproperty:myOrder.OrderId}
/// </example>
[LayoutRenderer("objecteventproperty")]
[ThreadAgnostic]
[ThreadSafe]
[MutableUnsafe]
public class ObjectEventPropertyLayoutRenderer : EventPropertiesLayoutRenderer
{
private readonly ObjectReflectionCache _objectReflectionCache = new ObjectReflectionCache();
private string[] _objectPropertyPath;

/// <inheritdoc/>
protected override void InitializeLayoutRenderer()
{
_objectPropertyPath = Item.SplitAndTrimTokens('.');
}

internal override bool TryGetValue(LogEventInfo logEvent, out object value)
{
value = null;
if (_objectPropertyPath?.Length >= 1)
{
if (!logEvent.HasProperties)
return false;

if (logEvent.Properties.TryGetValue(_objectPropertyPath[0], out value))
{
for (int i = 1; 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;
}
}

return false;
}
}
}
18 changes: 18 additions & 0 deletions tests/NLog.UnitTests/LayoutRenderers/EventPropertiesTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -127,5 +127,23 @@ 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 NestedProperty(string item, string expectedValue)
{
Layout layout = "${objecteventproperty:" + item + "}";
layout.Initialize(null);
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));
}
}
}

0 comments on commit c52ac48

Please sign in to comment.