diff --git a/src/Serilog.Settings.Combined/Class1.cs b/src/Serilog.Settings.Combined/Class1.cs deleted file mode 100644 index a1ff24b..0000000 --- a/src/Serilog.Settings.Combined/Class1.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Serilog.Settings.Combined -{ - public class Class1 - { - } -} diff --git a/src/Serilog.Settings.Combined/CombinedSettingsLoggerConfigurationExtensions.cs b/src/Serilog.Settings.Combined/CombinedSettingsLoggerConfigurationExtensions.cs new file mode 100644 index 0000000..efcc4e2 --- /dev/null +++ b/src/Serilog.Settings.Combined/CombinedSettingsLoggerConfigurationExtensions.cs @@ -0,0 +1,18 @@ +using System; +using Serilog.Configuration; +using Serilog.Settings.Combined; + +namespace Serilog +{ + public static class CombinedSettingsLoggerConfigurationExtensions + { + public static LoggerConfiguration Combined(this LoggerSettingsConfiguration lsc, Func build) + { + var configBuilder = new ConfigBuilder(); + configBuilder = (ConfigBuilder)build(configBuilder); + var enumerable = configBuilder.BuildCombinedEnumerable(); + + return lsc.KeyValuePairs(enumerable); + } + } +} diff --git a/src/Serilog.Settings.Combined/Properties/AssemblyInfo.cs b/src/Serilog.Settings.Combined/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..e47586a --- /dev/null +++ b/src/Serilog.Settings.Combined/Properties/AssemblyInfo.cs @@ -0,0 +1,4 @@ + +using System.Runtime.CompilerServices; +[assembly: InternalsVisibleTo("Serilog.Settings.Combined.Tests")] + diff --git a/src/Serilog.Settings.Combined/Serilog.Settings.Combined.csproj b/src/Serilog.Settings.Combined/Serilog.Settings.Combined.csproj index 9f5c4f4..0a95635 100644 --- a/src/Serilog.Settings.Combined/Serilog.Settings.Combined.csproj +++ b/src/Serilog.Settings.Combined/Serilog.Settings.Combined.csproj @@ -2,6 +2,11 @@ netstandard2.0 + Serilog + + + + diff --git a/src/Serilog.Settings.Combined/Settings/Combined/ConfigBuilder.cs b/src/Serilog.Settings.Combined/Settings/Combined/ConfigBuilder.cs new file mode 100644 index 0000000..959673c --- /dev/null +++ b/src/Serilog.Settings.Combined/Settings/Combined/ConfigBuilder.cs @@ -0,0 +1,41 @@ +using System.Collections.Generic; + +namespace Serilog.Settings.Combined +{ + class ConfigBuilder : IConfigBuilder + { + List>> _sources; + + public ConfigBuilder() + { + _sources = new List>>(); + } + + public IEnumerable> BuildCombinedEnumerable() + { + IEnumerable> Combined() + { + var result = new Dictionary(); + foreach (var source in _sources) + { + foreach (var kvp in source) + { + result[kvp.Key] = kvp.Value; + } + } + return result; + } + + foreach (var kvp in Combined()) + { + yield return kvp; + } + } + + public IConfigBuilder AddSource(IEnumerable> source) + { + _sources.Add(source); + return this; + } + } +} diff --git a/src/Serilog.Settings.Combined/Settings/Combined/ConfigBuilderExtensions.cs b/src/Serilog.Settings.Combined/Settings/Combined/ConfigBuilderExtensions.cs new file mode 100644 index 0000000..8ecd978 --- /dev/null +++ b/src/Serilog.Settings.Combined/Settings/Combined/ConfigBuilderExtensions.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; + +namespace Serilog.Settings.Combined +{ + public static class ConfigBuilderExtensions + { + public static IConfigBuilder AddKeyValuePairs(this IConfigBuilder builder, IReadOnlyDictionary keyValuePairs) + { + if (builder == null) throw new ArgumentNullException(nameof(builder)); + if (keyValuePairs == null) throw new ArgumentNullException(nameof(keyValuePairs)); + + return builder.AddSource(keyValuePairs); + } + + public static IConfigBuilder AddKeyValuePair(this IConfigBuilder builder, KeyValuePair keyValuePair) + { + if (builder == null) throw new ArgumentNullException(nameof(builder)); + + return builder.AddSource(new[] { keyValuePair }); + } + + public static IConfigBuilder AddKeyValuePair(this IConfigBuilder builder, string key, string value) + { + if (key == null) throw new ArgumentNullException(nameof(key)); + + return builder.AddSource(new Dictionary() + { + {key, value} + }); + } + } +} diff --git a/src/Serilog.Settings.Combined/Settings/Combined/IConfigBuilder.cs b/src/Serilog.Settings.Combined/Settings/Combined/IConfigBuilder.cs new file mode 100644 index 0000000..ee3d1d5 --- /dev/null +++ b/src/Serilog.Settings.Combined/Settings/Combined/IConfigBuilder.cs @@ -0,0 +1,9 @@ +using System.Collections.Generic; + +namespace Serilog.Settings.Combined +{ + public interface IConfigBuilder + { + IConfigBuilder AddSource(IEnumerable> source); + } +} diff --git a/test/Serilog.Settings.Combined.Tests/CombinedSettingsTests.cs b/test/Serilog.Settings.Combined.Tests/CombinedSettingsTests.cs new file mode 100644 index 0000000..79d8b98 --- /dev/null +++ b/test/Serilog.Settings.Combined.Tests/CombinedSettingsTests.cs @@ -0,0 +1,59 @@ +using System.Collections.Generic; +using Serilog.Events; +using Serilog.Tests.Support; +using Xunit; + +namespace Serilog.Settings.Combined.Tests +{ + public class CombinedSettingsTests + { + [Fact] + public void CombinedCanMergeMultipleKeyValuePairLists() + { + LogEvent evt = null; + var log = new LoggerConfiguration() + .ReadFrom.Combined(builder => builder + .AddKeyValuePairs(new Dictionary + { + {"enrich:with-property:UntouchedProp", "initialValue"}, + {"enrich:with-property:OverridenProp", "initialValue"}, + }) + .AddKeyValuePairs(new Dictionary + { + {"enrich:with-property:OverridenProp", "overridenValue"}, + {"enrich:with-property:NewProp", "value"}, + })) + .WriteTo.Sink(new DelegatingSink(e => evt = e)) + .CreateLogger(); + + log.Information("a message that should be enriched with properties"); + + Assert.NotNull(evt); + Assert.Equal("initialValue", evt.Properties["UntouchedProp"].LiteralValue()); + Assert.Equal("overridenValue", evt.Properties["OverridenProp"].LiteralValue()); + Assert.Equal("value", evt.Properties["NewProp"].LiteralValue()); + } + + [Fact] + public void CombinedCanMergeMultipleKeyValuePairs() + { + LogEvent evt = null; + var log = new LoggerConfiguration() + .ReadFrom.Combined(builder => builder + .AddKeyValuePair("enrich:with-property:UntouchedProp", "initialValue") + .AddKeyValuePair("enrich:with-property:OverridenProp", "initialValue") + .AddKeyValuePair("enrich:with-property:NewProp", "value") + .AddKeyValuePair(new KeyValuePair("enrich:with-property:OverridenProp", "overridenValue")) + ) + .WriteTo.Sink(new DelegatingSink(e => evt = e)) + .CreateLogger(); + + log.Information("a message that should be enriched with properties"); + + Assert.NotNull(evt); + Assert.Equal("initialValue", evt.Properties["UntouchedProp"].LiteralValue()); + Assert.Equal("overridenValue", evt.Properties["OverridenProp"].LiteralValue()); + Assert.Equal("value", evt.Properties["NewProp"].LiteralValue()); + } + } +} diff --git a/test/Serilog.Settings.Combined.Tests/ConfigBuilderTests.cs b/test/Serilog.Settings.Combined.Tests/ConfigBuilderTests.cs new file mode 100644 index 0000000..27eac65 --- /dev/null +++ b/test/Serilog.Settings.Combined.Tests/ConfigBuilderTests.cs @@ -0,0 +1,41 @@ +using System.Collections.Generic; +using System.Linq; +using Xunit; + +namespace Serilog.Settings.Combined.Tests +{ + public class ConfigBuilderTests + { + + [Fact] + public void ConfigBuilderConsumesEnumerablesAsLateAsPossible() + { + var consumeCount = 0; + + IEnumerable> Enumerable1() + { + consumeCount++; + yield break; + } + + IEnumerable> Enumerable2() + { + consumeCount++; + yield break; + } + + var builder = new ConfigBuilder(); + builder.AddSource(Enumerable1()); + builder.AddSource(Enumerable2()); + Assert.Equal(0, consumeCount); + + var combined = builder.BuildCombinedEnumerable(); + Assert.Equal(0, consumeCount); + + // ReSharper disable once ReturnValueOfPureMethodIsNotUsed + combined.ToList(); + + Assert.Equal(2, consumeCount); + } + } +} diff --git a/test/Serilog.Settings.Combined.Tests/Support/DelegatingSink.cs b/test/Serilog.Settings.Combined.Tests/Support/DelegatingSink.cs new file mode 100644 index 0000000..2131a11 --- /dev/null +++ b/test/Serilog.Settings.Combined.Tests/Support/DelegatingSink.cs @@ -0,0 +1,34 @@ +using System; +using Serilog.Core; +using Serilog.Events; + +namespace Serilog.Tests.Support +{ + public class DelegatingSink : ILogEventSink + { + readonly Action _write; + + public DelegatingSink(Action write) + { + if (write == null) throw new ArgumentNullException(nameof(write)); + _write = write; + } + + public void Emit(LogEvent logEvent) + { + _write(logEvent); + } + + public static LogEvent GetLogEvent(Action writeAction) + { + LogEvent result = null; + var l = new LoggerConfiguration() + .MinimumLevel.Verbose() + .WriteTo.Sink(new DelegatingSink(le => result = le)) + .CreateLogger(); + + writeAction(l); + return result; + } + } +} diff --git a/test/Serilog.Settings.Combined.Tests/Support/Extensions.cs b/test/Serilog.Settings.Combined.Tests/Support/Extensions.cs new file mode 100644 index 0000000..b4fa640 --- /dev/null +++ b/test/Serilog.Settings.Combined.Tests/Support/Extensions.cs @@ -0,0 +1,12 @@ +using Serilog.Events; + +namespace Serilog.Tests.Support +{ + public static class Extensions + { + public static object LiteralValue(this LogEventPropertyValue @this) + { + return ((ScalarValue)@this).Value; + } + } +} diff --git a/test/Serilog.Settings.Combined.Tests/Support/Some.cs b/test/Serilog.Settings.Combined.Tests/Support/Some.cs new file mode 100644 index 0000000..4fd00d6 --- /dev/null +++ b/test/Serilog.Settings.Combined.Tests/Support/Some.cs @@ -0,0 +1,102 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using Serilog.Core; +using Serilog.Events; +using Serilog.Parsing; + +namespace Serilog.Tests.Support +{ + static class Some + { + static int Counter; + + public static int Int() + { + return Interlocked.Increment(ref Counter); + } + + public static decimal Decimal() + { + return Int() + 0.123m; + } + + public static string String(string tag = null) + { + return (tag ?? "") + "__" + Int(); + } + + public static TimeSpan TimeSpan() + { + return System.TimeSpan.FromMinutes(Int()); + } + + public static DateTime Instant() + { + return new DateTime(2012, 10, 28) + TimeSpan(); + } + + public static DateTimeOffset OffsetInstant() + { + return new DateTimeOffset(Instant()); + } + + public static LogEvent LogEvent(string sourceContext, DateTimeOffset? timestamp = null, LogEventLevel level = LogEventLevel.Information) + { + return new LogEvent(timestamp ?? OffsetInstant(), level, + null, MessageTemplate(), + new List { new LogEventProperty(Constants.SourceContextPropertyName, new ScalarValue(sourceContext)) }); + } + + public static LogEvent LogEvent(DateTimeOffset? timestamp = null, LogEventLevel level = LogEventLevel.Information) + { + return new LogEvent(timestamp ?? OffsetInstant(), level, + null, MessageTemplate(), Enumerable.Empty()); + } + + public static LogEvent InformationEvent(DateTimeOffset? timestamp = null) + { + // ReSharper disable once RedundantArgumentDefaultValue + return LogEvent(timestamp, LogEventLevel.Information); + } + + public static LogEvent DebugEvent(DateTimeOffset? timestamp = null) + { + return LogEvent(timestamp, LogEventLevel.Debug); + } + + public static LogEvent WarningEvent(DateTimeOffset? timestamp = null) + { + return LogEvent(timestamp, LogEventLevel.Warning); + } + + public static LogEventProperty LogEventProperty() + { + return new LogEventProperty(String(), new ScalarValue(Int())); + } + + public static string NonexistentTempFilePath() + { + return Path.Combine(Path.GetTempPath(), Guid.NewGuid() + ".txt"); + } + + public static string TempFilePath() + { + return Path.GetTempFileName(); + } + + public static string TempFolderPath() + { + var dir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + Directory.CreateDirectory(dir); + return dir; + } + + public static MessageTemplate MessageTemplate() + { + return new MessageTemplateParser().Parse(String()); + } + } +} diff --git a/test/Serilog.Settings.Combined.Tests/UnitTest1.cs b/test/Serilog.Settings.Combined.Tests/UnitTest1.cs deleted file mode 100644 index 58852f6..0000000 --- a/test/Serilog.Settings.Combined.Tests/UnitTest1.cs +++ /dev/null @@ -1,13 +0,0 @@ -using Xunit; - -namespace Serilog.Settings.Combined.Tests -{ - public class UnitTest1 - { - [Fact] - public void Test1() - { - - } - } -}