Skip to content

Commit b5db2f9

Browse files
committed
Add support for .Destructure.XXX() methods
Supports Serilog "native" non-generic simple methods : - `ToMaximumCollectionCount` - `ToMaximumDepth` - `ToMaximumStringLength` and any *extension method* for `LoggerDestructuringConfiguration` This should fix serilog#1172
1 parent ed29c30 commit b5db2f9

6 files changed

+178
-5
lines changed

src/Serilog/Settings/KeyValuePairs/CallableConfigurationMethodFinder.cs

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,30 @@ internal static LoggerConfiguration FromLogContext(LoggerEnrichmentConfiguration
2727
{
2828
return loggerEnrichmentConfiguration.FromLogContext();
2929
}
30-
3130
static readonly MethodInfo SurrogateFromLogContextConfigurationMethod = typeof(CallableConfigurationMethodFinder).GetTypeInfo().DeclaredMethods.Single(m => m.Name == nameof(FromLogContext));
3231

32+
internal static LoggerConfiguration ToMaximumCollectionCount(LoggerDestructuringConfiguration loggerDestructuringConfiguration,
33+
int maximumCollectionCount)
34+
{
35+
return loggerDestructuringConfiguration.ToMaximumCollectionCount(maximumCollectionCount);
36+
}
37+
static readonly MethodInfo SurrogateFromDestructureToMaximumCollectionCountConfigurationMethod = typeof(CallableConfigurationMethodFinder).GetTypeInfo().DeclaredMethods.Single(m => m.Name == nameof(ToMaximumCollectionCount));
38+
39+
internal static LoggerConfiguration ToMaximumDepth(LoggerDestructuringConfiguration loggerDestructuringConfiguration,
40+
int maximumDestructuringDepth)
41+
{
42+
return loggerDestructuringConfiguration.ToMaximumDepth(maximumDestructuringDepth);
43+
}
44+
static readonly MethodInfo SurrogateFromDestructureToMaximumDepthConfigurationMethod = typeof(CallableConfigurationMethodFinder).GetTypeInfo().DeclaredMethods.Single(m => m.Name == nameof(ToMaximumDepth));
45+
46+
internal static LoggerConfiguration ToMaximumStringLength(LoggerDestructuringConfiguration loggerDestructuringConfiguration,
47+
int maximumStringLength)
48+
{
49+
return loggerDestructuringConfiguration.ToMaximumStringLength(maximumStringLength);
50+
}
51+
static readonly MethodInfo SurrogateFromDestructureToMaximumStringLengthConfigurationMethod = typeof(CallableConfigurationMethodFinder).GetTypeInfo().DeclaredMethods.Single(m => m.Name == nameof(ToMaximumStringLength));
52+
53+
3354
internal static IList<MethodInfo> FindConfigurationMethods(IEnumerable<Assembly> configurationAssemblies, Type configType)
3455
{
3556
var methods = configurationAssemblies
@@ -46,6 +67,14 @@ internal static IList<MethodInfo> FindConfigurationMethods(IEnumerable<Assembly>
4667
if (configType == typeof(LoggerEnrichmentConfiguration))
4768
methods.Add(SurrogateFromLogContextConfigurationMethod);
4869

70+
// Some of the useful Destructure configuration methods are defined as methods rather than extension methods
71+
if (configType == typeof(LoggerDestructuringConfiguration))
72+
{
73+
methods.Add(SurrogateFromDestructureToMaximumCollectionCountConfigurationMethod);
74+
methods.Add(SurrogateFromDestructureToMaximumDepthConfigurationMethod);
75+
methods.Add(SurrogateFromDestructureToMaximumStringLengthConfigurationMethod);
76+
}
77+
4978
return methods;
5079
}
5180
}

src/Serilog/Settings/KeyValuePairs/KeyValuePairSettings.cs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,13 @@ class KeyValuePairSettings : ILoggerSettings
3434
const string EnrichWithDirective = "enrich";
3535
const string EnrichWithPropertyDirective = "enrich:with-property";
3636
const string FilterDirective = "filter";
37+
const string DestructureDirective = "destructure";
3738

3839
const string UsingDirectiveFullFormPrefix = "using:";
3940
const string EnrichWithPropertyDirectivePrefix = "enrich:with-property:";
4041
const string MinimumLevelOverrideDirectivePrefix = "minimum-level:override:";
4142

42-
const string CallableDirectiveRegex = @"^(?<directive>audit-to|write-to|enrich|filter):(?<method>[A-Za-z0-9]*)(\.(?<argument>[A-Za-z0-9]*)){0,1}$";
43+
const string CallableDirectiveRegex = @"^(?<directive>audit-to|write-to|enrich|filter|destructure):(?<method>[A-Za-z0-9]*)(\.(?<argument>[A-Za-z0-9]*)){0,1}$";
4344
const string LevelSwitchDeclarationDirectiveRegex = @"^level-switch:(?<switchName>.*)$";
4445
const string LevelSwitchNameRegex = @"^\$[A-Za-z]+[A-Za-z0-9]*$";
4546

@@ -53,23 +54,26 @@ class KeyValuePairSettings : ILoggerSettings
5354
MinimumLevelControlledByDirective,
5455
EnrichWithPropertyDirective,
5556
EnrichWithDirective,
56-
FilterDirective
57+
FilterDirective,
58+
DestructureDirective
5759
};
5860

