Skip to content

Commit 6a56899

Browse files
authored
Merge pull request #110 from MV10/destructuring
Destructure support
2 parents 285a4ce + 43345f1 commit 6a56899

File tree

5 files changed

+201
-11
lines changed

5 files changed

+201
-11
lines changed

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,12 @@ Configuration is read from the `Serilog` section.
1414
{ "Name": "File", "Args": { "path": "%TEMP%\\Logs\\serilog-configuration-sample.txt" } }
1515
],
1616
"Enrich": ["FromLogContext", "WithMachineName", "WithThreadId"],
17+
"Destructure": [
18+
{ "Name": "With", "Args": { "policy": "Sample.CustomPolicy, Sample" } },
19+
{ "Name": "ToMaximumDepth", "Args": { "maximumDestructuringDepth": 4 } },
20+
{ "Name": "ToMaximumStringLength", "Args": { "maximumStringLength": 100 } },
21+
{ "Name": "ToMaximumCollectionCount", "Args": { "maximumCollectionCount": 10 } }
22+
],
1723
"Properties": {
1824
"Application": "Sample"
1925
}

sample/Sample/Program.cs

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using System.Linq;
77
using Serilog.Core;
88
using Serilog.Events;
9+
using System.Collections.Generic;
910

1011
namespace Sample
1112
{
@@ -32,17 +33,56 @@ public static void Main(string[] args)
3233
logger.ForContext(Constants.SourceContextPropertyName, "Microsoft").Error("Hello, world!");
3334
logger.ForContext(Constants.SourceContextPropertyName, "MyApp.Something.Tricky").Verbose("Hello, world!");
3435

35-
Console.WriteLine();
36+
logger.Information("Destructure with max object nesting depth:\n{@NestedObject}",
37+
new { FiveDeep = new { Two = new { Three = new { Four = new { Five = "the end" } } } } });
38+
39+
logger.Information("Destructure with max string length:\n{@LongString}",
40+
new { TwentyChars = "0123456789abcdefghij" });
41+
42+
logger.Information("Destructure with max collection count:\n{@BigData}",
43+
new { TenItems = new string[] { "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten" } });
44+
45+
logger.Information("Destructure with policy to strip password:\n{@LoginData}",
46+
new LoginData { Username = "BGates", Password = "isityearoflinuxyet" });
47+
48+
Console.WriteLine("\nPress \"q\" to quit, or any other key to run again.\n");
3649
}
37-
while (!args.Contains("--run-once") && (Console.ReadKey().KeyChar != 'q'));
50+
while(!args.Contains("--run-once") && (Console.ReadKey().KeyChar != 'q'));
3851
}
3952
}
4053

54+
// The filter syntax in the sample configuration file is
55+
// processed by the Serilog.Filters.Expressions package.
4156
public class CustomFilter : ILogEventFilter
4257
{
4358
public bool IsEnabled(LogEvent logEvent)
4459
{
4560
return true;
4661
}
4762
}
63+
64+
public class LoginData
65+
{
66+
public string Username;
67+
public string Password;
68+
}
69+
70+
public class CustomPolicy : IDestructuringPolicy
71+
{
72+
public bool TryDestructure(object value, ILogEventPropertyValueFactory propertyValueFactory, out LogEventPropertyValue result)
73+
{
74+
result = null;
75+
76+
if(value is LoginData)
77+
{
78+
result = new StructureValue(
79+
new List<LogEventProperty>
80+
{
81+
new LogEventProperty("Username", new ScalarValue(((LoginData)value).Username))
82+
});
83+
}
84+
85+
return (result != null);
86+
}
87+
}
4888
}

