Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -77,21 +77,40 @@ private CacheEntry CreateCacheEntry(Type 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)
{
Comment on lines +106 to +110

Check notice

Code scanning / CodeQL

Generic catch clause

Generic catch clause.
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))
{
Expand Down Expand Up @@ -119,19 +138,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