5961
static readonly Dictionary<string, Type> CallableDirectiveReceiverTypes = new Dictionary<string, Type>
6062
{
6163
["audit-to"] = typeof(LoggerAuditSinkConfiguration),
6264
["write-to"] = typeof(LoggerSinkConfiguration),
6365
["enrich"] = typeof(LoggerEnrichmentConfiguration),
64-
["filter"] = typeof(LoggerFilterConfiguration)
66+
["filter"] = typeof(LoggerFilterConfiguration),
67+
["destructure"] = typeof(LoggerDestructuringConfiguration),
6568
};
6669

6770
static readonly Dictionary<Type, Func<LoggerConfiguration, object>> CallableDirectiveReceivers = new Dictionary<Type, Func<LoggerConfiguration, object>>
6871
{
6972
[typeof(LoggerAuditSinkConfiguration)] = lc => lc.AuditTo,
7073
[typeof(LoggerSinkConfiguration)] = lc => lc.WriteTo,
7174
[typeof(LoggerEnrichmentConfiguration)] = lc => lc.Enrich,
72-
[typeof(LoggerFilterConfiguration)] = lc => lc.Filter
75+
[typeof(LoggerFilterConfiguration)] = lc => lc.Filter,
76+
[typeof(LoggerDestructuringConfiguration)] = lc => lc.Destructure,
7377
};
7478

7579
readonly IReadOnlyDictionary<string, string> _settings;

test/Serilog.Tests/Settings/CallableConfigurationMethodFinderTests.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,5 +26,24 @@ public void FindsEnricherSpecificConfigurationMethods()
2626
Assert.Contains("FromLogContext", eventEnrichers);
2727
Assert.Contains("WithDummyThreadId", eventEnrichers);
2828
}
29+
30+
[Fact]
31+
public void FindsDestructureSpecificConfigurationMethods()
32+
{
33+
var destructuringMethods = CallableConfigurationMethodFinder
34+
.FindConfigurationMethods(new[]
35+
{
36+
typeof(Log).GetTypeInfo().Assembly,
37+
typeof(DummyThreadIdEnricher).GetTypeInfo().Assembly,
38+
}, typeof(LoggerDestructuringConfiguration))
39+
.Select(m => m.Name)
40+
.Distinct()
41+
.ToList();
42+
43+
Assert.Contains(nameof(LoggerDestructuringConfiguration.ToMaximumCollectionCount), destructuringMethods);
44+
Assert.Contains(nameof(LoggerDestructuringConfiguration.ToMaximumDepth), destructuringMethods);
45+
Assert.Contains(nameof(LoggerDestructuringConfiguration.ToMaximumStringLength), destructuringMethods);
46+
Assert.Contains(nameof(DummyLoggerConfigurationExtensions.WithDummyHardCodedString), destructuringMethods);
47+
}
2948
}
3049
}

test/Serilog.Tests/Settings/KeyValuePairSettingsTests.cs

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -381,5 +381,96 @@ public void SinksAreConfiguredWithStaticMember()
381381

