Skip to content

Commit

Permalink
Merge pull request serilog#636 from johncrn/master
Browse files Browse the repository at this point in the history
Enable event enrichers from config
  • Loading branch information
nblumhardt committed Jan 20, 2016
2 parents 2a632ba + 98cf960 commit d208a3f
Show file tree
Hide file tree
Showing 5 changed files with 206 additions and 28 deletions.
94 changes: 67 additions & 27 deletions src/Serilog/Settings/KeyValuePairs/KeyValuePairSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,11 @@ class KeyValuePairSettings : ILoggerSettings
const string UsingDirective = "using";
const string WriteToDirective = "write-to";
const string MinimumLevelDirective = "minimum-level";
const string EnrichWithDirective = "enrich";
const string EnrichWithPropertyDirective = "enrich:with-property";

const string UsingDirectiveFullFormPrefix = "using:";
const string EnrichWithEventEnricherPrefix = "enrich:";
const string EnrichWithPropertyDirectivePrefix = "enrich:with-property:";

const string WriteToDirectiveRegex = @"^write-to:(?<method>[A-Za-z0-9]*)(\.(?<argument>[A-Za-z0-9]*)){0,1}$";
Expand All @@ -44,7 +46,8 @@ class KeyValuePairSettings : ILoggerSettings
UsingDirective,
WriteToDirective,
MinimumLevelDirective,
EnrichWithPropertyDirective
EnrichWithPropertyDirective,
EnrichWithDirective
};

readonly Dictionary<string, string> _settings;
Expand All @@ -71,57 +74,77 @@ public void Configure(LoggerConfiguration loggerConfiguration)
loggerConfiguration.MinimumLevel.Is(minimumLevel);
}

foreach (var enrichDirective in directives.Where(dir =>
foreach (var enrichProperyDirective in directives.Where(dir =>
dir.Key.StartsWith(EnrichWithPropertyDirectivePrefix) && dir.Key.Length > EnrichWithPropertyDirectivePrefix.Length))
{
var name = enrichDirective.Key.Substring(EnrichWithPropertyDirectivePrefix.Length);
loggerConfiguration.Enrich.WithProperty(name, enrichDirective.Value);
var name = enrichProperyDirective.Key.Substring(EnrichWithPropertyDirectivePrefix.Length);
loggerConfiguration.Enrich.WithProperty(name, enrichProperyDirective.Value);
}

var splitWriteTo = new Regex(WriteToDirectiveRegex);

var sinkDirectives = (from wt in directives
where splitWriteTo.IsMatch(wt.Key)
let match = splitWriteTo.Match(wt.Key)
let call = new
let call = new MethodArgumentValue
{
Method = match.Groups["method"].Value,
Argument = match.Groups["argument"].Value,
wt.Value
Value = wt.Value
}
group call by call.Method).ToList();

if (sinkDirectives.Any())
var eventEnricherDirectives = (from er in directives
where er.Key.StartsWith(EnrichWithEventEnricherPrefix) && !er.Key.StartsWith(EnrichWithPropertyDirectivePrefix) && er.Key.Length > EnrichWithEventEnricherPrefix.Length
let match = er.Key.Substring(EnrichWithEventEnricherPrefix.Length)
let call = new MethodArgumentValue
{
Method = match
}
group call by call.Method).ToList();

if (sinkDirectives.Any() || eventEnricherDirectives.Any())
{
var configurationAssemblies = LoadConfigurationAssemblies(directives);
var sinkConfigurationMethods = FindSinkConfigurationMethods(configurationAssemblies);

foreach (var sinkDirective in sinkDirectives)
if (sinkDirectives.Any())
{
var target = sinkConfigurationMethods
.Where(m => m.Name == sinkDirective.Key &&
m.GetParameters().Skip(1).All(p =>
ApplyDirectives(sinkDirectives, FindSinkConfigurationMethods(configurationAssemblies), loggerConfiguration.WriteTo);
}

if (eventEnricherDirectives.Any())
{
ApplyDirectives(eventEnricherDirectives, FindEventEnricherConfigurationMethods(configurationAssemblies), loggerConfiguration.Enrich);
}
}
}

private static void ApplyDirectives(List<IGrouping<string, MethodArgumentValue>> directives, IList<MethodInfo> configurationMethods, object loggerConfigMethod)
{
foreach (var directiveInfo in directives)
{
var target = configurationMethods
.Where(m => m.Name == directiveInfo.Key &&
m.GetParameters().Skip(1).All(p =>
#if NET40
(p.Attributes & ParameterAttributes.HasDefault) != ParameterAttributes.None
(p.Attributes & ParameterAttributes.HasDefault) != ParameterAttributes.None
#else
p.HasDefaultValue
p.HasDefaultValue
#endif
|| sinkDirective.Any(s => s.Argument == p.Name)))
.OrderByDescending(m => m.GetParameters().Length)
.FirstOrDefault();
|| directiveInfo.Any(s => s.Argument == p.Name)))
.OrderByDescending(m => m.GetParameters().Length)
.FirstOrDefault();

if (target != null)
{
var config = loggerConfiguration.WriteTo;
if (target != null)
{

var call = (from p in target.GetParameters().Skip(1)
let directive = sinkDirective.FirstOrDefault(s => s.Argument == p.Name)
select directive == null ? p.DefaultValue : ConvertToType(directive.Value, p.ParameterType)).ToList();
var call = (from p in target.GetParameters().Skip(1)
let directive = directiveInfo.FirstOrDefault(s => s.Argument == p.Name)
select directive == null ? p.DefaultValue : ConvertToType(directive.Value, p.ParameterType)).ToList();

call.Insert(0, config);
call.Insert(0, loggerConfigMethod);

target.Invoke(null, call.ToArray());
}
target.Invoke(null, call.ToArray());
}
}
}
Expand Down Expand Up @@ -170,6 +193,16 @@ internal static object ConvertToType(string value, Type toType)
}

