Skip to content

Commit

Permalink
Add support for .Destructure.XXX() methods
Browse files Browse the repository at this point in the history
Supports Serilog "native" non-generic simple  methods : 
- `ToMaximumCollectionCount`
- `ToMaximumDepth`
- `ToMaximumStringLength`

and any *extension method* for `LoggerDestructuringConfiguration`

This should fix serilog#1172
  • Loading branch information
tsimbalar authored and Twinki14 committed Dec 30, 2023
1 parent beabb1a commit ecfd636
Show file tree
Hide file tree
Showing 6 changed files with 178 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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<MethodInfo> FindConfigurationMethods(IEnumerable<Assembly> configurationAssemblies, Type configType)
{
var methods = configurationAssemblies
Expand All @@ -46,6 +67,14 @@ internal static IList<MethodInfo> FindConfigurationMethods(IEnumerable<Assembly>
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;
}
}
Expand Down
12 changes: 8 additions & 4 deletions src/Serilog/Settings/KeyValuePairs/KeyValuePairSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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 = @"^(?<directive>audit-to|write-to|enrich|filter):(?<method>[A-Za-z0-9]*)(\.(?<argument>[A-Za-z0-9]*)){0,1}$";
const string CallableDirectiveRegex = @"^(?<directive>audit-to|write-to|enrich|filter|destructure):(?<method>[A-Za-z0-9]*)(\.(?<argument>[A-Za-z0-9]*)){0,1}$";
const string LevelSwitchDeclarationDirectiveRegex = @"^level-switch:(?<switchName>.*)$";
const string LevelSwitchNameRegex = @"^\$[A-Za-z]+[A-Za-z0-9]*$";

Expand All @@ -53,23 +54,26 @@ class KeyValuePairSettings : ILoggerSettings
MinimumLevelControlledByDirective,
EnrichWithPropertyDirective,
EnrichWithDirective,
FilterDirective
FilterDirective,
DestructureDirective
};

static readonly Dictionary<string, Type> CallableDirectiveReceiverTypes = new Dictionary<string, Type>
{
["audit-to"] = typeof(LoggerAuditSinkConfiguration),
["write-to"] = typeof(LoggerSinkConfiguration),
["enrich"] = typeof(LoggerEnrichmentConfiguration),
["filter"] = typeof(LoggerFilterConfiguration)
["filter"] = typeof(LoggerFilterConfiguration),
["destructure"] = typeof(LoggerDestructuringConfiguration),
};

static readonly Dictionary<Type, Func<LoggerConfiguration, object>> CallableDirectiveReceivers = new Dictionary<Type, Func<LoggerConfiguration, object>>
{
[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<string, string> _settings;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
}
91 changes: 91 additions & 0 deletions test/Serilog.Tests/Settings/KeyValuePairSettingsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, string>
{
["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<string, string>
{
["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<string, string>
{
["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<string, string>
{
["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);
}

}
}
22 changes: 22 additions & 0 deletions test/TestDummies/DummyHardCodedStringDestructuringPolicy.cs
Original file line number Diff line number Diff line change
@@ -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;
}
}
}
8 changes: 8 additions & 0 deletions test/TestDummies/DummyLoggerConfigurationExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
}
}

0 comments on commit ecfd636

Please sign in to comment.