Skip to content

Commit

Permalink
Merge pull request #110 from MV10/destructuring
Browse files Browse the repository at this point in the history
Destructure support
  • Loading branch information
tsimbalar committed May 14, 2018
2 parents 285a4ce + 43345f1 commit 6a56899
Show file tree
Hide file tree
Showing 5 changed files with 201 additions and 11 deletions.
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@ Configuration is read from the `Serilog` section.
{ "Name": "File", "Args": { "path": "%TEMP%\\Logs\\serilog-configuration-sample.txt" } }
],
"Enrich": ["FromLogContext", "WithMachineName", "WithThreadId"],
"Destructure": [
{ "Name": "With", "Args": { "policy": "Sample.CustomPolicy, Sample" } },
{ "Name": "ToMaximumDepth", "Args": { "maximumDestructuringDepth": 4 } },
{ "Name": "ToMaximumStringLength", "Args": { "maximumStringLength": 100 } },
{ "Name": "ToMaximumCollectionCount", "Args": { "maximumCollectionCount": 10 } }
],
"Properties": {
"Application": "Sample"
}
Expand Down
44 changes: 42 additions & 2 deletions sample/Sample/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System.Linq;
using Serilog.Core;
using Serilog.Events;
using System.Collections.Generic;

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

Console.WriteLine();
logger.Information("Destructure with max object nesting depth:\n{@NestedObject}",
new { FiveDeep = new { Two = new { Three = new { Four = new { Five = "the end" } } } } });

logger.Information("Destructure with max string length:\n{@LongString}",
new { TwentyChars = "0123456789abcdefghij" });

logger.Information("Destructure with max collection count:\n{@BigData}",
new { TenItems = new string[] { "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten" } });

logger.Information("Destructure with policy to strip password:\n{@LoginData}",
new LoginData { Username = "BGates", Password = "isityearoflinuxyet" });

Console.WriteLine("\nPress \"q\" to quit, or any other key to run again.\n");
}
while (!args.Contains("--run-once") && (Console.ReadKey().KeyChar != 'q'));
while(!args.Contains("--run-once") && (Console.ReadKey().KeyChar != 'q'));
}
}

// The filter syntax in the sample configuration file is
// processed by the Serilog.Filters.Expressions package.
public class CustomFilter : ILogEventFilter
{
public bool IsEnabled(LogEvent logEvent)
{
return true;
}
}

public class LoginData
{
public string Username;
public string Password;
}

public class CustomPolicy : IDestructuringPolicy
{
public bool TryDestructure(object value, ILogEventPropertyValueFactory propertyValueFactory, out LogEventPropertyValue result)
{
result = null;

if(value is LoginData)
{
result = new StructureValue(
new List<LogEventProperty>
{
new LogEventProperty("Username", new ScalarValue(((LoginData)value).Username))
});
}

return (result != null);
}
}
}
18 changes: 18 additions & 0 deletions sample/Sample/appsettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,24 @@
"Properties": {
"Application": "Sample"
},
"Destructure": [
{
"Name": "With",
"Args": { "policy": "Sample.CustomPolicy, Sample" }
},
{
"Name": "ToMaximumDepth",
"Args": { "maximumDestructuringDepth": 3 }
},
{
"Name": "ToMaximumStringLength",
"Args": { "maximumStringLength": 10 }
},
{
"Name": "ToMaximumCollectionCount",
"Args": { "maximumCollectionCount": 5 }
}
],
"Filter": [
{
"Name": "ByIncludingOnly",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ public void Configure(LoggerConfiguration loggerConfiguration)
ApplyMinimumLevel(loggerConfiguration, declaredLevelSwitches);
ApplyEnrichment(loggerConfiguration, declaredLevelSwitches);
ApplyFilters(loggerConfiguration, declaredLevelSwitches);
ApplyDestructuring(loggerConfiguration, declaredLevelSwitches);
ApplySinks(loggerConfiguration, declaredLevelSwitches);
ApplyAuditSinks(loggerConfiguration, declaredLevelSwitches);
}
Expand Down Expand Up @@ -152,6 +153,16 @@ void ApplyFilters(LoggerConfiguration loggerConfiguration, IReadOnlyDictionary<s
}
}