internal static IList<MethodInfo> FindSinkConfigurationMethods(IEnumerable<Assembly> configurationAssemblies)
{
return FindConfigurationMethods(configurationAssemblies, typeof(LoggerSinkConfiguration));
}

internal static IList<MethodInfo> FindEventEnricherConfigurationMethods(IEnumerable<Assembly> configurationAssemblies)
{
return FindConfigurationMethods(configurationAssemblies, typeof(LoggerEnrichmentConfiguration));
}

internal static IList<MethodInfo> FindConfigurationMethods(IEnumerable<Assembly> configurationAssemblies, Type configType)
{
return configurationAssemblies
.SelectMany(a => a.
Expand All @@ -181,8 +214,15 @@ internal static IList<MethodInfo> FindSinkConfigurationMethods(IEnumerable<Assem
.Select(t => t.GetTypeInfo()).Where(t => t.IsSealed && t.IsAbstract && !t.IsNested))
.SelectMany(t => t.DeclaredMethods)
.Where(m => m.IsStatic && m.IsPublic && m.IsDefined(typeof(ExtensionAttribute), false))
.Where(m => m.GetParameters()[0].ParameterType == typeof(LoggerSinkConfiguration))
.Where(m => m.GetParameters()[0].ParameterType == configType)
.ToList();
}

private class MethodArgumentValue
{
public string Method { get; set; }
public string Argument { get; set; }
public string Value { get; set; }
}
}
}
105 changes: 105 additions & 0 deletions test/Serilog.Tests/Settings/AppSettingsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using Xunit;
using Serilog.Events;
using Serilog.Tests.Support;
using Serilog.Context;

namespace Serilog.Tests.AppSettings.Tests
{
Expand Down Expand Up @@ -68,6 +69,110 @@ public void CustomPrefixCannotBeSerilog()
Assert.Throws<ArgumentException>(() =>
new LoggerConfiguration().ReadFrom.AppSettings("serilog"));
}

[Fact]
public void ThreadIdEnricherIsApplied()
{
// Make sure we have the expected key in the App.config
Assert.NotNull(ConfigurationManager.AppSettings["serilog:enrich:WithThreadId"]);

LogEvent evt = null;
var log = new LoggerConfiguration()
.ReadFrom.AppSettings()
.WriteTo.Sink(new DelegatingSink(e => evt = e))
.CreateLogger();

log.Information("Has a ThreadId property with value generated by ThreadIdEnricher");

Assert.NotNull(evt);
Assert.NotNull(evt.Properties["ThreadId"]);
Assert.NotNull(evt.Properties["ThreadId"].LiteralValue() as int?);
}
#if !DOTNET5_1
[Fact]
public void MachineNameEnricherIsApplied()
{
// Make sure we have the expected key in the App.config
Assert.NotNull(ConfigurationManager.AppSettings["serilog:enrich:WithMachineName"]);

LogEvent evt = null;
var log = new LoggerConfiguration()
.ReadFrom.AppSettings()
.WriteTo.Sink(new DelegatingSink(e => evt = e))
.CreateLogger();

log.Information("Has a MachineName property with value generated by MachineNameEnricher");

Assert.NotNull(evt);
Assert.NotNull(evt.Properties["MachineName"]);
Assert.NotEmpty((string)evt.Properties["MachineName"].LiteralValue());
}

