From f6b1daabf30d005149bcc8113197b1107c038167 Mon Sep 17 00:00:00 2001 From: krajek Date: Sat, 15 May 2021 09:35:55 +0200 Subject: [PATCH 01/25] Fix reflection based destructuring for redefined property --- .../Core/ExceptionPropertiesBag.cs | 19 +++++++++++++++ .../ReflectionBasedDestructurerTest.cs | 23 +++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/Source/Serilog.Exceptions/Core/ExceptionPropertiesBag.cs b/Source/Serilog.Exceptions/Core/ExceptionPropertiesBag.cs index 84db0002..e082ddb6 100644 --- a/Source/Serilog.Exceptions/Core/ExceptionPropertiesBag.cs +++ b/Source/Serilog.Exceptions/Core/ExceptionPropertiesBag.cs @@ -56,9 +56,28 @@ public void AddProperty(string key, object? value) } } + key = this.MakeSureKeyIsUnique(key); + this.properties.Add(key, value); } + /// + /// We want to be as robust as possible + /// so even in case of multiple properties with the same name + /// we want to at least try carrying on and keep working. + /// + private string MakeSureKeyIsUnique(string key) + { + var i = 0; + while (this.properties.ContainsKey(key) && i < 5) + { + key += "$"; + i++; + } + + return key; + } + /// public bool ContainsProperty(string key) => this.properties.ContainsKey(key); } diff --git a/Tests/Serilog.Exceptions.Test/Destructurers/ReflectionBasedDestructurerTest.cs b/Tests/Serilog.Exceptions.Test/Destructurers/ReflectionBasedDestructurerTest.cs index 74e95b3f..9c726a50 100644 --- a/Tests/Serilog.Exceptions.Test/Destructurers/ReflectionBasedDestructurerTest.cs +++ b/Tests/Serilog.Exceptions.Test/Destructurers/ReflectionBasedDestructurerTest.cs @@ -9,6 +9,7 @@ namespace Serilog.Exceptions.Test.Destructurers using Serilog.Exceptions.Core; using Serilog.Exceptions.Destructurers; using Xunit; + using Xunit.Abstractions; using static LogJsonOutputUtils; public class ReflectionBasedDestructurerTest @@ -190,6 +191,8 @@ public void CanDestructureClassDataItem() Assert.Equal("ABC", destructuredStructDictionary[nameof(TestClass.ReferenceType)]); } + + [Fact] public void DestructuringDepthIsLimitedByConfiguredDepth() { @@ -384,6 +387,21 @@ public void CanDestructureObjectWithHiddenProperty() Assert.Equal(baseClass.HiddenProperty, info?[$"{typeof(BaseClass).FullName}.{nameof(BaseClass.HiddenProperty)}"]); } + [Fact] + public void CanDestructureObjectWithRedefinedProperty() + { + + var exception = new TestExceptionClassWithNewDefinition() { PublicProperty = 20 }; + + var propertiesBag = new ExceptionPropertiesBag(exception); + CreateReflectionBasedDestructurer().Destructure(exception, propertiesBag, EmptyDestructurer()); + + var properties = propertiesBag.GetResultDictionary(); + var info = properties[nameof(TestExceptionClassWithNewDefinition.PublicProperty)]; + } + + + private static void Test_ResultOfReflectionDestructurerShouldBeEquivalentToCustomOne( Exception exception, IExceptionDestructurer customDestructurer) @@ -578,6 +596,11 @@ internal class TestClass public string? ReferenceType { get; set; } } + internal class TestExceptionClassWithNewDefinition : TestException + { + public new int PublicProperty { get; set; } + } + internal class BaseClass { public virtual int HiddenProperty { get; set; } From f76a3e6a8fd8db57cda6dbebf7fe3e80fec61cbf Mon Sep 17 00:00:00 2001 From: krajek Date: Sat, 15 May 2021 10:02:20 +0200 Subject: [PATCH 02/25] Optimized version for NET5 --- .../Core/ExceptionPropertiesBag.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/Source/Serilog.Exceptions/Core/ExceptionPropertiesBag.cs b/Source/Serilog.Exceptions/Core/ExceptionPropertiesBag.cs index e082ddb6..3cd07431 100644 --- a/Source/Serilog.Exceptions/Core/ExceptionPropertiesBag.cs +++ b/Source/Serilog.Exceptions/Core/ExceptionPropertiesBag.cs @@ -56,9 +56,23 @@ public void AddProperty(string key, object? value) } } + this.AddPairToProperties(key, value); + } + + private void AddPairToProperties(string key, object? value) + { +#if NET5_0 + int i = 0; + while (!this.properties.TryAdd(key, value) && i < 5) + { + key += "$"; + i++; + } +#else key = this.MakeSureKeyIsUnique(key); this.properties.Add(key, value); +#endif } /// @@ -68,6 +82,7 @@ public void AddProperty(string key, object? value) /// private string MakeSureKeyIsUnique(string key) { + var i = 0; while (this.properties.ContainsKey(key) && i < 5) { From c7a037e8c291e3ef95d31dfa60dec52ddae069fe Mon Sep 17 00:00:00 2001 From: krajek Date: Sat, 15 May 2021 10:12:19 +0200 Subject: [PATCH 03/25] Fix style warnings --- Source/Serilog.Exceptions/Core/ExceptionPropertiesBag.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/Source/Serilog.Exceptions/Core/ExceptionPropertiesBag.cs b/Source/Serilog.Exceptions/Core/ExceptionPropertiesBag.cs index 3cd07431..d23df257 100644 --- a/Source/Serilog.Exceptions/Core/ExceptionPropertiesBag.cs +++ b/Source/Serilog.Exceptions/Core/ExceptionPropertiesBag.cs @@ -59,10 +59,13 @@ public void AddProperty(string key, object? value) this.AddPairToProperties(key, value); } + /// + public bool ContainsProperty(string key) => this.properties.ContainsKey(key); + private void AddPairToProperties(string key, object? value) { #if NET5_0 - int i = 0; + var i = 0; while (!this.properties.TryAdd(key, value) && i < 5) { key += "$"; @@ -82,7 +85,6 @@ private void AddPairToProperties(string key, object? value) /// private string MakeSureKeyIsUnique(string key) { - var i = 0; while (this.properties.ContainsKey(key) && i < 5) { @@ -92,8 +94,5 @@ private string MakeSureKeyIsUnique(string key) return key; } - - /// - public bool ContainsProperty(string key) => this.properties.ContainsKey(key); } } From b37bb300ac5a5b9df508d0eb323e1b93410c4386 Mon Sep 17 00:00:00 2001 From: krajek Date: Sat, 15 May 2021 10:14:36 +0200 Subject: [PATCH 04/25] MakeSureKeyIsUnique only for !NET5_0 --- Source/Serilog.Exceptions/Core/ExceptionPropertiesBag.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Source/Serilog.Exceptions/Core/ExceptionPropertiesBag.cs b/Source/Serilog.Exceptions/Core/ExceptionPropertiesBag.cs index d23df257..51930f6e 100644 --- a/Source/Serilog.Exceptions/Core/ExceptionPropertiesBag.cs +++ b/Source/Serilog.Exceptions/Core/ExceptionPropertiesBag.cs @@ -78,6 +78,7 @@ private void AddPairToProperties(string key, object? value) #endif } +#if !NET5_0 /// /// We want to be as robust as possible /// so even in case of multiple properties with the same name @@ -95,4 +96,5 @@ private string MakeSureKeyIsUnique(string key) return key; } } +#endif } From e8798b4acb15cfcc43f7b1baf6f553c56baa1430 Mon Sep 17 00:00:00 2001 From: krajek Date: Sat, 15 May 2021 10:15:00 +0200 Subject: [PATCH 05/25] Build fix --- Source/Serilog.Exceptions/Core/ExceptionPropertiesBag.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Serilog.Exceptions/Core/ExceptionPropertiesBag.cs b/Source/Serilog.Exceptions/Core/ExceptionPropertiesBag.cs index 51930f6e..8922ef51 100644 --- a/Source/Serilog.Exceptions/Core/ExceptionPropertiesBag.cs +++ b/Source/Serilog.Exceptions/Core/ExceptionPropertiesBag.cs @@ -95,6 +95,6 @@ private string MakeSureKeyIsUnique(string key) return key; } - } #endif + } } From e510174951c4bba64431382403d35a8fd574016b Mon Sep 17 00:00:00 2001 From: krajek Date: Sat, 15 May 2021 15:17:41 +0200 Subject: [PATCH 06/25] Remove multipe blank lines in row --- .../Destructurers/ReflectionBasedDestructurerTest.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Tests/Serilog.Exceptions.Test/Destructurers/ReflectionBasedDestructurerTest.cs b/Tests/Serilog.Exceptions.Test/Destructurers/ReflectionBasedDestructurerTest.cs index 9c726a50..1c4b0e09 100644 --- a/Tests/Serilog.Exceptions.Test/Destructurers/ReflectionBasedDestructurerTest.cs +++ b/Tests/Serilog.Exceptions.Test/Destructurers/ReflectionBasedDestructurerTest.cs @@ -191,8 +191,6 @@ public void CanDestructureClassDataItem() Assert.Equal("ABC", destructuredStructDictionary[nameof(TestClass.ReferenceType)]); } - - [Fact] public void DestructuringDepthIsLimitedByConfiguredDepth() { @@ -390,7 +388,6 @@ public void CanDestructureObjectWithHiddenProperty() [Fact] public void CanDestructureObjectWithRedefinedProperty() { - var exception = new TestExceptionClassWithNewDefinition() { PublicProperty = 20 }; var propertiesBag = new ExceptionPropertiesBag(exception); @@ -400,8 +397,6 @@ public void CanDestructureObjectWithRedefinedProperty() var info = properties[nameof(TestExceptionClassWithNewDefinition.PublicProperty)]; } - - private static void Test_ResultOfReflectionDestructurerShouldBeEquivalentToCustomOne( Exception exception, IExceptionDestructurer customDestructurer) From 9f9ed8be14c6f169e331a246d2341f76113f8d01 Mon Sep 17 00:00:00 2001 From: krajek Date: Mon, 17 May 2021 09:12:55 +0200 Subject: [PATCH 07/25] Document limit of 5 same named properties --- .../Core/ExceptionPropertiesBag.cs | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/Source/Serilog.Exceptions/Core/ExceptionPropertiesBag.cs b/Source/Serilog.Exceptions/Core/ExceptionPropertiesBag.cs index 8922ef51..eb423319 100644 --- a/Source/Serilog.Exceptions/Core/ExceptionPropertiesBag.cs +++ b/Source/Serilog.Exceptions/Core/ExceptionPropertiesBag.cs @@ -7,9 +7,16 @@ namespace Serilog.Exceptions.Core /// internal class ExceptionPropertiesBag : IExceptionPropertiesBag { + /// + /// In theory there should not be any properties with same names passed + /// but just as a defensive and robustness measure, we allow small amount + /// so that library can keep working in case of minor error (or unexpected + /// scenario) on the properties provider side. + /// + private const int AcceptableNumberOfSameNameProperties = 5; private readonly Exception exception; private readonly IExceptionPropertyFilter? filter; - private readonly Dictionary properties = new(); + private readonly Dictionary properties = new (); /// /// We keep a note on whether the results were collected to be sure that after that there are no changes. This @@ -62,11 +69,16 @@ public void AddProperty(string key, object? value) /// public bool ContainsProperty(string key) => this.properties.ContainsKey(key); + /// + /// We want to be as robust as possible + /// so even in case of multiple properties with the same name + /// we want to at least try carrying on and keep working. + /// private void AddPairToProperties(string key, object? value) { #if NET5_0 var i = 0; - while (!this.properties.TryAdd(key, value) && i < 5) + while (!this.properties.TryAdd(key, value) && i < AcceptableNumberOfSameNameProperties) { key += "$"; i++; @@ -79,15 +91,11 @@ private void AddPairToProperties(string key, object? value) } #if !NET5_0 - /// - /// We want to be as robust as possible - /// so even in case of multiple properties with the same name - /// we want to at least try carrying on and keep working. - /// + private string MakeSureKeyIsUnique(string key) { var i = 0; - while (this.properties.ContainsKey(key) && i < 5) + while (this.properties.ContainsKey(key) && i < AcceptableNumberOfSameNameProperties) { key += "$"; i++; From dfcf04c92b0be333f2f99e166999aa39abf0f818 Mon Sep 17 00:00:00 2001 From: krajek Date: Mon, 17 May 2021 11:58:27 +0200 Subject: [PATCH 08/25] Extract GetReplacementKey --- Source/Serilog.Exceptions/Core/ExceptionPropertiesBag.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Source/Serilog.Exceptions/Core/ExceptionPropertiesBag.cs b/Source/Serilog.Exceptions/Core/ExceptionPropertiesBag.cs index eb423319..7353c7ad 100644 --- a/Source/Serilog.Exceptions/Core/ExceptionPropertiesBag.cs +++ b/Source/Serilog.Exceptions/Core/ExceptionPropertiesBag.cs @@ -69,6 +69,9 @@ public void AddProperty(string key, object? value) /// public bool ContainsProperty(string key) => this.properties.ContainsKey(key); + private static string GetReplacementKey(string key) => key + "$"; + + /// /// We want to be as robust as possible /// so even in case of multiple properties with the same name @@ -80,7 +83,7 @@ private void AddPairToProperties(string key, object? value) var i = 0; while (!this.properties.TryAdd(key, value) && i < AcceptableNumberOfSameNameProperties) { - key += "$"; + key = GetReplacementKey(key); i++; } #else @@ -97,12 +100,13 @@ private string MakeSureKeyIsUnique(string key) var i = 0; while (this.properties.ContainsKey(key) && i < AcceptableNumberOfSameNameProperties) { - key += "$"; + key = GetReplacementKey(key); i++; } return key; } #endif + } } From e0e4d9b87e69d71a39eedcb9ba321f1b3e7f31f1 Mon Sep 17 00:00:00 2001 From: krajek Date: Sat, 22 May 2021 10:39:57 +0200 Subject: [PATCH 09/25] Add one more test --- .../ReflectionBasedDestructurerTest.cs | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/Tests/Serilog.Exceptions.Test/Destructurers/ReflectionBasedDestructurerTest.cs b/Tests/Serilog.Exceptions.Test/Destructurers/ReflectionBasedDestructurerTest.cs index 1c4b0e09..f000a1b0 100644 --- a/Tests/Serilog.Exceptions.Test/Destructurers/ReflectionBasedDestructurerTest.cs +++ b/Tests/Serilog.Exceptions.Test/Destructurers/ReflectionBasedDestructurerTest.cs @@ -397,6 +397,25 @@ public void CanDestructureObjectWithRedefinedProperty() var info = properties[nameof(TestExceptionClassWithNewDefinition.PublicProperty)]; } + [Fact] + public void CanDestructureObjectWithDataWithRedefinedProperty() + { + var exception = new RecursiveException + { + Node = new RecursiveNodeWithRedefinedProperty + { + Name = 123, + }, + }; + + var propertiesBag = new ExceptionPropertiesBag(exception); + CreateReflectionBasedDestructurer().Destructure(exception, propertiesBag, EmptyDestructurer()); + + var properties = propertiesBag.GetResultDictionary(); + var parent = (IDictionary?)properties[nameof(RecursiveException.Node)]; + Assert.Equal(123, parent?[nameof(RecursiveNode.Name)]); + } + private static void Test_ResultOfReflectionDestructurerShouldBeEquivalentToCustomOne( Exception exception, IExceptionDestructurer customDestructurer) @@ -568,6 +587,11 @@ public class RecursiveNode public RecursiveNode? Child { get; set; } } + public class RecursiveNodeWithRedefinedProperty : RecursiveNode + { + public new int Name { get; set; } + } + public class RecursiveException : Exception { public RecursiveNode? Node { get; set; } From 1dae8fd652c600fc77c3cf3e17bb48983ca2dcef Mon Sep 17 00:00:00 2001 From: krajek Date: Sat, 22 May 2021 10:58:12 +0200 Subject: [PATCH 10/25] Extract ReflectionInfoExtractor --- .../ReflectionBasedDestructurer.cs | 82 ++----------------- .../Reflection/ReflectionInfo.cs | 15 ++++ .../Reflection/ReflectionInfoExtractor.cs | 66 +++++++++++++++ .../Reflection/ReflectionPropertyInfo.cs | 20 +++++ 4 files changed, 106 insertions(+), 77 deletions(-) create mode 100644 Source/Serilog.Exceptions/Reflection/ReflectionInfo.cs create mode 100644 Source/Serilog.Exceptions/Reflection/ReflectionInfoExtractor.cs create mode 100644 Source/Serilog.Exceptions/Reflection/ReflectionPropertyInfo.cs diff --git a/Source/Serilog.Exceptions/Destructurers/ReflectionBasedDestructurer.cs b/Source/Serilog.Exceptions/Destructurers/ReflectionBasedDestructurer.cs index 31dc2aa5..f22d3192 100644 --- a/Source/Serilog.Exceptions/Destructurers/ReflectionBasedDestructurer.cs +++ b/Source/Serilog.Exceptions/Destructurers/ReflectionBasedDestructurer.cs @@ -9,6 +9,7 @@ namespace Serilog.Exceptions.Destructurers using System.Reflection; using System.Threading; using System.Threading.Tasks; + using Reflection; using Serilog.Exceptions.Core; /// @@ -23,10 +24,8 @@ public class ReflectionBasedDestructurer : IExceptionDestructurer private const string RefLabel = "$ref"; private const string CyclicReferenceMessage = "Cyclic reference"; private readonly int destructuringDepth; - private readonly object lockObj = new(); + private ReflectionInfoExtractor reflectionInfoExtractor = new (); - private readonly Dictionary reflectionInfoCache = new(); - private readonly PropertyInfo[] baseExceptionPropertiesForDestructuring; /// /// Initializes a new instance of the class. @@ -44,7 +43,6 @@ public ReflectionBasedDestructurer(int destructuringDepth) } this.destructuringDepth = destructuringDepth; - this.baseExceptionPropertiesForDestructuring = GetExceptionPropertiesForDestructuring(typeof(Exception)); } /// @@ -82,7 +80,7 @@ public void Destructure( destructureException, data => this.DestructureValueDictionary(data, 1, destructuredObjects, ref nextCyclicRefId)); - var reflectionInfo = this.GetOrCreateReflectionInfo(exception.GetType()); + var reflectionInfo = this.reflectionInfoExtractor.GetOrCreateReflectionInfo(exception.GetType()); this.AppendProperties( exception, @@ -113,22 +111,6 @@ public void Destructure( return refId; } - private static Func GenerateFastGetterForProperty(Type type, PropertyInfo property) - { - var objParam = Expression.Parameter(typeof(object), "num"); - var typedObj = Expression.Convert(objParam, type); - var memberExpression = Expression.Property(typedObj, property); - var typedResult = Expression.Convert(memberExpression, typeof(object)); - var resultLambda = Expression.Lambda>(typedResult, objParam); - return resultLambda.Compile(); - } - - private static PropertyInfo[] GetExceptionPropertiesForDestructuring(Type valueType) => - valueType - .GetProperties(BindingFlags.Public | BindingFlags.Instance) - .Where(x => x.CanRead && x.GetIndexParameters().Length == 0) - .ToArray(); - private static object DestructureUri(Uri value) => value.ToString(); private static void AppendTypeIfPossible(IExceptionPropertiesBag propertiesBag, Type valueType) @@ -190,19 +172,6 @@ private void AppendProperties( } } - private ReflectionInfo GenerateReflectionInfoForType(Type valueType) - { - var properties = GetExceptionPropertiesForDestructuring(valueType); - var propertyInfos = properties - .Select(p => new ReflectionPropertyInfo(p.Name, p.DeclaringType, GenerateFastGetterForProperty(valueType, p))) - .ToArray(); - var propertiesInfosExceptBaseOnes = propertyInfos - .Where(p => this.baseExceptionPropertiesForDestructuring.All(bp => bp.Name != p.Name)) - .ToArray(); - - return new ReflectionInfo(propertyInfos, propertiesInfosExceptBaseOnes); - } - private object? DestructureValue( object? value, int level, @@ -335,7 +304,7 @@ private object DestructureValueDictionary( var values = new Dictionary(); destructuredObjects.Add(value, values); - var reflectionInfo = this.GetOrCreateReflectionInfo(valueType); + var reflectionInfo = reflectionInfoExtractor.GetOrCreateReflectionInfo(valueType); foreach (var property in reflectionInfo.Properties) { @@ -399,47 +368,6 @@ private object DestructureTask( return values; } - private ReflectionInfo GetOrCreateReflectionInfo(Type valueType) - { - lock (this.lockObj) - { - if (!this.reflectionInfoCache.TryGetValue(valueType, out var reflectionInfo)) - { - reflectionInfo = this.GenerateReflectionInfoForType(valueType); - this.reflectionInfoCache.Add(valueType, reflectionInfo); - } - - return reflectionInfo; - } - } - - private class ReflectionInfo - { - public ReflectionInfo(ReflectionPropertyInfo[] properties, ReflectionPropertyInfo[] propertiesExceptBaseOnes) - { - this.Properties = properties; - this.PropertiesExceptBaseOnes = propertiesExceptBaseOnes; - } - - public ReflectionPropertyInfo[] Properties { get; } - - public ReflectionPropertyInfo[] PropertiesExceptBaseOnes { get; } - } - - private class ReflectionPropertyInfo - { - public ReflectionPropertyInfo(string name, Type? declaringType, Func getter) - { - this.Name = name; - this.DeclaringType = declaringType; - this.Getter = getter; - } - - public string Name { get; } - - public Type? DeclaringType { get; } - - public Func Getter { get; } - } + } } diff --git a/Source/Serilog.Exceptions/Reflection/ReflectionInfo.cs b/Source/Serilog.Exceptions/Reflection/ReflectionInfo.cs new file mode 100644 index 00000000..afc0da49 --- /dev/null +++ b/Source/Serilog.Exceptions/Reflection/ReflectionInfo.cs @@ -0,0 +1,15 @@ +namespace Serilog.Exceptions.Reflection +{ + internal class ReflectionInfo + { + public ReflectionInfo(ReflectionPropertyInfo[] properties, ReflectionPropertyInfo[] propertiesExceptBaseOnes) + { + this.Properties = properties; + this.PropertiesExceptBaseOnes = propertiesExceptBaseOnes; + } + + public ReflectionPropertyInfo[] Properties { get; } + + public ReflectionPropertyInfo[] PropertiesExceptBaseOnes { get; } + } +} diff --git a/Source/Serilog.Exceptions/Reflection/ReflectionInfoExtractor.cs b/Source/Serilog.Exceptions/Reflection/ReflectionInfoExtractor.cs new file mode 100644 index 00000000..fe40f308 --- /dev/null +++ b/Source/Serilog.Exceptions/Reflection/ReflectionInfoExtractor.cs @@ -0,0 +1,66 @@ +namespace Serilog.Exceptions.Reflection +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Linq.Expressions; + using System.Reflection; + + internal class ReflectionInfoExtractor + { + + private readonly object lockObj = new(); + private readonly Dictionary reflectionInfoCache = new(); + private readonly PropertyInfo[] baseExceptionPropertiesForDestructuring; + + public ReflectionInfoExtractor() + { + this.baseExceptionPropertiesForDestructuring = GetExceptionPropertiesForDestructuring(typeof(Exception)); + + } + + + public ReflectionInfo GetOrCreateReflectionInfo(Type valueType) + { + lock (this.lockObj) + { + if (!this.reflectionInfoCache.TryGetValue(valueType, out var reflectionInfo)) + { + reflectionInfo = this.GenerateReflectionInfoForType(valueType); + this.reflectionInfoCache.Add(valueType, reflectionInfo); + } + + return reflectionInfo; + } + } + + private ReflectionInfo GenerateReflectionInfoForType(Type valueType) + { + var properties = GetExceptionPropertiesForDestructuring(valueType); + var propertyInfos = properties + .Select(p => new ReflectionPropertyInfo(p.Name, p.DeclaringType, GenerateFastGetterForProperty(valueType, p))) + .ToArray(); + var propertiesInfosExceptBaseOnes = propertyInfos + .Where(p => this.baseExceptionPropertiesForDestructuring.All(bp => bp.Name != p.Name)) + .ToArray(); + + return new ReflectionInfo(propertyInfos, propertiesInfosExceptBaseOnes); + } + + private static Func GenerateFastGetterForProperty(Type type, PropertyInfo property) + { + var objParam = Expression.Parameter(typeof(object), "num"); + var typedObj = Expression.Convert(objParam, type); + var memberExpression = Expression.Property(typedObj, property); + var typedResult = Expression.Convert(memberExpression, typeof(object)); + var resultLambda = Expression.Lambda>(typedResult, objParam); + return resultLambda.Compile(); + } + + private static PropertyInfo[] GetExceptionPropertiesForDestructuring(Type valueType) => + valueType + .GetProperties(BindingFlags.Public | BindingFlags.Instance) + .Where(x => x.CanRead && x.GetIndexParameters().Length == 0) + .ToArray(); + } +} diff --git a/Source/Serilog.Exceptions/Reflection/ReflectionPropertyInfo.cs b/Source/Serilog.Exceptions/Reflection/ReflectionPropertyInfo.cs new file mode 100644 index 00000000..fc8564d5 --- /dev/null +++ b/Source/Serilog.Exceptions/Reflection/ReflectionPropertyInfo.cs @@ -0,0 +1,20 @@ +namespace Serilog.Exceptions.Reflection +{ + using System; + + internal class ReflectionPropertyInfo + { + public ReflectionPropertyInfo(string name, Type? declaringType, Func getter) + { + this.Name = name; + this.DeclaringType = declaringType; + this.Getter = getter; + } + + public string Name { get; } + + public Type? DeclaringType { get; } + + public Func Getter { get; } + } +} From 8248310eaab8a2a3adb309aca5a0a7a237896e4c Mon Sep 17 00:00:00 2001 From: krajek Date: Sat, 22 May 2021 11:01:47 +0200 Subject: [PATCH 11/25] Fix some whitespace issues --- .../ReflectionBasedDestructurer.cs | 2 -- .../Reflection/ReflectionInfoExtractor.cs | 26 +++++++++---------- 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/Source/Serilog.Exceptions/Destructurers/ReflectionBasedDestructurer.cs b/Source/Serilog.Exceptions/Destructurers/ReflectionBasedDestructurer.cs index f22d3192..a06b1ee2 100644 --- a/Source/Serilog.Exceptions/Destructurers/ReflectionBasedDestructurer.cs +++ b/Source/Serilog.Exceptions/Destructurers/ReflectionBasedDestructurer.cs @@ -367,7 +367,5 @@ private object DestructureTask( return values; } - - } } diff --git a/Source/Serilog.Exceptions/Reflection/ReflectionInfoExtractor.cs b/Source/Serilog.Exceptions/Reflection/ReflectionInfoExtractor.cs index fe40f308..ab39e815 100644 --- a/Source/Serilog.Exceptions/Reflection/ReflectionInfoExtractor.cs +++ b/Source/Serilog.Exceptions/Reflection/ReflectionInfoExtractor.cs @@ -34,19 +34,6 @@ public ReflectionInfo GetOrCreateReflectionInfo(Type valueType) } } - private ReflectionInfo GenerateReflectionInfoForType(Type valueType) - { - var properties = GetExceptionPropertiesForDestructuring(valueType); - var propertyInfos = properties - .Select(p => new ReflectionPropertyInfo(p.Name, p.DeclaringType, GenerateFastGetterForProperty(valueType, p))) - .ToArray(); - var propertiesInfosExceptBaseOnes = propertyInfos - .Where(p => this.baseExceptionPropertiesForDestructuring.All(bp => bp.Name != p.Name)) - .ToArray(); - - return new ReflectionInfo(propertyInfos, propertiesInfosExceptBaseOnes); - } - private static Func GenerateFastGetterForProperty(Type type, PropertyInfo property) { var objParam = Expression.Parameter(typeof(object), "num"); @@ -62,5 +49,18 @@ private static PropertyInfo[] GetExceptionPropertiesForDestructuring(Type valueT .GetProperties(BindingFlags.Public | BindingFlags.Instance) .Where(x => x.CanRead && x.GetIndexParameters().Length == 0) .ToArray(); + + private ReflectionInfo GenerateReflectionInfoForType(Type valueType) + { + var properties = GetExceptionPropertiesForDestructuring(valueType); + var propertyInfos = properties + .Select(p => new ReflectionPropertyInfo(p.Name, p.DeclaringType, GenerateFastGetterForProperty(valueType, p))) + .ToArray(); + var propertiesInfosExceptBaseOnes = propertyInfos + .Where(p => this.baseExceptionPropertiesForDestructuring.All(bp => bp.Name != p.Name)) + .ToArray(); + + return new ReflectionInfo(propertyInfos, propertiesInfosExceptBaseOnes); + } } } From 4aec164f147fdf0012fa7882c4d2ae5f3ac327ae Mon Sep 17 00:00:00 2001 From: krajek Date: Sat, 22 May 2021 21:36:10 +0200 Subject: [PATCH 12/25] First working version --- .../ReflectionBasedDestructurer.cs | 3 +- .../Reflection/ReflectionInfoExtractor.cs | 50 +++++++++--- .../Reflection/ReflectionPropertyInfo.cs | 53 ++++++++++++- .../Reflection/ReflectionInfoExtractorTest.cs | 76 +++++++++++++++++++ 4 files changed, 169 insertions(+), 13 deletions(-) create mode 100644 Tests/Serilog.Exceptions.Test/Reflection/ReflectionInfoExtractorTest.cs diff --git a/Source/Serilog.Exceptions/Destructurers/ReflectionBasedDestructurer.cs b/Source/Serilog.Exceptions/Destructurers/ReflectionBasedDestructurer.cs index a06b1ee2..83b61701 100644 --- a/Source/Serilog.Exceptions/Destructurers/ReflectionBasedDestructurer.cs +++ b/Source/Serilog.Exceptions/Destructurers/ReflectionBasedDestructurer.cs @@ -316,8 +316,7 @@ private object DestructureValueDictionary( level + 1, destructuredObjects, ref nextCyclicRefId); - var key = values.ContainsKey(property.Name) ? $"{property.DeclaringType?.FullName}.{property.Name}" : property.Name; - values.Add(key, destructuredValue); + values.Add(property.Name, destructuredValue); } catch (TargetInvocationException targetInvocationException) { diff --git a/Source/Serilog.Exceptions/Reflection/ReflectionInfoExtractor.cs b/Source/Serilog.Exceptions/Reflection/ReflectionInfoExtractor.cs index ab39e815..7e22a00d 100644 --- a/Source/Serilog.Exceptions/Reflection/ReflectionInfoExtractor.cs +++ b/Source/Serilog.Exceptions/Reflection/ReflectionInfoExtractor.cs @@ -8,16 +8,11 @@ namespace Serilog.Exceptions.Reflection internal class ReflectionInfoExtractor { - private readonly object lockObj = new(); private readonly Dictionary reflectionInfoCache = new(); - private readonly PropertyInfo[] baseExceptionPropertiesForDestructuring; - - public ReflectionInfoExtractor() - { - this.baseExceptionPropertiesForDestructuring = GetExceptionPropertiesForDestructuring(typeof(Exception)); + private readonly IList baseExceptionPropertiesForDestructuring; - } + public ReflectionInfoExtractor() => this.baseExceptionPropertiesForDestructuring = GetExceptionPropertiesForDestructuring(typeof(Exception)); public ReflectionInfo GetOrCreateReflectionInfo(Type valueType) @@ -44,11 +39,15 @@ private static Func GenerateFastGetterForProperty(Type type, Pro return resultLambda.Compile(); } - private static PropertyInfo[] GetExceptionPropertiesForDestructuring(Type valueType) => - valueType + private static List GetExceptionPropertiesForDestructuring(Type valueType) + { + var allProperties = valueType .GetProperties(BindingFlags.Public | BindingFlags.Instance) .Where(x => x.CanRead && x.GetIndexParameters().Length == 0) - .ToArray(); + .ToList(); + + return allProperties; + } private ReflectionInfo GenerateReflectionInfoForType(Type valueType) { @@ -56,11 +55,42 @@ private ReflectionInfo GenerateReflectionInfoForType(Type valueType) var propertyInfos = properties .Select(p => new ReflectionPropertyInfo(p.Name, p.DeclaringType, GenerateFastGetterForProperty(valueType, p))) .ToArray(); + + var groupedByName = new Dictionary>(); + foreach (var propertyInfo in propertyInfos) + { + if (groupedByName.ContainsKey(propertyInfo.Name)) + { + groupedByName[propertyInfo.Name].Add(propertyInfo); + } + else + { + groupedByName[propertyInfo.Name] = new List { propertyInfo }; + } + } + + foreach (var nameGroup in groupedByName) + { + if (nameGroup.Value.Count > 1) + { + foreach (var propertyInfoInGroupName in nameGroup.Value) + { + foreach (var otherPropertyInfoInGroupName in nameGroup.Value) + { + propertyInfoInGroupName.MarkNameWithFullNameIRedefineThatProperty(otherPropertyInfoInGroupName); + } + } + } + } + var propertiesInfosExceptBaseOnes = propertyInfos .Where(p => this.baseExceptionPropertiesForDestructuring.All(bp => bp.Name != p.Name)) .ToArray(); return new ReflectionInfo(propertyInfos, propertiesInfosExceptBaseOnes); } + + + } } diff --git a/Source/Serilog.Exceptions/Reflection/ReflectionPropertyInfo.cs b/Source/Serilog.Exceptions/Reflection/ReflectionPropertyInfo.cs index fc8564d5..d2859821 100644 --- a/Source/Serilog.Exceptions/Reflection/ReflectionPropertyInfo.cs +++ b/Source/Serilog.Exceptions/Reflection/ReflectionPropertyInfo.cs @@ -1,6 +1,7 @@ namespace Serilog.Exceptions.Reflection { using System; + using System.Reflection; internal class ReflectionPropertyInfo { @@ -11,10 +12,60 @@ public ReflectionPropertyInfo(string name, Type? declaringType, Func Getter { get; } + + private bool markedWithFullName = false; + + public void MarkNameWithFullName() + { + if (!this.markedWithFullName) + { + this.markedWithFullName = true; + this.Name = $"{this.DeclaringType?.FullName}.{this.Name}"; + } + } + + public void MarkNameWithFullNameIRedefineThatProperty(ReflectionPropertyInfo otherPropertyInfo) + { + if (otherPropertyInfo == this) + { + return; + } + + if (this.DeclaringType == null || otherPropertyInfo?.DeclaringType == null) + { + return; + } + + if (otherPropertyInfo?.Name != this.Name) + { + return; + } + + if (this.markedWithFullName) + { + return; + } + + var shouldMarkWithFullName = IsSubTypeOf( + otherPropertyInfo.DeclaringType, + this.DeclaringType); + + if (shouldMarkWithFullName) + { + this.MarkNameWithFullName(); + } + } + + private static bool IsSubTypeOf(Type possibleSubType, Type possibleBaseType) => +#if NETSTANDARD1_3 || NETSTANDARD1_6 + possibleBaseType.GetTypeInfo().IsSubclassOf(possibleBaseType); +#else + possibleSubType.IsSubclassOf(possibleBaseType); +#endif } } diff --git a/Tests/Serilog.Exceptions.Test/Reflection/ReflectionInfoExtractorTest.cs b/Tests/Serilog.Exceptions.Test/Reflection/ReflectionInfoExtractorTest.cs new file mode 100644 index 00000000..b02c0f65 --- /dev/null +++ b/Tests/Serilog.Exceptions.Test/Reflection/ReflectionInfoExtractorTest.cs @@ -0,0 +1,76 @@ +namespace Serilog.Exceptions.Test.Reflection +{ + using System.Linq; + using Exceptions.Reflection; + using FluentAssertions; + using Xunit; + + public class ReflectionInfoExtractorTest + { + private readonly ReflectionInfoExtractor reflectionInfoExtractor = new(); + + [Fact] + public void GivenObjectWithRedefinedProperty_ShouldDiscardBaseClassProperty() + { + var testObject = new TestObjectWithRedefinedProperty() { Name = 123 }; + + var reflectionInfo = this.reflectionInfoExtractor.GetOrCreateReflectionInfo(typeof(TestObjectWithRedefinedProperty)); + + reflectionInfo.Properties.Should().HaveCount(2); + + var namePropertyInfo = reflectionInfo.Properties.Should().ContainSingle(x => x.Name == "Name").Which; + namePropertyInfo.Name.Should().Be(nameof(TestObjectWithRedefinedProperty.Name)); + namePropertyInfo.DeclaringType.Should().Be(typeof(TestObjectWithRedefinedProperty)); + var nameGetter = namePropertyInfo.Getter; + var testObjectName = nameGetter(testObject); + testObjectName.Should().BeOfType().Which.Should().Be(123); + + var baseClassPropertyInfo = reflectionInfo + .Properties.Should().ContainSingle(x => x.Name == "Serilog.Exceptions.Test.Reflection.ReflectionInfoExtractorTest+TestObject.Name").Which; + var baseClassNameGetter = baseClassPropertyInfo.Getter; + var baseClassTestObjectName = baseClassNameGetter(testObject); + baseClassTestObjectName.Should().BeNull(); + } + + [Fact] + public void GivenObjectWithDoubleRedefinedProperty_ShouldMarkBaseClassPropertiesWithFullName() + { + var testObject = new TestObjectWithDoubleRedefinedProperty() { Name = 456.789 }; + + var reflectionInfo = this.reflectionInfoExtractor.GetOrCreateReflectionInfo(typeof(TestObjectWithDoubleRedefinedProperty)); + + var propertyNames = reflectionInfo.Properties + .Select(x => x.Name) + .ToList(); + propertyNames.Should().BeEquivalentTo( + new[] + { + "Name", + "Serilog.Exceptions.Test.Reflection.ReflectionInfoExtractorTest+TestObjectWithRedefinedProperty.Name", + "Serilog.Exceptions.Test.Reflection.ReflectionInfoExtractorTest+TestObject.Name", + }, + x => x.WithoutStrictOrdering()); + var namePropertyInfo = reflectionInfo.Properties.Should().ContainSingle(x => x.Name == "Name").Which; + namePropertyInfo.Name.Should().Be(nameof(TestObjectWithDoubleRedefinedProperty.Name)); + namePropertyInfo.DeclaringType.Should().Be(typeof(TestObjectWithDoubleRedefinedProperty)); + var nameGetter = namePropertyInfo.Getter; + var testObjectName = nameGetter(testObject); + testObjectName.Should().BeOfType().Which.Should().Be(456.789); + } + + public class TestObject + { + public string? Name { get; set; } + } + + public class TestObjectWithRedefinedProperty : TestObject + { + public new int Name { get; set; } + } + + public class TestObjectWithDoubleRedefinedProperty : TestObjectWithRedefinedProperty + { + public new double Name { get; set; } + } + } +} From acfae72e2e347579408907b4db4547bf201bb7f4 Mon Sep 17 00:00:00 2001 From: krajek Date: Sun, 23 May 2021 07:24:15 +0200 Subject: [PATCH 13/25] Fix few formatting issues --- .../Destructurers/ReflectionBasedDestructurer.cs | 3 +-- .../Reflection/ReflectionInfoExtractor.cs | 7 ++++--- .../Reflection/ReflectionPropertyInfo.cs | 3 +++ 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/Source/Serilog.Exceptions/Destructurers/ReflectionBasedDestructurer.cs b/Source/Serilog.Exceptions/Destructurers/ReflectionBasedDestructurer.cs index 83b61701..aaf70473 100644 --- a/Source/Serilog.Exceptions/Destructurers/ReflectionBasedDestructurer.cs +++ b/Source/Serilog.Exceptions/Destructurers/ReflectionBasedDestructurer.cs @@ -24,8 +24,7 @@ public class ReflectionBasedDestructurer : IExceptionDestructurer private const string RefLabel = "$ref"; private const string CyclicReferenceMessage = "Cyclic reference"; private readonly int destructuringDepth; - private ReflectionInfoExtractor reflectionInfoExtractor = new (); - + private ReflectionInfoExtractor reflectionInfoExtractor = new(); /// /// Initializes a new instance of the class. diff --git a/Source/Serilog.Exceptions/Reflection/ReflectionInfoExtractor.cs b/Source/Serilog.Exceptions/Reflection/ReflectionInfoExtractor.cs index 7e22a00d..7a8575f6 100644 --- a/Source/Serilog.Exceptions/Reflection/ReflectionInfoExtractor.cs +++ b/Source/Serilog.Exceptions/Reflection/ReflectionInfoExtractor.cs @@ -6,6 +6,10 @@ namespace Serilog.Exceptions.Reflection using System.Linq.Expressions; using System.Reflection; + /// + /// Utility class that analyzes type using reflection and provides + /// information about properties to be used in destructuring process. + /// internal class ReflectionInfoExtractor { private readonly object lockObj = new(); @@ -89,8 +93,5 @@ private ReflectionInfo GenerateReflectionInfoForType(Type valueType) return new ReflectionInfo(propertyInfos, propertiesInfosExceptBaseOnes); } - - - } } diff --git a/Source/Serilog.Exceptions/Reflection/ReflectionPropertyInfo.cs b/Source/Serilog.Exceptions/Reflection/ReflectionPropertyInfo.cs index d2859821..a18e4473 100644 --- a/Source/Serilog.Exceptions/Reflection/ReflectionPropertyInfo.cs +++ b/Source/Serilog.Exceptions/Reflection/ReflectionPropertyInfo.cs @@ -3,6 +3,9 @@ namespace Serilog.Exceptions.Reflection using System; using System.Reflection; + /// + /// Class that holds reflection information about a single property. + /// internal class ReflectionPropertyInfo { public ReflectionPropertyInfo(string name, Type? declaringType, Func getter) From 0342b1ad43f103d632328e3f7b42f507f0424e93 Mon Sep 17 00:00:00 2001 From: krajek Date: Sun, 23 May 2021 10:15:25 +0200 Subject: [PATCH 14/25] Extract MarkRedefinedPropertiesWithFullName --- .../Reflection/ReflectionInfoExtractor.cs | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/Source/Serilog.Exceptions/Reflection/ReflectionInfoExtractor.cs b/Source/Serilog.Exceptions/Reflection/ReflectionInfoExtractor.cs index 7a8575f6..cb9939e1 100644 --- a/Source/Serilog.Exceptions/Reflection/ReflectionInfoExtractor.cs +++ b/Source/Serilog.Exceptions/Reflection/ReflectionInfoExtractor.cs @@ -53,13 +53,9 @@ private static List GetExceptionPropertiesForDestructuring(Type va return allProperties; } - private ReflectionInfo GenerateReflectionInfoForType(Type valueType) + private static void MarkRedefinedPropertiesWithFullName(ReflectionPropertyInfo[] propertyInfos) { - var properties = GetExceptionPropertiesForDestructuring(valueType); - var propertyInfos = properties - .Select(p => new ReflectionPropertyInfo(p.Name, p.DeclaringType, GenerateFastGetterForProperty(valueType, p))) - .ToArray(); - + // First group by name var groupedByName = new Dictionary>(); foreach (var propertyInfo in propertyInfos) { @@ -69,10 +65,11 @@ private ReflectionInfo GenerateReflectionInfoForType(Type valueType) } else { - groupedByName[propertyInfo.Name] = new List { propertyInfo }; + groupedByName[propertyInfo.Name] = new List {propertyInfo}; } } + // Fix groups that have more than one property in it foreach (var nameGroup in groupedByName) { if (nameGroup.Value.Count > 1) @@ -86,6 +83,16 @@ private ReflectionInfo GenerateReflectionInfoForType(Type valueType) } } } + } + + private ReflectionInfo GenerateReflectionInfoForType(Type valueType) + { + var properties = GetExceptionPropertiesForDestructuring(valueType); + var propertyInfos = properties + .Select(p => new ReflectionPropertyInfo(p.Name, p.DeclaringType, GenerateFastGetterForProperty(valueType, p))) + .ToArray(); + + MarkRedefinedPropertiesWithFullName(propertyInfos); var propertiesInfosExceptBaseOnes = propertyInfos .Where(p => this.baseExceptionPropertiesForDestructuring.All(bp => bp.Name != p.Name)) From e37871a48e5cfac136cea7fecc9e8470cb87f7bc Mon Sep 17 00:00:00 2001 From: krajek Date: Mon, 24 May 2021 11:58:48 +0200 Subject: [PATCH 15/25] Fix some formatting warnings --- .../Destructurers/ReflectionBasedDestructurer.cs | 3 +-- .../Serilog.Exceptions/Reflection/ReflectionInfoExtractor.cs | 5 ++--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/Source/Serilog.Exceptions/Destructurers/ReflectionBasedDestructurer.cs b/Source/Serilog.Exceptions/Destructurers/ReflectionBasedDestructurer.cs index aaf70473..dec871c3 100644 --- a/Source/Serilog.Exceptions/Destructurers/ReflectionBasedDestructurer.cs +++ b/Source/Serilog.Exceptions/Destructurers/ReflectionBasedDestructurer.cs @@ -9,7 +9,7 @@ namespace Serilog.Exceptions.Destructurers using System.Reflection; using System.Threading; using System.Threading.Tasks; - using Reflection; + using Serilog.Exceptions.Reflection; using Serilog.Exceptions.Core; /// @@ -48,7 +48,6 @@ public ReflectionBasedDestructurer(int destructuringDepth) #pragma warning disable CA1819 // Properties should not return arrays public Type[] TargetTypes => new[] { typeof(Exception) }; #pragma warning restore CA1819 // Properties should not return arrays - /// public void Destructure( Exception exception, diff --git a/Source/Serilog.Exceptions/Reflection/ReflectionInfoExtractor.cs b/Source/Serilog.Exceptions/Reflection/ReflectionInfoExtractor.cs index cb9939e1..12e6e7fa 100644 --- a/Source/Serilog.Exceptions/Reflection/ReflectionInfoExtractor.cs +++ b/Source/Serilog.Exceptions/Reflection/ReflectionInfoExtractor.cs @@ -18,7 +18,6 @@ internal class ReflectionInfoExtractor public ReflectionInfoExtractor() => this.baseExceptionPropertiesForDestructuring = GetExceptionPropertiesForDestructuring(typeof(Exception)); - public ReflectionInfo GetOrCreateReflectionInfo(Type valueType) { lock (this.lockObj) @@ -55,7 +54,7 @@ private static List GetExceptionPropertiesForDestructuring(Type va private static void MarkRedefinedPropertiesWithFullName(ReflectionPropertyInfo[] propertyInfos) { - // First group by name + // First group by name var groupedByName = new Dictionary>(); foreach (var propertyInfo in propertyInfos) { @@ -65,7 +64,7 @@ private static void MarkRedefinedPropertiesWithFullName(ReflectionPropertyInfo[] } else { - groupedByName[propertyInfo.Name] = new List {propertyInfo}; + groupedByName[propertyInfo.Name] = new List { propertyInfo }; } } From fab3c6b77a3d4567d419139cd71adcc73c88716d Mon Sep 17 00:00:00 2001 From: krajek Date: Mon, 24 May 2021 12:16:53 +0200 Subject: [PATCH 16/25] Some more formatting and documentation fixes. --- .../ReflectionBasedDestructurer.cs | 1 + .../Reflection/ReflectionInfo.cs | 22 +++++++++++- .../Reflection/ReflectionPropertyInfo.cs | 35 +++++++++++++++++-- 3 files changed, 55 insertions(+), 3 deletions(-) diff --git a/Source/Serilog.Exceptions/Destructurers/ReflectionBasedDestructurer.cs b/Source/Serilog.Exceptions/Destructurers/ReflectionBasedDestructurer.cs index dec871c3..343b2c03 100644 --- a/Source/Serilog.Exceptions/Destructurers/ReflectionBasedDestructurer.cs +++ b/Source/Serilog.Exceptions/Destructurers/ReflectionBasedDestructurer.cs @@ -48,6 +48,7 @@ public ReflectionBasedDestructurer(int destructuringDepth) #pragma warning disable CA1819 // Properties should not return arrays public Type[] TargetTypes => new[] { typeof(Exception) }; #pragma warning restore CA1819 // Properties should not return arrays + /// public void Destructure( Exception exception, diff --git a/Source/Serilog.Exceptions/Reflection/ReflectionInfo.cs b/Source/Serilog.Exceptions/Reflection/ReflectionInfo.cs index afc0da49..29fac7ea 100644 --- a/Source/Serilog.Exceptions/Reflection/ReflectionInfo.cs +++ b/Source/Serilog.Exceptions/Reflection/ReflectionInfo.cs @@ -1,15 +1,35 @@ namespace Serilog.Exceptions.Reflection { + using System; + + /// + /// Contains metadata information about a type + /// useful for destructuring process. + /// internal class ReflectionInfo { - public ReflectionInfo(ReflectionPropertyInfo[] properties, ReflectionPropertyInfo[] propertiesExceptBaseOnes) + /// + /// Initializes a new instance of the class. + /// + /// All properties for a type. + /// All properties except of properties which are handled separately. + public ReflectionInfo( + ReflectionPropertyInfo[] properties, + ReflectionPropertyInfo[] propertiesExceptBaseOnes) { this.Properties = properties; this.PropertiesExceptBaseOnes = propertiesExceptBaseOnes; } + /// + /// Gets information about all properties to be destructured. + /// public ReflectionPropertyInfo[] Properties { get; } + /// + /// Gets information about properties other than base exception properties + /// which are handled separately. + /// public ReflectionPropertyInfo[] PropertiesExceptBaseOnes { get; } } } diff --git a/Source/Serilog.Exceptions/Reflection/ReflectionPropertyInfo.cs b/Source/Serilog.Exceptions/Reflection/ReflectionPropertyInfo.cs index a18e4473..ded9c42e 100644 --- a/Source/Serilog.Exceptions/Reflection/ReflectionPropertyInfo.cs +++ b/Source/Serilog.Exceptions/Reflection/ReflectionPropertyInfo.cs @@ -1,13 +1,27 @@ namespace Serilog.Exceptions.Reflection { using System; +#if NETSTANDARD1_3 || NETSTANDARD1_6 using System.Reflection; +#endif /// /// Class that holds reflection information about a single property. /// internal class ReflectionPropertyInfo { + /// + /// Marker that represents a decision whether to extend property name + /// with type name of declaring type, to avoid name uniqueness conflicts. + /// + private bool markedWithFullName; + + /// + /// Initializes a new instance of the class. + /// + /// The of the property without type name. + /// The type which declares the property. + /// Runtime function that can extract value of the property from object. public ReflectionPropertyInfo(string name, Type? declaringType, Func getter) { this.Name = name; @@ -15,14 +29,26 @@ public ReflectionPropertyInfo(string name, Type? declaringType, Func + /// Gets name of the property. + /// It includes type name if name conflicts with other property of derived class. + /// public string Name { get; private set; } + /// + /// Gets the type that declared the property. + /// public Type? DeclaringType { get; } + /// + /// Gets a function that extracts value of the property from an object. + /// public Func Getter { get; } - private bool markedWithFullName = false; - + /// + /// Idempotent action that extends property name with a type name + /// of its declaring type to avoid name uniqueness conflict. + /// public void MarkNameWithFullName() { if (!this.markedWithFullName) @@ -32,6 +58,11 @@ public void MarkNameWithFullName() } } + /// + /// Conditionally marks property with its declaring type name to avoid + /// name uniqueness conflict. + /// + /// Other property info that is be compared to detect name uniqueness conflict. public void MarkNameWithFullNameIRedefineThatProperty(ReflectionPropertyInfo otherPropertyInfo) { if (otherPropertyInfo == this) From 4d9283a2f1a93a3f59aefef688804d90c5799997 Mon Sep 17 00:00:00 2001 From: krajek Date: Mon, 24 May 2021 12:22:33 +0200 Subject: [PATCH 17/25] Fix some formatting warnings --- Source/Serilog.Exceptions/Core/ExceptionPropertiesBag.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Source/Serilog.Exceptions/Core/ExceptionPropertiesBag.cs b/Source/Serilog.Exceptions/Core/ExceptionPropertiesBag.cs index 7353c7ad..9a1608ae 100644 --- a/Source/Serilog.Exceptions/Core/ExceptionPropertiesBag.cs +++ b/Source/Serilog.Exceptions/Core/ExceptionPropertiesBag.cs @@ -16,7 +16,7 @@ internal class ExceptionPropertiesBag : IExceptionPropertiesBag private const int AcceptableNumberOfSameNameProperties = 5; private readonly Exception exception; private readonly IExceptionPropertyFilter? filter; - private readonly Dictionary properties = new (); + private readonly Dictionary properties = new(); /// /// We keep a note on whether the results were collected to be sure that after that there are no changes. This @@ -71,7 +71,6 @@ public void AddProperty(string key, object? value) private static string GetReplacementKey(string key) => key + "$"; - /// /// We want to be as robust as possible /// so even in case of multiple properties with the same name @@ -92,9 +91,7 @@ private void AddPairToProperties(string key, object? value) this.properties.Add(key, value); #endif } - #if !NET5_0 - private string MakeSureKeyIsUnique(string key) { var i = 0; From d867b86c2451674b5516cddbced4f35e633e01fe Mon Sep 17 00:00:00 2001 From: krajek Date: Mon, 24 May 2021 12:30:47 +0200 Subject: [PATCH 18/25] Fix one last formatting issue --- Source/Serilog.Exceptions/Core/ExceptionPropertiesBag.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Source/Serilog.Exceptions/Core/ExceptionPropertiesBag.cs b/Source/Serilog.Exceptions/Core/ExceptionPropertiesBag.cs index 9a1608ae..caa5c0ae 100644 --- a/Source/Serilog.Exceptions/Core/ExceptionPropertiesBag.cs +++ b/Source/Serilog.Exceptions/Core/ExceptionPropertiesBag.cs @@ -91,6 +91,7 @@ private void AddPairToProperties(string key, object? value) this.properties.Add(key, value); #endif } + #if !NET5_0 private string MakeSureKeyIsUnique(string key) { From 3a00a30656dc4e6aec8737a5dd37f7c9ffc4fa70 Mon Sep 17 00:00:00 2001 From: krajek Date: Tue, 25 May 2021 16:59:05 +0200 Subject: [PATCH 19/25] Readability improvements --- .../Reflection/ReflectionInfoExtractor.cs | 34 +++++++++++++------ .../Reflection/ReflectionPropertyInfo.cs | 2 +- 2 files changed, 24 insertions(+), 12 deletions(-) diff --git a/Source/Serilog.Exceptions/Reflection/ReflectionInfoExtractor.cs b/Source/Serilog.Exceptions/Reflection/ReflectionInfoExtractor.cs index 12e6e7fa..5d34b6de 100644 --- a/Source/Serilog.Exceptions/Reflection/ReflectionInfoExtractor.cs +++ b/Source/Serilog.Exceptions/Reflection/ReflectionInfoExtractor.cs @@ -54,7 +54,7 @@ private static List GetExceptionPropertiesForDestructuring(Type va private static void MarkRedefinedPropertiesWithFullName(ReflectionPropertyInfo[] propertyInfos) { - // First group by name + // First, prepare a dictionary of properties grouped by name var groupedByName = new Dictionary>(); foreach (var propertyInfo in propertyInfos) { @@ -68,18 +68,30 @@ private static void MarkRedefinedPropertiesWithFullName(ReflectionPropertyInfo[] } } - // Fix groups that have more than one property in it - foreach (var nameGroup in groupedByName) + // Then, fix groups that have more than one property in it + // It means that there is a name uniqueness conflict which needs to be resolved + foreach (var nameAndProperties in groupedByName) { - if (nameGroup.Value.Count > 1) + var properties = nameAndProperties.Value; + if (properties.Count > 1) { - foreach (var propertyInfoInGroupName in nameGroup.Value) - { - foreach (var otherPropertyInfoInGroupName in nameGroup.Value) - { - propertyInfoInGroupName.MarkNameWithFullNameIRedefineThatProperty(otherPropertyInfoInGroupName); - } - } + FixGroupOfPropertiesWithConflictingNames(properties); + } + } + } + + private static void FixGroupOfPropertiesWithConflictingNames(List properties) + { + // Very simplistic approach, just check each pair separately. + // The implementation has O(N^2) complexity but in practice + // N will be extremely rarely other than 2. + for (var i = 0; i < properties.Count; i++) + { + for (var j = i + 1; j < properties.Count; j++) + { + var property1 = properties[i]; + var property2 = properties[j]; + property2.MarkNameWithFullNameIfRedefinesThatProperty(property1); } } } diff --git a/Source/Serilog.Exceptions/Reflection/ReflectionPropertyInfo.cs b/Source/Serilog.Exceptions/Reflection/ReflectionPropertyInfo.cs index ded9c42e..c161de12 100644 --- a/Source/Serilog.Exceptions/Reflection/ReflectionPropertyInfo.cs +++ b/Source/Serilog.Exceptions/Reflection/ReflectionPropertyInfo.cs @@ -63,7 +63,7 @@ public void MarkNameWithFullName() /// name uniqueness conflict. /// /// Other property info that is be compared to detect name uniqueness conflict. - public void MarkNameWithFullNameIRedefineThatProperty(ReflectionPropertyInfo otherPropertyInfo) + public void MarkNameWithFullNameIfRedefinesThatProperty(ReflectionPropertyInfo otherPropertyInfo) { if (otherPropertyInfo == this) { From 3c20e86a29d0fb2f8da399e2c61761b4ffe9cdcb Mon Sep 17 00:00:00 2001 From: krajek Date: Tue, 25 May 2021 19:48:39 +0200 Subject: [PATCH 20/25] Re-fix double loop checking each pair of properties --- .../Reflection/ReflectionInfoExtractor.cs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/Source/Serilog.Exceptions/Reflection/ReflectionInfoExtractor.cs b/Source/Serilog.Exceptions/Reflection/ReflectionInfoExtractor.cs index 5d34b6de..276af55c 100644 --- a/Source/Serilog.Exceptions/Reflection/ReflectionInfoExtractor.cs +++ b/Source/Serilog.Exceptions/Reflection/ReflectionInfoExtractor.cs @@ -85,13 +85,11 @@ private static void FixGroupOfPropertiesWithConflictingNames(List Date: Wed, 26 May 2021 22:32:25 +0200 Subject: [PATCH 21/25] Fix some style cop issues --- .../Destructurers/ReflectionBasedDestructurer.cs | 9 ++++----- .../Reflection/ReflectionInfoExtractor.cs | 9 +++++++++ 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/Source/Serilog.Exceptions/Destructurers/ReflectionBasedDestructurer.cs b/Source/Serilog.Exceptions/Destructurers/ReflectionBasedDestructurer.cs index 343b2c03..258976a1 100644 --- a/Source/Serilog.Exceptions/Destructurers/ReflectionBasedDestructurer.cs +++ b/Source/Serilog.Exceptions/Destructurers/ReflectionBasedDestructurer.cs @@ -1,16 +1,15 @@ namespace Serilog.Exceptions.Destructurers { + using Serilog.Exceptions.Core; + using Serilog.Exceptions.Reflection; using System; using System.Collections; using System.Collections.Generic; using System.Globalization; using System.Linq; - using System.Linq.Expressions; using System.Reflection; using System.Threading; using System.Threading.Tasks; - using Serilog.Exceptions.Reflection; - using Serilog.Exceptions.Core; /// /// Destructures exceptions by gathering all public non-indexer properties using reflection and then dynamically @@ -24,7 +23,7 @@ public class ReflectionBasedDestructurer : IExceptionDestructurer private const string RefLabel = "$ref"; private const string CyclicReferenceMessage = "Cyclic reference"; private readonly int destructuringDepth; - private ReflectionInfoExtractor reflectionInfoExtractor = new(); + private readonly ReflectionInfoExtractor reflectionInfoExtractor = new(); /// /// Initializes a new instance of the class. @@ -303,7 +302,7 @@ private object DestructureValueDictionary( var values = new Dictionary(); destructuredObjects.Add(value, values); - var reflectionInfo = reflectionInfoExtractor.GetOrCreateReflectionInfo(valueType); + var reflectionInfo = this.reflectionInfoExtractor.GetOrCreateReflectionInfo(valueType); foreach (var property in reflectionInfo.Properties) { diff --git a/Source/Serilog.Exceptions/Reflection/ReflectionInfoExtractor.cs b/Source/Serilog.Exceptions/Reflection/ReflectionInfoExtractor.cs index 276af55c..0fd0d173 100644 --- a/Source/Serilog.Exceptions/Reflection/ReflectionInfoExtractor.cs +++ b/Source/Serilog.Exceptions/Reflection/ReflectionInfoExtractor.cs @@ -16,8 +16,17 @@ internal class ReflectionInfoExtractor private readonly Dictionary reflectionInfoCache = new(); private readonly IList baseExceptionPropertiesForDestructuring; + /// + /// Initializes a new instance of the class. + /// public ReflectionInfoExtractor() => this.baseExceptionPropertiesForDestructuring = GetExceptionPropertiesForDestructuring(typeof(Exception)); + /// + /// Gets reflection info for relevant properties of "/> + /// from cache or by generating it if they are not yet present in cache. + /// + /// The type for which properties are to be analyzed. + /// The reflection info for relevant properties of . public ReflectionInfo GetOrCreateReflectionInfo(Type valueType) { lock (this.lockObj) From 2dff72800f6cb2b74c60c4feb88fbc398bf56b48 Mon Sep 17 00:00:00 2001 From: krajek Date: Thu, 27 May 2021 15:01:26 +0200 Subject: [PATCH 22/25] Re-arrange usings for style cop properties --- .../Destructurers/ReflectionBasedDestructurer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Serilog.Exceptions/Destructurers/ReflectionBasedDestructurer.cs b/Source/Serilog.Exceptions/Destructurers/ReflectionBasedDestructurer.cs index 258976a1..9e306846 100644 --- a/Source/Serilog.Exceptions/Destructurers/ReflectionBasedDestructurer.cs +++ b/Source/Serilog.Exceptions/Destructurers/ReflectionBasedDestructurer.cs @@ -1,7 +1,5 @@ namespace Serilog.Exceptions.Destructurers { - using Serilog.Exceptions.Core; - using Serilog.Exceptions.Reflection; using System; using System.Collections; using System.Collections.Generic; @@ -10,6 +8,8 @@ namespace Serilog.Exceptions.Destructurers using System.Reflection; using System.Threading; using System.Threading.Tasks; + using Serilog.Exceptions.Core; + using Serilog.Exceptions.Reflection; /// /// Destructures exceptions by gathering all public non-indexer properties using reflection and then dynamically From 50eb409bec75d233476bb6bb4ff60092fc8dc10c Mon Sep 17 00:00:00 2001 From: krajek Date: Thu, 27 May 2021 15:05:43 +0200 Subject: [PATCH 23/25] Remove freaking whitespace --- .../Destructurers/ReflectionBasedDestructurer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Serilog.Exceptions/Destructurers/ReflectionBasedDestructurer.cs b/Source/Serilog.Exceptions/Destructurers/ReflectionBasedDestructurer.cs index 9e306846..d6e27e0d 100644 --- a/Source/Serilog.Exceptions/Destructurers/ReflectionBasedDestructurer.cs +++ b/Source/Serilog.Exceptions/Destructurers/ReflectionBasedDestructurer.cs @@ -28,7 +28,7 @@ public class ReflectionBasedDestructurer : IExceptionDestructurer /// /// Initializes a new instance of the class. /// - /// Maximum depth to which destructurer will go when destructuring exception + /// Maximum depth to which the destructurer will go when destructuring exception /// object graph. public ReflectionBasedDestructurer(int destructuringDepth) { From a6941b0a23768a42c9767a2a9e8a1fd936bedc52 Mon Sep 17 00:00:00 2001 From: krajek Date: Fri, 28 May 2021 07:22:28 +0200 Subject: [PATCH 24/25] Stylecop reorder namespace issue --- .../Reflection/ReflectionInfoExtractorTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/Serilog.Exceptions.Test/Reflection/ReflectionInfoExtractorTest.cs b/Tests/Serilog.Exceptions.Test/Reflection/ReflectionInfoExtractorTest.cs index b02c0f65..0194a9ec 100644 --- a/Tests/Serilog.Exceptions.Test/Reflection/ReflectionInfoExtractorTest.cs +++ b/Tests/Serilog.Exceptions.Test/Reflection/ReflectionInfoExtractorTest.cs @@ -1,8 +1,8 @@ namespace Serilog.Exceptions.Test.Reflection { using System.Linq; - using Exceptions.Reflection; using FluentAssertions; + using Serilog.Exceptions.Reflection; using Xunit; public class ReflectionInfoExtractorTest From 7d5e24895c34852e3229d33f6b51b541f6e0db69 Mon Sep 17 00:00:00 2001 From: krajek Date: Thu, 10 Jun 2021 17:15:41 +0200 Subject: [PATCH 25/25] Use `Name` instead of `FullName` --- .../Reflection/ReflectionInfoExtractor.cs | 2 +- .../Reflection/ReflectionPropertyInfo.cs | 20 +++++++++---------- .../ReflectionBasedDestructurerTest.cs | 2 +- .../Reflection/ReflectionInfoExtractorTest.cs | 6 +++--- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/Source/Serilog.Exceptions/Reflection/ReflectionInfoExtractor.cs b/Source/Serilog.Exceptions/Reflection/ReflectionInfoExtractor.cs index 0fd0d173..6595259c 100644 --- a/Source/Serilog.Exceptions/Reflection/ReflectionInfoExtractor.cs +++ b/Source/Serilog.Exceptions/Reflection/ReflectionInfoExtractor.cs @@ -98,7 +98,7 @@ private static void FixGroupOfPropertiesWithConflictingNames(List - private bool markedWithFullName; + private bool markedWithTypeName; /// /// Initializes a new instance of the class. @@ -49,12 +49,12 @@ public ReflectionPropertyInfo(string name, Type? declaringType, Func - public void MarkNameWithFullName() + public void MarkNameWithTypeName() { - if (!this.markedWithFullName) + if (!this.markedWithTypeName) { - this.markedWithFullName = true; - this.Name = $"{this.DeclaringType?.FullName}.{this.Name}"; + this.markedWithTypeName = true; + this.Name = $"{this.DeclaringType?.Name}.{this.Name}"; } } @@ -63,7 +63,7 @@ public void MarkNameWithFullName() /// name uniqueness conflict. /// /// Other property info that is be compared to detect name uniqueness conflict. - public void MarkNameWithFullNameIfRedefinesThatProperty(ReflectionPropertyInfo otherPropertyInfo) + public void MarkNameWithTypeNameIfRedefinesThatProperty(ReflectionPropertyInfo otherPropertyInfo) { if (otherPropertyInfo == this) { @@ -80,18 +80,18 @@ public void MarkNameWithFullNameIfRedefinesThatProperty(ReflectionPropertyInfo o return; } - if (this.markedWithFullName) + if (this.markedWithTypeName) { return; } - var shouldMarkWithFullName = IsSubTypeOf( + var shouldMarkWithTypeName = IsSubTypeOf( otherPropertyInfo.DeclaringType, this.DeclaringType); - if (shouldMarkWithFullName) + if (shouldMarkWithTypeName) { - this.MarkNameWithFullName(); + this.MarkNameWithTypeName(); } } diff --git a/Tests/Serilog.Exceptions.Test/Destructurers/ReflectionBasedDestructurerTest.cs b/Tests/Serilog.Exceptions.Test/Destructurers/ReflectionBasedDestructurerTest.cs index f000a1b0..0915c79e 100644 --- a/Tests/Serilog.Exceptions.Test/Destructurers/ReflectionBasedDestructurerTest.cs +++ b/Tests/Serilog.Exceptions.Test/Destructurers/ReflectionBasedDestructurerTest.cs @@ -382,7 +382,7 @@ public void CanDestructureObjectWithHiddenProperty() var properties = propertiesBag.GetResultDictionary(); var info = properties[nameof(HiddenException.Info)] as IDictionary; Assert.Equal(derived.HiddenProperty, info?[nameof(DerivedClass.HiddenProperty)]); - Assert.Equal(baseClass.HiddenProperty, info?[$"{typeof(BaseClass).FullName}.{nameof(BaseClass.HiddenProperty)}"]); + Assert.Equal(baseClass.HiddenProperty, info?[$"{nameof(BaseClass)}.{nameof(BaseClass.HiddenProperty)}"]); } [Fact] diff --git a/Tests/Serilog.Exceptions.Test/Reflection/ReflectionInfoExtractorTest.cs b/Tests/Serilog.Exceptions.Test/Reflection/ReflectionInfoExtractorTest.cs index 0194a9ec..263cd44c 100644 --- a/Tests/Serilog.Exceptions.Test/Reflection/ReflectionInfoExtractorTest.cs +++ b/Tests/Serilog.Exceptions.Test/Reflection/ReflectionInfoExtractorTest.cs @@ -26,7 +26,7 @@ public void GivenObjectWithRedefinedProperty_ShouldDiscardBaseClassProperty() testObjectName.Should().BeOfType().Which.Should().Be(123); var baseClassPropertyInfo = reflectionInfo - .Properties.Should().ContainSingle(x => x.Name == "Serilog.Exceptions.Test.Reflection.ReflectionInfoExtractorTest+TestObject.Name").Which; + .Properties.Should().ContainSingle(x => x.Name == "TestObject.Name").Which; var baseClassNameGetter = baseClassPropertyInfo.Getter; var baseClassTestObjectName = baseClassNameGetter(testObject); baseClassTestObjectName.Should().BeNull(); @@ -46,8 +46,8 @@ public void GivenObjectWithDoubleRedefinedProperty_ShouldMarkBaseClassProperties new[] { "Name", - "Serilog.Exceptions.Test.Reflection.ReflectionInfoExtractorTest+TestObjectWithRedefinedProperty.Name", - "Serilog.Exceptions.Test.Reflection.ReflectionInfoExtractorTest+TestObject.Name", + "TestObjectWithRedefinedProperty.Name", + "TestObject.Name", }, x => x.WithoutStrictOrdering()); var namePropertyInfo = reflectionInfo.Properties.Should().ContainSingle(x => x.Name == "Name").Which;