Skip to content

Commit 74fb413

Browse files
authored
Merge pull request #120 from tsimbalar/destructure-asscalar
Add support for .Destructure.AsScalar(Type scalarType)
2 parents 930f014 + d9d6cec commit 74fb413

File tree

8 files changed

+242
-61
lines changed

8 files changed

+242
-61
lines changed

sample/Sample/Program.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ public static void Main(string[] args)
4040
new { TwentyChars = "0123456789abcdefghij" });
4141

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

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

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

Lines changed: 14 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
using Serilog.Core;
1212
using Serilog.Debugging;
1313
using Serilog.Events;
14-
using System.Linq.Expressions;
1514
using System.Text.RegularExpressions;
1615

1716
namespace Serilog.Settings.Configuration
@@ -155,10 +154,10 @@ void ApplyFilters(LoggerConfiguration loggerConfiguration, IReadOnlyDictionary<s
155154

156155
void ApplyDestructuring(LoggerConfiguration loggerConfiguration, IReadOnlyDictionary<string, LoggingLevelSwitch> declaredLevelSwitches)
157156
{
158-
var filterDirective = _section.GetSection("Destructure");
159-
if(filterDirective.GetChildren().Any())
157+
var destructureDirective = _section.GetSection("Destructure");
158+
if (destructureDirective.GetChildren().Any())
160159
{
161-
var methodCalls = GetMethodCalls(filterDirective);
160+
var methodCalls = GetMethodCalls(destructureDirective);
162161
CallConfigurationMethods(methodCalls, FindDestructureConfigurationMethods(_configurationAssemblies), loggerConfiguration.Destructure, declaredLevelSwitches);
163162
}
164163
}
@@ -221,9 +220,11 @@ internal ILookup<string, Dictionary<string, IConfigurationArgumentValue>> GetMet
221220
where child.Value == null
222221
let name = GetSectionName(child)
223222
let callArgs = (from argument in child.GetSection("Args").GetChildren()
224-
select new {
223+
select new
224+
{
225225
Name = argument.Key,
226-
Value = GetArgumentValue(argument) }).ToDictionary(p => p.Name, p => p.Value)
226+
Value = GetArgumentValue(argument)
227+
}).ToDictionary(p => p.Name, p => p.Value)
227228
select new { Name = name, Args = callArgs }))
228229
.ToLookup(p => p.Name, p => p.Args);
229230

@@ -330,7 +331,7 @@ static void CallConfigurationMethods(ILookup<string, Dictionary<string, IConfigu
330331
select directive.Key == null ? p.DefaultValue : directive.Value.ConvertTo(p.ParameterType, declaredLevelSwitches)).ToList();
331332

332333
var parm = methodInfo.GetParameters().FirstOrDefault(i => i.ParameterType == typeof(IConfiguration));
333-
if(parm != null) call[parm.Position - 1] = _configuration;
334+
if (parm != null) call[parm.Position - 1] = _configuration;
334335

335336
call.Insert(0, receiver);
336337

