diff --git a/src/Serilog/Configuration/LoggerSettingsConfiguration.cs b/src/Serilog/Configuration/LoggerSettingsConfiguration.cs index afd1499d3..6acbf70ac 100644 --- a/src/Serilog/Configuration/LoggerSettingsConfiguration.cs +++ b/src/Serilog/Configuration/LoggerSettingsConfiguration.cs @@ -14,6 +14,7 @@ using System; using System.Collections.Generic; +using System.Linq; using Serilog.Settings.KeyValuePairs; namespace Serilog.Configuration @@ -49,9 +50,20 @@ public LoggerConfiguration Settings(ILoggerSettings settings) /// /// A list of key-value pairs describing logger settings. /// Configuration object allowing method chaining. + /// In case of duplicate keys, the last value for the key is kept and the previous ones are ignored. public LoggerConfiguration KeyValuePairs(IEnumerable> settings) { if (settings == null) throw new ArgumentNullException(nameof(settings)); + var uniqueSettings = new Dictionary(); + foreach (var kvp in settings) + { + uniqueSettings[kvp.Key] = kvp.Value; + } + return KeyValuePairs(uniqueSettings); + } + + LoggerConfiguration KeyValuePairs(IReadOnlyDictionary settings) + { return Settings(new KeyValuePairSettings(settings)); } } diff --git a/src/Serilog/Settings/KeyValuePairs/KeyValuePairSettings.cs b/src/Serilog/Settings/KeyValuePairs/KeyValuePairSettings.cs index 69c619efd..96d8a8ae5 100644 --- a/src/Serilog/Settings/KeyValuePairs/KeyValuePairSettings.cs +++ b/src/Serilog/Settings/KeyValuePairs/KeyValuePairSettings.cs @@ -14,7 +14,6 @@ using System; using System.Collections.Generic; -using System.Collections.ObjectModel; using System.Linq; using System.Reflection; using System.Text.RegularExpressions; @@ -73,21 +72,20 @@ class KeyValuePairSettings : ILoggerSettings [typeof(LoggerFilterConfiguration)] = lc => lc.Filter }; - readonly Dictionary _settings; + readonly IReadOnlyDictionary _settings; - public KeyValuePairSettings(IEnumerable> settings) + public KeyValuePairSettings(IReadOnlyDictionary settings) { - if (settings == null) throw new ArgumentNullException(nameof(settings)); - _settings = settings.ToDictionary(s => s.Key, s => s.Value); + _settings = settings ?? throw new ArgumentNullException(nameof(settings)); } public void Configure(LoggerConfiguration loggerConfiguration) { if (loggerConfiguration == null) throw new ArgumentNullException(nameof(loggerConfiguration)); - var directives = _settings.Keys - .Where(k => _supportedDirectives.Any(k.StartsWith)) - .ToDictionary(k => k, k => _settings[k]); + var directives = _settings + .Where(kvp => _supportedDirectives.Any(kvp.Key.StartsWith)) + .ToDictionary(kvp => kvp.Key, kvp => kvp.Value); var declaredLevelSwitches = ParseNamedLevelSwitchDeclarationDirectives(directives); @@ -169,7 +167,7 @@ internal static bool IsValidSwitchName(string input) return Regex.IsMatch(input, LevelSwitchNameRegex); } - static IReadOnlyDictionary ParseNamedLevelSwitchDeclarationDirectives(Dictionary directives) + static IReadOnlyDictionary ParseNamedLevelSwitchDeclarationDirectives(IReadOnlyDictionary directives) { var matchLevelSwitchDeclarations = new Regex(LevelSwitchDeclarationDirectiveRegex); @@ -204,7 +202,7 @@ where matchLevelSwitchDeclarations.IsMatch(wt.Key) } namedSwitches.Add(switchName, newSwitch); } - return new ReadOnlyDictionary(namedSwitches); + return namedSwitches; } static LoggingLevelSwitch LookUpSwitchByName(string switchName, IReadOnlyDictionary declaredLevelSwitches) @@ -255,7 +253,7 @@ internal static MethodInfo SelectConfigurationMethod(IEnumerable can .FirstOrDefault(); } - internal static IEnumerable LoadConfigurationAssemblies(Dictionary directives) + internal static IEnumerable LoadConfigurationAssemblies(IReadOnlyDictionary directives) { var configurationAssemblies = new List { typeof(ILogger).GetTypeInfo().Assembly }; diff --git a/test/Serilog.Tests/Settings/KeyValuePairSettingsTests.cs b/test/Serilog.Tests/Settings/KeyValuePairSettingsTests.cs index 9b7e7f9fd..275f82b82 100644 --- a/test/Serilog.Tests/Settings/KeyValuePairSettingsTests.cs +++ b/test/Serilog.Tests/Settings/KeyValuePairSettingsTests.cs @@ -15,6 +15,26 @@ namespace Serilog.Tests.Settings { public class KeyValuePairSettingsTests { + [Fact] + public void LastValueIsTakenWhenKeysAreDuplicate() + { + LogEvent evt = null; + var log = new LoggerConfiguration() + .ReadFrom.KeyValuePairs(new List> + { + new KeyValuePair("enrich:with-property:App", "InitialValue"), + new KeyValuePair("enrich:with-property:App", "OverridenValue"), + new KeyValuePair("enrich:with-property:App", "FinalValue") + }) + .WriteTo.Sink(new DelegatingSink(e => evt = e)) + .CreateLogger(); + + log.Information("Has a test property"); + + Assert.NotNull(evt); + Assert.Equal("FinalValue", evt.Properties["App"].LiteralValue()); + } + [Fact] public void FindsConfigurationAssemblies() { @@ -181,7 +201,7 @@ public void LoggingLevelSwitchWithInvalidNameThrowsFormatException() ["level-switch:switchNameNotStartingWithDollar"] = "Warning", }; - var ex = Assert.Throws(() => new LoggerConfiguration() + var ex = Assert.Throws(() => new LoggerConfiguration() .ReadFrom.KeyValuePairs(settings)); Assert.Contains("\"switchNameNotStartingWithDollar\"", ex.Message); @@ -301,19 +321,19 @@ public void LoggingLevelSwitchCanBeUsedForMinimumLevelOverrides() .CreateLogger(); var systemLogger = log.ForContext(Constants.SourceContextPropertyName, "System.Bar"); - + log.Write(Some.InformationEvent()); Assert.False(evt is null, "Minimul level is Debug. It should log Information messages"); evt = null; - + // ReSharper disable HeuristicUnreachableCode systemLogger.Write(Some.InformationEvent()); Assert.True(evt is null, "LoggingLevelSwitch initial level was Warning for logger System.*. It should not log Information messages for SourceContext System.Bar"); systemLogger.Write(Some.WarningEvent()); Assert.False(evt is null, "LoggingLevelSwitch initial level was Warning for logger System.*. It should log Warning messages for SourceContext System.Bar"); - // ReSharper disable HeuristicUnreachableCode + evt = null; var controlSwitch = DummyWithLevelSwitchSink.ControlLevelSwitch;