void ApplyDestructuring(LoggerConfiguration loggerConfiguration, IReadOnlyDictionary<string, LoggingLevelSwitch> declaredLevelSwitches)
{
var filterDirective = _section.GetSection("Destructure");
if(filterDirective.GetChildren().Any())
{
var methodCalls = GetMethodCalls(filterDirective);
CallConfigurationMethods(methodCalls, FindDestructureConfigurationMethods(_configurationAssemblies), loggerConfiguration.Destructure, declaredLevelSwitches);
}
}

void ApplySinks(LoggerConfiguration loggerConfiguration, IReadOnlyDictionary<string, LoggingLevelSwitch> declaredLevelSwitches)
{
var writeToDirective = _section.GetSection("WriteTo");
Expand Down Expand Up @@ -339,7 +350,7 @@ internal static MethodInfo SelectConfigurationMethod(IEnumerable<MethodInfo> can

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

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

internal static IList<MethodInfo> FindAuditSinkConfigurationMethods(IReadOnlyCollection<Assembly> configurationAssemblies)
{
var found = FindConfigurationMethods(configurationAssemblies, typeof(LoggerAuditSinkConfiguration));
var found = FindConfigurationExtensionMethods(configurationAssemblies, typeof(LoggerAuditSinkConfiguration));

return found;
}

internal static IList<MethodInfo> FindFilterConfigurationMethods(IReadOnlyCollection<Assembly> configurationAssemblies)
{
var found = FindConfigurationMethods(configurationAssemblies, typeof(LoggerFilterConfiguration));
var found = FindConfigurationExtensionMethods(configurationAssemblies, typeof(LoggerFilterConfiguration));
if (configurationAssemblies.Contains(typeof(LoggerFilterConfiguration).GetTypeInfo().Assembly))
found.Add(GetSurrogateConfigurationMethod<LoggerFilterConfiguration, ILogEventFilter, object>((c, f, _) => With(c, f)));

return found;
}

internal static IList<MethodInfo> FindDestructureConfigurationMethods(IReadOnlyCollection<Assembly> configurationAssemblies)
{
var found = FindConfigurationExtensionMethods(configurationAssemblies, typeof(LoggerDestructuringConfiguration));
if(configurationAssemblies.Contains(typeof(LoggerDestructuringConfiguration).GetTypeInfo().Assembly))
{
found.Add(GetSurrogateConfigurationMethod<LoggerDestructuringConfiguration, IDestructuringPolicy, object>((c, d, _) => With(c, d)));
found.Add(GetSurrogateConfigurationMethod<LoggerDestructuringConfiguration, int, object>((c, m, _) => ToMaximumDepth(c, m)));
found.Add(GetSurrogateConfigurationMethod<LoggerDestructuringConfiguration, int, object>((c, m, _) => ToMaximumStringLength(c, m)));
found.Add(GetSurrogateConfigurationMethod<LoggerDestructuringConfiguration, int, object>((c, m, _) => ToMaximumCollectionCount(c, m)));
}

return found;
}

internal static IList<MethodInfo> FindEventEnricherConfigurationMethods(IReadOnlyCollection<Assembly> configurationAssemblies)
{
var found = FindConfigurationMethods(configurationAssemblies, typeof(LoggerEnrichmentConfiguration));
var found = FindConfigurationExtensionMethods(configurationAssemblies, typeof(LoggerEnrichmentConfiguration));
if (configurationAssemblies.Contains(typeof(LoggerEnrichmentConfiguration).GetTypeInfo().Assembly))
found.Add(GetSurrogateConfigurationMethod<LoggerEnrichmentConfiguration, object, object>((c, _, __) => FromLogContext(c)));

return found;
}

internal static IList<MethodInfo> FindConfigurationMethods(IReadOnlyCollection<Assembly> configurationAssemblies, Type configType)
internal static IList<MethodInfo> FindConfigurationExtensionMethods(IReadOnlyCollection<Assembly> configurationAssemblies, Type configType)
{
return configurationAssemblies
.SelectMany(a => a.ExportedTypes
Expand All @@ -383,15 +408,34 @@ internal static IList<MethodInfo> FindConfigurationMethods(IReadOnlyCollection<A
.ToList();
}

// don't support (yet?) arrays in the parameter list (ILogEventEnricher[])
/*
Pass-through calls to various Serilog config methods which are
implemented as instance methods rather than extension methods. The
FindXXXConfigurationMethods calls (above) use these to add method
invocation expressions as surrogates so that SelectConfigurationMethod
has a way to match and invoke these instance methods.
*/

// TODO: add overload for array argument (ILogEventEnricher[])
internal static LoggerConfiguration With(LoggerFilterConfiguration loggerFilterConfiguration, ILogEventFilter filter)
=> loggerFilterConfiguration.With(filter);

// Unlike the other configuration methods, FromLogContext is an instance method rather than an extension.
// TODO: add overload for array argument (IDestructuringPolicy[])
internal static LoggerConfiguration With(LoggerDestructuringConfiguration loggerDestructuringConfiguration, IDestructuringPolicy policy)
=> loggerDestructuringConfiguration.With(policy);

internal static LoggerConfiguration ToMaximumDepth(LoggerDestructuringConfiguration loggerDestructuringConfiguration, int maximumDestructuringDepth)
=> loggerDestructuringConfiguration.ToMaximumDepth(maximumDestructuringDepth);

internal static LoggerConfiguration ToMaximumStringLength(LoggerDestructuringConfiguration loggerDestructuringConfiguration, int maximumStringLength)
=> loggerDestructuringConfiguration.ToMaximumStringLength(maximumStringLength);

internal static LoggerConfiguration ToMaximumCollectionCount(LoggerDestructuringConfiguration loggerDestructuringConfiguration, int maximumCollectionCount)
=> loggerDestructuringConfiguration.ToMaximumCollectionCount(maximumCollectionCount);

internal static LoggerConfiguration FromLogContext(LoggerEnrichmentConfiguration loggerEnrichmentConfiguration)
=> loggerEnrichmentConfiguration.FromLogContext();

// Unlike the other configuration methods, Logger is an instance method rather than an extension.
internal static LoggerConfiguration Logger(
LoggerSinkConfiguration loggerSinkConfiguration,
Action<LoggerConfiguration> configureLogger,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -492,7 +492,6 @@ public void SinkWithIntArrayArgument()
Assert.Equal(1, DummyRollingFileSink.Emitted.Count);
}


[Trait("Bugfix", "#91")]
[Fact]
public void WriteToLoggerWithRestrictedToMinimumLevelIsSupported()
Expand Down Expand Up @@ -597,5 +596,88 @@ public void InconsistentComplexVsScalarArgumentValuesThrowsIOE()
Assert.Contains("The value for the argument", ex.Message);
Assert.Contains("'Serilog:WriteTo:0:Args:pathFormat'", ex.Message);
}

[Fact]
public void DestructureLimitsNestingDepth()
{
var json = @"{
""Serilog"": {
""Destructure"": [
{
""Name"": ""ToMaximumDepth"",
""Args"": { ""maximumDestructuringDepth"": 3 }
}]
}
}";

var NestedObject = new
{
A = new
{
B = new
{
C = new
{
D = "F"
}
}
}
};

var msg = GetDestructuredProperty(NestedObject, json);

Assert.Contains("C", msg);
Assert.DoesNotContain("D", msg);
}

[Fact]
public void DestructureLimitsStringLength()
{
var json = @"{
""Serilog"": {
""Destructure"": [
{
""Name"": ""ToMaximumStringLength"",
""Args"": { ""maximumStringLength"": 3 }
}]
}
}";

var inputString = "ABCDEFGH";
var msg = GetDestructuredProperty(inputString, json);

Assert.Equal("\"AB…\"", msg);
}

[Fact]
public void DestructureLimitsCollectionCount()
{
var json = @"{
""Serilog"": {
""Destructure"": [
{
""Name"": ""ToMaximumCollectionCount"",
""Args"": { ""maximumCollectionCount"": 3 }
}]
}
}";

var collection = new[] { 1, 2, 3, 4, 5, 6 };
var msg = GetDestructuredProperty(collection, json);

Assert.Contains("3", msg);
Assert.DoesNotContain("4", msg);
}

private string GetDestructuredProperty(object x, string json)
{
LogEvent evt = null;
var log = ConfigFromJson(json)
.WriteTo.Sink(new DelegatingSink(e => evt = e))
.CreateLogger();
log.Information("{@X}", x);
var result = evt.Properties["X"].ToString();
return result;
}
}
}

0 comments on commit 6a56899

Please sign in to comment.