382382
Assert.Equal(ConsoleThemes.Theme1, DummyConsoleSink.Theme);
383383
}
384+
385+
[Fact]
386+
public void DestructuringToMaximumDepthIsApplied()
387+
{
388+
LogEvent evt = null;
389+
var log = new LoggerConfiguration()
390+
.ReadFrom.KeyValuePairs(new Dictionary<string, string>
391+
{
392+
["destructure:ToMaximumDepth.maximumDestructuringDepth"] = "3"
393+
})
394+
.WriteTo.Sink(new DelegatingSink(e => evt = e))
395+
.CreateLogger();
396+
397+
var nestedObject = new
398+
{
399+
A = new
400+
{
401+
B = new
402+
{
403+
C = new
404+
{
405+
D = "F"
406+
}
407+
}
408+
}
409+
};
410+
411+
log.Information("Destructuring a big graph {@DeeplyNested}", nestedObject);
412+
var formattedProperty = evt.Properties["DeeplyNested"].ToString();
413+
414+
Assert.Contains("C", formattedProperty);
415+
Assert.DoesNotContain("D", formattedProperty);
416+
}
417+
418+
[Fact]
419+
public void DestructuringToMaximumStringLengthIsApplied()
420+
{
421+
LogEvent evt = null;
422+
var log = new LoggerConfiguration()
423+
.ReadFrom.KeyValuePairs(new Dictionary<string, string>
424+
{
425+
["destructure:ToMaximumStringLength.maximumStringLength"] = "3"
426+
})
427+
.WriteTo.Sink(new DelegatingSink(e => evt = e))
428+
.CreateLogger();
429+
430+
log.Information("Destructuring a long string {@LongString}", "ABCDEFGH");
431+
var formattedProperty = evt.Properties["LongString"].ToString();
432+
433+
Assert.Equal("\"AB…\"", formattedProperty);
434+
}
435+
436+
[Fact]
437+
public void DestructuringToMaximumCollectionCountIsApplied()
438+
{
439+
LogEvent evt = null;
440+
var log = new LoggerConfiguration()
441+
.ReadFrom.KeyValuePairs(new Dictionary<string, string>
442+
{
443+
["destructure:ToMaximumCollectionCount.maximumCollectionCount"] = "3"
444+
})
445+
.WriteTo.Sink(new DelegatingSink(e => evt = e))
446+
.CreateLogger();
447+
448+
var collection = new[] { 1, 2, 3, 4, 5, 6 };
449+
log.Information("Destructuring a big collection {@BigCollection}", collection);
450+
var formattedProperty = evt.Properties["BigCollection"].ToString();
451+
452+
Assert.Contains("3", formattedProperty);
453+
Assert.DoesNotContain("4", formattedProperty);
454+
}
455+
456+
[Fact]
457+
public void DestructuringWithCustomExtensionMethodIsApplied()
458+
{
459+
LogEvent evt = null;
460+
var log = new LoggerConfiguration()
461+
.ReadFrom.KeyValuePairs(new Dictionary<string, string>
462+
{
463+
["using:TestDummies"] = typeof(DummyLoggerConfigurationExtensions).GetTypeInfo().Assembly.FullName,
464+
["destructure:WithDummyHardCodedString.hardCodedString"] = "hardcoded"
465+
})
466+
.WriteTo.Sink(new DelegatingSink(e => evt = e))
467+
.CreateLogger();
468+
469+
log.Information("Destructuring a big collection {@Input}", new { Foo = "Bar" });
470+
var formattedProperty = evt.Properties["Input"].ToString();
471+
472+
Assert.Equal("\"hardcoded\"", formattedProperty);
473+
}
474+
384475
}
385476
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
using System;
2+
using Serilog.Core;
3+
using Serilog.Events;
4+
5+
namespace TestDummies
6+
{
7+
public class DummyHardCodedStringDestructuringPolicy : IDestructuringPolicy
8+
{
9+
readonly string _hardCodedString;
10+
11+
public DummyHardCodedStringDestructuringPolicy(string hardCodedString)
12+
{
13+
_hardCodedString = hardCodedString ?? throw new ArgumentNullException(nameof(hardCodedString));
14+
}
15+
16+
public bool TryDestructure(object value, ILogEventPropertyValueFactory propertyValueFactory, out LogEventPropertyValue result)
17+
{
18+
result = new ScalarValue(_hardCodedString);
19+
return true;
20+
}
21+
}
22+
}

test/TestDummies/DummyLoggerConfigurationExtensions.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,5 +86,13 @@ public static LoggerConfiguration DummyWrap(
8686
logEventLevel,
8787
levelSwitch);
8888
}
89+
90+
public static LoggerConfiguration WithDummyHardCodedString(
91+
this LoggerDestructuringConfiguration loggerDestructuringConfiguration,
92+
string hardCodedString
93+
)
94+
{
95+
return loggerDestructuringConfiguration.With(new DummyHardCodedStringDestructuringPolicy(hardCodedString));
96+
}
8997
}
9098
}

0 commit comments

Comments
 (0)