Skip to content

Commit

Permalink
Compile property accessor (#98)
Browse files Browse the repository at this point in the history
  • Loading branch information
sungam3r authored Jan 24, 2024
1 parent 6e12414 commit e6667c6
Show file tree
Hide file tree
Showing 2 changed files with 57 additions and 23 deletions.
28 changes: 28 additions & 0 deletions src/Destructurama.Attributed.Tests/AttributedDestructuringTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,28 @@ namespace Destructurama.Attributed.Tests;
[TestFixture]
public class AttributedDestructuringTests
{
[Test]
public void Throwing_Accessor_Should_Be_Handled()
{
// Setup
LogEvent evt = null!;

var log = new LoggerConfiguration()
.Destructure.UsingAttributes()
.WriteTo.Sink(new DelegatingSink(e => evt = e))
.CreateLogger();
var obj = new ClassWithThrowingAccessor();

// Execute
log.Information("Here is {@Customized}", obj);

// Verify
var sv = (StructureValue)evt.Properties["Customized"];
sv.Properties.Count.ShouldBe(1);
sv.Properties[0].Name.ShouldBe("BadProperty");
sv.Properties[0].Value.ShouldBeOfType<ScalarValue>().Value.ShouldBe("***");
}

[Test]
public void AttributesAreConsultedWhenDestructuring()
{
Expand Down Expand Up @@ -51,6 +73,12 @@ public void AttributesAreConsultedWhenDestructuring()
str.Contains("This is a password").ShouldBeFalse();
}

public class ClassWithThrowingAccessor
{
[LogMasked]
public string? BadProperty => throw new FormatException("oops");
}

[LogAsScalar]
public class ImmutableScalar
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
using System.Collections;
using System.Collections.Concurrent;
using System.Diagnostics.CodeAnalysis;
using System.Linq.Expressions;
using System.Reflection;
using Destructurama.Util;
using Serilog.Core;
Expand Down Expand Up @@ -53,10 +54,9 @@ private CacheEntry CreateCacheEntry(Type type)
return new(classDestructurer.CreateLogEventPropertyValue);

var properties = type.GetPropertiesRecursive().ToList();
if (!_options.IgnoreNullProperties
&& properties.All(pi =>
pi.GetCustomAttribute<IPropertyDestructuringAttribute>() == null
&& pi.GetCustomAttribute<IPropertyOptionalIgnoreAttribute>() == null))
if (!_options.IgnoreNullProperties && properties.All(pi =>
pi.GetCustomAttribute<IPropertyDestructuringAttribute>() == null
&& pi.GetCustomAttribute<IPropertyOptionalIgnoreAttribute>() == null))
{
return CacheEntry.Ignore;
}
Expand All @@ -74,21 +74,40 @@ private CacheEntry CreateCacheEntry(Type type)
if (_options.IgnoreNullProperties && !optionalIgnoreAttributes.Any() && !destructuringAttributes.Any() && typeof(IEnumerable).IsAssignableFrom(type))
return CacheEntry.Ignore;

return new CacheEntry((o, f) => MakeStructure(o, properties, optionalIgnoreAttributes, destructuringAttributes, f, type));
var propertiesWithAccessors = properties.Select(p => (p, Compile(p))).ToList();
return new CacheEntry((o, f) => MakeStructure(o, propertiesWithAccessors, optionalIgnoreAttributes, destructuringAttributes, f, type));

static Func<object, object> Compile(PropertyInfo property)
{
var objParameterExpr = Expression.Parameter(typeof(object), "instance");
var instanceExpr = Expression.Convert(objParameterExpr, property.DeclaringType);
var propertyExpr = Expression.Property(instanceExpr, property);
var propertyObjExpr = Expression.Convert(propertyExpr, typeof(object));
return Expression.Lambda<Func<object, object>>(propertyObjExpr, objParameterExpr).Compile();
}
}

private LogEventPropertyValue MakeStructure(
object o,
IEnumerable<PropertyInfo> loggedProperties,
IDictionary<PropertyInfo, IPropertyOptionalIgnoreAttribute> optionalIgnoreAttributes,
IDictionary<PropertyInfo, IPropertyDestructuringAttribute> destructuringAttributes,
List<(PropertyInfo Property, Func<object, object> Accessor)> loggedProperties,
Dictionary<PropertyInfo, IPropertyOptionalIgnoreAttribute> optionalIgnoreAttributes,
Dictionary<PropertyInfo, IPropertyDestructuringAttribute> destructuringAttributes,
ILogEventPropertyValueFactory propertyValueFactory,
Type type)
{
var structureProperties = new List<LogEventProperty>();
foreach (var pi in loggedProperties)
foreach (var (pi, accessor) in loggedProperties)
{
var propValue = SafeGetPropValue(o, pi);
object propValue;
try
{
propValue = accessor(o);
}
catch (Exception ex)
{
SelfLog.WriteLine("The property accessor {0} threw exception {1}", pi, ex);
propValue = $"The property accessor threw an exception: {ex.GetType().Name}";
}

if (optionalIgnoreAttributes.TryGetValue(pi, out var optionalIgnoreAttribute) && optionalIgnoreAttribute.ShouldPropertyBeIgnored(pi.Name, propValue, pi.PropertyType))
continue;
Expand All @@ -110,19 +129,6 @@ private LogEventPropertyValue MakeStructure(
return new StructureValue(structureProperties, type.Name);
}

private static object SafeGetPropValue(object o, PropertyInfo pi)
{
try
{
return pi.GetValue(o);
}
catch (TargetInvocationException ex)
{
SelfLog.WriteLine("The property accessor {0} threw exception {1}", pi, ex);
return $"The property accessor threw an exception: {ex.InnerException!.GetType().Name}";
}
}

internal static void Clear()
{
_cache.Clear();
Expand Down

0 comments on commit e6667c6

Please sign in to comment.