sample/Sample/appsettings.json

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,24 @@
4343
"Properties": {
4444
"Application": "Sample"
4545
},
46+
"Destructure": [
47+
{
48+
"Name": "With",
49+
"Args": { "policy": "Sample.CustomPolicy, Sample" }
50+
},
51+
{
52+
"Name": "ToMaximumDepth",
53+
"Args": { "maximumDestructuringDepth": 3 }
54+
},
55+
{
56+
"Name": "ToMaximumStringLength",
57+
"Args": { "maximumStringLength": 10 }
58+
},
59+
{
60+
"Name": "ToMaximumCollectionCount",
61+
"Args": { "maximumCollectionCount": 5 }
62+
}
63+
],
4664
"Filter": [
4765
{
4866
"Name": "ByIncludingOnly",

src/Serilog.Settings.Configuration/Settings/Configuration/ConfigurationReader.cs

Lines changed: 52 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ public void Configure(LoggerConfiguration loggerConfiguration)
5757
ApplyMinimumLevel(loggerConfiguration, declaredLevelSwitches);
5858
ApplyEnrichment(loggerConfiguration, declaredLevelSwitches);
5959
ApplyFilters(loggerConfiguration, declaredLevelSwitches);
60+
ApplyDestructuring(loggerConfiguration, declaredLevelSwitches);
6061
ApplySinks(loggerConfiguration, declaredLevelSwitches);
6162
ApplyAuditSinks(loggerConfiguration, declaredLevelSwitches);
6263
}
@@ -152,6 +153,16 @@ void ApplyFilters(LoggerConfiguration loggerConfiguration, IReadOnlyDictionary<s
152153
}
153154
}
154155

