diff --git a/src/Serilog/Settings/KeyValuePairs/CallableConfigurationMethodFinder.cs b/src/Serilog/Settings/KeyValuePairs/CallableConfigurationMethodFinder.cs index 7660b33bd..e4d46cf9a 100644 --- a/src/Serilog/Settings/KeyValuePairs/CallableConfigurationMethodFinder.cs +++ b/src/Serilog/Settings/KeyValuePairs/CallableConfigurationMethodFinder.cs @@ -27,9 +27,30 @@ internal static LoggerConfiguration FromLogContext(LoggerEnrichmentConfiguration { return loggerEnrichmentConfiguration.FromLogContext(); } - static readonly MethodInfo SurrogateFromLogContextConfigurationMethod = typeof(CallableConfigurationMethodFinder).GetTypeInfo().DeclaredMethods.Single(m => m.Name == nameof(FromLogContext)); + internal static LoggerConfiguration ToMaximumCollectionCount(LoggerDestructuringConfiguration loggerDestructuringConfiguration, + int maximumCollectionCount) + { + return loggerDestructuringConfiguration.ToMaximumCollectionCount(maximumCollectionCount); + } + static readonly MethodInfo SurrogateFromDestructureToMaximumCollectionCountConfigurationMethod = typeof(CallableConfigurationMethodFinder).GetTypeInfo().DeclaredMethods.Single(m => m.Name == nameof(ToMaximumCollectionCount)); + + internal static LoggerConfiguration ToMaximumDepth(LoggerDestructuringConfiguration loggerDestructuringConfiguration, + int maximumDestructuringDepth) + { + return loggerDestructuringConfiguration.ToMaximumDepth(maximumDestructuringDepth); + } + static readonly MethodInfo SurrogateFromDestructureToMaximumDepthConfigurationMethod = typeof(CallableConfigurationMethodFinder).GetTypeInfo().DeclaredMethods.Single(m => m.Name == nameof(ToMaximumDepth)); + + internal static LoggerConfiguration ToMaximumStringLength(LoggerDestructuringConfiguration loggerDestructuringConfiguration, + int maximumStringLength) + { + return loggerDestructuringConfiguration.ToMaximumStringLength(maximumStringLength); + } + static readonly MethodInfo SurrogateFromDestructureToMaximumStringLengthConfigurationMethod = typeof(CallableConfigurationMethodFinder).GetTypeInfo().DeclaredMethods.Single(m => m.Name == nameof(ToMaximumStringLength)); + + internal static IList FindConfigurationMethods(IEnumerable configurationAssemblies, Type configType) { var methods = configurationAssemblies @@ -46,6 +67,14 @@ internal static IList FindConfigurationMethods(IEnumerable if (configType == typeof(LoggerEnrichmentConfiguration)) methods.Add(SurrogateFromLogContextConfigurationMethod); + // Some of the useful Destructure configuration methods are defined as methods rather than extension methods + if (configType == typeof(LoggerDestructuringConfiguration)) + { + methods.Add(SurrogateFromDestructureToMaximumCollectionCountConfigurationMethod); + methods.Add(SurrogateFromDestructureToMaximumDepthConfigurationMethod); + methods.Add(SurrogateFromDestructureToMaximumStringLengthConfigurationMethod); + } + return methods; } } diff --git a/src/Serilog/Settings/KeyValuePairs/KeyValuePairSettings.cs b/src/Serilog/Settings/KeyValuePairs/KeyValuePairSettings.cs index 96d8a8ae5..535cbf989 100644 --- a/src/Serilog/Settings/KeyValuePairs/KeyValuePairSettings.cs +++ b/src/Serilog/Settings/KeyValuePairs/KeyValuePairSettings.cs @@ -34,12 +34,13 @@ class KeyValuePairSettings : ILoggerSettings const string EnrichWithDirective = "enrich"; const string EnrichWithPropertyDirective = "enrich:with-property"; const string FilterDirective = "filter"; + const string DestructureDirective = "destructure"; const string UsingDirectiveFullFormPrefix = "using:"; const string EnrichWithPropertyDirectivePrefix = "enrich:with-property:"; const string MinimumLevelOverrideDirectivePrefix = "minimum-level:override:"; - const string CallableDirectiveRegex = @"^(?audit-to|write-to|enrich|filter):(?[A-Za-z0-9]*)(\.(?[A-Za-z0-9]*)){0,1}$"; + const string CallableDirectiveRegex = @"^(?audit-to|write-to|enrich|filter|destructure):(?[A-Za-z0-9]*)(\.(?[A-Za-z0-9]*)){0,1}$"; const string LevelSwitchDeclarationDirectiveRegex = @"^level-switch:(?.*)$"; const string LevelSwitchNameRegex = @"^\$[A-Za-z]+[A-Za-z0-9]*$"; @@ -53,7 +54,8 @@ class KeyValuePairSettings : ILoggerSettings MinimumLevelControlledByDirective, EnrichWithPropertyDirective, EnrichWithDirective, - FilterDirective + FilterDirective, + DestructureDirective }; static readonly Dictionary CallableDirectiveReceiverTypes = new Dictionary @@ -61,7 +63,8 @@ class KeyValuePairSettings : ILoggerSettings ["audit-to"] = typeof(LoggerAuditSinkConfiguration), ["write-to"] = typeof(LoggerSinkConfiguration), ["enrich"] = typeof(LoggerEnrichmentConfiguration), - ["filter"] = typeof(LoggerFilterConfiguration) + ["filter"] = typeof(LoggerFilterConfiguration), + ["destructure"] = typeof(LoggerDestructuringConfiguration), }; static readonly Dictionary> CallableDirectiveReceivers = new Dictionary> @@ -69,7 +72,8 @@ class KeyValuePairSettings : ILoggerSettings [typeof(LoggerAuditSinkConfiguration)] = lc => lc.AuditTo, [typeof(LoggerSinkConfiguration)] = lc => lc.WriteTo, [typeof(LoggerEnrichmentConfiguration)] = lc => lc.Enrich, - [typeof(LoggerFilterConfiguration)] = lc => lc.Filter + [typeof(LoggerFilterConfiguration)] = lc => lc.Filter, + [typeof(LoggerDestructuringConfiguration)] = lc => lc.Destructure, }; readonly IReadOnlyDictionary _settings; diff --git a/test/Serilog.Tests/Settings/CallableConfigurationMethodFinderTests.cs b/test/Serilog.Tests/Settings/CallableConfigurationMethodFinderTests.cs index 6ff1e6965..c705ba7cd 100644 --- a/test/Serilog.Tests/Settings/CallableConfigurationMethodFinderTests.cs +++ b/test/Serilog.Tests/Settings/CallableConfigurationMethodFinderTests.cs @@ -26,5 +26,24 @@ public void FindsEnricherSpecificConfigurationMethods() Assert.Contains("FromLogContext", eventEnrichers); Assert.Contains("WithDummyThreadId", eventEnrichers); } + + [Fact] + public void FindsDestructureSpecificConfigurationMethods() + { + var destructuringMethods = CallableConfigurationMethodFinder + .FindConfigurationMethods(new[] + { + typeof(Log).GetTypeInfo().Assembly, + typeof(DummyThreadIdEnricher).GetTypeInfo().Assembly, + }, typeof(LoggerDestructuringConfiguration)) + .Select(m => m.Name) + .Distinct() + .ToList(); + + Assert.Contains(nameof(LoggerDestructuringConfiguration.ToMaximumCollectionCount), destructuringMethods); + Assert.Contains(nameof(LoggerDestructuringConfiguration.ToMaximumDepth), destructuringMethods); + Assert.Contains(nameof(LoggerDestructuringConfiguration.ToMaximumStringLength), destructuringMethods); + Assert.Contains(nameof(DummyLoggerConfigurationExtensions.WithDummyHardCodedString), destructuringMethods); + } } } diff --git a/test/Serilog.Tests/Settings/KeyValuePairSettingsTests.cs b/test/Serilog.Tests/Settings/KeyValuePairSettingsTests.cs index 48122004a..550645ae7 100644 --- a/test/Serilog.Tests/Settings/KeyValuePairSettingsTests.cs +++ b/test/Serilog.Tests/Settings/KeyValuePairSettingsTests.cs @@ -381,5 +381,96 @@ public void SinksAreConfiguredWithStaticMember() Assert.Equal(ConsoleThemes.Theme1, DummyConsoleSink.Theme); } + + [Fact] + public void DestructuringToMaximumDepthIsApplied() + { + LogEvent evt = null; + var log = new LoggerConfiguration() + .ReadFrom.KeyValuePairs(new Dictionary + { + ["destructure:ToMaximumDepth.maximumDestructuringDepth"] = "3" + }) + .WriteTo.Sink(new DelegatingSink(e => evt = e)) + .CreateLogger(); + + var nestedObject = new + { + A = new + { + B = new + { + C = new + { + D = "F" + } + } + } + }; + + log.Information("Destructuring a big graph {@DeeplyNested}", nestedObject); + var formattedProperty = evt.Properties["DeeplyNested"].ToString(); + + Assert.Contains("C", formattedProperty); + Assert.DoesNotContain("D", formattedProperty); + } + + [Fact] + public void DestructuringToMaximumStringLengthIsApplied() + { + LogEvent evt = null; + var log = new LoggerConfiguration() + .ReadFrom.KeyValuePairs(new Dictionary + { + ["destructure:ToMaximumStringLength.maximumStringLength"] = "3" + }) + .WriteTo.Sink(new DelegatingSink(e => evt = e)) + .CreateLogger(); + + log.Information("Destructuring a long string {@LongString}", "ABCDEFGH"); + var formattedProperty = evt.Properties["LongString"].ToString(); + + Assert.Equal("\"AB…\"", formattedProperty); + } + + [Fact] + public void DestructuringToMaximumCollectionCountIsApplied() + { + LogEvent evt = null; + var log = new LoggerConfiguration() + .ReadFrom.KeyValuePairs(new Dictionary + { + ["destructure:ToMaximumCollectionCount.maximumCollectionCount"] = "3" + }) + .WriteTo.Sink(new DelegatingSink(e => evt = e)) + .CreateLogger(); + + var collection = new[] { 1, 2, 3, 4, 5, 6 }; + log.Information("Destructuring a big collection {@BigCollection}", collection); + var formattedProperty = evt.Properties["BigCollection"].ToString(); + + Assert.Contains("3", formattedProperty); + Assert.DoesNotContain("4", formattedProperty); + } + + [Fact] + public void DestructuringWithCustomExtensionMethodIsApplied() + { + LogEvent evt = null; + var log = new LoggerConfiguration() + .ReadFrom.KeyValuePairs(new Dictionary + { + ["using:TestDummies"] = typeof(DummyLoggerConfigurationExtensions).GetTypeInfo().Assembly.FullName, + ["destructure:WithDummyHardCodedString.hardCodedString"] = "hardcoded" + }) + .WriteTo.Sink(new DelegatingSink(e => evt = e)) + .CreateLogger(); + + log.Information("Destructuring a big collection {@Input}", new { Foo = "Bar" }); + var formattedProperty = evt.Properties["Input"].ToString(); + + Assert.Equal("\"hardcoded\"", formattedProperty); + } + } } diff --git a/test/TestDummies/DummyHardCodedStringDestructuringPolicy.cs b/test/TestDummies/DummyHardCodedStringDestructuringPolicy.cs new file mode 100644 index 000000000..25d272490 --- /dev/null +++ b/test/TestDummies/DummyHardCodedStringDestructuringPolicy.cs @@ -0,0 +1,22 @@ +using System; +using Serilog.Core; +using Serilog.Events; + +namespace TestDummies +{ + public class DummyHardCodedStringDestructuringPolicy : IDestructuringPolicy + { + readonly string _hardCodedString; + + public DummyHardCodedStringDestructuringPolicy(string hardCodedString) + { + _hardCodedString = hardCodedString ?? throw new ArgumentNullException(nameof(hardCodedString)); + } + + public bool TryDestructure(object value, ILogEventPropertyValueFactory propertyValueFactory, out LogEventPropertyValue result) + { + result = new ScalarValue(_hardCodedString); + return true; + } + } +} diff --git a/test/TestDummies/DummyLoggerConfigurationExtensions.cs b/test/TestDummies/DummyLoggerConfigurationExtensions.cs index a3e7d683f..aa6e61932 100644 --- a/test/TestDummies/DummyLoggerConfigurationExtensions.cs +++ b/test/TestDummies/DummyLoggerConfigurationExtensions.cs @@ -86,5 +86,13 @@ public static LoggerConfiguration DummyWrap( logEventLevel, levelSwitch); } + + public static LoggerConfiguration WithDummyHardCodedString( + this LoggerDestructuringConfiguration loggerDestructuringConfiguration, + string hardCodedString + ) + { + return loggerDestructuringConfiguration.With(new DummyHardCodedStringDestructuringPolicy(hardCodedString)); + } } }