Skip to content

Destructure support #110

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
May 14, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
}
}
}