156+
void ApplyDestructuring(LoggerConfiguration loggerConfiguration, IReadOnlyDictionary<string, LoggingLevelSwitch> declaredLevelSwitches)
157+
{
158+
var filterDirective = _section.GetSection("Destructure");
159+
if(filterDirective.GetChildren().Any())
160+
{
161+
var methodCalls = GetMethodCalls(filterDirective);
162+
CallConfigurationMethods(methodCalls, FindDestructureConfigurationMethods(_configurationAssemblies), loggerConfiguration.Destructure, declaredLevelSwitches);
163+
}
164+
}
165+
155166
void ApplySinks(LoggerConfiguration loggerConfiguration, IReadOnlyDictionary<string, LoggingLevelSwitch> declaredLevelSwitches)
156167
{
157168
var writeToDirective = _section.GetSection("WriteTo");
@@ -339,7 +350,7 @@ internal static MethodInfo SelectConfigurationMethod(IEnumerable<MethodInfo> can
339350

340351
internal static IList<MethodInfo> FindSinkConfigurationMethods(IReadOnlyCollection<Assembly> configurationAssemblies)
341352
{
342-
var found = FindConfigurationMethods(configurationAssemblies, typeof(LoggerSinkConfiguration));
353+
var found = FindConfigurationExtensionMethods(configurationAssemblies, typeof(LoggerSinkConfiguration));
343354
if (configurationAssemblies.Contains(typeof(LoggerSinkConfiguration).GetTypeInfo().Assembly))
344355
found.Add(GetSurrogateConfigurationMethod<LoggerSinkConfiguration, Action<LoggerConfiguration>, LoggingLevelSwitch>((c, a, s) => Logger(c, a, LevelAlias.Minimum, s)));
345356

@@ -348,30 +359,44 @@ internal static IList<MethodInfo> FindSinkConfigurationMethods(IReadOnlyCollecti
348359

349360
internal static IList<MethodInfo> FindAuditSinkConfigurationMethods(IReadOnlyCollection<Assembly> configurationAssemblies)
350361
{
351-
var found = FindConfigurationMethods(configurationAssemblies, typeof(LoggerAuditSinkConfiguration));
362+
var found = FindConfigurationExtensionMethods(configurationAssemblies, typeof(LoggerAuditSinkConfiguration));
352363

353364
return found;
354365
}
355366

356367
internal static IList<MethodInfo> FindFilterConfigurationMethods(IReadOnlyCollection<Assembly> configurationAssemblies)
357368
{
358-
var found = FindConfigurationMethods(configurationAssemblies, typeof(LoggerFilterConfiguration));
369+
var found = FindConfigurationExtensionMethods(configurationAssemblies, typeof(LoggerFilterConfiguration));
359370
if (configurationAssemblies.Contains(typeof(LoggerFilterConfiguration).GetTypeInfo().Assembly))
360371
found.Add(GetSurrogateConfigurationMethod<LoggerFilterConfiguration, ILogEventFilter, object>((c, f, _) => With(c, f)));
361372

362373
return found;
363374
}
364375

376+
internal static IList<MethodInfo> FindDestructureConfigurationMethods(IReadOnlyCollection<Assembly> configurationAssemblies)
377+
{
378+
var found = FindConfigurationExtensionMethods(configurationAssemblies, typeof(LoggerDestructuringConfiguration));
379+
if(configurationAssemblies.Contains(typeof(LoggerDestructuringConfiguration).GetTypeInfo().Assembly))
380+
{
381+
found.Add(GetSurrogateConfigurationMethod<LoggerDestructuringConfiguration, IDestructuringPolicy, object>((c, d, _) => With(c, d)));
382+
found.Add(GetSurrogateConfigurationMethod<LoggerDestructuringConfiguration, int, object>((c, m, _) => ToMaximumDepth(c, m)));
383+
found.Add(GetSurrogateConfigurationMethod<LoggerDestructuringConfiguration, int, object>((c, m, _) => ToMaximumStringLength(c, m)));
384+
found.Add(GetSurrogateConfigurationMethod<LoggerDestructuringConfiguration, int, object>((c, m, _) => ToMaximumCollectionCount(c, m)));
385+
}
386+
387+
return found;
388+
}
389+
365390
internal static IList<MethodInfo> FindEventEnricherConfigurationMethods(IReadOnlyCollection<Assembly> configurationAssemblies)
366391
{
367-
var found = FindConfigurationMethods(configurationAssemblies, typeof(LoggerEnrichmentConfiguration));
392+
var found = FindConfigurationExtensionMethods(configurationAssemblies, typeof(LoggerEnrichmentConfiguration));
368393
if (configurationAssemblies.Contains(typeof(LoggerEnrichmentConfiguration).GetTypeInfo().Assembly))
369394
found.Add(GetSurrogateConfigurationMethod<LoggerEnrichmentConfiguration, object, object>((c, _, __) => FromLogContext(c)));
370395

371396
return found;
372397
}
373398

374-
internal static IList<MethodInfo> FindConfigurationMethods(IReadOnlyCollection<Assembly> configurationAssemblies, Type configType)
399+
internal static IList<MethodInfo> FindConfigurationExtensionMethods(IReadOnlyCollection<Assembly> configurationAssemblies, Type configType)
375400
{
376401
return configurationAssemblies
377402
.SelectMany(a => a.ExportedTypes
@@ -383,15 +408,34 @@ internal static IList<MethodInfo> FindConfigurationMethods(IReadOnlyCollection<A
383408
.ToList();
384409
}
385410

386-
// don't support (yet?) arrays in the parameter list (ILogEventEnricher[])
411+
/*
412+
Pass-through calls to various Serilog config methods which are
413+
implemented as instance methods rather than extension methods. The
414+
FindXXXConfigurationMethods calls (above) use these to add method
415+
invocation expressions as surrogates so that SelectConfigurationMethod
416+
has a way to match and invoke these instance methods.
417+
*/
418+
419+
// TODO: add overload for array argument (ILogEventEnricher[])
387420
internal static LoggerConfiguration With(LoggerFilterConfiguration loggerFilterConfiguration, ILogEventFilter filter)
388421
=> loggerFilterConfiguration.With(filter);
389422

390-
// Unlike the other configuration methods, FromLogContext is an instance method rather than an extension.
423+
// TODO: add overload for array argument (IDestructuringPolicy[])
424+
internal static LoggerConfiguration With(LoggerDestructuringConfiguration loggerDestructuringConfiguration, IDestructuringPolicy policy)
425+
=> loggerDestructuringConfiguration.With(policy);
426+
427+
internal static LoggerConfiguration ToMaximumDepth(LoggerDestructuringConfiguration loggerDestructuringConfiguration, int maximumDestructuringDepth)
428+
=> loggerDestructuringConfiguration.ToMaximumDepth(maximumDestructuringDepth);
429+
430+
internal static LoggerConfiguration ToMaximumStringLength(LoggerDestructuringConfiguration loggerDestructuringConfiguration, int maximumStringLength)
431+
=> loggerDestructuringConfiguration.ToMaximumStringLength(maximumStringLength);
432+
433+
internal static LoggerConfiguration ToMaximumCollectionCount(LoggerDestructuringConfiguration loggerDestructuringConfiguration, int maximumCollectionCount)
434+
=> loggerDestructuringConfiguration.ToMaximumCollectionCount(maximumCollectionCount);
435+
391436
internal static LoggerConfiguration FromLogContext(LoggerEnrichmentConfiguration loggerEnrichmentConfiguration)
392437
=> loggerEnrichmentConfiguration.FromLogContext();
393438

394-
// Unlike the other configuration methods, Logger is an instance method rather than an extension.
395439
internal static LoggerConfiguration Logger(
396440
LoggerSinkConfiguration loggerSinkConfiguration,
397441
Action<LoggerConfiguration> configureLogger,

test/Serilog.Settings.Configuration.Tests/ConfigurationSettingsTests.cs

Lines changed: 83 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -492,7 +492,6 @@ public void SinkWithIntArrayArgument()
492492
Assert.Equal(1, DummyRollingFileSink.Emitted.Count);
493493
}
494494

495-
496495
[Trait("Bugfix", "#91")]
497496
[Fact]
498497
public void WriteToLoggerWithRestrictedToMinimumLevelIsSupported()
@@ -597,5 +596,88 @@ public void InconsistentComplexVsScalarArgumentValuesThrowsIOE()
597596
Assert.Contains("The value for the argument", ex.Message);
598597
Assert.Contains("'Serilog:WriteTo:0:Args:pathFormat'", ex.Message);
599598
}
599+
600+
[Fact]
601+
public void DestructureLimitsNestingDepth()
602+
{
603+
var json = @"{
604+
""Serilog"": {
605+
""Destructure"": [
606+
{
607+
""Name"": ""ToMaximumDepth"",
608+
""Args"": { ""maximumDestructuringDepth"": 3 }
609+
}]
610+
}
611+
}";
612+
613+
var NestedObject = new
614+
{
615+
A = new
616+
{
617+
B = new
618+
{
619+
C = new
620+
{
621+
D = "F"
622+
}
623+
}
624+
}
625+
};
626+
627+
var msg = GetDestructuredProperty(NestedObject, json);
628+
629+
Assert.Contains("C", msg);
630+
Assert.DoesNotContain("D", msg);
631+
}
632+
633+
[Fact]
634+
public void DestructureLimitsStringLength()
635+
{
636+
var json = @"{
637+
""Serilog"": {
638+
""Destructure"": [
639+
{
640+
""Name"": ""ToMaximumStringLength"",
641+
""Args"": { ""maximumStringLength"": 3 }
642+
}]
643+
}
644+
}";
645+
646+
var inputString = "ABCDEFGH";
647+
var msg = GetDestructuredProperty(inputString, json);
648+
649+
Assert.Equal("\"AB…\"", msg);
650+
}
651+
652+
[Fact]
653+
public void DestructureLimitsCollectionCount()
654+
{
655+
var json = @"{
656+
""Serilog"": {
657+
""Destructure"": [
658+
{
659+
""Name"": ""ToMaximumCollectionCount"",
660+
""Args"": { ""maximumCollectionCount"": 3 }
661+
}]
662+
}
663+
}";
664+
665+
var collection = new[] { 1, 2, 3, 4, 5, 6 };
666+
var msg = GetDestructuredProperty(collection, json);
667+
668+
Assert.Contains("3", msg);
669+
Assert.DoesNotContain("4", msg);
670+
}
671+
672+
private string GetDestructuredProperty(object x, string json)
673+
{
674+
LogEvent evt = null;
675+
var log = ConfigFromJson(json)
676+
.WriteTo.Sink(new DelegatingSink(e => evt = e))
677+
.CreateLogger();
678+
log.Information("{@X}", x);
679+
var result = evt.Properties["X"].ToString();
680+
return result;
681+
}
600682
}
601683
}

0 commit comments

Comments
 (0)