@@ -352,7 +353,7 @@ internal static IList<MethodInfo> FindSinkConfigurationMethods(IReadOnlyCollecti
352353
{
353354
var found = FindConfigurationExtensionMethods(configurationAssemblies, typeof(LoggerSinkConfiguration));
354355
if (configurationAssemblies.Contains(typeof(LoggerSinkConfiguration).GetTypeInfo().Assembly))
355-
found.Add(GetSurrogateConfigurationMethod<LoggerSinkConfiguration, Action<LoggerConfiguration>, LoggingLevelSwitch>((c, a, s) => Logger(c, a, LevelAlias.Minimum, s)));
356+
found.AddRange(SurrogateConfigurationMethods.WriteTo);
356357

357358
return found;
358359
}
@@ -368,21 +369,16 @@ internal static IList<MethodInfo> FindFilterConfigurationMethods(IReadOnlyCollec
368369
{
369370
var found = FindConfigurationExtensionMethods(configurationAssemblies, typeof(LoggerFilterConfiguration));
370371
if (configurationAssemblies.Contains(typeof(LoggerFilterConfiguration).GetTypeInfo().Assembly))
371-
found.Add(GetSurrogateConfigurationMethod<LoggerFilterConfiguration, ILogEventFilter, object>((c, f, _) => With(c, f)));
372+
found.AddRange(SurrogateConfigurationMethods.Filter);
372373

373374
return found;
374375
}
375376

376377
internal static IList<MethodInfo> FindDestructureConfigurationMethods(IReadOnlyCollection<Assembly> configurationAssemblies)
377378
{
378379
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-
}
380+
if (configurationAssemblies.Contains(typeof(LoggerDestructuringConfiguration).GetTypeInfo().Assembly))
381+
found.AddRange(SurrogateConfigurationMethods.Destructure);
386382

387383
return found;
388384
}
@@ -391,12 +387,12 @@ internal static IList<MethodInfo> FindEventEnricherConfigurationMethods(IReadOnl
391387
{
392388
var found = FindConfigurationExtensionMethods(configurationAssemblies, typeof(LoggerEnrichmentConfiguration));
393389
if (configurationAssemblies.Contains(typeof(LoggerEnrichmentConfiguration).GetTypeInfo().Assembly))
394-
found.Add(GetSurrogateConfigurationMethod<LoggerEnrichmentConfiguration, object, object>((c, _, __) => FromLogContext(c)));
390+
found.AddRange(SurrogateConfigurationMethods.Enrich);
395391

396392
return found;
397393
}
398394

399-
internal static IList<MethodInfo> FindConfigurationExtensionMethods(IReadOnlyCollection<Assembly> configurationAssemblies, Type configType)
395+
internal static List<MethodInfo> FindConfigurationExtensionMethods(IReadOnlyCollection<Assembly> configurationAssemblies, Type configType)
400396
{
401397
return configurationAssemblies
402398
.SelectMany(a => a.ExportedTypes
@@ -408,45 +404,6 @@ internal static IList<MethodInfo> FindConfigurationExtensionMethods(IReadOnlyCol
408404
.ToList();
409405
}
410406

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[])
420-
internal static LoggerConfiguration With(LoggerFilterConfiguration loggerFilterConfiguration, ILogEventFilter filter)
421-
=> loggerFilterConfiguration.With(filter);
422-
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-
436-
internal static LoggerConfiguration FromLogContext(LoggerEnrichmentConfiguration loggerEnrichmentConfiguration)
437-
=> loggerEnrichmentConfiguration.FromLogContext();
438-
439-
// Unlike the other configuration methods, Logger is an instance method rather than an extension.
440-
internal static LoggerConfiguration Logger(
441-
LoggerSinkConfiguration loggerSinkConfiguration,
442-
Action<LoggerConfiguration> configureLogger,
443-
LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum,
444-
LoggingLevelSwitch levelSwitch = null)
445-
=> loggerSinkConfiguration.Logger(configureLogger, restrictedToMinimumLevel, levelSwitch);
446-
447-
internal static MethodInfo GetSurrogateConfigurationMethod<TConfiguration, TArg1, TArg2>(Expression<Action<TConfiguration, TArg1, TArg2>> method)
448-
=> (method.Body as MethodCallExpression)?.Method;
449-
450407
internal static bool IsValidSwitchName(string input)
451408
{
452409
return Regex.IsMatch(input, LevelSwitchNameRegex);

src/Serilog.Settings.Configuration/Settings/Configuration/StringArgumentValue.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ public StringArgumentValue(Func<string> valueProducer, Func<IChangeToken> change
2727
static readonly Dictionary<Type, Func<string, object>> ExtendedTypeConversions = new Dictionary<Type, Func<string, object>>
2828
{
2929
{ typeof(Uri), s => new Uri(s) },
30-
{ typeof(TimeSpan), s => TimeSpan.Parse(s) }
30+
{ typeof(TimeSpan), s => TimeSpan.Parse(s) },
31+
{ typeof(Type), s => Type.GetType(s, throwOnError:true) },
3132
};
3233

3334
public object ConvertTo(Type toType, IReadOnlyDictionary<string, LoggingLevelSwitch> declaredLevelSwitches)
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq.Expressions;
4+
using System.Reflection;
5+
using Serilog.Configuration;
6+
using Serilog.Core;
7+
using Serilog.Events;
8+
9+
namespace Serilog.Settings.Configuration
10+
{
11+
/// <summary>
12+
/// Contains "fake extension" methods for the Serilog configuration API.
13+
/// By default the settings knows how to find extension methods, but some configuration
14+
/// are actually "regular" method calls and would not be found otherwise.
15+
///
16+
/// This static class contains internal methods that can be used instead.
17+
///
18+
/// </summary>
19+
static class SurrogateConfigurationMethods
20+
{
21+
public static IEnumerable<MethodInfo> WriteTo
22+
{
23+
get
24+
{
25+
yield return GetSurrogateConfigurationMethod<LoggerSinkConfiguration, Action<LoggerConfiguration>, LoggingLevelSwitch>((c, a, s) => Logger(c, a, LevelAlias.Minimum, s));
26+
}
27+
}
28+
29+
public static IEnumerable<MethodInfo> Filter
30+
{
31+
get
32+
{
33+
yield return GetSurrogateConfigurationMethod<LoggerFilterConfiguration, ILogEventFilter, object>((c, f, _) => With(c, f));
34+
}
35+
}
36+
37+
public static IEnumerable<MethodInfo> Destructure
38+
{
39+
get
40+
{
41+
yield return GetSurrogateConfigurationMethod<LoggerDestructuringConfiguration, IDestructuringPolicy, object>((c, d, _) => With(c, d));
42+
yield return GetSurrogateConfigurationMethod<LoggerDestructuringConfiguration, int, object>((c, m, _) => ToMaximumDepth(c, m));
43+
yield return GetSurrogateConfigurationMethod<LoggerDestructuringConfiguration, int, object>((c, m, _) => ToMaximumStringLength(c, m));
44+
yield return GetSurrogateConfigurationMethod<LoggerDestructuringConfiguration, int, object>((c, m, _) => ToMaximumCollectionCount(c, m));
45+
yield return GetSurrogateConfigurationMethod<LoggerDestructuringConfiguration, Type, object>((c, t, _) => AsScalar(c, t));
46+
}
47+
}
48+
49+
public static IEnumerable<MethodInfo> Enrich
50+
{
51+
get
52+
{
53+
yield return GetSurrogateConfigurationMethod<LoggerEnrichmentConfiguration, object, object>((c, _, __) => FromLogContext(c));
54+
}
55+
}
56+
57+
static MethodInfo GetSurrogateConfigurationMethod<TConfiguration, TArg1, TArg2>(Expression<Action<TConfiguration, TArg1, TArg2>> method)
58+
=> (method.Body as MethodCallExpression)?.Method;
59+
60+
/*
61+
Pass-through calls to various Serilog config methods which are
62+
implemented as instance methods rather than extension methods. The
63+
FindXXXConfigurationMethods calls (above) use these to add method
64+
invocation expressions as surrogates so that SelectConfigurationMethod
65+
has a way to match and invoke these instance methods.
66+
*/
67+
68+
// TODO: add overload for array argument (ILogEventEnricher[])
69+
static LoggerConfiguration With(LoggerFilterConfiguration loggerFilterConfiguration, ILogEventFilter filter)
70+
=> loggerFilterConfiguration.With(filter);
71+
72+
// TODO: add overload for array argument (IDestructuringPolicy[])
73+
static LoggerConfiguration With(LoggerDestructuringConfiguration loggerDestructuringConfiguration, IDestructuringPolicy policy)
74+
=> loggerDestructuringConfiguration.With(policy);
75+
76+
static LoggerConfiguration ToMaximumDepth(LoggerDestructuringConfiguration loggerDestructuringConfiguration, int maximumDestructuringDepth)
77+
=> loggerDestructuringConfiguration.ToMaximumDepth(maximumDestructuringDepth);
78+
79+
static LoggerConfiguration ToMaximumStringLength(LoggerDestructuringConfiguration loggerDestructuringConfiguration, int maximumStringLength)
80+
=> loggerDestructuringConfiguration.ToMaximumStringLength(maximumStringLength);
81+
82+
static LoggerConfiguration ToMaximumCollectionCount(LoggerDestructuringConfiguration loggerDestructuringConfiguration, int maximumCollectionCount)
83+
=> loggerDestructuringConfiguration.ToMaximumCollectionCount(maximumCollectionCount);
84+
85+
static LoggerConfiguration AsScalar(LoggerDestructuringConfiguration loggerDestructuringConfiguration, Type scalarType)
86+
=> loggerDestructuringConfiguration.AsScalar(scalarType);
87+
88+
static LoggerConfiguration FromLogContext(LoggerEnrichmentConfiguration loggerEnrichmentConfiguration)
89+
=> loggerEnrichmentConfiguration.FromLogContext();
90+
91+
// Unlike the other configuration methods, Logger is an instance method rather than an extension.
92+
static LoggerConfiguration Logger(
93+
LoggerSinkConfiguration loggerSinkConfiguration,
94+
Action<LoggerConfiguration> configureLogger,
95+
LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum,
96+
LoggingLevelSwitch levelSwitch = null)
97+
=> loggerSinkConfiguration.Logger(configureLogger, restrictedToMinimumLevel, levelSwitch);
98+
}
99+
}

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

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -679,5 +679,77 @@ private string GetDestructuredProperty(object x, string json)
679679
var result = evt.Properties["X"].ToString();
680680
return result;
681681
}
682+
683+
[Fact]
684+
public void DestructuringWithCustomExtensionMethodIsApplied()
685+
{
686+
var json = @"{
687+
""Serilog"": {
688+
""Using"": [""TestDummies""],
689+
""Destructure"": [
690+
{
691+
""Name"": ""WithDummyHardCodedString"",
692+
""Args"": { ""hardCodedString"": ""hardcoded"" }
693+
}]
694+
}
695+
}";
696+
697+
LogEvent evt = null;
698+
var log = ConfigFromJson(json)
699+
.WriteTo.Sink(new DelegatingSink(e => evt = e))
700+
.CreateLogger();
701+
log.Information("Destructuring with hard-coded policy {@Input}", new { Foo = "Bar" });
702+
var formattedProperty = evt.Properties["Input"].ToString();
703+
704+
Assert.Equal("\"hardcoded\"", formattedProperty);
705+
}
706+
707+
[Fact]
708+
public void DestructuringAsScalarIsAppliedWithShortTypeName()
709+
{
710+
var json = @"{
711+
""Serilog"": {
712+
""Destructure"": [
713+
{
714+
""Name"": ""AsScalar"",
715+
""Args"": { ""scalarType"": ""System.Version"" }
716+
}]
717+
}
718+
}";
719+
720+
LogEvent evt = null;
721+
var log = ConfigFromJson(json)
722+
.WriteTo.Sink(new DelegatingSink(e => evt = e))
723+
.CreateLogger();
724+
725+
log.Information("Destructuring as scalar {@Scalarized}", new Version(2,3));
726+
var prop = evt.Properties["Scalarized"];
727+
728+
Assert.IsType<ScalarValue>(prop);
729+
}
730+
731+
[Fact]
732+
public void DestructuringAsScalarIsAppliedWithAssemblyQualifiedName()
733+
{
734+
var json = $@"{{
735+
""Serilog"": {{
736+
""Destructure"": [
737+
{{
738+
""Name"": ""AsScalar"",
739+
""Args"": {{ ""scalarType"": ""{typeof(Version).AssemblyQualifiedName}"" }}
740+
}}]
741+
}}
742+
}}";
743+
744+
LogEvent evt = null;
745+
var log = ConfigFromJson(json)
746+
.WriteTo.Sink(new DelegatingSink(e => evt = e))
747+
.CreateLogger();
748+
749+
log.Information("Destructuring as scalar {@Scalarized}", new Version(2,3));
750+
var prop = evt.Properties["Scalarized"];
751+
752+
Assert.IsType<ScalarValue>(prop);
753+
}
682754
}
683755
}

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

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,5 +163,27 @@ public void ReferencingUndeclaredLevelSwitchThrows()
163163
Assert.Contains("$mySwitch", ex.Message);
164164
Assert.Contains("\"LevelSwitches\":{\"$mySwitch\":", ex.Message);
165165
}
166+
167+
[Fact]
168+
public void StringValuesConvertToTypeFromShortTypeName()
169+
{
170+
var shortTypeName = "System.Version";
171+
var stringArgumentValue = new StringArgumentValue(() => shortTypeName);
172+
173+
var actual = (Type)stringArgumentValue.ConvertTo(typeof(Type), new Dictionary<string, LoggingLevelSwitch>());
174+
175+
Assert.Equal(typeof(Version), actual);
176+
}
177+
178+
[Fact]
179+
public void StringValuesConvertToTypeFromAssemblyQualifiedName()
180+
{
181+
var assemblyQualifiedName = typeof(Version).AssemblyQualifiedName;
182+
var stringArgumentValue = new StringArgumentValue(() => assemblyQualifiedName);
183+
184+
var actual = (Type)stringArgumentValue.ConvertTo(typeof(Type), new Dictionary<string, LoggingLevelSwitch>());
185+
186+
Assert.Equal(typeof(Version), actual);
187+
}
166188
}
167-
}
189+
}
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+
}

0 commit comments

Comments
 (0)