[Fact]
public void EnrivonmentUserNameEnricherIsApplied()
{
// Make sure we have the expected key in the App.config
Assert.NotNull(ConfigurationManager.AppSettings["serilog:enrich:WithEnvironmentUserName"]);

LogEvent evt = null;
var log = new LoggerConfiguration()
.ReadFrom.AppSettings()
.WriteTo.Sink(new DelegatingSink(e => evt = e))
.CreateLogger();

log.Information("Has a EnrivonmentUserName property with value generated by EnrivonmentUserNameEnricher");

Assert.NotNull(evt);
Assert.NotNull(evt.Properties["EnvironmentUserName"]);
Assert.NotEmpty((string)evt.Properties["EnvironmentUserName"].LiteralValue());
}
#endif

#if PROCESS
[Fact]
public void ProcessIdEnricherIsApplied()
{
// Make sure we have the expected key in the App.config
Assert.NotNull(ConfigurationManager.AppSettings["serilog:enrich:WithProcessId"]);

LogEvent evt = null;
var log = new LoggerConfiguration()
.ReadFrom.AppSettings()
.WriteTo.Sink(new DelegatingSink(e => evt = e))
.CreateLogger();

log.Information("Has a ProcessId property with value generated by ProcessIdEnricher");

Assert.NotNull(evt);
Assert.NotNull(evt.Properties["ProcessId"]);
Assert.NotNull(evt.Properties["ProcessId"].LiteralValue() as int?);
}
#endif

#if LOGCONTEXT
[Fact]
public void LogContextEnricherIsApplied()
{
// Make sure we have the expected key in the App.config
Assert.NotNull(ConfigurationManager.AppSettings["serilog:enrich:FromLogContext"]);

LogEvent evt = null;
var log = new LoggerConfiguration()
.ReadFrom.AppSettings()
.WriteTo.Sink(new DelegatingSink(e => evt = e))
.CreateLogger();

using (LogContext.PushProperty("A", 1))
{
log.Information("Has a LogContext property with value generated by LogContextEnricher");
}

Assert.NotNull(evt);
Assert.NotNull(evt.Properties["A"]);
Assert.NotNull(evt.Properties["A"].LiteralValue() as int?);
Assert.Equal(1, (int)evt.Properties["A"].LiteralValue());
}
#endif
}
}
#endif
28 changes: 28 additions & 0 deletions test/Serilog.Tests/Settings/KeyValuePairSettingsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,34 @@ public void FindsConfigurationMethodsWithinAnAssembly()
Assert.True(configurationMethods.Contains("Trace"));
}

[Fact]
public void FindsEventEnrichersWithinAnAssembly()
{
var eventEnrichers = KeyValuePairSettings
.FindEventEnricherConfigurationMethods(new[] { typeof(RollingFileSink)
#if DNXCORE50
.GetTypeInfo()
#endif
.Assembly
})
.Select(m => m.Name)
.Distinct()
.ToList();


#if LOGCONTEXT
Assert.True(eventEnrichers.Contains("FromLogContext"));
#endif
#if !DOTNET5_1
Assert.True(eventEnrichers.Contains("WithEnvironmentUserName"));
Assert.True(eventEnrichers.Contains("WithMachineName"));
#endif
#if PROCESS
Assert.True(eventEnrichers.Contains("WithProcessId"));
#endif
Assert.True(eventEnrichers.Contains("WithThreadId"));
}

[Fact]
public void PropertyEnrichmentIsApplied()
{
Expand Down
5 changes: 5 additions & 0 deletions test/Serilog.Tests/app.config
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,10 @@
<add key="serilog:enrich:with-property:Path" value="%PATH%" />
<add key="custom1:serilog:minimum-level" value="Warning" />
<add key="custom2:serilog:minimum-level" value="Error" />
<add key="serilog:enrich:WithThreadId"/>
<add key="serilog:enrich:WithMachineName"/>
<add key="serilog:enrich:WithProcessId"/>
<add key="serilog:enrich:WithEnvironmentUserName"/>
<add key="serilog:enrich:FromLogContext"/>
</appSettings>
</configuration>
2 changes: 1 addition & 1 deletion test/Serilog.Tests/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
"dnx451": {
"compilationOptions": {
"keyFile": "../../assets/Serilog.snk",
"define": [ "APPSETTINGS", "LOGCONTEXT", "FILE_IO", "PERIODIC_BATCHING", "INTERNAL_TESTS" ]
"define": [ "APPSETTINGS", "LOGCONTEXT", "PROCESS", "FILE_IO", "PERIODIC_BATCHING", "INTERNAL_TESTS" ]
},
"frameworkAssemblies": {
"System.Configuration": ""
Expand Down

0 comments on commit d208a3f

Please sign in to comment.