diff --git a/src/Serilog/Parameters/PropertyValueConverter.cs b/src/Serilog/Parameters/PropertyValueConverter.cs index 712af6f18..657836dba 100755 --- a/src/Serilog/Parameters/PropertyValueConverter.cs +++ b/src/Serilog/Parameters/PropertyValueConverter.cs @@ -74,7 +74,7 @@ public PropertyValueConverter( new SimpleScalarConversionPolicy(BuiltInScalarTypes.Concat(additionalScalarTypes)), new NullableScalarConversionPolicy(), new EnumScalarConversionPolicy(), - new ByteArrayScalarConversionPolicy(), + new ByteArrayScalarConversionPolicy() }; _destructuringPolicies = additionalDestructuringPolicies @@ -162,6 +162,29 @@ LogEventPropertyValue CreatePropertyValue(object value, Destructuring destructur } } + if (TryConvertEnumerable(value, destructuring, valueType, limiter, out var enumerableResult)) + return enumerableResult; + + if (TryConvertValueTuple(value, destructuring, valueType, limiter, out var tupleResult)) + return tupleResult; + + if (destructuring == Destructuring.Destructure) + { + var type = value.GetType(); + var typeTag = type.Name; + if (typeTag.Length <= 0 || IsCompilerGeneratedType(type)) + { + typeTag = null; + } + + return new StructureValue(GetProperties(value, limiter), typeTag); + } + + return new ScalarValue(value.ToString()); + } + + bool TryConvertEnumerable(object value, Destructuring destructuring, Type valueType, DepthLimiter limiter, out LogEventPropertyValue result) + { var enumerable = value as IEnumerable; if (enumerable != null) { @@ -179,30 +202,69 @@ LogEventPropertyValue CreatePropertyValue(object value, Destructuring destructur var keyProperty = typeInfo.GetDeclaredProperty("Key"); var valueProperty = typeInfo.GetDeclaredProperty("Value"); - return new DictionaryValue(enumerable.Cast().Take(_maximumCollectionCount) + result = new DictionaryValue(enumerable + .Cast() + .Take(_maximumCollectionCount) .Select(kvp => new KeyValuePair( - (ScalarValue)limiter.CreatePropertyValue(keyProperty.GetValue(kvp), destructuring), - limiter.CreatePropertyValue(valueProperty.GetValue(kvp), destructuring))) + (ScalarValue) limiter.CreatePropertyValue(keyProperty.GetValue(kvp), destructuring), + limiter.CreatePropertyValue(valueProperty.GetValue(kvp), destructuring))) .Where(kvp => kvp.Key.Value != null)); + return true; } - return new SequenceValue( - enumerable.Cast().Take(_maximumCollectionCount).Select(o => limiter.CreatePropertyValue(o, destructuring))); + result = new SequenceValue(enumerable + .Cast() + .Take(_maximumCollectionCount) + .Select(o => limiter.CreatePropertyValue(o, destructuring))); + + return true; } - if (destructuring == Destructuring.Destructure) + result = null; + return false; + } + + static bool TryConvertValueTuple(object value, Destructuring destructuring, Type valueType, DepthLimiter limiter, out LogEventPropertyValue result) + { + if (!(value is IStructuralEquatable && valueType.IsConstructedGenericType)) { - var type = value.GetType(); - var typeTag = type.Name; - if (typeTag.Length <= 0 || IsCompilerGeneratedType(type)) + result = null; + return false; + } + + var definition = valueType.GetGenericTypeDefinition(); + + // Ignore the 8+ value case for now. +#if VALUETUPLE + if (definition == typeof(ValueTuple<>) || definition == typeof(ValueTuple<,>) || + definition == typeof(ValueTuple<,,>) || definition == typeof(ValueTuple<,,,>) || + definition == typeof(ValueTuple<,,,,>) || definition == typeof(ValueTuple<,,,,,>) || + definition == typeof(ValueTuple<,,,,,,>)) +#else + var defn = definition.FullName; + if (defn == "System.ValueTuple`1" || defn == "System.ValueTuple`2" || + defn == "System.ValueTuple`3" || defn == "System.ValueTuple`4" || + defn == "System.ValueTuple`5" || defn == "System.ValueTuple`6" || + defn == "System.ValueTuple`7") +#endif + { + var elements = new List(); + foreach (var field in valueType.GetTypeInfo().DeclaredFields) { - typeTag = null; + if (field.IsPublic && !field.IsStatic) + { + var fieldValue = field.GetValue(value); + var propertyValue = limiter.CreatePropertyValue(fieldValue, destructuring); + elements.Add(propertyValue); + } } - return new StructureValue(GetProperties(value, limiter), typeTag); + result = new SequenceValue(elements); + return true; } - return new ScalarValue(value.ToString()); + result = null; + return false; } LogEventPropertyValue Stringify(object value) diff --git a/src/Serilog/Serilog.csproj b/src/Serilog/Serilog.csproj index 698c466f6..ad479c83b 100644 --- a/src/Serilog/Serilog.csproj +++ b/src/Serilog/Serilog.csproj @@ -44,18 +44,6 @@ - - $(DefineConstants);REMOTING;HASHTABLE - - - - $(DefineConstants);ASYNCLOCAL - - - - $(DefineConstants);ASYNCLOCAL;HASHTABLE - - @@ -71,4 +59,16 @@ + + $(DefineConstants);REMOTING;HASHTABLE + + + + $(DefineConstants);ASYNCLOCAL + + + + $(DefineConstants);ASYNCLOCAL;HASHTABLE + + diff --git a/test/Serilog.Tests/Parameters/PropertyValueConverterTests.cs b/test/Serilog.Tests/Parameters/PropertyValueConverterTests.cs index 025f1e979..3728fb0a2 100644 --- a/test/Serilog.Tests/Parameters/PropertyValueConverterTests.cs +++ b/test/Serilog.Tests/Parameters/PropertyValueConverterTests.cs @@ -8,6 +8,8 @@ using Serilog.Parsing; using Serilog.Tests.Support; +// ReSharper disable UnusedAutoPropertyAccessor.Global, UnusedParameter.Local + namespace Serilog.Tests.Parameters { public class PropertyValueConverterTests @@ -188,7 +190,7 @@ public class BaseWithProps public class DerivedWithOverrides : BaseWithProps { - new public string PropA { get; set; } + public new string PropA { get; set; } public override string PropB { get; set; } public string PropD { get; set; } } @@ -216,7 +218,7 @@ public void NewAndInheritedPropertiesAppearOnlyOnce() class HasIndexer { - public string this[int index] { get { return "Indexer"; } } + public string this[int index] => "Indexer"; } [Fact] @@ -231,7 +233,7 @@ public void IndexerPropertiesAreIgnoredWhenDestructuring() // (reducing garbage). class HasItem { - public string Item { get { return "Item"; } } + public string Item => "Item"; } [Fact] @@ -253,6 +255,55 @@ public void CSharpAnonymousTypesAreRecognizedWhenDestructuring() var structuredValue = (StructureValue)result; Assert.Equal(null, structuredValue.TypeTag); } + + [Fact] + public void ValueTuplesAreRecognizedWhenDestructuring() + { + var o = (1, "A", new[] { "B" }); + var result = _converter.CreatePropertyValue(o); + + var sequenceValue = Assert.IsType(result); + + Assert.Equal(3, sequenceValue.Elements.Count); + Assert.Equal(new ScalarValue(1), sequenceValue.Elements[0]); + Assert.Equal(new ScalarValue("A"), sequenceValue.Elements[1]); + var nested = Assert.IsType(sequenceValue.Elements[2]); + Assert.Equal(1, nested.Elements.Count); + Assert.Equal(new ScalarValue("B"), nested.Elements[0]); + } + + [Fact] + public void AllTupleLengthsUpToSevenAreSupportedForCapturing() + { + var tuples = new object[] + { + ValueTuple.Create(1), + (1, 2), + (1, 2, 3), + (1, 2, 3, 4), + (1, 2, 3, 4, 5), + (1, 2, 3, 4, 5, 6), + (1, 2, 3, 4, 5, 6, 7) + }; + + foreach (var t in tuples) + Assert.IsType(_converter.CreatePropertyValue(t)); + } + + [Fact] + public void EightPlusValueTupleElementsAreIgnoredByCapturing() + { + var scalar = _converter.CreatePropertyValue((1, 2, 3, 4, 5, 6, 7, 8)); + Assert.IsType(scalar); + } + + [Fact] + public void ValueTupleDestructuringIsTransitivelyApplied() + { + var tuple = _converter.CreatePropertyValue(ValueTuple.Create(new {A = 1}), true); + var sequence = Assert.IsType(tuple); + Assert.IsType(sequence.Elements[0]); + } } } diff --git a/test/Serilog.Tests/Serilog.Tests.csproj b/test/Serilog.Tests/Serilog.Tests.csproj index c0fded0b0..c6ff91916 100644 --- a/test/Serilog.Tests/Serilog.Tests.csproj +++ b/test/Serilog.Tests/Serilog.Tests.csproj @@ -18,6 +18,7 @@ +