From 340d20854c1bcacc1d8b14f01bd429c932e4d5f9 Mon Sep 17 00:00:00 2001 From: Julian Verdurmen <304NotModified@users.noreply.github.com> Date: Sat, 6 Aug 2016 16:25:50 +0200 Subject: [PATCH 01/53] Added codecov.io integration --- appveyor.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 19d0d28e9..185375dde 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -10,7 +10,12 @@ install: - ps: $env:Path = "$env:DOTNET_INSTALL_DIR;$env:Path" build_script: - ps: ./Build.ps1 -test: off +test_script: + - nuget.exe install OpenCover -ExcludeVersion + - OpenCover\tools\OpenCover.Console.exe -register:user -filter:"+[Serilog]*" -target:"dotnet.exe" "-targetargs:test test\Serilog.Tests" -returntargetcode -hideskipped:All -output:coverage.xml + - "SET PATH=C:\\Python34;C:\\Python34\\Scripts;%PATH%" + - pip install codecov + - codecov -f "coverage.xml" artifacts: - path: artifacts/Serilog.*.nupkg deploy: From 79ab6b068bd745df21b9835375855e20aab2f611 Mon Sep 17 00:00:00 2001 From: Artur Krajewski Date: Fri, 23 Sep 2016 18:35:16 +0200 Subject: [PATCH 02/53] Simple trimming of strings, according to conf(default 1000). --- .../LoggerDestructuringConfiguration.cs | 21 ++++++++++++++++++- src/Serilog/LoggerConfiguration.cs | 11 ++++++++-- .../Parameters/PropertyValueConverter.cs | 11 ++++++++++ .../Core/LogEventPropertyCapturingTests.cs | 2 +- .../Core/MessageTemplateTests.cs | 2 +- .../Events/LogEventPropertyValueTests.cs | 2 +- .../Serilog.Tests/LoggerConfigurationTests.cs | 21 +++++++++++++++++++ .../Parameters/PropertyValueConverterTests.cs | 2 +- 8 files changed, 65 insertions(+), 7 deletions(-) diff --git a/src/Serilog/Configuration/LoggerDestructuringConfiguration.cs b/src/Serilog/Configuration/LoggerDestructuringConfiguration.cs index 96f439509..ff5330f95 100644 --- a/src/Serilog/Configuration/LoggerDestructuringConfiguration.cs +++ b/src/Serilog/Configuration/LoggerDestructuringConfiguration.cs @@ -27,20 +27,24 @@ public class LoggerDestructuringConfiguration readonly Action _addScalar; readonly Action _addPolicy; readonly Action _setMaximumDepth; + readonly Action _setMaximumStringLength; internal LoggerDestructuringConfiguration( LoggerConfiguration loggerConfiguration, Action addScalar, Action addPolicy, - Action setMaximumDepth) + Action setMaximumDepth, + Action setMaximumStringLength) { if (loggerConfiguration == null) throw new ArgumentNullException(nameof(loggerConfiguration)); if (addScalar == null) throw new ArgumentNullException(nameof(addScalar)); if (addPolicy == null) throw new ArgumentNullException(nameof(addPolicy)); + if (setMaximumDepth == null) throw new ArgumentNullException(nameof(setMaximumStringLength)); _loggerConfiguration = loggerConfiguration; _addScalar = addScalar; _addPolicy = addPolicy; _setMaximumDepth = setMaximumDepth; + _setMaximumStringLength = setMaximumStringLength; } /// @@ -149,6 +153,21 @@ public LoggerConfiguration ToMaximumDepth(int maximumDestructuringDepth) _setMaximumDepth(maximumDestructuringDepth); return _loggerConfiguration; } + + /// + /// When destructuring objects, string values can be restricted to specified length + /// thus avoiding bloating payload. Limit is applied to each value separately, + /// sum of length of strings can exceed limit. + /// + /// The maximum string length. + /// Configuration object allowing method chaining. + /// When passed length is less or equal to 2 + public LoggerConfiguration ToMaximumStringLength(int maximumStringLength) + { + if (maximumStringLength < 2) throw new ArgumentOutOfRangeException(nameof(maximumStringLength), maximumStringLength, "Maximum string length have to be greater than 1"); + _setMaximumStringLength(maximumStringLength); + return _loggerConfiguration; + } } } diff --git a/src/Serilog/LoggerConfiguration.cs b/src/Serilog/LoggerConfiguration.cs index 764d937e2..5f9c0b412 100644 --- a/src/Serilog/LoggerConfiguration.cs +++ b/src/Serilog/LoggerConfiguration.cs @@ -39,6 +39,7 @@ public class LoggerConfiguration LogEventLevel _minimumLevel = LogEventLevel.Information; LoggingLevelSwitch _levelSwitch; int _maximumDestructuringDepth = 10; + int _maximumStringLength = 1000; bool _loggerCreated; void ApplyInheritedConfiguration(LoggerConfiguration child) @@ -109,7 +110,8 @@ public LoggerDestructuringConfiguration Destructure this, _additionalScalarTypes.Add, _additionalDestructuringPolicies.Add, - depth => _maximumDestructuringDepth = depth); + depth => _maximumDestructuringDepth = depth, + length => _maximumStringLength = length); } } @@ -150,7 +152,12 @@ public Logger CreateLogger() sink = new FilteringSink(sink, _filters, auditing); } - var converter = new PropertyValueConverter(_maximumDestructuringDepth, _additionalScalarTypes, _additionalDestructuringPolicies, auditing); + var converter = new PropertyValueConverter( + _maximumDestructuringDepth, + _maximumStringLength, + _additionalScalarTypes, + _additionalDestructuringPolicies, + auditing); var processor = new MessageTemplateProcessor(converter); ILogEventEnricher enricher; diff --git a/src/Serilog/Parameters/PropertyValueConverter.cs b/src/Serilog/Parameters/PropertyValueConverter.cs index 8ea88b03f..e5ceb1d7e 100755 --- a/src/Serilog/Parameters/PropertyValueConverter.cs +++ b/src/Serilog/Parameters/PropertyValueConverter.cs @@ -46,10 +46,12 @@ partial class PropertyValueConverter : ILogEventPropertyFactory, ILogEventProper readonly IDestructuringPolicy[] _destructuringPolicies; readonly IScalarConversionPolicy[] _scalarConversionPolicies; readonly int _maximumDestructuringDepth; + readonly int _maximumStringLength; readonly bool _propagateExceptions; public PropertyValueConverter( int maximumDestructuringDepth, + int maximumStringLength, IEnumerable additionalScalarTypes, IEnumerable additionalDestructuringPolicies, bool propagateExceptions) @@ -57,9 +59,11 @@ public PropertyValueConverter( if (additionalScalarTypes == null) throw new ArgumentNullException(nameof(additionalScalarTypes)); if (additionalDestructuringPolicies == null) throw new ArgumentNullException(nameof(additionalDestructuringPolicies)); if (maximumDestructuringDepth < 0) throw new ArgumentOutOfRangeException(nameof(maximumDestructuringDepth)); + if (maximumStringLength < 2) throw new ArgumentOutOfRangeException(nameof(maximumDestructuringDepth)); _maximumDestructuringDepth = maximumDestructuringDepth; _propagateExceptions = propagateExceptions; + _maximumStringLength = maximumStringLength; _scalarConversionPolicies = new IScalarConversionPolicy[] { @@ -126,6 +130,12 @@ LogEventPropertyValue CreatePropertyValue(object value, Destructuring destructur var valueType = value.GetType(); var limiter = new DepthLimiter(depth, _maximumDestructuringDepth, this); + var stringValue = value as string; + if (stringValue != null && stringValue.Length > _maximumStringLength) + { + value = stringValue.Substring(0, _maximumStringLength); + } + foreach (var scalarConversionPolicy in _scalarConversionPolicies) { ScalarValue converted; @@ -241,3 +251,4 @@ internal static bool IsCompilerGeneratedType(Type type) } } } + diff --git a/test/Serilog.Tests/Core/LogEventPropertyCapturingTests.cs b/test/Serilog.Tests/Core/LogEventPropertyCapturingTests.cs index b81834b43..dc7499bb5 100644 --- a/test/Serilog.Tests/Core/LogEventPropertyCapturingTests.cs +++ b/test/Serilog.Tests/Core/LogEventPropertyCapturingTests.cs @@ -179,7 +179,7 @@ static IEnumerable Capture(string messageTemplate, params obje { var mt = new MessageTemplateParser().Parse(messageTemplate); var binder = new PropertyBinder( - new PropertyValueConverter(10, Enumerable.Empty(), Enumerable.Empty(), false)); + new PropertyValueConverter(10, 1000, Enumerable.Empty(), Enumerable.Empty(), false)); return binder.ConstructProperties(mt, properties); } diff --git a/test/Serilog.Tests/Core/MessageTemplateTests.cs b/test/Serilog.Tests/Core/MessageTemplateTests.cs index 93c6dc3f7..93be20b1f 100755 --- a/test/Serilog.Tests/Core/MessageTemplateTests.cs +++ b/test/Serilog.Tests/Core/MessageTemplateTests.cs @@ -126,7 +126,7 @@ static string Render(string messageTemplate, params object[] properties) static string Render(IFormatProvider formatProvider, string messageTemplate, params object[] properties) { var mt = new MessageTemplateParser().Parse(messageTemplate); - var binder = new PropertyBinder(new PropertyValueConverter(10, Enumerable.Empty(), Enumerable.Empty(), false)); + var binder = new PropertyBinder(new PropertyValueConverter(10, 1000, Enumerable.Empty(), Enumerable.Empty(), false)); var props = binder.ConstructProperties(mt, properties); var output = new StringBuilder(); var writer = new StringWriter(output); diff --git a/test/Serilog.Tests/Events/LogEventPropertyValueTests.cs b/test/Serilog.Tests/Events/LogEventPropertyValueTests.cs index 170dd2fe2..b3b0093a7 100644 --- a/test/Serilog.Tests/Events/LogEventPropertyValueTests.cs +++ b/test/Serilog.Tests/Events/LogEventPropertyValueTests.cs @@ -27,7 +27,7 @@ namespace Serilog.Tests.Events public class LogEventPropertyValueTests { readonly PropertyValueConverter _converter = - new PropertyValueConverter(10, Enumerable.Empty(), Enumerable.Empty(), false); + new PropertyValueConverter(10, 1000, Enumerable.Empty(), Enumerable.Empty(), false); [Fact] public void AnEnumIsConvertedToANonStringScalarValue() diff --git a/test/Serilog.Tests/LoggerConfigurationTests.cs b/test/Serilog.Tests/LoggerConfigurationTests.cs index a755bda26..b0b22a478 100644 --- a/test/Serilog.Tests/LoggerConfigurationTests.cs +++ b/test/Serilog.Tests/LoggerConfigurationTests.cs @@ -277,6 +277,27 @@ public void MaximumDestructuringDepthIsEffective() Assert.DoesNotContain(xs, "D"); } + [Fact] + public void MaximumStringLengthEffective() + { + var x = new + { + TooLongText = "1234" + }; + + LogEvent evt = null; + var log = new LoggerConfiguration() + .WriteTo.Sink(new DelegatingSink(e => evt = e)) + .Destructure.ToMaximumStringLength(3) + .CreateLogger(); + + log.Information("{@X}", x); + var limitedText = evt.Properties["X"].ToString(); + + Assert.Contains("123", limitedText); + Assert.DoesNotContain("1234", limitedText); + } + [Fact] public void DynamicallySwitchingSinkRestrictsOutput() { diff --git a/test/Serilog.Tests/Parameters/PropertyValueConverterTests.cs b/test/Serilog.Tests/Parameters/PropertyValueConverterTests.cs index 354202eab..8163114c1 100644 --- a/test/Serilog.Tests/Parameters/PropertyValueConverterTests.cs +++ b/test/Serilog.Tests/Parameters/PropertyValueConverterTests.cs @@ -13,7 +13,7 @@ namespace Serilog.Tests.Parameters public class PropertyValueConverterTests { readonly PropertyValueConverter _converter = - new PropertyValueConverter(10, Enumerable.Empty(), Enumerable.Empty(), false); + new PropertyValueConverter(10, 1000, Enumerable.Empty(), Enumerable.Empty(), false); [Fact] public void UnderDestructuringAByteArrayIsAScalarValue() From 6afcb8ca97d732e8391245e2d830cf0721e74e34 Mon Sep 17 00:00:00 2001 From: Artur Krajewski Date: Fri, 23 Sep 2016 22:21:24 +0200 Subject: [PATCH 03/53] =?UTF-8?q?Using=20=E2=80=A6=20character=20when=20tr?= =?UTF-8?q?uncating=20string.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Serilog/Parameters/PropertyValueConverter.cs | 2 +- test/Serilog.Tests/LoggerConfigurationTests.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Serilog/Parameters/PropertyValueConverter.cs b/src/Serilog/Parameters/PropertyValueConverter.cs index e5ceb1d7e..ee988db9c 100755 --- a/src/Serilog/Parameters/PropertyValueConverter.cs +++ b/src/Serilog/Parameters/PropertyValueConverter.cs @@ -133,7 +133,7 @@ LogEventPropertyValue CreatePropertyValue(object value, Destructuring destructur var stringValue = value as string; if (stringValue != null && stringValue.Length > _maximumStringLength) { - value = stringValue.Substring(0, _maximumStringLength); + value = stringValue.Substring(0, _maximumStringLength-1) +"…"; } foreach (var scalarConversionPolicy in _scalarConversionPolicies) diff --git a/test/Serilog.Tests/LoggerConfigurationTests.cs b/test/Serilog.Tests/LoggerConfigurationTests.cs index b0b22a478..dcdc80271 100644 --- a/test/Serilog.Tests/LoggerConfigurationTests.cs +++ b/test/Serilog.Tests/LoggerConfigurationTests.cs @@ -294,8 +294,8 @@ public void MaximumStringLengthEffective() log.Information("{@X}", x); var limitedText = evt.Properties["X"].ToString(); - Assert.Contains("123", limitedText); - Assert.DoesNotContain("1234", limitedText); + Assert.Contains("12…", limitedText); + Assert.DoesNotContain("123", limitedText); } [Fact] From 138bdb091bf08c4555f187e9d9f50f63afaf0409 Mon Sep 17 00:00:00 2001 From: Artur Krajewski Date: Fri, 23 Sep 2016 22:24:49 +0200 Subject: [PATCH 04/53] Test fo case of not truncating string. --- test/Serilog.Tests/LoggerConfigurationTests.cs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/test/Serilog.Tests/LoggerConfigurationTests.cs b/test/Serilog.Tests/LoggerConfigurationTests.cs index dcdc80271..89b854187 100644 --- a/test/Serilog.Tests/LoggerConfigurationTests.cs +++ b/test/Serilog.Tests/LoggerConfigurationTests.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; +using System.Runtime.InteropServices.ComTypes; using Xunit; using Serilog.Core; using Serilog.Core.Filters; @@ -277,25 +278,26 @@ public void MaximumDestructuringDepthIsEffective() Assert.DoesNotContain(xs, "D"); } - [Fact] - public void MaximumStringLengthEffective() + [Theory] + [InlineData("1234", "12…", 3)] + [InlineData("123", "123", 3)] + public void MaximumStringLengthEffective(string text, string textAfter, int limit) { var x = new { - TooLongText = "1234" + TooLongText = text }; LogEvent evt = null; var log = new LoggerConfiguration() .WriteTo.Sink(new DelegatingSink(e => evt = e)) - .Destructure.ToMaximumStringLength(3) + .Destructure.ToMaximumStringLength(limit) .CreateLogger(); log.Information("{@X}", x); var limitedText = evt.Properties["X"].ToString(); - Assert.Contains("12…", limitedText); - Assert.DoesNotContain("123", limitedText); + Assert.Contains(textAfter, limitedText); } [Fact] From 680589ba5450eac17f54252db3ed0582cceb4657 Mon Sep 17 00:00:00 2001 From: Artur Krajewski Date: Fri, 23 Sep 2016 22:41:25 +0200 Subject: [PATCH 05/53] Truncation of stringified objects. --- .../Parameters/PropertyValueConverter.cs | 20 +++++++++-- .../Serilog.Tests/LoggerConfigurationTests.cs | 34 ++++++++++++++++++- 2 files changed, 50 insertions(+), 4 deletions(-) diff --git a/src/Serilog/Parameters/PropertyValueConverter.cs b/src/Serilog/Parameters/PropertyValueConverter.cs index ee988db9c..ff83856f8 100755 --- a/src/Serilog/Parameters/PropertyValueConverter.cs +++ b/src/Serilog/Parameters/PropertyValueConverter.cs @@ -125,15 +125,19 @@ LogEventPropertyValue CreatePropertyValue(object value, Destructuring destructur return new ScalarValue(null); if (destructuring == Destructuring.Stringify) - return new ScalarValue(value.ToString()); + { + var stringified = value.ToString(); + var truncated = TruncateIfNecessary(stringified); + return new ScalarValue(truncated); + } var valueType = value.GetType(); var limiter = new DepthLimiter(depth, _maximumDestructuringDepth, this); var stringValue = value as string; - if (stringValue != null && stringValue.Length > _maximumStringLength) + if (stringValue != null) { - value = stringValue.Substring(0, _maximumStringLength-1) +"…"; + value = TruncateIfNecessary(stringValue); } foreach (var scalarConversionPolicy in _scalarConversionPolicies) @@ -196,6 +200,16 @@ LogEventPropertyValue CreatePropertyValue(object value, Destructuring destructur return new ScalarValue(value.ToString()); } + string TruncateIfNecessary(string text) + { + if (text.Length > _maximumStringLength) + { + return text.Substring(0, _maximumStringLength - 1) + "…"; + } + + return text; + } + bool IsValueTypeDictionary(Type valueType) { return valueType.IsConstructedGenericType && diff --git a/test/Serilog.Tests/LoggerConfigurationTests.cs b/test/Serilog.Tests/LoggerConfigurationTests.cs index 89b854187..72508c2b2 100644 --- a/test/Serilog.Tests/LoggerConfigurationTests.cs +++ b/test/Serilog.Tests/LoggerConfigurationTests.cs @@ -281,7 +281,7 @@ public void MaximumDestructuringDepthIsEffective() [Theory] [InlineData("1234", "12…", 3)] [InlineData("123", "123", 3)] - public void MaximumStringLengthEffective(string text, string textAfter, int limit) + public void MaximumStringLengthEffectiveForCapturedObject(string text, string textAfter, int limit) { var x = new { @@ -300,6 +300,38 @@ public void MaximumStringLengthEffective(string text, string textAfter, int limi Assert.Contains(textAfter, limitedText); } + [Fact] + public void MaximumStringLengthEffectiveForStringifiedObject() + { + var x = new ToStringOfLength(4); + + LogEvent evt = null; + var log = new LoggerConfiguration() + .WriteTo.Sink(new DelegatingSink(e => evt = e)) + .Destructure.ToMaximumStringLength(3) + .CreateLogger(); + + log.Information("{$X}", x); + var limitedText = evt.Properties["X"].ToString(); + + Assert.Contains("##…", limitedText); + } + + class ToStringOfLength + { + private int _toStringOfLength; + + public ToStringOfLength(int toStringOfLength) + { + _toStringOfLength = toStringOfLength; + } + + public override string ToString() + { + return new string('#', _toStringOfLength); + } + } + [Fact] public void DynamicallySwitchingSinkRestrictsOutput() { From 37203f696538fa9ff653f020017a5a9563ac96ef Mon Sep 17 00:00:00 2001 From: Artur Krajewski Date: Sat, 24 Sep 2016 07:18:11 +0200 Subject: [PATCH 06/53] Test for limiting length of simple string. --- test/Serilog.Tests/LoggerConfigurationTests.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/test/Serilog.Tests/LoggerConfigurationTests.cs b/test/Serilog.Tests/LoggerConfigurationTests.cs index 72508c2b2..e985d7dd3 100644 --- a/test/Serilog.Tests/LoggerConfigurationTests.cs +++ b/test/Serilog.Tests/LoggerConfigurationTests.cs @@ -278,6 +278,23 @@ public void MaximumDestructuringDepthIsEffective() Assert.DoesNotContain(xs, "D"); } + [Fact] + public void MaximumStringLengthEffectiveForString() + { + var x = "ABCD"; + + LogEvent evt = null; + var log = new LoggerConfiguration() + .WriteTo.Sink(new DelegatingSink(e => evt = e)) + .Destructure.ToMaximumStringLength(3) + .CreateLogger(); + + log.Information("{$X}", x); + var limitedText = evt.Properties["X"].ToString(); + + Assert.Equal("\"AB…\"", limitedText); + } + [Theory] [InlineData("1234", "12…", 3)] [InlineData("123", "123", 3)] From 9efea23d6e31ebfd3b341829ddc7355f1bcd8e67 Mon Sep 17 00:00:00 2001 From: Artur Krajewski Date: Sat, 24 Sep 2016 07:50:34 +0200 Subject: [PATCH 07/53] Truncating plain object. --- .../Parameters/PropertyValueConverter.cs | 13 +++++++++---- test/Serilog.Tests/LoggerConfigurationTests.cs | 17 +++++++++++++++++ 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/src/Serilog/Parameters/PropertyValueConverter.cs b/src/Serilog/Parameters/PropertyValueConverter.cs index ff83856f8..1fc6f2f3c 100755 --- a/src/Serilog/Parameters/PropertyValueConverter.cs +++ b/src/Serilog/Parameters/PropertyValueConverter.cs @@ -126,9 +126,7 @@ LogEventPropertyValue CreatePropertyValue(object value, Destructuring destructur if (destructuring == Destructuring.Stringify) { - var stringified = value.ToString(); - var truncated = TruncateIfNecessary(stringified); - return new ScalarValue(truncated); + return Stringify(value); } var valueType = value.GetType(); @@ -197,7 +195,14 @@ LogEventPropertyValue CreatePropertyValue(object value, Destructuring destructur return new StructureValue(GetProperties(value, limiter), typeTag); } - return new ScalarValue(value.ToString()); + return Stringify(value); + } + + private LogEventPropertyValue Stringify(object value) + { + var stringified = value.ToString(); + var truncated = TruncateIfNecessary(stringified); + return new ScalarValue(truncated); } string TruncateIfNecessary(string text) diff --git a/test/Serilog.Tests/LoggerConfigurationTests.cs b/test/Serilog.Tests/LoggerConfigurationTests.cs index e985d7dd3..e178c2e05 100644 --- a/test/Serilog.Tests/LoggerConfigurationTests.cs +++ b/test/Serilog.Tests/LoggerConfigurationTests.cs @@ -334,6 +334,23 @@ public void MaximumStringLengthEffectiveForStringifiedObject() Assert.Contains("##…", limitedText); } + [Fact] + public void MaximumStringLengthEffectiveForObject() + { + var x = new ToStringOfLength(4); + + LogEvent evt = null; + var log = new LoggerConfiguration() + .WriteTo.Sink(new DelegatingSink(e => evt = e)) + .Destructure.ToMaximumStringLength(3) + .CreateLogger(); + + log.Information("{X}", x); + var limitedText = evt.Properties["X"].ToString(); + + Assert.Contains("##…", limitedText); + } + class ToStringOfLength { private int _toStringOfLength; From 71f9df533782ba04715ecb827447ae6621371cb4 Mon Sep 17 00:00:00 2001 From: Artur Krajewski Date: Sat, 24 Sep 2016 07:54:52 +0200 Subject: [PATCH 08/53] Removed $ from test of string truncation. --- test/Serilog.Tests/LoggerConfigurationTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Serilog.Tests/LoggerConfigurationTests.cs b/test/Serilog.Tests/LoggerConfigurationTests.cs index e178c2e05..b2469f128 100644 --- a/test/Serilog.Tests/LoggerConfigurationTests.cs +++ b/test/Serilog.Tests/LoggerConfigurationTests.cs @@ -289,7 +289,7 @@ public void MaximumStringLengthEffectiveForString() .Destructure.ToMaximumStringLength(3) .CreateLogger(); - log.Information("{$X}", x); + log.Information("{X}", x); var limitedText = evt.Properties["X"].ToString(); Assert.Equal("\"AB…\"", limitedText); From e07294af3c5bc0cfe3c428e99b62320a930d292c Mon Sep 17 00:00:00 2001 From: Artur Krajewski Date: Sat, 24 Sep 2016 08:03:26 +0200 Subject: [PATCH 09/53] Test for invalid string limit. --- test/Serilog.Tests/LoggerConfigurationTests.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/test/Serilog.Tests/LoggerConfigurationTests.cs b/test/Serilog.Tests/LoggerConfigurationTests.cs index b2469f128..63d98f00d 100644 --- a/test/Serilog.Tests/LoggerConfigurationTests.cs +++ b/test/Serilog.Tests/LoggerConfigurationTests.cs @@ -278,6 +278,14 @@ public void MaximumDestructuringDepthIsEffective() Assert.DoesNotContain(xs, "D"); } + [Fact] + public void MaximumStringLengthThrowsForLimitLowerThan2() + { + var ex = Assert.Throws( + () => new LoggerConfiguration().Destructure.ToMaximumStringLength(1)); + Assert.Equal(1, ex.ActualValue); + } + [Fact] public void MaximumStringLengthEffectiveForString() { From 0b92e59a7e79c991c416414c3c683543c74d3e21 Mon Sep 17 00:00:00 2001 From: Artur Krajewski Date: Wed, 28 Sep 2016 15:24:27 +0200 Subject: [PATCH 10/53] Default maximum string length int.MaxValue to avoid breaking changed. --- src/Serilog/LoggerConfiguration.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Serilog/LoggerConfiguration.cs b/src/Serilog/LoggerConfiguration.cs index 5f9c0b412..498ac7f50 100644 --- a/src/Serilog/LoggerConfiguration.cs +++ b/src/Serilog/LoggerConfiguration.cs @@ -39,7 +39,7 @@ public class LoggerConfiguration LogEventLevel _minimumLevel = LogEventLevel.Information; LoggingLevelSwitch _levelSwitch; int _maximumDestructuringDepth = 10; - int _maximumStringLength = 1000; + int _maximumStringLength = int.MaxValue; bool _loggerCreated; void ApplyInheritedConfiguration(LoggerConfiguration child) From 8cd082eb1567b5eac7d55c46ae8960a7b8d7ea22 Mon Sep 17 00:00:00 2001 From: Artur Krajewski Date: Wed, 28 Sep 2016 15:27:04 +0200 Subject: [PATCH 11/53] Rewording of exception for max string length validation. --- src/Serilog/Configuration/LoggerDestructuringConfiguration.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Serilog/Configuration/LoggerDestructuringConfiguration.cs b/src/Serilog/Configuration/LoggerDestructuringConfiguration.cs index ff5330f95..b13510c38 100644 --- a/src/Serilog/Configuration/LoggerDestructuringConfiguration.cs +++ b/src/Serilog/Configuration/LoggerDestructuringConfiguration.cs @@ -164,7 +164,7 @@ public LoggerConfiguration ToMaximumDepth(int maximumDestructuringDepth) /// When passed length is less or equal to 2 public LoggerConfiguration ToMaximumStringLength(int maximumStringLength) { - if (maximumStringLength < 2) throw new ArgumentOutOfRangeException(nameof(maximumStringLength), maximumStringLength, "Maximum string length have to be greater than 1"); + if (maximumStringLength < 2) throw new ArgumentOutOfRangeException(nameof(maximumStringLength), maximumStringLength, "Maximum string length must be at least two."); _setMaximumStringLength(maximumStringLength); return _loggerConfiguration; } From d283c117ee03b83567100b8a09c648765c915f51 Mon Sep 17 00:00:00 2001 From: Artur Krajewski Date: Wed, 28 Sep 2016 16:54:32 +0200 Subject: [PATCH 12/53] In default destructuring mode strings are not truncated. But they are when they are captured or when they are stringified. --- .../Parameters/PropertyValueConverter.cs | 11 +++-- .../Serilog.Tests/LoggerConfigurationTests.cs | 40 +++++++++++++++++-- 2 files changed, 44 insertions(+), 7 deletions(-) diff --git a/src/Serilog/Parameters/PropertyValueConverter.cs b/src/Serilog/Parameters/PropertyValueConverter.cs index 1fc6f2f3c..4caed637e 100755 --- a/src/Serilog/Parameters/PropertyValueConverter.cs +++ b/src/Serilog/Parameters/PropertyValueConverter.cs @@ -132,10 +132,13 @@ LogEventPropertyValue CreatePropertyValue(object value, Destructuring destructur var valueType = value.GetType(); var limiter = new DepthLimiter(depth, _maximumDestructuringDepth, this); - var stringValue = value as string; - if (stringValue != null) + if (destructuring == Destructuring.Destructure) { - value = TruncateIfNecessary(stringValue); + var stringValue = value as string; + if (stringValue != null) + { + value = TruncateIfNecessary(stringValue); + } } foreach (var scalarConversionPolicy in _scalarConversionPolicies) @@ -195,7 +198,7 @@ LogEventPropertyValue CreatePropertyValue(object value, Destructuring destructur return new StructureValue(GetProperties(value, limiter), typeTag); } - return Stringify(value); + return new ScalarValue(value.ToString()); } private LogEventPropertyValue Stringify(object value) diff --git a/test/Serilog.Tests/LoggerConfigurationTests.cs b/test/Serilog.Tests/LoggerConfigurationTests.cs index 63d98f00d..0c7b2bd08 100644 --- a/test/Serilog.Tests/LoggerConfigurationTests.cs +++ b/test/Serilog.Tests/LoggerConfigurationTests.cs @@ -287,7 +287,7 @@ public void MaximumStringLengthThrowsForLimitLowerThan2() } [Fact] - public void MaximumStringLengthEffectiveForString() + public void MaximumStringLengthNOTEffectiveForString() { var x = "ABCD"; @@ -300,6 +300,40 @@ public void MaximumStringLengthEffectiveForString() log.Information("{X}", x); var limitedText = evt.Properties["X"].ToString(); + Assert.Equal("\"ABCD\"", limitedText); + } + + [Fact] + public void MaximumStringLengthEffectiveForCapturedString() + { + var x = "ABCD"; + + LogEvent evt = null; + var log = new LoggerConfiguration() + .WriteTo.Sink(new DelegatingSink(e => evt = e)) + .Destructure.ToMaximumStringLength(3) + .CreateLogger(); + + log.Information("{@X}", x); + var limitedText = evt.Properties["X"].ToString(); + + Assert.Equal("\"AB…\"", limitedText); + } + + [Fact] + public void MaximumStringLengthEffectiveForStringifiedString() + { + var x = "ABCD"; + + LogEvent evt = null; + var log = new LoggerConfiguration() + .WriteTo.Sink(new DelegatingSink(e => evt = e)) + .Destructure.ToMaximumStringLength(3) + .CreateLogger(); + + log.Information("{$X}", x); + var limitedText = evt.Properties["X"].ToString(); + Assert.Equal("\"AB…\"", limitedText); } @@ -343,7 +377,7 @@ public void MaximumStringLengthEffectiveForStringifiedObject() } [Fact] - public void MaximumStringLengthEffectiveForObject() + public void MaximumStringLengthNOTEffectiveForObject() { var x = new ToStringOfLength(4); @@ -356,7 +390,7 @@ public void MaximumStringLengthEffectiveForObject() log.Information("{X}", x); var limitedText = evt.Properties["X"].ToString(); - Assert.Contains("##…", limitedText); + Assert.Contains("####", limitedText); } class ToStringOfLength From bb1a55045aa64790fb5b79f56f2974ae58dcb4b5 Mon Sep 17 00:00:00 2001 From: Artur Krajewski Date: Wed, 28 Sep 2016 17:19:37 +0200 Subject: [PATCH 13/53] Test for object NOT limited with default destructuring mode. --- test/Serilog.Tests/LoggerConfigurationTests.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/test/Serilog.Tests/LoggerConfigurationTests.cs b/test/Serilog.Tests/LoggerConfigurationTests.cs index 0c7b2bd08..f4173a483 100644 --- a/test/Serilog.Tests/LoggerConfigurationTests.cs +++ b/test/Serilog.Tests/LoggerConfigurationTests.cs @@ -376,6 +376,23 @@ public void MaximumStringLengthEffectiveForStringifiedObject() Assert.Contains("##…", limitedText); } + [Fact] + public void MaximumStringNOTLengthEffectiveForObject() + { + var x = new ToStringOfLength(4); + + LogEvent evt = null; + var log = new LoggerConfiguration() + .WriteTo.Sink(new DelegatingSink(e => evt = e)) + .Destructure.ToMaximumStringLength(3) + .CreateLogger(); + + log.Information("{X}", x); + var limitedText = evt.Properties["X"].ToString(); + + Assert.Contains("####", limitedText); + } + [Fact] public void MaximumStringLengthNOTEffectiveForObject() { From 26f175abd8bbd1a7c57768849771a824201f7d15 Mon Sep 17 00:00:00 2001 From: Nicholas Blumhardt Date: Wed, 5 Oct 2016 11:37:34 +1000 Subject: [PATCH 14/53] Dev version bump [Skip CI] --- src/Serilog/project.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Serilog/project.json b/src/Serilog/project.json index 602758fad..8111ded4a 100644 --- a/src/Serilog/project.json +++ b/src/Serilog/project.json @@ -1,5 +1,5 @@ -{ - "version": "2.3.0-*", +{ + "version": "2.3.1-*", "description": "Simple .NET logging with fully-structured events", "authors": [ "Serilog Contributors" ], From c2f883bca329fd713771c92e47bad6fcbb63ebc4 Mon Sep 17 00:00:00 2001 From: Nicholas Blumhardt Date: Wed, 5 Oct 2016 11:44:58 +1000 Subject: [PATCH 15/53] Link to the Stack Overflow tag [Skip CI] --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5840a7715..b1939a77e 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -#Serilog [![Build status](https://ci.appveyor.com/api/projects/status/b9rm3l7kduryjgcj/branch/master?svg=true)](https://ci.appveyor.com/project/serilog/serilog/branch/master) [![NuGet Version](http://img.shields.io/nuget/v/Serilog.svg?style=flat)](https://www.nuget.org/packages/Serilog/) [![Rager Releases](http://rager.io/badge.svg?url=https%3A%2F%2Fwww.nuget.org%2Fpackages%2FSerilog%2F)](http://rager.io/projects/search?badge=1&query=nuget.org/packages/Serilog/) [![Join the chat at https://gitter.im/serilog/serilog](https://img.shields.io/gitter/room/serilog/serilog.svg)](https://gitter.im/serilog/serilog) +#Serilog [![Build status](https://ci.appveyor.com/api/projects/status/b9rm3l7kduryjgcj/branch/master?svg=true)](https://ci.appveyor.com/project/serilog/serilog/branch/master) [![NuGet Version](http://img.shields.io/nuget/v/Serilog.svg?style=flat)](https://www.nuget.org/packages/Serilog/) [![Rager Releases](http://rager.io/badge.svg?url=https%3A%2F%2Fwww.nuget.org%2Fpackages%2FSerilog%2F)](http://rager.io/projects/search?badge=1&query=nuget.org/packages/Serilog/) [![Join the chat at https://gitter.im/serilog/serilog](https://img.shields.io/gitter/room/serilog/serilog.svg)](https://gitter.im/serilog/serilog) [![Stack Overflow](https://img.shields.io/badge/stackoverflow-serilog-orange.svg)](http://stackoverflow.com/questions/tagged/serilog) Serilog combines the best features of traditional and structured diagnostic logging in an easy-to-use package. From dd6f01d7f6f2cfdf064fbba02d100143186299ac Mon Sep 17 00:00:00 2001 From: Nicholas Blumhardt Date: Wed, 5 Oct 2016 12:20:04 +1000 Subject: [PATCH 16/53] Update changelist for latest releases [Skip CI] --- CHANGES.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 13359d17b..26b75dfd7 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,17 @@ +2.3.0 + * #870 - fix dispose for level-restricted sinks + * #852 - fix dictionary capturing when key/value are anonymous types + * #835 - avoid `RemotingException` via `LogContext` + * #855 - allow custom enum rendering (better `ICustomFormatter` support + * #841 - `audit-to` in key-value settings + +2.2.1 + * #835 (fix for .NET 4.6+ only) + +2.2.0 + * #826 - audit-style logging + * #819 - deprecate virtual extension points on JsonFormatter + 2.1.0 * #782 - provide `Destructure.ByTransformingWhere()` * #779 - capture additional parameters even when template is malformed From cded991931e5928ed02beb666a0bd9de37854cf7 Mon Sep 17 00:00:00 2001 From: Nicholas Blumhardt Date: Wed, 5 Oct 2016 15:37:04 +1000 Subject: [PATCH 17/53] Dev version bump - payload limiting is coming in [Skip CI] --- src/Serilog/project.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Serilog/project.json b/src/Serilog/project.json index 8111ded4a..cf257b196 100644 --- a/src/Serilog/project.json +++ b/src/Serilog/project.json @@ -1,5 +1,5 @@ { - "version": "2.3.1-*", + "version": "2.4.0-*", "description": "Simple .NET logging with fully-structured events", "authors": [ "Serilog Contributors" ], From f947c17e8cad313f839bba7500874b7d5d859504 Mon Sep 17 00:00:00 2001 From: Artur Krajewski Date: Wed, 5 Oct 2016 10:54:55 +0200 Subject: [PATCH 18/53] Removed private from Stringify. --- src/Serilog/Parameters/PropertyValueConverter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Serilog/Parameters/PropertyValueConverter.cs b/src/Serilog/Parameters/PropertyValueConverter.cs index 4caed637e..8ee1a3a1c 100755 --- a/src/Serilog/Parameters/PropertyValueConverter.cs +++ b/src/Serilog/Parameters/PropertyValueConverter.cs @@ -201,7 +201,7 @@ LogEventPropertyValue CreatePropertyValue(object value, Destructuring destructur return new ScalarValue(value.ToString()); } - private LogEventPropertyValue Stringify(object value) + LogEventPropertyValue Stringify(object value) { var stringified = value.ToString(); var truncated = TruncateIfNecessary(stringified); From 8828af83e5b172a1884646a161a070cd7e9f8779 Mon Sep 17 00:00:00 2001 From: Artur Krajewski Date: Wed, 5 Oct 2016 21:57:24 +0200 Subject: [PATCH 19/53] Limits of collection and dictionaries length. --- .../LoggerDestructuringConfiguration.cs | 24 +++++++++++++++++-- src/Serilog/LoggerConfiguration.cs | 5 +++- .../Parameters/PropertyValueConverter.cs | 8 +++++-- .../Core/LogEventPropertyCapturingTests.cs | 2 +- .../Core/MessageTemplateTests.cs | 2 +- .../Events/LogEventPropertyValueTests.cs | 2 +- .../Parameters/PropertyValueConverterTests.cs | 2 +- 7 files changed, 36 insertions(+), 9 deletions(-) diff --git a/src/Serilog/Configuration/LoggerDestructuringConfiguration.cs b/src/Serilog/Configuration/LoggerDestructuringConfiguration.cs index b13510c38..debcbc1ad 100644 --- a/src/Serilog/Configuration/LoggerDestructuringConfiguration.cs +++ b/src/Serilog/Configuration/LoggerDestructuringConfiguration.cs @@ -13,6 +13,7 @@ // limitations under the License. using System; +using System.Collections; using Serilog.Core; using Serilog.Policies; @@ -28,23 +29,27 @@ public class LoggerDestructuringConfiguration readonly Action _addPolicy; readonly Action _setMaximumDepth; readonly Action _setMaximumStringLength; + readonly Action _setMaximumCollectionLength; internal LoggerDestructuringConfiguration( LoggerConfiguration loggerConfiguration, Action addScalar, Action addPolicy, Action setMaximumDepth, - Action setMaximumStringLength) + Action setMaximumStringLength, + Action setMaximumCollectionLength) { if (loggerConfiguration == null) throw new ArgumentNullException(nameof(loggerConfiguration)); if (addScalar == null) throw new ArgumentNullException(nameof(addScalar)); if (addPolicy == null) throw new ArgumentNullException(nameof(addPolicy)); if (setMaximumDepth == null) throw new ArgumentNullException(nameof(setMaximumStringLength)); + if (setMaximumCollectionLength == null) throw new ArgumentNullException(nameof(setMaximumCollectionLength)); _loggerConfiguration = loggerConfiguration; _addScalar = addScalar; _addPolicy = addPolicy; _setMaximumDepth = setMaximumDepth; _setMaximumStringLength = setMaximumStringLength; + _setMaximumCollectionLength = setMaximumCollectionLength; } /// @@ -161,13 +166,28 @@ public LoggerConfiguration ToMaximumDepth(int maximumDestructuringDepth) /// /// The maximum string length. /// Configuration object allowing method chaining. - /// When passed length is less or equal to 2 + /// When passed length is less than 2 public LoggerConfiguration ToMaximumStringLength(int maximumStringLength) { if (maximumStringLength < 2) throw new ArgumentOutOfRangeException(nameof(maximumStringLength), maximumStringLength, "Maximum string length must be at least two."); _setMaximumStringLength(maximumStringLength); return _loggerConfiguration; } + + /// + /// When destructuring objects, collections be restricted to specified length + /// thus avoiding bloating payload. Limit is applied to each collection separately, + /// sum of length of collection can exceed limit. + /// Applies limit to all including dictionaries. + /// + /// Configuration object allowing method chaining. + /// When passed length is less than 1 + public LoggerConfiguration ToMaximumCollectionLengthLength(int maximumCollectionLength) + { + if (maximumCollectionLength < 1) throw new ArgumentOutOfRangeException(nameof(maximumCollectionLength), maximumCollectionLength, "Maximum collection length must be at least one."); + _setMaximumCollectionLength(maximumCollectionLength); + return _loggerConfiguration; + } } } diff --git a/src/Serilog/LoggerConfiguration.cs b/src/Serilog/LoggerConfiguration.cs index 498ac7f50..25772ca10 100644 --- a/src/Serilog/LoggerConfiguration.cs +++ b/src/Serilog/LoggerConfiguration.cs @@ -40,6 +40,7 @@ public class LoggerConfiguration LoggingLevelSwitch _levelSwitch; int _maximumDestructuringDepth = 10; int _maximumStringLength = int.MaxValue; + int _maximumCollectionLength = int.MaxValue; bool _loggerCreated; void ApplyInheritedConfiguration(LoggerConfiguration child) @@ -111,7 +112,8 @@ public LoggerDestructuringConfiguration Destructure _additionalScalarTypes.Add, _additionalDestructuringPolicies.Add, depth => _maximumDestructuringDepth = depth, - length => _maximumStringLength = length); + length => _maximumStringLength = length, + length => _maximumCollectionLength = length); } } @@ -155,6 +157,7 @@ public Logger CreateLogger() var converter = new PropertyValueConverter( _maximumDestructuringDepth, _maximumStringLength, + _maximumCollectionLength, _additionalScalarTypes, _additionalDestructuringPolicies, auditing); diff --git a/src/Serilog/Parameters/PropertyValueConverter.cs b/src/Serilog/Parameters/PropertyValueConverter.cs index 8ee1a3a1c..eba35edb0 100755 --- a/src/Serilog/Parameters/PropertyValueConverter.cs +++ b/src/Serilog/Parameters/PropertyValueConverter.cs @@ -47,11 +47,13 @@ partial class PropertyValueConverter : ILogEventPropertyFactory, ILogEventProper readonly IScalarConversionPolicy[] _scalarConversionPolicies; readonly int _maximumDestructuringDepth; readonly int _maximumStringLength; + readonly int _maximumCollectionLength; readonly bool _propagateExceptions; public PropertyValueConverter( int maximumDestructuringDepth, int maximumStringLength, + int maximumCollectionLength, IEnumerable additionalScalarTypes, IEnumerable additionalDestructuringPolicies, bool propagateExceptions) @@ -60,10 +62,12 @@ public PropertyValueConverter( if (additionalDestructuringPolicies == null) throw new ArgumentNullException(nameof(additionalDestructuringPolicies)); if (maximumDestructuringDepth < 0) throw new ArgumentOutOfRangeException(nameof(maximumDestructuringDepth)); if (maximumStringLength < 2) throw new ArgumentOutOfRangeException(nameof(maximumDestructuringDepth)); + if (maximumCollectionLength < 1) throw new ArgumentOutOfRangeException(nameof(maximumCollectionLength)); _maximumDestructuringDepth = maximumDestructuringDepth; _propagateExceptions = propagateExceptions; _maximumStringLength = maximumStringLength; + _maximumCollectionLength = maximumCollectionLength; _scalarConversionPolicies = new IScalarConversionPolicy[] { @@ -175,7 +179,7 @@ LogEventPropertyValue CreatePropertyValue(object value, Destructuring destructur var keyProperty = typeInfo.GetDeclaredProperty("Key"); var valueProperty = typeInfo.GetDeclaredProperty("Value"); - return new DictionaryValue(enumerable.Cast() + return new DictionaryValue(enumerable.Cast().Take(_maximumCollectionLength) .Select(kvp => new KeyValuePair( (ScalarValue)limiter.CreatePropertyValue(keyProperty.GetValue(kvp), destructuring), limiter.CreatePropertyValue(valueProperty.GetValue(kvp), destructuring))) @@ -183,7 +187,7 @@ LogEventPropertyValue CreatePropertyValue(object value, Destructuring destructur } return new SequenceValue( - enumerable.Cast().Select(o => limiter.CreatePropertyValue(o, destructuring))); + enumerable.Cast().Take(_maximumCollectionLength).Select(o => limiter.CreatePropertyValue(o, destructuring))); } if (destructuring == Destructuring.Destructure) diff --git a/test/Serilog.Tests/Core/LogEventPropertyCapturingTests.cs b/test/Serilog.Tests/Core/LogEventPropertyCapturingTests.cs index dc7499bb5..7cb4a3ae7 100644 --- a/test/Serilog.Tests/Core/LogEventPropertyCapturingTests.cs +++ b/test/Serilog.Tests/Core/LogEventPropertyCapturingTests.cs @@ -179,7 +179,7 @@ static IEnumerable Capture(string messageTemplate, params obje { var mt = new MessageTemplateParser().Parse(messageTemplate); var binder = new PropertyBinder( - new PropertyValueConverter(10, 1000, Enumerable.Empty(), Enumerable.Empty(), false)); + new PropertyValueConverter(10, 1000, 1000, Enumerable.Empty(), Enumerable.Empty(), false)); return binder.ConstructProperties(mt, properties); } diff --git a/test/Serilog.Tests/Core/MessageTemplateTests.cs b/test/Serilog.Tests/Core/MessageTemplateTests.cs index 93be20b1f..b76185d36 100755 --- a/test/Serilog.Tests/Core/MessageTemplateTests.cs +++ b/test/Serilog.Tests/Core/MessageTemplateTests.cs @@ -126,7 +126,7 @@ static string Render(string messageTemplate, params object[] properties) static string Render(IFormatProvider formatProvider, string messageTemplate, params object[] properties) { var mt = new MessageTemplateParser().Parse(messageTemplate); - var binder = new PropertyBinder(new PropertyValueConverter(10, 1000, Enumerable.Empty(), Enumerable.Empty(), false)); + var binder = new PropertyBinder(new PropertyValueConverter(10, 1000, 1000, Enumerable.Empty(), Enumerable.Empty(), false)); var props = binder.ConstructProperties(mt, properties); var output = new StringBuilder(); var writer = new StringWriter(output); diff --git a/test/Serilog.Tests/Events/LogEventPropertyValueTests.cs b/test/Serilog.Tests/Events/LogEventPropertyValueTests.cs index b3b0093a7..534351075 100644 --- a/test/Serilog.Tests/Events/LogEventPropertyValueTests.cs +++ b/test/Serilog.Tests/Events/LogEventPropertyValueTests.cs @@ -27,7 +27,7 @@ namespace Serilog.Tests.Events public class LogEventPropertyValueTests { readonly PropertyValueConverter _converter = - new PropertyValueConverter(10, 1000, Enumerable.Empty(), Enumerable.Empty(), false); + new PropertyValueConverter(10, 1000, 1000, Enumerable.Empty(), Enumerable.Empty(), false); [Fact] public void AnEnumIsConvertedToANonStringScalarValue() diff --git a/test/Serilog.Tests/Parameters/PropertyValueConverterTests.cs b/test/Serilog.Tests/Parameters/PropertyValueConverterTests.cs index 8163114c1..025f1e979 100644 --- a/test/Serilog.Tests/Parameters/PropertyValueConverterTests.cs +++ b/test/Serilog.Tests/Parameters/PropertyValueConverterTests.cs @@ -13,7 +13,7 @@ namespace Serilog.Tests.Parameters public class PropertyValueConverterTests { readonly PropertyValueConverter _converter = - new PropertyValueConverter(10, 1000, Enumerable.Empty(), Enumerable.Empty(), false); + new PropertyValueConverter(10, 1000, 1000, Enumerable.Empty(), Enumerable.Empty(), false); [Fact] public void UnderDestructuringAByteArrayIsAScalarValue() From 09092ea8c5793cdd11dc0bc724cc171fbb84e230 Mon Sep 17 00:00:00 2001 From: Nicholas Blumhardt Date: Thu, 6 Oct 2016 09:53:41 +1000 Subject: [PATCH 20/53] A more informative and welcoming README (#873) A more informative and welcoming README [Skip CI] --- README.md | 106 ++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 100 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index b1939a77e..476abb42c 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,110 @@ #Serilog [![Build status](https://ci.appveyor.com/api/projects/status/b9rm3l7kduryjgcj/branch/master?svg=true)](https://ci.appveyor.com/project/serilog/serilog/branch/master) [![NuGet Version](http://img.shields.io/nuget/v/Serilog.svg?style=flat)](https://www.nuget.org/packages/Serilog/) [![Rager Releases](http://rager.io/badge.svg?url=https%3A%2F%2Fwww.nuget.org%2Fpackages%2FSerilog%2F)](http://rager.io/projects/search?badge=1&query=nuget.org/packages/Serilog/) [![Join the chat at https://gitter.im/serilog/serilog](https://img.shields.io/gitter/room/serilog/serilog.svg)](https://gitter.im/serilog/serilog) [![Stack Overflow](https://img.shields.io/badge/stackoverflow-serilog-orange.svg)](http://stackoverflow.com/questions/tagged/serilog) -Serilog combines the best features of traditional and structured diagnostic logging in an easy-to-use package. +Serilog is a diagnostic logging library for .NET applications. It is easy to set up, has a clean API, and runs on all recent .NET platforms. While it's useful even in the simplest applications, Serilog's support for structured logging shines when instrumenting complex, distributed, and asynchronous applications and systems. -* **[2.0 Upgrade Guide](https://github.com/serilog/serilog/wiki/2.x-Upgrade-Guide)** and [Release Notes](https://github.com/serilog/serilog/blob/dev/CHANGES.md) -* [Homepage](http://serilog.net) -* [Documentation](https://github.com/serilog/serilog/wiki) +[![Serilog](https://serilog.net/images/serilog-180px.png)](https://serilog.net) -Would you like to help make Serilog even better? We keep a list of issues that are approachable for newcomers under the [up-for-grabs](https://github.com/serilog/serilog/issues?labels=up-for-grabs&state=open) label! +Like many other libraries for .NET, Serilog provides diagnostic logging to [files](https://github.com/serilog/serilog-sinks-rollingfile), the [console](https://github.com/serilog/serilog-sinks-literate), and [many other outputs](https://github.com/serilog/serilog/wiki/Provided-Sinks). + +```csharp +var log = new LoggerConfiguration() + .WriteTo.LiterateConsole() + .WriteTo.RollingFile("log-{Date}.txt") + .CreateLogger(); + +log.Information("Hello, Serilog!"); +``` + +Unlike other logging libraries, Serilog is built from the ground up to record _structured event data_. + +```csharp +var position = new { Latitude = 25, Longitude = 134 }; +var elapsedMs = 34; + +log.Information("Processed {@Position} in {Elapsed} ms.", position, elapsedMs); +``` + +Serilog uses [message templates](https://messagetemplates.org), a simple DSL that extends .NET format strings with _named_ as well as positional parameters. Instead of formatting events immediately into text, Serilog captures the values associated with each named parameter. + +The example above records two properties, `Position` and `Elapsed`, in the log event. The `@` operator in front of `Position` tells Serilog to _serialize_ the object passed in, rather than convert it using `ToString()`. Serilog's deep and rich support for structured event data opens up a huge range of diagnostic possibilities not available when using traditional loggers. + +Rendered into [JSON format](https://github.com/serilog/serilog-formatting-compact) for example, these properties appear alongside the timestamp, level, and message like: + +```json +{"Position": {"Latitude": 25, "Longitude": 134}, "Elapsed": 34} +``` + +Back-ends that are capable of recording structured event data make log searches and analysis possible without log parsing or regular expressions. + +Supporting structured data doesn't mean giving up text: when Serilog writes events to files or the console, the template and properties are rendered into friendly human-readable text just like a traditional logging library would produce: + +``` +09:14:22 [INF] Processed { Latitude: 25, Longitude: 134 } in 34 ms. +``` + +> **Upgrading from Serilog 1.x?** Check out the [2.0 Upgrade Guide](https://github.com/serilog/serilog/wiki/2.x-Upgrade-Guide) and [Release Notes](https://github.com/serilog/serilog/blob/dev/CHANGES.md). + +### Features + + * **Community-backed and actively developed** + * Format-based logging API with familiar [levels](https://github.com/serilog/serilog/wiki/Configuration-Basics#minimum-level) like `Debug`, `Information`, `Warning`, `Error`, and so-on + * Discoverable C# configuration syntax and optional [XML](https://github.com/serilog/serilog-settings-appsettings) or [JSON](https://github.com/serilog/serilog-settings-configuration) configuration support + * Efficient when enabled, extremely low overhead when a logging level is switched off + * Best-in-class .NET Core support, including a [provider for _Microsoft.Extensions.Logging_](https://github.com/serilog/serilog-extensions-logging) + * Support for a [comprehensive range of sinks](https://github.com/serilog/serilog/wiki/Provided-Sinks), including files, the console, on-premises and cloud-based log servers, databases, and message queues + * Sophisticated [enrichment](https://github.com/serilog/serilog/wiki/Enrichment) of log events with contextual information, including scoped (`LogContext`) properties, thread and process identifiers, and domain-specific correlation ids such as `HttpRequestId` + * Zero-shared-state `Logger` objects, with an optional global static `Log` class + * Format-agnostic logging pipeline that can emit events in plain text, JSON, in-memory `LogEvent` objects (including [Rx pipelines](https://github.com/serilog/serilog-sinks-observable)) and other formats + +### Getting started + +Serilog is installed from NuGet. To view log events, one or more sinks need to be installed as well, here we'll use the pretty-printing "literate" console sink, and a rolling file set: + +``` +Install-Package Serilog +Install-Package Serilog.Sinks.Literate +Install-Package Serilog.Sinks.RollingFile +``` + +The simplest way to set up Serilog is using the static `Log` class. A `LoggerConfiguration` is used to create and assign the default logger. + +```csharp +public class Program +{ + public static void Main() + { + Log.Logger = new LoggerConfiguration() + .MinimumLevel.Information() + .WriteTo.LiterateConsole() + .WriteTo.RollingFile("log-{Date}.txt") + .CreateLogger(); + } +} +``` + + +Find more, including a runnable example application, under the [Getting Started topic](https://github.com/serilog/serilog/wiki/Getting-Started) in the [documentation](https://github.com/serilog/serilog/wiki/). + +### Getting help + +To learn more about Serilog, check out the [documentation](https://github.com/serilog/serilog/wiki) - you'll find information there on the most common scenarios. If Serilog isn't working the way you expect, you may find the [troubleshooting guide](https://github.com/serilog/serilog/wiki/Debugging-and-Diagnostics) useful. + +Serilog has an active and helpful community who are happy to help point you in the right direction or work through any issues you might encounter. You can get in touch via: + + * [Stack Overflow](http://stackoverflow.com/questions/tagged/serilog) - this is the best place to start if you have a question + * Our [issue tracker](https://github.com/serilog/serilog/issues) here on GitHub + * [Gitter chat](https://gitter.im/serilog/serilog) + * The [#serilog tag on Twitter](https://twitter.com/search?q=%23serilog) + +### Contributing + +Would you like to help make Serilog even better? We keep a list of issues that are approachable for newcomers under the [up-for-grabs](https://github.com/serilog/serilog/issues?labels=up-for-grabs&state=open) label. Before starting work on a pull request, we suggest commenting on, or raising, an issue on the issue tracker so that we can help and coordinate efforts. + +### Detailed build status Branch | AppVeyor | Travis ------------- | ------------- |------------- dev | [![Build status](https://ci.appveyor.com/api/projects/status/b9rm3l7kduryjgcj/branch/dev?svg=true)](https://ci.appveyor.com/project/serilog/serilog/branch/dev) | [![Build Status](https://travis-ci.org/serilog/serilog.svg?branch=dev)](https://travis-ci.org/serilog/serilog) master | [![Build status](https://ci.appveyor.com/api/projects/status/b9rm3l7kduryjgcj/branch/master?svg=true)](https://ci.appveyor.com/project/serilog/serilog/branch/master) | [![Build Status](https://travis-ci.org/serilog/serilog.svg?branch=master)](https://travis-ci.org/serilog/serilog) -_Copyright © 2013-2016 Serilog Contributors - Provided under the [Apache License, Version 2.0](http://apache.org/licenses/LICENSE-2.0.html). Needle and thread logo a derivative of work by [Kenneth Appiah](http://www.kensets.com/)._ +_Serilog is copyright © 2013-2016 Serilog Contributors - Provided under the [Apache License, Version 2.0](http://apache.org/licenses/LICENSE-2.0.html). Needle and thread logo a derivative of work by [Kenneth Appiah](http://www.kensets.com/)._ From 5e5fa770a7fd84f0caaf542b2345fbe6842c162c Mon Sep 17 00:00:00 2001 From: Artur Krajewski Date: Thu, 6 Oct 2016 08:49:52 +0200 Subject: [PATCH 21/53] Basic tests for collection limiting. --- .../LoggerDestructuringConfiguration.cs | 2 +- .../Serilog.Tests/LoggerConfigurationTests.cs | 64 ++++++++++++++----- 2 files changed, 49 insertions(+), 17 deletions(-) diff --git a/src/Serilog/Configuration/LoggerDestructuringConfiguration.cs b/src/Serilog/Configuration/LoggerDestructuringConfiguration.cs index debcbc1ad..e54528a27 100644 --- a/src/Serilog/Configuration/LoggerDestructuringConfiguration.cs +++ b/src/Serilog/Configuration/LoggerDestructuringConfiguration.cs @@ -182,7 +182,7 @@ public LoggerConfiguration ToMaximumStringLength(int maximumStringLength) /// /// Configuration object allowing method chaining. /// When passed length is less than 1 - public LoggerConfiguration ToMaximumCollectionLengthLength(int maximumCollectionLength) + public LoggerConfiguration ToMaximumCollectionLength(int maximumCollectionLength) { if (maximumCollectionLength < 1) throw new ArgumentOutOfRangeException(nameof(maximumCollectionLength), maximumCollectionLength, "Maximum collection length must be at least one."); _setMaximumCollectionLength(maximumCollectionLength); diff --git a/test/Serilog.Tests/LoggerConfigurationTests.cs b/test/Serilog.Tests/LoggerConfigurationTests.cs index f4173a483..ccc0a0046 100644 --- a/test/Serilog.Tests/LoggerConfigurationTests.cs +++ b/test/Serilog.Tests/LoggerConfigurationTests.cs @@ -377,7 +377,7 @@ public void MaximumStringLengthEffectiveForStringifiedObject() } [Fact] - public void MaximumStringNOTLengthEffectiveForObject() + public void MaximumStringLengthNOTEffectiveForObject() { var x = new ToStringOfLength(4); @@ -393,36 +393,68 @@ public void MaximumStringNOTLengthEffectiveForObject() Assert.Contains("####", limitedText); } + class ToStringOfLength + { + private int _toStringOfLength; + + public ToStringOfLength(int toStringOfLength) + { + _toStringOfLength = toStringOfLength; + } + + public override string ToString() + { + return new string('#', _toStringOfLength); + } + } + [Fact] - public void MaximumStringLengthNOTEffectiveForObject() + public void MaximumStringCollectionThrowsForLimitLowerThan1() { - var x = new ToStringOfLength(4); + var ex = Assert.Throws( + () => new LoggerConfiguration().Destructure.ToMaximumCollectionLength(0)); + Assert.Equal(0, ex.ActualValue); + } + + [Fact] + public void MaximumCollectionLengthEffectiveForArray() + { + var x = new[] { 1, 2, 3, 4 }; LogEvent evt = null; var log = new LoggerConfiguration() .WriteTo.Sink(new DelegatingSink(e => evt = e)) - .Destructure.ToMaximumStringLength(3) + .Destructure.ToMaximumCollectionLength(3) .CreateLogger(); log.Information("{X}", x); - var limitedText = evt.Properties["X"].ToString(); + var limitedCollection = evt.Properties["X"].ToString(); - Assert.Contains("####", limitedText); + Assert.Contains("3", limitedCollection); + Assert.DoesNotContain("4", limitedCollection); } - class ToStringOfLength + [Fact] + public void MaximumCollectionLengthEffectiveForDictionary() { - private int _toStringOfLength; - - public ToStringOfLength(int toStringOfLength) + var x = new Dictionary { - _toStringOfLength = toStringOfLength; - } + {"1", 1}, + {"2", 2}, + {"3", 3} + }; - public override string ToString() - { - return new string('#', _toStringOfLength); - } + LogEvent evt = null; + var log = new LoggerConfiguration() + .WriteTo.Sink(new DelegatingSink(e => evt = e)) + .Destructure.ToMaximumCollectionLength(2) + .CreateLogger(); + + log.Information("{X}", x); + var limitedCollection = evt.Properties["X"].ToString(); + + Assert.Contains("2", limitedCollection); + Assert.DoesNotContain("3", limitedCollection); } [Fact] From 3417ac8bae8ec7f7c790f14c7ffdb28b985ea0dd Mon Sep 17 00:00:00 2001 From: Artur Krajewski Date: Thu, 6 Oct 2016 09:10:44 +0200 Subject: [PATCH 22/53] Simplified tests for payload limiting. --- .../Serilog.Tests/LoggerConfigurationTests.cs | 97 +++++-------------- 1 file changed, 23 insertions(+), 74 deletions(-) diff --git a/test/Serilog.Tests/LoggerConfigurationTests.cs b/test/Serilog.Tests/LoggerConfigurationTests.cs index ccc0a0046..a6c17f0dc 100644 --- a/test/Serilog.Tests/LoggerConfigurationTests.cs +++ b/test/Serilog.Tests/LoggerConfigurationTests.cs @@ -5,6 +5,7 @@ using System.Reflection; using System.Runtime.InteropServices.ComTypes; using Xunit; +using Serilog.Configuration; using Serilog.Core; using Serilog.Core.Filters; using Serilog.Events; @@ -265,14 +266,7 @@ public void MaximumDestructuringDepthIsEffective() } }; - LogEvent evt = null; - var log = new LoggerConfiguration() - .WriteTo.Sink(new DelegatingSink(e => evt = e)) - .Destructure.ToMaximumDepth(3) - .CreateLogger(); - - log.Information("{@X}", x); - var xs = evt.Properties["X"].ToString(); + var xs = LogAndGetAsString(x, conf => conf.Destructure.ToMaximumStringLength(3), "@"); Assert.Contains("C", xs); Assert.DoesNotContain(xs, "D"); @@ -290,15 +284,7 @@ public void MaximumStringLengthThrowsForLimitLowerThan2() public void MaximumStringLengthNOTEffectiveForString() { var x = "ABCD"; - - LogEvent evt = null; - var log = new LoggerConfiguration() - .WriteTo.Sink(new DelegatingSink(e => evt = e)) - .Destructure.ToMaximumStringLength(3) - .CreateLogger(); - - log.Information("{X}", x); - var limitedText = evt.Properties["X"].ToString(); + var limitedText = LogAndGetAsString(x, conf => conf.Destructure.ToMaximumStringLength(3)); Assert.Equal("\"ABCD\"", limitedText); } @@ -308,14 +294,7 @@ public void MaximumStringLengthEffectiveForCapturedString() { var x = "ABCD"; - LogEvent evt = null; - var log = new LoggerConfiguration() - .WriteTo.Sink(new DelegatingSink(e => evt = e)) - .Destructure.ToMaximumStringLength(3) - .CreateLogger(); - - log.Information("{@X}", x); - var limitedText = evt.Properties["X"].ToString(); + var limitedText = LogAndGetAsString(x, conf => conf.Destructure.ToMaximumStringLength(3), "@"); Assert.Equal("\"AB…\"", limitedText); } @@ -325,14 +304,7 @@ public void MaximumStringLengthEffectiveForStringifiedString() { var x = "ABCD"; - LogEvent evt = null; - var log = new LoggerConfiguration() - .WriteTo.Sink(new DelegatingSink(e => evt = e)) - .Destructure.ToMaximumStringLength(3) - .CreateLogger(); - - log.Information("{$X}", x); - var limitedText = evt.Properties["X"].ToString(); + var limitedText = LogAndGetAsString(x, conf => conf.Destructure.ToMaximumStringLength(3), "$"); Assert.Equal("\"AB…\"", limitedText); } @@ -347,14 +319,7 @@ public void MaximumStringLengthEffectiveForCapturedObject(string text, string te TooLongText = text }; - LogEvent evt = null; - var log = new LoggerConfiguration() - .WriteTo.Sink(new DelegatingSink(e => evt = e)) - .Destructure.ToMaximumStringLength(limit) - .CreateLogger(); - - log.Information("{@X}", x); - var limitedText = evt.Properties["X"].ToString(); + var limitedText = LogAndGetAsString(x, conf => conf.Destructure.ToMaximumStringLength(limit), "@"); Assert.Contains(textAfter, limitedText); } @@ -364,15 +329,7 @@ public void MaximumStringLengthEffectiveForStringifiedObject() { var x = new ToStringOfLength(4); - LogEvent evt = null; - var log = new LoggerConfiguration() - .WriteTo.Sink(new DelegatingSink(e => evt = e)) - .Destructure.ToMaximumStringLength(3) - .CreateLogger(); - - log.Information("{$X}", x); - var limitedText = evt.Properties["X"].ToString(); - + var limitedText = LogAndGetAsString(x, conf => conf.Destructure.ToMaximumStringLength(3), "$"); Assert.Contains("##…", limitedText); } @@ -381,14 +338,7 @@ public void MaximumStringLengthNOTEffectiveForObject() { var x = new ToStringOfLength(4); - LogEvent evt = null; - var log = new LoggerConfiguration() - .WriteTo.Sink(new DelegatingSink(e => evt = e)) - .Destructure.ToMaximumStringLength(3) - .CreateLogger(); - - log.Information("{X}", x); - var limitedText = evt.Properties["X"].ToString(); + var limitedText = LogAndGetAsString(x, conf => conf.Destructure.ToMaximumStringLength(3)); Assert.Contains("####", limitedText); } @@ -421,14 +371,7 @@ public void MaximumCollectionLengthEffectiveForArray() { var x = new[] { 1, 2, 3, 4 }; - LogEvent evt = null; - var log = new LoggerConfiguration() - .WriteTo.Sink(new DelegatingSink(e => evt = e)) - .Destructure.ToMaximumCollectionLength(3) - .CreateLogger(); - - log.Information("{X}", x); - var limitedCollection = evt.Properties["X"].ToString(); + var limitedCollection = LogAndGetAsString(x, conf => conf.Destructure.ToMaximumCollectionLength(3)); Assert.Contains("3", limitedCollection); Assert.DoesNotContain("4", limitedCollection); @@ -444,19 +387,25 @@ public void MaximumCollectionLengthEffectiveForDictionary() {"3", 3} }; - LogEvent evt = null; - var log = new LoggerConfiguration() - .WriteTo.Sink(new DelegatingSink(e => evt = e)) - .Destructure.ToMaximumCollectionLength(2) - .CreateLogger(); - - log.Information("{X}", x); - var limitedCollection = evt.Properties["X"].ToString(); + var limitedCollection = LogAndGetAsString(x, conf => conf.Destructure.ToMaximumCollectionLength(2)); Assert.Contains("2", limitedCollection); Assert.DoesNotContain("3", limitedCollection); } + private string LogAndGetAsString(object x, Func conf, string destructuringSymbol = "") + { + LogEvent evt = null; + var logConf = new LoggerConfiguration() + .WriteTo.Sink(new DelegatingSink(e => evt = e)); + logConf = conf(logConf); + var log = logConf.CreateLogger(); + + log.Information($"{{{destructuringSymbol}X}}", x); + var result = evt.Properties["X"].ToString(); + return result; + } + [Fact] public void DynamicallySwitchingSinkRestrictsOutput() { From f68f470e50a0058e7fdc98c455304cbd86469cfd Mon Sep 17 00:00:00 2001 From: Artur Krajewski Date: Mon, 10 Oct 2016 21:52:58 +0200 Subject: [PATCH 23/53] Tests for array and dictionary not exceeding limit. --- .../Serilog.Tests/LoggerConfigurationTests.cs | 28 +++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/test/Serilog.Tests/LoggerConfigurationTests.cs b/test/Serilog.Tests/LoggerConfigurationTests.cs index a6c17f0dc..bce08a650 100644 --- a/test/Serilog.Tests/LoggerConfigurationTests.cs +++ b/test/Serilog.Tests/LoggerConfigurationTests.cs @@ -367,7 +367,17 @@ public void MaximumStringCollectionThrowsForLimitLowerThan1() } [Fact] - public void MaximumCollectionLengthEffectiveForArray() + public void MaximumCollectionLengthNotEffectiveForArrayAsLongAsLimit() + { + var x = new[] { 1, 2, 3 }; + + var limitedCollection = LogAndGetAsString(x, conf => conf.Destructure.ToMaximumCollectionLength(3)); + + Assert.Contains("3", limitedCollection); + } + + [Fact] + public void MaximumCollectionLengthEffectiveForArrayThanLimit() { var x = new[] { 1, 2, 3, 4 }; @@ -378,7 +388,7 @@ public void MaximumCollectionLengthEffectiveForArray() } [Fact] - public void MaximumCollectionLengthEffectiveForDictionary() + public void MaximumCollectionLengthEffectiveForDictionaryWithMoreKeysThanLimit() { var x = new Dictionary { @@ -393,6 +403,20 @@ public void MaximumCollectionLengthEffectiveForDictionary() Assert.DoesNotContain("3", limitedCollection); } + [Fact] + public void MaximumCollectionLengthNotEffectiveForDictionaryWithAsManyKeysAsLimit() + { + var x = new Dictionary + { + {"1", 1}, + {"2", 2}, + }; + + var limitedCollection = LogAndGetAsString(x, conf => conf.Destructure.ToMaximumCollectionLength(2)); + + Assert.Contains("2", limitedCollection); + } + private string LogAndGetAsString(object x, Func conf, string destructuringSymbol = "") { LogEvent evt = null; From 750e75520cb5d546a667e7186092dfeeb93acedd Mon Sep 17 00:00:00 2001 From: Sergey Komisarchik Date: Wed, 19 Oct 2016 14:10:47 +0300 Subject: [PATCH 24/53] message template cache perf --- ...lateCacheBenchmark_Cached-report-github.md | 46 ++++++++++++ ...ateCacheBenchmark_Leaking-report-github.md | 22 ++++++ ...lateCacheBenchmark_Warmup-report-github.md | 40 ++++++++++ ...lateCacheBenchmark_Cached-report-github.md | 46 ++++++++++++ ...ateCacheBenchmark_Leaking-report-github.md | 22 ++++++ ...lateCacheBenchmark_Warmup-report-github.md | 40 ++++++++++ .../Core/Pipeline/MessageTemplateCache.cs | 14 +++- src/Serilog/Properties/AssemblyInfo.cs | 7 ++ src/Serilog/project.json | 7 +- test/Serilog.PerformanceTests/Harness.cs | 12 ++- .../MessageTemplateCacheBenchmark_Cached.cs | 73 +++++++++++++++++++ .../MessageTemplateCacheBenchmark_Leaking.cs | 60 +++++++++++++++ .../MessageTemplateCacheBenchmark_Warmup.cs | 58 +++++++++++++++ .../ConcurrentDictionaryTemplateCache.cs | 54 ++++++++++++++ .../Support/DictionaryMessageTemplateCache.cs | 57 +++++++++++++++ .../Support/NoOpMessageTemplateParser.cs | 16 ++++ test/Serilog.PerformanceTests/project.json | 6 +- 17 files changed, 569 insertions(+), 11 deletions(-) create mode 100644 results/net4.5.2/MessageTemplateCache/MessageTemplateCacheBenchmark_Cached-report-github.md create mode 100644 results/net4.5.2/MessageTemplateCache/MessageTemplateCacheBenchmark_Leaking-report-github.md create mode 100644 results/net4.5.2/MessageTemplateCache/MessageTemplateCacheBenchmark_Warmup-report-github.md create mode 100644 results/netcoreapp1.0/MessageTemplateCache/MessageTemplateCacheBenchmark_Cached-report-github.md create mode 100644 results/netcoreapp1.0/MessageTemplateCache/MessageTemplateCacheBenchmark_Leaking-report-github.md create mode 100644 results/netcoreapp1.0/MessageTemplateCache/MessageTemplateCacheBenchmark_Warmup-report-github.md create mode 100644 test/Serilog.PerformanceTests/MessageTemplateCacheBenchmark/MessageTemplateCacheBenchmark_Cached.cs create mode 100644 test/Serilog.PerformanceTests/MessageTemplateCacheBenchmark/MessageTemplateCacheBenchmark_Leaking.cs create mode 100644 test/Serilog.PerformanceTests/MessageTemplateCacheBenchmark/MessageTemplateCacheBenchmark_Warmup.cs create mode 100644 test/Serilog.PerformanceTests/Support/ConcurrentDictionaryTemplateCache.cs create mode 100644 test/Serilog.PerformanceTests/Support/DictionaryMessageTemplateCache.cs create mode 100644 test/Serilog.PerformanceTests/Support/NoOpMessageTemplateParser.cs diff --git a/results/net4.5.2/MessageTemplateCache/MessageTemplateCacheBenchmark_Cached-report-github.md b/results/net4.5.2/MessageTemplateCache/MessageTemplateCacheBenchmark_Cached-report-github.md new file mode 100644 index 000000000..eb27c3f52 --- /dev/null +++ b/results/net4.5.2/MessageTemplateCache/MessageTemplateCacheBenchmark_Cached-report-github.md @@ -0,0 +1,46 @@ +```ini + +Host Process Environment Information: +BenchmarkDotNet.Core=v0.9.9.0 +OS=Microsoft Windows NT 6.2.9200.0 +Processor=Intel(R) Core(TM) i7-4790 CPU 3.60GHz, ProcessorCount=8 +Frequency=3507509 ticks, Resolution=285.1026 ns, Timer=TSC +CLR=MS.NET 4.0.30319.42000, Arch=64-bit RELEASE [RyuJIT] +GC=Concurrent Workstation +JitModules=clrjit-v4.6.1586.0 + +Type=MessageTemplateCacheBenchmark_Cached Mode=Throughput + +``` + Method | Items | MaxDegreeOfParallelism | Median | StdDev | Scaled | Scaled-SD | +----------- |------ |----------------------- |------------ |----------- |------- |---------- | + **Dictionary** | **10** | **-1** | **5.1555 us** | **0.0507 us** | **1.00** | **0.00** | + Hashtable | 10 | -1 | 4.4010 us | 0.0434 us | 0.85 | 0.01 | + Concurrent | 10 | -1 | 4.4323 us | 0.0677 us | 0.86 | 0.02 | + **Dictionary** | **10** | **1** | **2.9110 us** | **0.1116 us** | **1.00** | **0.00** | + Hashtable | 10 | 1 | 2.8101 us | 0.1591 us | 0.97 | 0.07 | + Concurrent | 10 | 1 | 2.8076 us | 0.1195 us | 0.98 | 0.05 | + **Dictionary** | **20** | **-1** | **9.5123 us** | **0.1870 us** | **1.00** | **0.00** | + Hashtable | 20 | -1 | 5.7014 us | 0.0338 us | 0.60 | 0.01 | + Concurrent | 20 | -1 | 5.7452 us | 0.0450 us | 0.60 | 0.01 | + **Dictionary** | **20** | **1** | **4.4900 us** | **0.2552 us** | **1.00** | **0.00** | + Hashtable | 20 | 1 | 3.9176 us | 0.2345 us | 0.89 | 0.07 | + Concurrent | 20 | 1 | 3.8974 us | 0.2235 us | 0.88 | 0.07 | + **Dictionary** | **50** | **-1** | **27.0377 us** | **1.2542 us** | **1.00** | **0.00** | + Hashtable | 50 | -1 | 7.5979 us | 0.0627 us | 0.28 | 0.01 | + Concurrent | 50 | -1 | 7.6123 us | 0.0427 us | 0.28 | 0.01 | + **Dictionary** | **50** | **1** | **8.0037 us** | **0.5410 us** | **1.00** | **0.00** | + Hashtable | 50 | 1 | 7.1405 us | 0.5055 us | 0.90 | 0.08 | + Concurrent | 50 | 1 | 7.2504 us | 0.5599 us | 0.90 | 0.09 | + **Dictionary** | **100** | **-1** | **53.3566 us** | **7.5268 us** | **1.00** | **0.00** | + Hashtable | 100 | -1 | 10.1725 us | 0.0903 us | 0.19 | 0.02 | + Concurrent | 100 | -1 | 10.1390 us | 0.1140 us | 0.19 | 0.02 | + **Dictionary** | **100** | **1** | **13.4704 us** | **0.0694 us** | **1.00** | **0.00** | + Hashtable | 100 | 1 | 11.7881 us | 0.6524 us | 0.88 | 0.05 | + Concurrent | 100 | 1 | 12.0876 us | 0.8071 us | 0.91 | 0.06 | + **Dictionary** | **1000** | **-1** | **482.1057 us** | **17.5977 us** | **1.00** | **0.00** | + Hashtable | 1000 | -1 | 33.1846 us | 0.3355 us | 0.07 | 0.00 | + Concurrent | 1000 | -1 | 32.8517 us | 0.3190 us | 0.07 | 0.00 | + **Dictionary** | **1000** | **1** | **124.2795 us** | **0.7927 us** | **1.00** | **0.00** | + Hashtable | 1000 | 1 | 105.6937 us | 1.1198 us | 0.85 | 0.01 | + Concurrent | 1000 | 1 | 103.0948 us | 0.8157 us | 0.83 | 0.01 | diff --git a/results/net4.5.2/MessageTemplateCache/MessageTemplateCacheBenchmark_Leaking-report-github.md b/results/net4.5.2/MessageTemplateCache/MessageTemplateCacheBenchmark_Leaking-report-github.md new file mode 100644 index 000000000..a20c45f4e --- /dev/null +++ b/results/net4.5.2/MessageTemplateCache/MessageTemplateCacheBenchmark_Leaking-report-github.md @@ -0,0 +1,22 @@ +```ini + +Host Process Environment Information: +BenchmarkDotNet.Core=v0.9.9.0 +OS=Microsoft Windows NT 6.2.9200.0 +Processor=Intel(R) Core(TM) i7-4790 CPU 3.60GHz, ProcessorCount=8 +Frequency=3507509 ticks, Resolution=285.1026 ns, Timer=TSC +CLR=MS.NET 4.0.30319.42000, Arch=64-bit RELEASE [RyuJIT] +GC=Concurrent Workstation +JitModules=clrjit-v4.6.1586.0 + +Type=MessageTemplateCacheBenchmark_Leaking Mode=Throughput + +``` + Method | Items | MaxDegreeOfParallelism | Median | StdDev | Scaled | Scaled-SD | +----------- |------ |----------------------- |------------ |---------- |------- |---------- | + **Dictionary** | **10000** | **-1** | **4.2101 ms** | **0.1586 ms** | **1.00** | **0.00** | + Hashtable | 10000 | -1 | 3.2149 ms | 0.0669 ms | 0.76 | 0.03 | + Concurrent | 10000 | -1 | 114.5999 ms | 9.5658 ms | 26.59 | 2.44 | + **Dictionary** | **10000** | **1** | **1.9974 ms** | **0.0107 ms** | **1.00** | **0.00** | + Hashtable | 10000 | 1 | 2.0848 ms | 0.0108 ms | 1.04 | 0.01 | + Concurrent | 10000 | 1 | 176.0598 ms | 0.9919 ms | 88.22 | 0.67 | diff --git a/results/net4.5.2/MessageTemplateCache/MessageTemplateCacheBenchmark_Warmup-report-github.md b/results/net4.5.2/MessageTemplateCache/MessageTemplateCacheBenchmark_Warmup-report-github.md new file mode 100644 index 000000000..fec79be6a --- /dev/null +++ b/results/net4.5.2/MessageTemplateCache/MessageTemplateCacheBenchmark_Warmup-report-github.md @@ -0,0 +1,40 @@ +```ini + +Host Process Environment Information: +BenchmarkDotNet.Core=v0.9.9.0 +OS=Microsoft Windows NT 6.2.9200.0 +Processor=Intel(R) Core(TM) i7-4790 CPU 3.60GHz, ProcessorCount=8 +Frequency=3507509 ticks, Resolution=285.1026 ns, Timer=TSC +CLR=MS.NET 4.0.30319.42000, Arch=64-bit RELEASE [RyuJIT] +GC=Concurrent Workstation +JitModules=clrjit-v4.6.1586.0 + +Type=MessageTemplateCacheBenchmark_Warmup Mode=Throughput + +``` + Method | Items | MaxDegreeOfParallelism | Median | StdDev | Scaled | Scaled-SD | +----------- |------ |----------------------- |---------------- |-------------- |------- |---------- | + **Dictionary** | **10** | **-1** | **8.9109 us** | **0.0617 us** | **1.00** | **0.00** | + Hashtable | 10 | -1 | 9.8728 us | 1.2285 us | 1.14 | 0.14 | + Concurrent | 10 | -1 | 21.8053 us | 1.1545 us | 2.43 | 0.13 | + **Dictionary** | **10** | **1** | **4.2491 us** | **0.1455 us** | **1.00** | **0.00** | + Hashtable | 10 | 1 | 4.3648 us | 0.1695 us | 1.02 | 0.05 | + Concurrent | 10 | 1 | 10.6738 us | 1.2851 us | 2.63 | 0.31 | + **Dictionary** | **100** | **-1** | **102.2525 us** | **1.4686 us** | **1.00** | **0.00** | + Hashtable | 100 | -1 | 106.2933 us | 15.0920 us | 1.08 | 0.15 | + Concurrent | 100 | -1 | 832.1438 us | 15.1188 us | 8.09 | 0.18 | + **Dictionary** | **100** | **1** | **24.2769 us** | **0.4100 us** | **1.00** | **0.00** | + Hashtable | 100 | 1 | 25.9633 us | 0.3417 us | 1.06 | 0.02 | + Concurrent | 100 | 1 | 382.6723 us | 70.3974 us | 15.89 | 2.90 | + **Dictionary** | **1000** | **-1** | **850.1014 us** | **6.2974 us** | **1.00** | **0.00** | + Hashtable | 1000 | -1 | 838.4812 us | 8.0631 us | 0.99 | 0.01 | + Concurrent | 1000 | -1 | 34,932.4300 us | 2,431.0637 us | 41.29 | 2.87 | + **Dictionary** | **1000** | **1** | **243.0266 us** | **1.8920 us** | **1.00** | **0.00** | + Hashtable | 1000 | 1 | 273.0008 us | 3.9633 us | 1.12 | 0.02 | + Concurrent | 1000 | 1 | 15,031.3941 us | 1,152.7616 us | 61.35 | 4.74 | + **Dictionary** | **10000** | **-1** | **4,649.2931 us** | **189.7156 us** | **1.00** | **0.00** | + Hashtable | 10000 | -1 | 4,395.5897 us | 157.3345 us | 0.95 | 0.05 | + Concurrent | 10000 | -1 | 330,528.5888 us | 7,753.0369 us | 71.13 | 3.30 | + **Dictionary** | **10000** | **1** | **2,054.1521 us** | **11.6541 us** | **1.00** | **0.00** | + Hashtable | 10000 | 1 | 2,099.7652 us | 12.9432 us | 1.02 | 0.01 | + Concurrent | 10000 | 1 | 176,623.3173 us | 1,099.8169 us | 85.92 | 0.71 | diff --git a/results/netcoreapp1.0/MessageTemplateCache/MessageTemplateCacheBenchmark_Cached-report-github.md b/results/netcoreapp1.0/MessageTemplateCache/MessageTemplateCacheBenchmark_Cached-report-github.md new file mode 100644 index 000000000..de7a22445 --- /dev/null +++ b/results/netcoreapp1.0/MessageTemplateCache/MessageTemplateCacheBenchmark_Cached-report-github.md @@ -0,0 +1,46 @@ +```ini + +Host Process Environment Information: +BenchmarkDotNet.Core=v0.9.9.0 +OS=Windows +Processor=?, ProcessorCount=8 +Frequency=3507509 ticks, Resolution=285.1026 ns, Timer=TSC +CLR=CORE, Arch=64-bit ? [RyuJIT] +GC=Concurrent Workstation +dotnet cli version: 1.0.0-preview2-003131 + +Type=MessageTemplateCacheBenchmark_Cached Mode=Throughput + +``` + Method | Items | MaxDegreeOfParallelism | Median | StdDev | Scaled | Scaled-SD | +----------- |------ |----------------------- |------------ |----------- |------- |---------- | + **Dictionary** | **10** | **-1** | **5.4201 us** | **0.0512 us** | **1.00** | **0.00** | + Hashtable | 10 | -1 | 4.9545 us | 0.0347 us | 0.91 | 0.01 | + Concurrent | 10 | -1 | 4.9253 us | 0.0159 us | 0.91 | 0.01 | + **Dictionary** | **10** | **1** | **2.0893 us** | **0.0253 us** | **1.00** | **0.00** | + Hashtable | 10 | 1 | 1.9488 us | 0.0386 us | 0.93 | 0.02 | + Concurrent | 10 | 1 | 1.9073 us | 0.0197 us | 0.91 | 0.01 | + **Dictionary** | **20** | **-1** | **9.7756 us** | **0.2778 us** | **1.00** | **0.00** | + Hashtable | 20 | -1 | 6.0307 us | 0.0230 us | 0.61 | 0.02 | + Concurrent | 20 | -1 | 5.9965 us | 0.0173 us | 0.60 | 0.02 | + **Dictionary** | **20** | **1** | **3.4780 us** | **0.0507 us** | **1.00** | **0.00** | + Hashtable | 20 | 1 | 3.2690 us | 0.0749 us | 0.94 | 0.02 | + Concurrent | 20 | 1 | 3.0224 us | 0.0822 us | 0.87 | 0.03 | + **Dictionary** | **50** | **-1** | **29.9933 us** | **1.9805 us** | **1.00** | **0.00** | + Hashtable | 50 | -1 | 7.3773 us | 0.0626 us | 0.25 | 0.02 | + Concurrent | 50 | -1 | 7.2335 us | 0.0518 us | 0.24 | 0.02 | + **Dictionary** | **50** | **1** | **7.4501 us** | **0.0993 us** | **1.00** | **0.00** | + Hashtable | 50 | 1 | 7.0496 us | 0.1044 us | 0.94 | 0.02 | + Concurrent | 50 | 1 | 6.3908 us | 0.0815 us | 0.86 | 0.02 | + **Dictionary** | **100** | **-1** | **66.0669 us** | **10.1367 us** | **1.00** | **0.00** | + Hashtable | 100 | -1 | 9.9072 us | 0.1425 us | 0.15 | 0.02 | + Concurrent | 100 | -1 | 9.6922 us | 0.1097 us | 0.15 | 0.02 | + **Dictionary** | **100** | **1** | **14.1209 us** | **0.2217 us** | **1.00** | **0.00** | + Hashtable | 100 | 1 | 13.1885 us | 0.1276 us | 0.93 | 0.02 | + Concurrent | 100 | 1 | 12.0479 us | 0.2229 us | 0.85 | 0.02 | + **Dictionary** | **1000** | **-1** | **477.3456 us** | **15.2028 us** | **1.00** | **0.00** | + Hashtable | 1000 | -1 | 31.2365 us | 0.3421 us | 0.07 | 0.00 | + Concurrent | 1000 | -1 | 30.6801 us | 0.2434 us | 0.06 | 0.00 | + **Dictionary** | **1000** | **1** | **134.5746 us** | **1.0971 us** | **1.00** | **0.00** | + Hashtable | 1000 | 1 | 126.6081 us | 0.8346 us | 0.94 | 0.01 | + Concurrent | 1000 | 1 | 114.9060 us | 1.3409 us | 0.85 | 0.01 | diff --git a/results/netcoreapp1.0/MessageTemplateCache/MessageTemplateCacheBenchmark_Leaking-report-github.md b/results/netcoreapp1.0/MessageTemplateCache/MessageTemplateCacheBenchmark_Leaking-report-github.md new file mode 100644 index 000000000..2f2d82da6 --- /dev/null +++ b/results/netcoreapp1.0/MessageTemplateCache/MessageTemplateCacheBenchmark_Leaking-report-github.md @@ -0,0 +1,22 @@ +```ini + +Host Process Environment Information: +BenchmarkDotNet.Core=v0.9.9.0 +OS=Windows +Processor=?, ProcessorCount=8 +Frequency=3507509 ticks, Resolution=285.1026 ns, Timer=TSC +CLR=CORE, Arch=64-bit ? [RyuJIT] +GC=Concurrent Workstation +dotnet cli version: 1.0.0-preview2-003131 + +Type=MessageTemplateCacheBenchmark_Leaking Mode=Throughput + +``` + Method | Items | MaxDegreeOfParallelism | Median | StdDev | Scaled | Scaled-SD | +----------- |------ |----------------------- |------------ |----------- |------- |---------- | + **Dictionary** | **10000** | **-1** | **4.0866 ms** | **0.1704 ms** | **1.00** | **0.00** | + Hashtable | 10000 | -1 | 2.3930 ms | 0.1114 ms | 0.59 | 0.04 | + Concurrent | 10000 | -1 | 94.0910 ms | 13.6877 ms | 22.44 | 3.46 | + **Dictionary** | **10000** | **1** | **2.3378 ms** | **0.0076 ms** | **1.00** | **0.00** | + Hashtable | 10000 | 1 | 2.4907 ms | 0.0108 ms | 1.07 | 0.01 | + Concurrent | 10000 | 1 | 165.0691 ms | 1.1892 ms | 70.74 | 0.54 | diff --git a/results/netcoreapp1.0/MessageTemplateCache/MessageTemplateCacheBenchmark_Warmup-report-github.md b/results/netcoreapp1.0/MessageTemplateCache/MessageTemplateCacheBenchmark_Warmup-report-github.md new file mode 100644 index 000000000..5a0cb14ae --- /dev/null +++ b/results/netcoreapp1.0/MessageTemplateCache/MessageTemplateCacheBenchmark_Warmup-report-github.md @@ -0,0 +1,40 @@ +```ini + +Host Process Environment Information: +BenchmarkDotNet.Core=v0.9.9.0 +OS=Windows +Processor=?, ProcessorCount=8 +Frequency=3507509 ticks, Resolution=285.1026 ns, Timer=TSC +CLR=CORE, Arch=64-bit ? [RyuJIT] +GC=Concurrent Workstation +dotnet cli version: 1.0.0-preview2-003131 + +Type=MessageTemplateCacheBenchmark_Warmup Mode=Throughput + +``` + Method | Items | MaxDegreeOfParallelism | Median | StdDev | Scaled | Scaled-SD | +----------- |------ |----------------------- |---------------- |-------------- |------- |---------- | + **Dictionary** | **10** | **-1** | **9.6902 us** | **0.0664 us** | **1.00** | **0.00** | + Hashtable | 10 | -1 | 7.2262 us | 0.1284 us | 0.74 | 0.01 | + Concurrent | 10 | -1 | 9.5027 us | 0.6200 us | 1.00 | 0.06 | + **Dictionary** | **10** | **1** | **3.3790 us** | **0.0236 us** | **1.00** | **0.00** | + Hashtable | 10 | 1 | 3.4664 us | 0.0544 us | 1.03 | 0.02 | + Concurrent | 10 | 1 | 4.7689 us | 0.3187 us | 1.47 | 0.09 | + **Dictionary** | **100** | **-1** | **126.1621 us** | **19.4774 us** | **1.00** | **0.00** | + Hashtable | 100 | -1 | 101.6898 us | 8.7118 us | 0.81 | 0.10 | + Concurrent | 100 | -1 | 404.3201 us | 33.8885 us | 3.08 | 0.39 | + **Dictionary** | **100** | **1** | **26.9025 us** | **0.1860 us** | **1.00** | **0.00** | + Hashtable | 100 | 1 | 29.0055 us | 0.4187 us | 1.07 | 0.02 | + Concurrent | 100 | 1 | 107.1666 us | 14.1945 us | 4.07 | 0.53 | + **Dictionary** | **1000** | **-1** | **833.5471 us** | **11.3689 us** | **1.00** | **0.00** | + Hashtable | 1000 | -1 | 830.9745 us | 11.1378 us | 0.99 | 0.02 | + Concurrent | 1000 | -1 | 15,259.1376 us | 1,588.6467 us | 18.34 | 1.91 | + **Dictionary** | **1000** | **1** | **257.6129 us** | **1.8266 us** | **1.00** | **0.00** | + Hashtable | 1000 | 1 | 293.7693 us | 2.1159 us | 1.14 | 0.01 | + Concurrent | 1000 | 1 | 6,819.1691 us | 1,207.1580 us | 27.51 | 4.68 | + **Dictionary** | **10000** | **-1** | **4,318.6810 us** | **112.2715 us** | **1.00** | **0.00** | + Hashtable | 10000 | -1 | 4,112.6765 us | 54.6069 us | 0.95 | 0.03 | + Concurrent | 10000 | -1 | 324,148.5624 us | 9,763.5470 us | 75.11 | 2.91 | + **Dictionary** | **10000** | **1** | **2,343.6103 us** | **11.5641 us** | **1.00** | **0.00** | + Hashtable | 10000 | 1 | 2,484.4611 us | 6.8263 us | 1.06 | 0.01 | + Concurrent | 10000 | 1 | 165,379.1908 us | 1,588.0393 us | 70.54 | 0.74 | diff --git a/src/Serilog/Core/Pipeline/MessageTemplateCache.cs b/src/Serilog/Core/Pipeline/MessageTemplateCache.cs index c0705c3c8..0688ee0e1 100644 --- a/src/Serilog/Core/Pipeline/MessageTemplateCache.cs +++ b/src/Serilog/Core/Pipeline/MessageTemplateCache.cs @@ -15,15 +15,21 @@ using System; using System.Collections.Generic; using Serilog.Events; +using System.Collections; namespace Serilog.Core.Pipeline { class MessageTemplateCache : IMessageTemplateParser { readonly IMessageTemplateParser _innerParser; - readonly Dictionary _templates = new Dictionary(); readonly object _templatesLock = new object(); +#if HASHTABLE + readonly Hashtable _templates = new Hashtable(); +#else + readonly Dictionary _templates = new Dictionary(); +#endif + const int MaxCacheItems = 1000; const int MaxCachedTemplateLength = 1024; @@ -40,10 +46,16 @@ public MessageTemplate Parse(string messageTemplate) if (messageTemplate.Length > MaxCachedTemplateLength) return _innerParser.Parse(messageTemplate); +#if HASHTABLE + var result = (MessageTemplate)_templates[messageTemplate]; + if (result != null) + return result; +#else MessageTemplate result; lock(_templatesLock) if (_templates.TryGetValue(messageTemplate, out result)) return result; +#endif result = _innerParser.Parse(messageTemplate); diff --git a/src/Serilog/Properties/AssemblyInfo.cs b/src/Serilog/Properties/AssemblyInfo.cs index 1237c0e24..454ccad28 100644 --- a/src/Serilog/Properties/AssemblyInfo.cs +++ b/src/Serilog/Properties/AssemblyInfo.cs @@ -12,3 +12,10 @@ "d18dbf6d5a25af5ce9016f281014d79dc3b4201ac646c451830fc7e61a2dfd633d34c39f87b818" + "94191652df5ac63cc40c77f3542f702bda692e6e8a9158353df189007a49da0f3cfd55eb250066" + "b19485ec")] + +[assembly: InternalsVisibleTo("Serilog.PerformanceTests, PublicKey=" + + "0024000004800000940000000602000000240000525341310004000001000100fb8d13fd344a1c" + + "6fe0fe83ef33c1080bf30690765bc6eb0df26ebfdf8f21670c64265b30db09f73a0dea5b3db4c9" + + "d18dbf6d5a25af5ce9016f281014d79dc3b4201ac646c451830fc7e61a2dfd633d34c39f87b818" + + "94191652df5ac63cc40c77f3542f702bda692e6e8a9158353df189007a49da0f3cfd55eb250066" + + "b19485ec")] diff --git a/src/Serilog/project.json b/src/Serilog/project.json index cf257b196..4d8c226fb 100644 --- a/src/Serilog/project.json +++ b/src/Serilog/project.json @@ -19,12 +19,12 @@ "frameworks": { "net4.5": { "buildOptions": { - "define": [ "REMOTING" ] + "define": [ "REMOTING", "HASHTABLE" ] } }, "net4.6": { "buildOptions": { - "define": [ "ASYNCLOCAL" ] + "define": [ "ASYNCLOCAL", "HASHTABLE" ] } }, "netstandard1.0": { @@ -44,11 +44,12 @@ }, "netstandard1.3": { "buildOptions": { - "define": [ "ASYNCLOCAL" ] + "define": [ "ASYNCLOCAL", "HASHTABLE" ] }, "dependencies": { "Microsoft.CSharp": "4.0.1", "System.Collections": "4.0.11", + "System.Collections.NonGeneric": "4.0.1", "System.Dynamic.Runtime": "4.0.11", "System.Globalization": "4.0.11", "System.Linq": "4.1.0", diff --git a/test/Serilog.PerformanceTests/Harness.cs b/test/Serilog.PerformanceTests/Harness.cs index 2ab3e109c..67c388578 100644 --- a/test/Serilog.PerformanceTests/Harness.cs +++ b/test/Serilog.PerformanceTests/Harness.cs @@ -13,17 +13,21 @@ // See the License for the specific language governing permissions and // limitations under the License. -using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Running; -using Serilog; -using Serilog.Events; -using System; using Xunit; namespace Serilog.PerformanceTests { public class Harness { + [Fact] + public void MessageTemplateCacheBenchmark() + { + BenchmarkRunner.Run(); + BenchmarkRunner.Run(); + BenchmarkRunner.Run(); + } + [Fact] public void LogContextEnrichment() { diff --git a/test/Serilog.PerformanceTests/MessageTemplateCacheBenchmark/MessageTemplateCacheBenchmark_Cached.cs b/test/Serilog.PerformanceTests/MessageTemplateCacheBenchmark/MessageTemplateCacheBenchmark_Cached.cs new file mode 100644 index 000000000..4f776549a --- /dev/null +++ b/test/Serilog.PerformanceTests/MessageTemplateCacheBenchmark/MessageTemplateCacheBenchmark_Cached.cs @@ -0,0 +1,73 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using BenchmarkDotNet.Attributes; +using Serilog.Core; +using Serilog.Core.Pipeline; +using Serilog.PerformanceTests.Support; + +namespace Serilog.PerformanceTests +{ + public class MessageTemplateCacheBenchmark_Cached + { + const string DefaultOutputTemplate = "{Timestamp:yyyy-MM-dd HH:mm:ss} [{Level}] {Message}{NewLine}{Exception}"; + + List _templateList; + + ConcurrentDictionaryMessageTemplateCache _concurrentCache; + DictionaryMessageTemplateCache _dictionaryCache; + MessageTemplateCache _hashtableCache; + + [Params(10, 20, 50, 100, 1000)] + public int Items { get; set; } + + [Params(1, -1)] + public int MaxDegreeOfParallelism { get; set; } + + [Setup] + public void Setup() + { + _templateList = Enumerable.Range(0, Items).Select(x => $"{DefaultOutputTemplate}_{Guid.NewGuid()}").ToList(); + + _concurrentCache = new ConcurrentDictionaryMessageTemplateCache(NoOpMessageTemplateParser.Instance); + _dictionaryCache = new DictionaryMessageTemplateCache(NoOpMessageTemplateParser.Instance); + _hashtableCache = new MessageTemplateCache(NoOpMessageTemplateParser.Instance); + + foreach (var t in _templateList) + { + _concurrentCache.Parse(t); + _dictionaryCache.Parse(t); + _hashtableCache.Parse(t); + } + } + + [Benchmark(Baseline = true)] + public void Dictionary() + { + Run(() => _dictionaryCache); + } + + [Benchmark] + public void Hashtable() + { + Run(() => _hashtableCache); + } + + [Benchmark] + public void Concurrent() + { + Run(() => _concurrentCache); + } + + void Run(Func cacheFactory) where T : IMessageTemplateParser + { + var cache = cacheFactory(); + + Parallel.ForEach( + _templateList, + new ParallelOptions() { MaxDegreeOfParallelism = MaxDegreeOfParallelism }, + t => cache.Parse(t)); + } + } +} \ No newline at end of file diff --git a/test/Serilog.PerformanceTests/MessageTemplateCacheBenchmark/MessageTemplateCacheBenchmark_Leaking.cs b/test/Serilog.PerformanceTests/MessageTemplateCacheBenchmark/MessageTemplateCacheBenchmark_Leaking.cs new file mode 100644 index 000000000..76ee3ba61 --- /dev/null +++ b/test/Serilog.PerformanceTests/MessageTemplateCacheBenchmark/MessageTemplateCacheBenchmark_Leaking.cs @@ -0,0 +1,60 @@ +using BenchmarkDotNet.Attributes; +using Serilog.Core; +using Serilog.Core.Pipeline; +using Serilog.PerformanceTests.Support; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Serilog.PerformanceTests +{ + public class MessageTemplateCacheBenchmark_Leaking + { + const string DefaultOutputTemplate = "{Timestamp:yyyy-MM-dd HH:mm:ss} [{Level}] {Message}{NewLine}{Exception}"; + const int MaxCacheItems = 1100; + + List _templateList; + + [Params(10000)] + public int Items { get; set; } + + [Params(1, -1)] + public int MaxDegreeOfParallelism { get; set; } + + [Setup] + public void Setup() + { + _templateList = Enumerable.Range(0, Items).Select(x => $"{DefaultOutputTemplate}_{Guid.NewGuid()}").ToList(); + } + + [Benchmark(Baseline = true)] + public void Dictionary() + { + Run(() => new DictionaryMessageTemplateCache(NoOpMessageTemplateParser.Instance)); + } + + [Benchmark] + public void Hashtable() + { + Run(() => new MessageTemplateCache(NoOpMessageTemplateParser.Instance)); + } + + [Benchmark] + public void Concurrent() + { + Run(() => new ConcurrentDictionaryMessageTemplateCache(NoOpMessageTemplateParser.Instance)); + } + + void Run(Func cacheFactory) where T : IMessageTemplateParser + { + var cache = cacheFactory(); + + Parallel.For( + 0, + _templateList.Count, + new ParallelOptions() { MaxDegreeOfParallelism = MaxDegreeOfParallelism }, + idx => cache.Parse(_templateList[idx % MaxCacheItems])); + } + } +} diff --git a/test/Serilog.PerformanceTests/MessageTemplateCacheBenchmark/MessageTemplateCacheBenchmark_Warmup.cs b/test/Serilog.PerformanceTests/MessageTemplateCacheBenchmark/MessageTemplateCacheBenchmark_Warmup.cs new file mode 100644 index 000000000..e0bfdf526 --- /dev/null +++ b/test/Serilog.PerformanceTests/MessageTemplateCacheBenchmark/MessageTemplateCacheBenchmark_Warmup.cs @@ -0,0 +1,58 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using BenchmarkDotNet.Attributes; +using Serilog.Core; +using Serilog.Core.Pipeline; +using Serilog.PerformanceTests.Support; + +namespace Serilog.PerformanceTests +{ + public class MessageTemplateCacheBenchmark_Warmup + { + const string DefaultOutputTemplate = "{Timestamp:yyyy-MM-dd HH:mm:ss} [{Level}] {Message}{NewLine}{Exception}"; + + List _templateList; + + [Params(10, 100, 1000, 10000)] + public int Items { get; set; } + + [Params(1, -1)] + public int MaxDegreeOfParallelism { get; set; } + + [Setup] + public void Setup() + { + _templateList = Enumerable.Range(0, Items).Select(x => $"{DefaultOutputTemplate}_{Guid.NewGuid()}").ToList(); + } + + [Benchmark(Baseline = true)] + public void Dictionary() + { + Run(() => new DictionaryMessageTemplateCache(NoOpMessageTemplateParser.Instance)); + } + + [Benchmark] + public void Hashtable() + { + Run(() => new MessageTemplateCache(NoOpMessageTemplateParser.Instance)); + } + + [Benchmark] + public void Concurrent() + { + Run(() => new ConcurrentDictionaryMessageTemplateCache(NoOpMessageTemplateParser.Instance)); + } + + void Run(Func cacheFactory) where T : IMessageTemplateParser + { + var cache = cacheFactory(); + + Parallel.ForEach( + _templateList, + new ParallelOptions() { MaxDegreeOfParallelism = MaxDegreeOfParallelism }, + t => cache.Parse(t)); + } + } +} \ No newline at end of file diff --git a/test/Serilog.PerformanceTests/Support/ConcurrentDictionaryTemplateCache.cs b/test/Serilog.PerformanceTests/Support/ConcurrentDictionaryTemplateCache.cs new file mode 100644 index 000000000..5a9329859 --- /dev/null +++ b/test/Serilog.PerformanceTests/Support/ConcurrentDictionaryTemplateCache.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Concurrent; +using Serilog.Core; +using Serilog.Events; + +namespace Serilog.PerformanceTests.Support +{ + class ConcurrentDictionaryMessageTemplateCache : IMessageTemplateParser + { + readonly IMessageTemplateParser _innerParser; + + readonly ConcurrentDictionary _templates = new ConcurrentDictionary(); + + const int MaxCacheItems = 1000; + const int MaxCachedTemplateLength = 1024; + + public ConcurrentDictionaryMessageTemplateCache(IMessageTemplateParser innerParser) + { + if (innerParser == null) throw new ArgumentNullException(nameof(innerParser)); + _innerParser = innerParser; + } + + public MessageTemplate Parse(string messageTemplate) + { + if (messageTemplate == null) throw new ArgumentNullException(nameof(messageTemplate)); + + if (messageTemplate.Length > MaxCachedTemplateLength) + return _innerParser.Parse(messageTemplate); + + MessageTemplate result; + if (_templates.TryGetValue(messageTemplate, out result)) + return result; + + result = _innerParser.Parse(messageTemplate); + + { + // Exceeding MaxCacheItems is *not* the sunny day scenario; all we're doing here is preventing out-of-memory + // conditions when the library is used incorrectly. Correct use (templates, rather than + // direct message strings) should barely, if ever, overflow this cache. + + // Changing workloads through the lifecycle of an app instance mean we can gain some ground by + // potentially dropping templates generated only in startup, or only during specific infrequent + // activities. + + if (_templates.Count == MaxCacheItems) + _templates.Clear(); + + _templates[messageTemplate] = result; + } + + return result; + } + } +} \ No newline at end of file diff --git a/test/Serilog.PerformanceTests/Support/DictionaryMessageTemplateCache.cs b/test/Serilog.PerformanceTests/Support/DictionaryMessageTemplateCache.cs new file mode 100644 index 000000000..8a2f5a820 --- /dev/null +++ b/test/Serilog.PerformanceTests/Support/DictionaryMessageTemplateCache.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections.Generic; +using Serilog.Core; +using Serilog.Events; + +namespace Serilog.PerformanceTests.Support +{ + class DictionaryMessageTemplateCache : IMessageTemplateParser + { + readonly IMessageTemplateParser _innerParser; + readonly object _templatesLock = new object(); + + readonly Dictionary _templates = new Dictionary(); + + const int MaxCacheItems = 1000; + const int MaxCachedTemplateLength = 1024; + + public DictionaryMessageTemplateCache(IMessageTemplateParser innerParser) + { + if (innerParser == null) throw new ArgumentNullException(nameof(innerParser)); + _innerParser = innerParser; + } + + public MessageTemplate Parse(string messageTemplate) + { + if (messageTemplate == null) throw new ArgumentNullException(nameof(messageTemplate)); + + if (messageTemplate.Length > MaxCachedTemplateLength) + return _innerParser.Parse(messageTemplate); + + MessageTemplate result; + lock (_templatesLock) + if (_templates.TryGetValue(messageTemplate, out result)) + return result; + + result = _innerParser.Parse(messageTemplate); + + lock (_templatesLock) + { + // Exceeding MaxCacheItems is *not* the sunny day scenario; all we're doing here is preventing out-of-memory + // conditions when the library is used incorrectly. Correct use (templates, rather than + // direct message strings) should barely, if ever, overflow this cache. + + // Changing workloads through the lifecycle of an app instance mean we can gain some ground by + // potentially dropping templates generated only in startup, or only during specific infrequent + // activities. + + if (_templates.Count == MaxCacheItems) + _templates.Clear(); + + _templates[messageTemplate] = result; + } + + return result; + } + } +} \ No newline at end of file diff --git a/test/Serilog.PerformanceTests/Support/NoOpMessageTemplateParser.cs b/test/Serilog.PerformanceTests/Support/NoOpMessageTemplateParser.cs new file mode 100644 index 000000000..74b1088fd --- /dev/null +++ b/test/Serilog.PerformanceTests/Support/NoOpMessageTemplateParser.cs @@ -0,0 +1,16 @@ +using System.Linq; +using Serilog.Core; +using Serilog.Events; +using Serilog.Parsing; + +namespace Serilog.PerformanceTests.Support +{ + class NoOpMessageTemplateParser : IMessageTemplateParser + { + public static readonly NoOpMessageTemplateParser Instance = new NoOpMessageTemplateParser(); + + static readonly MessageTemplate ConstTemplate = new MessageTemplate("text", Enumerable.Empty()); + + public MessageTemplate Parse(string messageTemplate) => ConstTemplate; + } +} \ No newline at end of file diff --git a/test/Serilog.PerformanceTests/project.json b/test/Serilog.PerformanceTests/project.json index 2ebb7562c..70bc3da6f 100755 --- a/test/Serilog.PerformanceTests/project.json +++ b/test/Serilog.PerformanceTests/project.json @@ -11,9 +11,7 @@ "keyFile": "../../assets/Serilog.snk" }, "frameworks": { - "net4.5.2": { - }, - "netcoreapp1.0": { + "netcoreapp1.0": { "dependencies": { "Microsoft.NETCore.App": { "type": "platform", @@ -25,6 +23,8 @@ "dnxcore50", "portable-net45+win8" ] + }, + "net4.5.2": { } } } From fda5404b97289656866ee9f6be34d9a8be0e6c5a Mon Sep 17 00:00:00 2001 From: Sergey Komisarchik Date: Wed, 19 Oct 2016 22:10:27 +0300 Subject: [PATCH 25/53] cached perf fix --- ...lateCacheBenchmark_Cached-report-github.md | 64 +++++++++---------- ...lateCacheBenchmark_Cached-report-github.md | 64 +++++++++---------- .../MessageTemplateCacheBenchmark_Cached.cs | 8 ++- 3 files changed, 69 insertions(+), 67 deletions(-) diff --git a/results/net4.5.2/MessageTemplateCache/MessageTemplateCacheBenchmark_Cached-report-github.md b/results/net4.5.2/MessageTemplateCache/MessageTemplateCacheBenchmark_Cached-report-github.md index eb27c3f52..c50909a8e 100644 --- a/results/net4.5.2/MessageTemplateCache/MessageTemplateCacheBenchmark_Cached-report-github.md +++ b/results/net4.5.2/MessageTemplateCache/MessageTemplateCacheBenchmark_Cached-report-github.md @@ -12,35 +12,35 @@ JitModules=clrjit-v4.6.1586.0 Type=MessageTemplateCacheBenchmark_Cached Mode=Throughput ``` - Method | Items | MaxDegreeOfParallelism | Median | StdDev | Scaled | Scaled-SD | ------------ |------ |----------------------- |------------ |----------- |------- |---------- | - **Dictionary** | **10** | **-1** | **5.1555 us** | **0.0507 us** | **1.00** | **0.00** | - Hashtable | 10 | -1 | 4.4010 us | 0.0434 us | 0.85 | 0.01 | - Concurrent | 10 | -1 | 4.4323 us | 0.0677 us | 0.86 | 0.02 | - **Dictionary** | **10** | **1** | **2.9110 us** | **0.1116 us** | **1.00** | **0.00** | - Hashtable | 10 | 1 | 2.8101 us | 0.1591 us | 0.97 | 0.07 | - Concurrent | 10 | 1 | 2.8076 us | 0.1195 us | 0.98 | 0.05 | - **Dictionary** | **20** | **-1** | **9.5123 us** | **0.1870 us** | **1.00** | **0.00** | - Hashtable | 20 | -1 | 5.7014 us | 0.0338 us | 0.60 | 0.01 | - Concurrent | 20 | -1 | 5.7452 us | 0.0450 us | 0.60 | 0.01 | - **Dictionary** | **20** | **1** | **4.4900 us** | **0.2552 us** | **1.00** | **0.00** | - Hashtable | 20 | 1 | 3.9176 us | 0.2345 us | 0.89 | 0.07 | - Concurrent | 20 | 1 | 3.8974 us | 0.2235 us | 0.88 | 0.07 | - **Dictionary** | **50** | **-1** | **27.0377 us** | **1.2542 us** | **1.00** | **0.00** | - Hashtable | 50 | -1 | 7.5979 us | 0.0627 us | 0.28 | 0.01 | - Concurrent | 50 | -1 | 7.6123 us | 0.0427 us | 0.28 | 0.01 | - **Dictionary** | **50** | **1** | **8.0037 us** | **0.5410 us** | **1.00** | **0.00** | - Hashtable | 50 | 1 | 7.1405 us | 0.5055 us | 0.90 | 0.08 | - Concurrent | 50 | 1 | 7.2504 us | 0.5599 us | 0.90 | 0.09 | - **Dictionary** | **100** | **-1** | **53.3566 us** | **7.5268 us** | **1.00** | **0.00** | - Hashtable | 100 | -1 | 10.1725 us | 0.0903 us | 0.19 | 0.02 | - Concurrent | 100 | -1 | 10.1390 us | 0.1140 us | 0.19 | 0.02 | - **Dictionary** | **100** | **1** | **13.4704 us** | **0.0694 us** | **1.00** | **0.00** | - Hashtable | 100 | 1 | 11.7881 us | 0.6524 us | 0.88 | 0.05 | - Concurrent | 100 | 1 | 12.0876 us | 0.8071 us | 0.91 | 0.06 | - **Dictionary** | **1000** | **-1** | **482.1057 us** | **17.5977 us** | **1.00** | **0.00** | - Hashtable | 1000 | -1 | 33.1846 us | 0.3355 us | 0.07 | 0.00 | - Concurrent | 1000 | -1 | 32.8517 us | 0.3190 us | 0.07 | 0.00 | - **Dictionary** | **1000** | **1** | **124.2795 us** | **0.7927 us** | **1.00** | **0.00** | - Hashtable | 1000 | 1 | 105.6937 us | 1.1198 us | 0.85 | 0.01 | - Concurrent | 1000 | 1 | 103.0948 us | 0.8157 us | 0.83 | 0.01 | + Method | Items | MaxDegreeOfParallelism | Median | StdDev | Scaled | Scaled-SD | +----------- |------ |----------------------- |-------------- |----------- |------- |---------- | + **Dictionary** | **10** | **-1** | **479.2330 us** | **8.4171 us** | **1.00** | **0.00** | + Hashtable | 10 | -1 | 31.3853 us | 0.9122 us | 0.07 | 0.00 | + Concurrent | 10 | -1 | 30.8517 us | 0.5337 us | 0.06 | 0.00 | + **Dictionary** | **10** | **1** | **118.6307 us** | **2.1275 us** | **1.00** | **0.00** | + Hashtable | 10 | 1 | 101.8377 us | 3.3030 us | 0.86 | 0.03 | + Concurrent | 10 | 1 | 99.8312 us | 2.0072 us | 0.85 | 0.02 | + **Dictionary** | **20** | **-1** | **814.2458 us** | **4.7079 us** | **1.00** | **0.00** | + Hashtable | 20 | -1 | 54.0605 us | 0.8628 us | 0.07 | 0.00 | + Concurrent | 20 | -1 | 54.0682 us | 1.1269 us | 0.07 | 0.00 | + **Dictionary** | **20** | **1** | **247.6677 us** | **3.9224 us** | **1.00** | **0.00** | + Hashtable | 20 | 1 | 212.1726 us | 7.2594 us | 0.85 | 0.03 | + Concurrent | 20 | 1 | 201.6514 us | 7.5841 us | 0.81 | 0.03 | + **Dictionary** | **50** | **-1** | **1,522.8901 us** | **7.7962 us** | **1.00** | **0.00** | + Hashtable | 50 | -1 | 123.5203 us | 2.2916 us | 0.08 | 0.00 | + Concurrent | 50 | -1 | 121.7588 us | 2.8585 us | 0.08 | 0.00 | + **Dictionary** | **50** | **1** | **591.6731 us** | **5.2648 us** | **1.00** | **0.00** | + Hashtable | 50 | 1 | 515.3277 us | 10.1826 us | 0.87 | 0.02 | + Concurrent | 50 | 1 | 493.4330 us | 4.9567 us | 0.83 | 0.01 | + **Dictionary** | **100** | **-1** | **2,469.4364 us** | **10.4088 us** | **1.00** | **0.00** | + Hashtable | 100 | -1 | 237.0286 us | 2.6360 us | 0.10 | 0.00 | + Concurrent | 100 | -1 | 238.5870 us | 3.0985 us | 0.10 | 0.00 | + **Dictionary** | **100** | **1** | **1,157.9635 us** | **8.0010 us** | **1.00** | **0.00** | + Hashtable | 100 | 1 | 982.2426 us | 16.9287 us | 0.85 | 0.02 | + Concurrent | 100 | 1 | 964.2989 us | 19.8753 us | 0.84 | 0.02 | + **Dictionary** | **1000** | **-1** | **2,479.2089 us** | **10.0258 us** | **1.00** | **0.00** | + Hashtable | 1000 | -1 | 244.9505 us | 3.0286 us | 0.10 | 0.00 | + Concurrent | 1000 | -1 | 239.5800 us | 3.2452 us | 0.10 | 0.00 | + **Dictionary** | **1000** | **1** | **1,164.8552 us** | **6.4559 us** | **1.00** | **0.00** | + Hashtable | 1000 | 1 | 1,002.4865 us | 10.4370 us | 0.86 | 0.01 | + Concurrent | 1000 | 1 | 973.6633 us | 13.2212 us | 0.84 | 0.01 | diff --git a/results/netcoreapp1.0/MessageTemplateCache/MessageTemplateCacheBenchmark_Cached-report-github.md b/results/netcoreapp1.0/MessageTemplateCache/MessageTemplateCacheBenchmark_Cached-report-github.md index de7a22445..1b299e7ac 100644 --- a/results/netcoreapp1.0/MessageTemplateCache/MessageTemplateCacheBenchmark_Cached-report-github.md +++ b/results/netcoreapp1.0/MessageTemplateCache/MessageTemplateCacheBenchmark_Cached-report-github.md @@ -12,35 +12,35 @@ dotnet cli version: 1.0.0-preview2-003131 Type=MessageTemplateCacheBenchmark_Cached Mode=Throughput ``` - Method | Items | MaxDegreeOfParallelism | Median | StdDev | Scaled | Scaled-SD | ------------ |------ |----------------------- |------------ |----------- |------- |---------- | - **Dictionary** | **10** | **-1** | **5.4201 us** | **0.0512 us** | **1.00** | **0.00** | - Hashtable | 10 | -1 | 4.9545 us | 0.0347 us | 0.91 | 0.01 | - Concurrent | 10 | -1 | 4.9253 us | 0.0159 us | 0.91 | 0.01 | - **Dictionary** | **10** | **1** | **2.0893 us** | **0.0253 us** | **1.00** | **0.00** | - Hashtable | 10 | 1 | 1.9488 us | 0.0386 us | 0.93 | 0.02 | - Concurrent | 10 | 1 | 1.9073 us | 0.0197 us | 0.91 | 0.01 | - **Dictionary** | **20** | **-1** | **9.7756 us** | **0.2778 us** | **1.00** | **0.00** | - Hashtable | 20 | -1 | 6.0307 us | 0.0230 us | 0.61 | 0.02 | - Concurrent | 20 | -1 | 5.9965 us | 0.0173 us | 0.60 | 0.02 | - **Dictionary** | **20** | **1** | **3.4780 us** | **0.0507 us** | **1.00** | **0.00** | - Hashtable | 20 | 1 | 3.2690 us | 0.0749 us | 0.94 | 0.02 | - Concurrent | 20 | 1 | 3.0224 us | 0.0822 us | 0.87 | 0.03 | - **Dictionary** | **50** | **-1** | **29.9933 us** | **1.9805 us** | **1.00** | **0.00** | - Hashtable | 50 | -1 | 7.3773 us | 0.0626 us | 0.25 | 0.02 | - Concurrent | 50 | -1 | 7.2335 us | 0.0518 us | 0.24 | 0.02 | - **Dictionary** | **50** | **1** | **7.4501 us** | **0.0993 us** | **1.00** | **0.00** | - Hashtable | 50 | 1 | 7.0496 us | 0.1044 us | 0.94 | 0.02 | - Concurrent | 50 | 1 | 6.3908 us | 0.0815 us | 0.86 | 0.02 | - **Dictionary** | **100** | **-1** | **66.0669 us** | **10.1367 us** | **1.00** | **0.00** | - Hashtable | 100 | -1 | 9.9072 us | 0.1425 us | 0.15 | 0.02 | - Concurrent | 100 | -1 | 9.6922 us | 0.1097 us | 0.15 | 0.02 | - **Dictionary** | **100** | **1** | **14.1209 us** | **0.2217 us** | **1.00** | **0.00** | - Hashtable | 100 | 1 | 13.1885 us | 0.1276 us | 0.93 | 0.02 | - Concurrent | 100 | 1 | 12.0479 us | 0.2229 us | 0.85 | 0.02 | - **Dictionary** | **1000** | **-1** | **477.3456 us** | **15.2028 us** | **1.00** | **0.00** | - Hashtable | 1000 | -1 | 31.2365 us | 0.3421 us | 0.07 | 0.00 | - Concurrent | 1000 | -1 | 30.6801 us | 0.2434 us | 0.06 | 0.00 | - **Dictionary** | **1000** | **1** | **134.5746 us** | **1.0971 us** | **1.00** | **0.00** | - Hashtable | 1000 | 1 | 126.6081 us | 0.8346 us | 0.94 | 0.01 | - Concurrent | 1000 | 1 | 114.9060 us | 1.3409 us | 0.85 | 0.01 | + Method | Items | MaxDegreeOfParallelism | Median | StdDev | Scaled | Scaled-SD | +----------- |------ |----------------------- |-------------- |----------- |------- |---------- | + **Dictionary** | **10** | **-1** | **471.8337 us** | **12.2518 us** | **1.00** | **0.00** | + Hashtable | 10 | -1 | 30.1689 us | 0.6795 us | 0.06 | 0.00 | + Concurrent | 10 | -1 | 29.0805 us | 0.1960 us | 0.06 | 0.00 | + **Dictionary** | **10** | **1** | **130.2107 us** | **3.1653 us** | **1.00** | **0.00** | + Hashtable | 10 | 1 | 122.2643 us | 2.4861 us | 0.94 | 0.03 | + Concurrent | 10 | 1 | 111.4388 us | 1.5693 us | 0.86 | 0.02 | + **Dictionary** | **20** | **-1** | **807.0063 us** | **2.6482 us** | **1.00** | **0.00** | + Hashtable | 20 | -1 | 52.2398 us | 1.0080 us | 0.06 | 0.00 | + Concurrent | 20 | -1 | 51.2831 us | 0.3521 us | 0.06 | 0.00 | + **Dictionary** | **20** | **1** | **259.5397 us** | **7.4273 us** | **1.00** | **0.00** | + Hashtable | 20 | 1 | 245.7154 us | 7.1712 us | 0.94 | 0.04 | + Concurrent | 20 | 1 | 222.5680 us | 2.6695 us | 0.85 | 0.02 | + **Dictionary** | **50** | **-1** | **1,296.5404 us** | **21.9341 us** | **1.00** | **0.00** | + Hashtable | 50 | -1 | 121.7466 us | 1.9856 us | 0.09 | 0.00 | + Concurrent | 50 | -1 | 117.7603 us | 0.9613 us | 0.09 | 0.00 | + **Dictionary** | **50** | **1** | **650.9873 us** | **8.5592 us** | **1.00** | **0.00** | + Hashtable | 50 | 1 | 615.8303 us | 11.6568 us | 0.94 | 0.02 | + Concurrent | 50 | 1 | 557.0763 us | 6.8188 us | 0.86 | 0.02 | + **Dictionary** | **100** | **-1** | **2,330.0390 us** | **10.0342 us** | **1.00** | **0.00** | + Hashtable | 100 | -1 | 235.0997 us | 3.6531 us | 0.10 | 0.00 | + Concurrent | 100 | -1 | 227.1950 us | 2.0706 us | 0.10 | 0.00 | + **Dictionary** | **100** | **1** | **1,306.6364 us** | **60.9756 us** | **1.00** | **0.00** | + Hashtable | 100 | 1 | 1,214.4314 us | 18.8564 us | 0.92 | 0.04 | + Concurrent | 100 | 1 | 1,113.6760 us | 8.7944 us | 0.84 | 0.04 | + **Dictionary** | **1000** | **-1** | **2,338.3248 us** | **20.7148 us** | **1.00** | **0.00** | + Hashtable | 1000 | -1 | 238.6618 us | 1.8611 us | 0.10 | 0.00 | + Concurrent | 1000 | -1 | 232.2733 us | 1.5743 us | 0.10 | 0.00 | + **Dictionary** | **1000** | **1** | **1,311.6107 us** | **12.7315 us** | **1.00** | **0.00** | + Hashtable | 1000 | 1 | 1,246.3194 us | 8.1155 us | 0.95 | 0.01 | + Concurrent | 1000 | 1 | 1,136.7721 us | 15.0574 us | 0.87 | 0.01 | diff --git a/test/Serilog.PerformanceTests/MessageTemplateCacheBenchmark/MessageTemplateCacheBenchmark_Cached.cs b/test/Serilog.PerformanceTests/MessageTemplateCacheBenchmark/MessageTemplateCacheBenchmark_Cached.cs index 4f776549a..40b22cd80 100644 --- a/test/Serilog.PerformanceTests/MessageTemplateCacheBenchmark/MessageTemplateCacheBenchmark_Cached.cs +++ b/test/Serilog.PerformanceTests/MessageTemplateCacheBenchmark/MessageTemplateCacheBenchmark_Cached.cs @@ -63,11 +63,13 @@ public void Concurrent() void Run(Func cacheFactory) where T : IMessageTemplateParser { var cache = cacheFactory(); + var iterations = Math.Min(10000, Items * 100); - Parallel.ForEach( - _templateList, + Parallel.For( + 0, + iterations, new ParallelOptions() { MaxDegreeOfParallelism = MaxDegreeOfParallelism }, - t => cache.Parse(t)); + idx => cache.Parse(_templateList[idx % Items])); } } } \ No newline at end of file From 97f297c288557034ff99ff13eabcd7ad36f261b0 Mon Sep 17 00:00:00 2001 From: Sergey Komisarchik Date: Wed, 19 Oct 2016 22:36:57 +0300 Subject: [PATCH 26/53] merge warmup and leaking tests into one with customizable overflow rate --- ...ateCacheBenchmark_Leaking-report-github.md | 34 ++++++++--- ...lateCacheBenchmark_Warmup-report-github.md | 40 ------------- ...ateCacheBenchmark_Leaking-report-github.md | 34 ++++++++--- ...lateCacheBenchmark_Warmup-report-github.md | 40 ------------- test/Serilog.PerformanceTests/Harness.cs | 1 - .../MessageTemplateCacheBenchmark_Leaking.cs | 8 ++- .../MessageTemplateCacheBenchmark_Warmup.cs | 58 ------------------- 7 files changed, 58 insertions(+), 157 deletions(-) delete mode 100644 results/net4.5.2/MessageTemplateCache/MessageTemplateCacheBenchmark_Warmup-report-github.md delete mode 100644 results/netcoreapp1.0/MessageTemplateCache/MessageTemplateCacheBenchmark_Warmup-report-github.md delete mode 100644 test/Serilog.PerformanceTests/MessageTemplateCacheBenchmark/MessageTemplateCacheBenchmark_Warmup.cs diff --git a/results/net4.5.2/MessageTemplateCache/MessageTemplateCacheBenchmark_Leaking-report-github.md b/results/net4.5.2/MessageTemplateCache/MessageTemplateCacheBenchmark_Leaking-report-github.md index a20c45f4e..a21baf25b 100644 --- a/results/net4.5.2/MessageTemplateCache/MessageTemplateCacheBenchmark_Leaking-report-github.md +++ b/results/net4.5.2/MessageTemplateCache/MessageTemplateCacheBenchmark_Leaking-report-github.md @@ -12,11 +12,29 @@ JitModules=clrjit-v4.6.1586.0 Type=MessageTemplateCacheBenchmark_Leaking Mode=Throughput ``` - Method | Items | MaxDegreeOfParallelism | Median | StdDev | Scaled | Scaled-SD | ------------ |------ |----------------------- |------------ |---------- |------- |---------- | - **Dictionary** | **10000** | **-1** | **4.2101 ms** | **0.1586 ms** | **1.00** | **0.00** | - Hashtable | 10000 | -1 | 3.2149 ms | 0.0669 ms | 0.76 | 0.03 | - Concurrent | 10000 | -1 | 114.5999 ms | 9.5658 ms | 26.59 | 2.44 | - **Dictionary** | **10000** | **1** | **1.9974 ms** | **0.0107 ms** | **1.00** | **0.00** | - Hashtable | 10000 | 1 | 2.0848 ms | 0.0108 ms | 1.04 | 0.01 | - Concurrent | 10000 | 1 | 176.0598 ms | 0.9919 ms | 88.22 | 0.67 | + Method | Items | OverflowCount | MaxDegreeOfParallelism | Median | StdDev | Scaled | Scaled-SD | +----------- |------ |-------------- |----------------------- |------------ |----------- |------- |---------- | + **Dictionary** | **10000** | **1** | **-1** | **3.9319 ms** | **0.1209 ms** | **1.00** | **0.00** | + Hashtable | 10000 | 1 | -1 | 3.3042 ms | 0.1371 ms | 0.83 | 0.04 | + Concurrent | 10000 | 1 | -1 | 92.5456 ms | 6.2102 ms | 23.77 | 1.73 | + **Dictionary** | **10000** | **1** | **1** | **2.0104 ms** | **0.0072 ms** | **1.00** | **0.00** | + Hashtable | 10000 | 1 | 1 | 2.0956 ms | 0.0115 ms | 1.04 | 0.01 | + Concurrent | 10000 | 1 | 1 | 176.4643 ms | 1.1110 ms | 87.81 | 0.62 | + **Dictionary** | **10000** | **10** | **-1** | **4.0999 ms** | **0.1572 ms** | **1.00** | **0.00** | + Hashtable | 10000 | 10 | -1 | 3.2378 ms | 0.0898 ms | 0.79 | 0.04 | + Concurrent | 10000 | 10 | -1 | 99.3250 ms | 7.0231 ms | 24.21 | 1.93 | + **Dictionary** | **10000** | **10** | **1** | **2.0057 ms** | **0.0085 ms** | **1.00** | **0.00** | + Hashtable | 10000 | 10 | 1 | 2.0805 ms | 0.0107 ms | 1.04 | 0.01 | + Concurrent | 10000 | 10 | 1 | 176.0989 ms | 0.9909 ms | 87.83 | 0.60 | + **Dictionary** | **10000** | **100** | **-1** | **4.2080 ms** | **0.1332 ms** | **1.00** | **0.00** | + Hashtable | 10000 | 100 | -1 | 3.1794 ms | 0.1479 ms | 0.77 | 0.04 | + Concurrent | 10000 | 100 | -1 | 116.0221 ms | 8.9935 ms | 27.49 | 2.28 | + **Dictionary** | **10000** | **100** | **1** | **2.0018 ms** | **0.0070 ms** | **1.00** | **0.00** | + Hashtable | 10000 | 100 | 1 | 2.0884 ms | 0.0116 ms | 1.04 | 0.01 | + Concurrent | 10000 | 100 | 1 | 177.2720 ms | 0.9531 ms | 88.50 | 0.55 | + **Dictionary** | **10000** | **1000** | **-1** | **4.4081 ms** | **0.1243 ms** | **1.00** | **0.00** | + Hashtable | 10000 | 1000 | -1 | 3.7401 ms | 0.2270 ms | 0.87 | 0.06 | + Concurrent | 10000 | 1000 | -1 | 201.6415 ms | 28.0265 ms | 45.39 | 6.50 | + **Dictionary** | **10000** | **1000** | **1** | **2.0047 ms** | **0.0061 ms** | **1.00** | **0.00** | + Hashtable | 10000 | 1000 | 1 | 2.0889 ms | 0.0116 ms | 1.04 | 0.01 | + Concurrent | 10000 | 1000 | 1 | 176.6271 ms | 1.3462 ms | 88.05 | 0.70 | diff --git a/results/net4.5.2/MessageTemplateCache/MessageTemplateCacheBenchmark_Warmup-report-github.md b/results/net4.5.2/MessageTemplateCache/MessageTemplateCacheBenchmark_Warmup-report-github.md deleted file mode 100644 index fec79be6a..000000000 --- a/results/net4.5.2/MessageTemplateCache/MessageTemplateCacheBenchmark_Warmup-report-github.md +++ /dev/null @@ -1,40 +0,0 @@ -```ini - -Host Process Environment Information: -BenchmarkDotNet.Core=v0.9.9.0 -OS=Microsoft Windows NT 6.2.9200.0 -Processor=Intel(R) Core(TM) i7-4790 CPU 3.60GHz, ProcessorCount=8 -Frequency=3507509 ticks, Resolution=285.1026 ns, Timer=TSC -CLR=MS.NET 4.0.30319.42000, Arch=64-bit RELEASE [RyuJIT] -GC=Concurrent Workstation -JitModules=clrjit-v4.6.1586.0 - -Type=MessageTemplateCacheBenchmark_Warmup Mode=Throughput - -``` - Method | Items | MaxDegreeOfParallelism | Median | StdDev | Scaled | Scaled-SD | ------------ |------ |----------------------- |---------------- |-------------- |------- |---------- | - **Dictionary** | **10** | **-1** | **8.9109 us** | **0.0617 us** | **1.00** | **0.00** | - Hashtable | 10 | -1 | 9.8728 us | 1.2285 us | 1.14 | 0.14 | - Concurrent | 10 | -1 | 21.8053 us | 1.1545 us | 2.43 | 0.13 | - **Dictionary** | **10** | **1** | **4.2491 us** | **0.1455 us** | **1.00** | **0.00** | - Hashtable | 10 | 1 | 4.3648 us | 0.1695 us | 1.02 | 0.05 | - Concurrent | 10 | 1 | 10.6738 us | 1.2851 us | 2.63 | 0.31 | - **Dictionary** | **100** | **-1** | **102.2525 us** | **1.4686 us** | **1.00** | **0.00** | - Hashtable | 100 | -1 | 106.2933 us | 15.0920 us | 1.08 | 0.15 | - Concurrent | 100 | -1 | 832.1438 us | 15.1188 us | 8.09 | 0.18 | - **Dictionary** | **100** | **1** | **24.2769 us** | **0.4100 us** | **1.00** | **0.00** | - Hashtable | 100 | 1 | 25.9633 us | 0.3417 us | 1.06 | 0.02 | - Concurrent | 100 | 1 | 382.6723 us | 70.3974 us | 15.89 | 2.90 | - **Dictionary** | **1000** | **-1** | **850.1014 us** | **6.2974 us** | **1.00** | **0.00** | - Hashtable | 1000 | -1 | 838.4812 us | 8.0631 us | 0.99 | 0.01 | - Concurrent | 1000 | -1 | 34,932.4300 us | 2,431.0637 us | 41.29 | 2.87 | - **Dictionary** | **1000** | **1** | **243.0266 us** | **1.8920 us** | **1.00** | **0.00** | - Hashtable | 1000 | 1 | 273.0008 us | 3.9633 us | 1.12 | 0.02 | - Concurrent | 1000 | 1 | 15,031.3941 us | 1,152.7616 us | 61.35 | 4.74 | - **Dictionary** | **10000** | **-1** | **4,649.2931 us** | **189.7156 us** | **1.00** | **0.00** | - Hashtable | 10000 | -1 | 4,395.5897 us | 157.3345 us | 0.95 | 0.05 | - Concurrent | 10000 | -1 | 330,528.5888 us | 7,753.0369 us | 71.13 | 3.30 | - **Dictionary** | **10000** | **1** | **2,054.1521 us** | **11.6541 us** | **1.00** | **0.00** | - Hashtable | 10000 | 1 | 2,099.7652 us | 12.9432 us | 1.02 | 0.01 | - Concurrent | 10000 | 1 | 176,623.3173 us | 1,099.8169 us | 85.92 | 0.71 | diff --git a/results/netcoreapp1.0/MessageTemplateCache/MessageTemplateCacheBenchmark_Leaking-report-github.md b/results/netcoreapp1.0/MessageTemplateCache/MessageTemplateCacheBenchmark_Leaking-report-github.md index 2f2d82da6..e8207b26d 100644 --- a/results/netcoreapp1.0/MessageTemplateCache/MessageTemplateCacheBenchmark_Leaking-report-github.md +++ b/results/netcoreapp1.0/MessageTemplateCache/MessageTemplateCacheBenchmark_Leaking-report-github.md @@ -12,11 +12,29 @@ dotnet cli version: 1.0.0-preview2-003131 Type=MessageTemplateCacheBenchmark_Leaking Mode=Throughput ``` - Method | Items | MaxDegreeOfParallelism | Median | StdDev | Scaled | Scaled-SD | ------------ |------ |----------------------- |------------ |----------- |------- |---------- | - **Dictionary** | **10000** | **-1** | **4.0866 ms** | **0.1704 ms** | **1.00** | **0.00** | - Hashtable | 10000 | -1 | 2.3930 ms | 0.1114 ms | 0.59 | 0.04 | - Concurrent | 10000 | -1 | 94.0910 ms | 13.6877 ms | 22.44 | 3.46 | - **Dictionary** | **10000** | **1** | **2.3378 ms** | **0.0076 ms** | **1.00** | **0.00** | - Hashtable | 10000 | 1 | 2.4907 ms | 0.0108 ms | 1.07 | 0.01 | - Concurrent | 10000 | 1 | 165.0691 ms | 1.1892 ms | 70.74 | 0.54 | + Method | Items | OverflowCount | MaxDegreeOfParallelism | Median | StdDev | Scaled | Scaled-SD | +----------- |------ |-------------- |----------------------- |------------ |----------- |------- |---------- | + **Dictionary** | **10000** | **1** | **-1** | **3.9183 ms** | **0.1968 ms** | **1.00** | **0.00** | + Hashtable | 10000 | 1 | -1 | 2.1291 ms | 0.0444 ms | 0.55 | 0.03 | + Concurrent | 10000 | 1 | -1 | 69.0551 ms | 5.2627 ms | 17.73 | 1.59 | + **Dictionary** | **10000** | **1** | **1** | **2.3437 ms** | **0.0091 ms** | **1.00** | **0.00** | + Hashtable | 10000 | 1 | 1 | 2.4470 ms | 0.0135 ms | 1.04 | 0.01 | + Concurrent | 10000 | 1 | 1 | 167.0585 ms | 1.3400 ms | 71.17 | 0.62 | + **Dictionary** | **10000** | **10** | **-1** | **3.9524 ms** | **0.2059 ms** | **1.00** | **0.00** | + Hashtable | 10000 | 10 | -1 | 2.2075 ms | 0.0741 ms | 0.56 | 0.03 | + Concurrent | 10000 | 10 | -1 | 77.3839 ms | 9.4562 ms | 19.32 | 2.57 | + **Dictionary** | **10000** | **10** | **1** | **2.3351 ms** | **0.0081 ms** | **1.00** | **0.00** | + Hashtable | 10000 | 10 | 1 | 2.4537 ms | 0.0128 ms | 1.05 | 0.01 | + Concurrent | 10000 | 10 | 1 | 166.3683 ms | 1.7963 ms | 71.21 | 0.79 | + **Dictionary** | **10000** | **100** | **-1** | **4.0189 ms** | **0.1066 ms** | **1.00** | **0.00** | + Hashtable | 10000 | 100 | -1 | 2.4080 ms | 0.0545 ms | 0.60 | 0.02 | + Concurrent | 10000 | 100 | -1 | 95.7208 ms | 12.9543 ms | 22.97 | 3.26 | + **Dictionary** | **10000** | **100** | **1** | **2.3307 ms** | **0.0147 ms** | **1.00** | **0.00** | + Hashtable | 10000 | 100 | 1 | 2.4447 ms | 0.0132 ms | 1.05 | 0.01 | + Concurrent | 10000 | 100 | 1 | 165.2893 ms | 1.0848 ms | 70.90 | 0.63 | + **Dictionary** | **10000** | **1000** | **-1** | **4.3837 ms** | **0.1868 ms** | **1.00** | **0.00** | + Hashtable | 10000 | 1000 | -1 | 3.3281 ms | 0.1305 ms | 0.77 | 0.04 | + Concurrent | 10000 | 1000 | -1 | 178.7427 ms | 34.4240 ms | 40.66 | 8.10 | + **Dictionary** | **10000** | **1000** | **1** | **2.3404 ms** | **0.0208 ms** | **1.00** | **0.00** | + Hashtable | 10000 | 1000 | 1 | 2.4727 ms | 0.0146 ms | 1.05 | 0.01 | + Concurrent | 10000 | 1000 | 1 | 165.6182 ms | 1.1273 ms | 70.66 | 0.77 | diff --git a/results/netcoreapp1.0/MessageTemplateCache/MessageTemplateCacheBenchmark_Warmup-report-github.md b/results/netcoreapp1.0/MessageTemplateCache/MessageTemplateCacheBenchmark_Warmup-report-github.md deleted file mode 100644 index 5a0cb14ae..000000000 --- a/results/netcoreapp1.0/MessageTemplateCache/MessageTemplateCacheBenchmark_Warmup-report-github.md +++ /dev/null @@ -1,40 +0,0 @@ -```ini - -Host Process Environment Information: -BenchmarkDotNet.Core=v0.9.9.0 -OS=Windows -Processor=?, ProcessorCount=8 -Frequency=3507509 ticks, Resolution=285.1026 ns, Timer=TSC -CLR=CORE, Arch=64-bit ? [RyuJIT] -GC=Concurrent Workstation -dotnet cli version: 1.0.0-preview2-003131 - -Type=MessageTemplateCacheBenchmark_Warmup Mode=Throughput - -``` - Method | Items | MaxDegreeOfParallelism | Median | StdDev | Scaled | Scaled-SD | ------------ |------ |----------------------- |---------------- |-------------- |------- |---------- | - **Dictionary** | **10** | **-1** | **9.6902 us** | **0.0664 us** | **1.00** | **0.00** | - Hashtable | 10 | -1 | 7.2262 us | 0.1284 us | 0.74 | 0.01 | - Concurrent | 10 | -1 | 9.5027 us | 0.6200 us | 1.00 | 0.06 | - **Dictionary** | **10** | **1** | **3.3790 us** | **0.0236 us** | **1.00** | **0.00** | - Hashtable | 10 | 1 | 3.4664 us | 0.0544 us | 1.03 | 0.02 | - Concurrent | 10 | 1 | 4.7689 us | 0.3187 us | 1.47 | 0.09 | - **Dictionary** | **100** | **-1** | **126.1621 us** | **19.4774 us** | **1.00** | **0.00** | - Hashtable | 100 | -1 | 101.6898 us | 8.7118 us | 0.81 | 0.10 | - Concurrent | 100 | -1 | 404.3201 us | 33.8885 us | 3.08 | 0.39 | - **Dictionary** | **100** | **1** | **26.9025 us** | **0.1860 us** | **1.00** | **0.00** | - Hashtable | 100 | 1 | 29.0055 us | 0.4187 us | 1.07 | 0.02 | - Concurrent | 100 | 1 | 107.1666 us | 14.1945 us | 4.07 | 0.53 | - **Dictionary** | **1000** | **-1** | **833.5471 us** | **11.3689 us** | **1.00** | **0.00** | - Hashtable | 1000 | -1 | 830.9745 us | 11.1378 us | 0.99 | 0.02 | - Concurrent | 1000 | -1 | 15,259.1376 us | 1,588.6467 us | 18.34 | 1.91 | - **Dictionary** | **1000** | **1** | **257.6129 us** | **1.8266 us** | **1.00** | **0.00** | - Hashtable | 1000 | 1 | 293.7693 us | 2.1159 us | 1.14 | 0.01 | - Concurrent | 1000 | 1 | 6,819.1691 us | 1,207.1580 us | 27.51 | 4.68 | - **Dictionary** | **10000** | **-1** | **4,318.6810 us** | **112.2715 us** | **1.00** | **0.00** | - Hashtable | 10000 | -1 | 4,112.6765 us | 54.6069 us | 0.95 | 0.03 | - Concurrent | 10000 | -1 | 324,148.5624 us | 9,763.5470 us | 75.11 | 2.91 | - **Dictionary** | **10000** | **1** | **2,343.6103 us** | **11.5641 us** | **1.00** | **0.00** | - Hashtable | 10000 | 1 | 2,484.4611 us | 6.8263 us | 1.06 | 0.01 | - Concurrent | 10000 | 1 | 165,379.1908 us | 1,588.0393 us | 70.54 | 0.74 | diff --git a/test/Serilog.PerformanceTests/Harness.cs b/test/Serilog.PerformanceTests/Harness.cs index 67c388578..6b9a0dd1e 100644 --- a/test/Serilog.PerformanceTests/Harness.cs +++ b/test/Serilog.PerformanceTests/Harness.cs @@ -24,7 +24,6 @@ public class Harness public void MessageTemplateCacheBenchmark() { BenchmarkRunner.Run(); - BenchmarkRunner.Run(); BenchmarkRunner.Run(); } diff --git a/test/Serilog.PerformanceTests/MessageTemplateCacheBenchmark/MessageTemplateCacheBenchmark_Leaking.cs b/test/Serilog.PerformanceTests/MessageTemplateCacheBenchmark/MessageTemplateCacheBenchmark_Leaking.cs index 76ee3ba61..153d45171 100644 --- a/test/Serilog.PerformanceTests/MessageTemplateCacheBenchmark/MessageTemplateCacheBenchmark_Leaking.cs +++ b/test/Serilog.PerformanceTests/MessageTemplateCacheBenchmark/MessageTemplateCacheBenchmark_Leaking.cs @@ -12,13 +12,16 @@ namespace Serilog.PerformanceTests public class MessageTemplateCacheBenchmark_Leaking { const string DefaultOutputTemplate = "{Timestamp:yyyy-MM-dd HH:mm:ss} [{Level}] {Message}{NewLine}{Exception}"; - const int MaxCacheItems = 1100; + const int MaxCacheItems = 1000; List _templateList; [Params(10000)] public int Items { get; set; } + [Params(1, 10, 100, 1000)] + public int OverflowCount { get; set; } + [Params(1, -1)] public int MaxDegreeOfParallelism { get; set; } @@ -49,12 +52,13 @@ public void Concurrent() void Run(Func cacheFactory) where T : IMessageTemplateParser { var cache = cacheFactory(); + var total = MaxCacheItems + OverflowCount; Parallel.For( 0, _templateList.Count, new ParallelOptions() { MaxDegreeOfParallelism = MaxDegreeOfParallelism }, - idx => cache.Parse(_templateList[idx % MaxCacheItems])); + idx => cache.Parse(_templateList[idx % total])); } } } diff --git a/test/Serilog.PerformanceTests/MessageTemplateCacheBenchmark/MessageTemplateCacheBenchmark_Warmup.cs b/test/Serilog.PerformanceTests/MessageTemplateCacheBenchmark/MessageTemplateCacheBenchmark_Warmup.cs deleted file mode 100644 index e0bfdf526..000000000 --- a/test/Serilog.PerformanceTests/MessageTemplateCacheBenchmark/MessageTemplateCacheBenchmark_Warmup.cs +++ /dev/null @@ -1,58 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using BenchmarkDotNet.Attributes; -using Serilog.Core; -using Serilog.Core.Pipeline; -using Serilog.PerformanceTests.Support; - -namespace Serilog.PerformanceTests -{ - public class MessageTemplateCacheBenchmark_Warmup - { - const string DefaultOutputTemplate = "{Timestamp:yyyy-MM-dd HH:mm:ss} [{Level}] {Message}{NewLine}{Exception}"; - - List _templateList; - - [Params(10, 100, 1000, 10000)] - public int Items { get; set; } - - [Params(1, -1)] - public int MaxDegreeOfParallelism { get; set; } - - [Setup] - public void Setup() - { - _templateList = Enumerable.Range(0, Items).Select(x => $"{DefaultOutputTemplate}_{Guid.NewGuid()}").ToList(); - } - - [Benchmark(Baseline = true)] - public void Dictionary() - { - Run(() => new DictionaryMessageTemplateCache(NoOpMessageTemplateParser.Instance)); - } - - [Benchmark] - public void Hashtable() - { - Run(() => new MessageTemplateCache(NoOpMessageTemplateParser.Instance)); - } - - [Benchmark] - public void Concurrent() - { - Run(() => new ConcurrentDictionaryMessageTemplateCache(NoOpMessageTemplateParser.Instance)); - } - - void Run(Func cacheFactory) where T : IMessageTemplateParser - { - var cache = cacheFactory(); - - Parallel.ForEach( - _templateList, - new ParallelOptions() { MaxDegreeOfParallelism = MaxDegreeOfParallelism }, - t => cache.Parse(t)); - } - } -} \ No newline at end of file From 3e47423e563cca24dd917191d6405acbbcb339fb Mon Sep 17 00:00:00 2001 From: Joshua Clark Date: Fri, 28 Oct 2016 19:20:50 -0400 Subject: [PATCH 27/53] Fix for issue #885, formatting floating point special values NaN/PositiveInfinity/NegativeInfinity as JSON strings and unit tests --- .../Formatting/Json/JsonValueFormatter.cs | 9 +++++- .../Json/JsonValueFormatterTests.cs | 28 ++++++++++++++++++- 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/src/Serilog/Formatting/Json/JsonValueFormatter.cs b/src/Serilog/Formatting/Json/JsonValueFormatter.cs index f32724b0a..53b50cb3c 100644 --- a/src/Serilog/Formatting/Json/JsonValueFormatter.cs +++ b/src/Serilog/Formatting/Json/JsonValueFormatter.cs @@ -184,7 +184,14 @@ protected virtual void FormatLiteralValue(object value, TextWriter output) if (value is double || value is float) { - FormatApproximateNumericValue((IFormattable)value, output); + if ((value is double && (double.IsNaN((double)value) || double.IsInfinity((double)value))) + || (value is float && (float.IsNaN((float)value) || float.IsInfinity((float)value)))) + { + FormatStringValue(value.ToString(), output); + return; + } + + FormatApproximateNumericValue((IFormattable)value, output); return; } diff --git a/test/Serilog.Tests/Formatting/Json/JsonValueFormatterTests.cs b/test/Serilog.Tests/Formatting/Json/JsonValueFormatterTests.cs index 98d5ae814..89a4528c2 100644 --- a/test/Serilog.Tests/Formatting/Json/JsonValueFormatterTests.cs +++ b/test/Serilog.Tests/Formatting/Json/JsonValueFormatterTests.cs @@ -49,7 +49,33 @@ public void DoubleFormatsAsNumber() JsonLiteralTypesAreFormatted(123.45, "123.45"); } - [Fact] + [Fact] + public void DoubleSpecialsFormatAsString() + { + string format = "\"{0}\""; + + JsonLiteralTypesAreFormatted(double.NaN, string.Format(format, double.NaN)); + JsonLiteralTypesAreFormatted(double.PositiveInfinity, string.Format(format, double.PositiveInfinity)); + JsonLiteralTypesAreFormatted(double.NegativeInfinity, string.Format(format, double.NegativeInfinity)); + } + + [Fact] + public void FloatFormatsAsNumber() + { + JsonLiteralTypesAreFormatted(123.45f, "123.45"); + } + + [Fact] + public void FloatSpecialsFormatAsString() + { + string format = "\"{0}\""; + + JsonLiteralTypesAreFormatted(float.NaN, string.Format(format, float.NaN)); + JsonLiteralTypesAreFormatted(float.PositiveInfinity, string.Format(format, float.PositiveInfinity)); + JsonLiteralTypesAreFormatted(float.NegativeInfinity, string.Format(format, float.NegativeInfinity)); + } + + [Fact] public void DecimalFormatsAsNumber() { JsonLiteralTypesAreFormatted(123.45m, "123.45"); From 675889b156d634287097fd2bdd8b030149f8f524 Mon Sep 17 00:00:00 2001 From: Jared Klopper Date: Sun, 30 Oct 2016 21:11:54 +1300 Subject: [PATCH 28/53] Allow overriding log level via config files --- .../KeyValuePairs/KeyValuePairSettings.cs | 11 ++++++++ .../Settings/KeyValuePairSettingsTests.cs | 27 +++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/src/Serilog/Settings/KeyValuePairs/KeyValuePairSettings.cs b/src/Serilog/Settings/KeyValuePairs/KeyValuePairSettings.cs index 3c1f2753e..49e113895 100644 --- a/src/Serilog/Settings/KeyValuePairs/KeyValuePairSettings.cs +++ b/src/Serilog/Settings/KeyValuePairs/KeyValuePairSettings.cs @@ -35,6 +35,7 @@ class KeyValuePairSettings : ILoggerSettings const string UsingDirectiveFullFormPrefix = "using:"; const string EnrichWithEventEnricherPrefix = "enrich:"; const string EnrichWithPropertyDirectivePrefix = "enrich:with-property:"; + const string MinimumLevelOverrideDirectivePrefix = "minimum-level:override:"; const string AuditOrWriteToDirectiveRegex = @"^(?audit-to|write-to):(?[A-Za-z0-9]*)(\.(?[A-Za-z0-9]*)){0,1}$"; @@ -79,6 +80,16 @@ public void Configure(LoggerConfiguration loggerConfiguration) loggerConfiguration.Enrich.WithProperty(name, enrichProperyDirective.Value); } + foreach (var minimumLevelOverrideDirective in directives.Where(dir => + dir.Key.StartsWith(MinimumLevelOverrideDirectivePrefix) && dir.Key.Length > MinimumLevelOverrideDirectivePrefix.Length)) + { + LogEventLevel overriddenLevel; + if (Enum.TryParse(minimumLevelOverrideDirective.Value, out overriddenLevel)) { + var namespacePrefix = minimumLevelOverrideDirective.Key.Substring(MinimumLevelOverrideDirectivePrefix.Length); + loggerConfiguration.MinimumLevel.Override(namespacePrefix, overriddenLevel); + } + } + var splitWriteTo = new Regex(AuditOrWriteToDirectiveRegex); var sinkDirectives = (from wt in directives diff --git a/test/Serilog.Tests/Settings/KeyValuePairSettingsTests.cs b/test/Serilog.Tests/Settings/KeyValuePairSettingsTests.cs index 53ebb0d96..b19af9824 100644 --- a/test/Serilog.Tests/Settings/KeyValuePairSettingsTests.cs +++ b/test/Serilog.Tests/Settings/KeyValuePairSettingsTests.cs @@ -186,5 +186,32 @@ public void AuditSinksAreConfigured() Assert.Equal(0, DummyRollingFileSink.Emitted.Count); Assert.Equal(1, DummyRollingFileAuditSink.Emitted.Count); } + + [Fact] + public void TestMinimumLevelOverrides() { + var settings = new Dictionary + { + ["minimum-level:override:Microsoft"] = "Warning", + }; + + LogEvent evt = null; + + var log = new LoggerConfiguration() + .ReadFrom.KeyValuePairs(settings) + .WriteTo.Sink(new DelegatingSink(e => evt = e)) + .CreateLogger(); + + var microsoftLogger = log.ForContext(); + microsoftLogger.Write(Some.InformationEvent()); + + Assert.Null(evt); + + microsoftLogger.Warning("Bad things"); + Assert.NotNull(evt); + + evt = null; + log.Write(Some.InformationEvent()); + Assert.NotNull(evt); + } } } \ No newline at end of file From 8a017eef521406e408d6112d63f343797c192233 Mon Sep 17 00:00:00 2001 From: Joshua Clark Date: Tue, 1 Nov 2016 08:48:37 -0400 Subject: [PATCH 29/53] replaced switched on type with separate functions --- .../Formatting/Json/JsonValueFormatter.cs | 48 ++++++++++++------- 1 file changed, 32 insertions(+), 16 deletions(-) diff --git a/src/Serilog/Formatting/Json/JsonValueFormatter.cs b/src/Serilog/Formatting/Json/JsonValueFormatter.cs index 53b50cb3c..712290c94 100644 --- a/src/Serilog/Formatting/Json/JsonValueFormatter.cs +++ b/src/Serilog/Formatting/Json/JsonValueFormatter.cs @@ -182,18 +182,17 @@ protected virtual void FormatLiteralValue(object value, TextWriter output) return; } - if (value is double || value is float) - { - if ((value is double && (double.IsNaN((double)value) || double.IsInfinity((double)value))) - || (value is float && (float.IsNaN((float)value) || float.IsInfinity((float)value)))) - { - FormatStringValue(value.ToString(), output); - return; - } - - FormatApproximateNumericValue((IFormattable)value, output); - return; - } + if (value is double) + { + FormatDoubleValue((double)value, output); + return; + } + + if (value is float) + { + FormatFloatValue((float)value, output); + return; + } if (value is bool) { @@ -228,10 +227,27 @@ static void FormatBooleanValue(bool value, TextWriter output) output.Write(value ? "true" : "false"); } - static void FormatApproximateNumericValue(IFormattable value, TextWriter output) - { - output.Write(value.ToString("R", CultureInfo.InvariantCulture)); - } + static void FormatFloatValue(float value, TextWriter output) + { + if (float.IsNaN(value) || float.IsInfinity(value)) + { + FormatStringValue(value.ToString(), output); + return; + } + + output.Write(value.ToString("R", CultureInfo.InvariantCulture)); + } + + static void FormatDoubleValue(double value, TextWriter output) + { + if (double.IsNaN(value) || double.IsInfinity(value)) + { + FormatStringValue(value.ToString(), output); + return; + } + + output.Write(value.ToString("R", CultureInfo.InvariantCulture)); + } static void FormatExactNumericValue(IFormattable value, TextWriter output) { From 150f0f9a0e8e5514db81e6e24025e58c21b6256d Mon Sep 17 00:00:00 2001 From: Joshua Clark Date: Tue, 1 Nov 2016 22:57:57 -0400 Subject: [PATCH 30/53] Requested changes: invariant culture and literal expected values in tests --- .../Formatting/Json/JsonValueFormatter.cs | 4 ++-- .../Formatting/Json/JsonValueFormatterTests.cs | 18 +++++++----------- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/src/Serilog/Formatting/Json/JsonValueFormatter.cs b/src/Serilog/Formatting/Json/JsonValueFormatter.cs index 712290c94..b90f2679d 100644 --- a/src/Serilog/Formatting/Json/JsonValueFormatter.cs +++ b/src/Serilog/Formatting/Json/JsonValueFormatter.cs @@ -231,7 +231,7 @@ static void FormatFloatValue(float value, TextWriter output) { if (float.IsNaN(value) || float.IsInfinity(value)) { - FormatStringValue(value.ToString(), output); + FormatStringValue(value.ToString(CultureInfo.InvariantCulture), output); return; } @@ -242,7 +242,7 @@ static void FormatDoubleValue(double value, TextWriter output) { if (double.IsNaN(value) || double.IsInfinity(value)) { - FormatStringValue(value.ToString(), output); + FormatStringValue(value.ToString(CultureInfo.InvariantCulture), output); return; } diff --git a/test/Serilog.Tests/Formatting/Json/JsonValueFormatterTests.cs b/test/Serilog.Tests/Formatting/Json/JsonValueFormatterTests.cs index 89a4528c2..45e22324a 100644 --- a/test/Serilog.Tests/Formatting/Json/JsonValueFormatterTests.cs +++ b/test/Serilog.Tests/Formatting/Json/JsonValueFormatterTests.cs @@ -52,13 +52,11 @@ public void DoubleFormatsAsNumber() [Fact] public void DoubleSpecialsFormatAsString() { - string format = "\"{0}\""; - - JsonLiteralTypesAreFormatted(double.NaN, string.Format(format, double.NaN)); - JsonLiteralTypesAreFormatted(double.PositiveInfinity, string.Format(format, double.PositiveInfinity)); - JsonLiteralTypesAreFormatted(double.NegativeInfinity, string.Format(format, double.NegativeInfinity)); + JsonLiteralTypesAreFormatted(double.NaN, "\"NaN\""); + JsonLiteralTypesAreFormatted(double.PositiveInfinity, "\"Infinity\""); + JsonLiteralTypesAreFormatted(double.NegativeInfinity, "\"-Infinity\""); } - + [Fact] public void FloatFormatsAsNumber() { @@ -68,11 +66,9 @@ public void FloatFormatsAsNumber() [Fact] public void FloatSpecialsFormatAsString() { - string format = "\"{0}\""; - - JsonLiteralTypesAreFormatted(float.NaN, string.Format(format, float.NaN)); - JsonLiteralTypesAreFormatted(float.PositiveInfinity, string.Format(format, float.PositiveInfinity)); - JsonLiteralTypesAreFormatted(float.NegativeInfinity, string.Format(format, float.NegativeInfinity)); + JsonLiteralTypesAreFormatted(float.NaN, "\"NaN\""); + JsonLiteralTypesAreFormatted(float.PositiveInfinity, "\"Infinity\""); + JsonLiteralTypesAreFormatted(float.NegativeInfinity, "\"-Infinity\""); } [Fact] From 1a484f207e7c5fa7d7fe584230e03a91eb149f92 Mon Sep 17 00:00:00 2001 From: Joshua Clark Date: Fri, 4 Nov 2016 08:16:10 -0400 Subject: [PATCH 31/53] convention tests for log level write methods --- test/Serilog.Tests/Core/LoggerTests.cs | 266 ++++++++++++++++++++++++- 1 file changed, 265 insertions(+), 1 deletion(-) diff --git a/test/Serilog.Tests/Core/LoggerTests.cs b/test/Serilog.Tests/Core/LoggerTests.cs index 930b82d38..8373a8f02 100644 --- a/test/Serilog.Tests/Core/LoggerTests.cs +++ b/test/Serilog.Tests/Core/LoggerTests.cs @@ -6,6 +6,9 @@ using Serilog.Core; using Serilog.Events; using Serilog.Tests.Support; +using System.Reflection; +using Xunit.Sdk; +using System.Text.RegularExpressions; namespace Serilog.Tests.Core { @@ -116,5 +119,266 @@ public void PropertiesCanBeBound() Assert.Equal("Name", property.Name); Assert.Equal("World", property.Value.LiteralValue()); } - } + + [Fact] + public void VerboseMethodsMatchConvention() + { + ValidateConventionForMethodSet("Verbose"); + } + + [Fact] + public void DebugMethodsMatchConvention() + { + ValidateConventionForMethodSet("Debug"); + } + + [Fact] + public void InformationMethodsMatchConvention() + { + ValidateConventionForMethodSet("Information"); + } + + [Fact] + public void WarningMethodsMatchConvention() + { + ValidateConventionForMethodSet("Warning"); + } + + [Fact] + public void ErrorMethodsMatchConvention() + { + ValidateConventionForMethodSet("Error"); + } + + [Fact] + public void FatalMethodsMatchConvention() + { + ValidateConventionForMethodSet("Fatal"); + } + + private void ValidateConventionForMethodSet(string setName) + { + var methodSet = typeof(Logger).GetMethods().Where(method => method.Name == setName); + + var testMethods = typeof(LoggerTests).GetRuntimeMethods() + .Where(method => Regex.IsMatch(method.Name, "ValidateMethod\\d")); + + foreach (var method in methodSet) + { + Assert.Equal(method.ReturnType, typeof(void)); + + Assert.True(method.IsPublic); + + var messageTemplateAttr = method.GetCustomAttribute(); + + Assert.NotNull(messageTemplateAttr); + + Assert.Equal(messageTemplateAttr.MessageTemplateParameterName, "messageTemplate"); + + var signatureMatch = false; + + foreach (var test in testMethods) + { + try + { + test.Invoke(this, new object[] { method }); + + signatureMatch = true; + + break; + } + catch (Exception e) + when (e is TargetInvocationException + && ((TargetInvocationException)e).GetBaseException() is XunitException) + { + continue; + } + } + + Assert.True(signatureMatch, $"{method} did not match any known convention"); + } + } + + + // Method0 (string messageTemplate) : void + private void ValidateMethod0(MethodInfo method) + { + VerifyMethodSignature(method, expectedArgCount: 1); + + GetLoggerAndInvoke(method, "message"); + } + + // Method1 (string messageTemplate, T propertyValue) : void + private void ValidateMethod1(MethodInfo method) + { + VerifyMethodSignature(method, isGeneric: true, expectedArgCount: 2); + + GetLoggerAndInvokeGeneric(method, new Type[] { typeof(string) }, "message", "value0"); + } + + // Method2 (string messageTemplate, T0 propertyValue0, T1 propertyValue1) : void + private void ValidateMethod2(MethodInfo method) + { + VerifyMethodSignature(method, isGeneric: true, expectedArgCount: 3); + + GetLoggerAndInvokeGeneric(method, new Type[] { typeof(string), typeof(string) }, + "message", "value0", "value1"); + } + + // Method3 (string messageTemplate, T0 propertyValue0, T1 propertyValue1, T2 propertyValue2) : void + private void ValidateMethod3(MethodInfo method) + { + VerifyMethodSignature(method, isGeneric: true, expectedArgCount: 4); + + GetLoggerAndInvokeGeneric(method, new Type[] { typeof(string), typeof(string), typeof(string) }, + "message", "value0", "value1", "value2"); + } + + // Method4 (string messageTemplate, params object[] propertyValues) : void + private void ValidateMethod4(MethodInfo method) + { + VerifyMethodSignature(method, expectedArgCount: 2); + + GetLoggerAndInvoke(method, "message", new object[] { "value0", "value1", "value2" }); + } + + // Method5 (Exception exception, string messageTemplate) : void + private void ValidateMethod5(MethodInfo method) + { + VerifyMethodSignature(method, hasExceptionArg: true, expectedArgCount: 2); + + GetLoggerAndInvoke(method, new Exception("test"), "message"); + } + + // Method6 (Exception exception, string messageTemplate, T propertyValue) : void + private void ValidateMethod6(MethodInfo method) + { + VerifyMethodSignature(method, hasExceptionArg: true, isGeneric: true, expectedArgCount: 3); + + GetLoggerAndInvokeGeneric(method, new Type[] { typeof(string) }, new Exception("test"), "message", "value0"); + } + + // Method7 (Exception exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1) : void + private void ValidateMethod7(MethodInfo method) + { + VerifyMethodSignature(method, hasExceptionArg: true, isGeneric: true, expectedArgCount: 4); + + GetLoggerAndInvokeGeneric(method, new Type[] { typeof(string), typeof(string) }, + new Exception("test"), "message", "value0", "value1"); + } + + // Method8 (Exception exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1, T2 propertyValue2) : void + private void ValidateMethod8(MethodInfo method) + { + VerifyMethodSignature(method, hasExceptionArg: true, isGeneric: true, expectedArgCount: 5); + + var typeOfString = typeof(string); + + GetLoggerAndInvokeGeneric(method, new Type[] { typeof(string), typeof(string), typeof(string) }, + new Exception("test"), "message", "value0", "value1", "value2"); + } + + // Method9 (Exception exception, string messageTemplate, params object[] propertyValues) : void + private void ValidateMethod9(MethodInfo method) + { + VerifyMethodSignature(method, hasExceptionArg: true, expectedArgCount: 3); + + GetLoggerAndInvoke(method, new Exception("test"), "message", new object[] { "value0", "value1", "value2" }); + } + + private static void GetLoggerAndInvoke(MethodInfo method, params object[] parameters) + { + var logger = new LoggerConfiguration().CreateLogger(); + + method.Invoke(logger, parameters); + } + + private static void GetLoggerAndInvokeGeneric(MethodInfo method, Type[] typeArgs, params object[] parameters) + { + var logger = new LoggerConfiguration().CreateLogger(); + + method.MakeGenericMethod(typeArgs).Invoke(logger, parameters); + } + + // parameters will always be ordered so single evaluation method will work + private static void VerifyMethodSignature(MethodInfo method, bool hasExceptionArg = false, bool isGeneric = false, int expectedArgCount = 1) + { + var parameters = method.GetParameters(); + + Assert.Equal(parameters.Length, expectedArgCount); + + int index = 0; + + if (hasExceptionArg) //verify exception arg type and name + { + Assert.Equal(parameters[index].ParameterType, typeof(Exception)); + + Assert.Equal(parameters[index].Name, "exception"); + + index++; + } + + //check message template argument + Assert.Equal(parameters[index].ParameterType, typeof(string)); + + Assert.Equal(parameters[index].Name, "messageTemplate"); + + index++; + + if (isGeneric) //validate type arguments, generic parameters, and cross-reference + { + Assert.True(method.IsGenericMethod); + + var genericTypeArgs = method.GetGenericArguments(); + + //multiple generic argument convention T0...Tx : T0 propertyValue0... Tx propertyValueX + if (genericTypeArgs.Length > 1) + { + for (int i = 0; i < genericTypeArgs.Length; i++, index++) + { + Assert.Equal(genericTypeArgs[i].Name, $"T{i}"); + + var genericConstraints = genericTypeArgs[i].GetTypeInfo().GetGenericParameterConstraints(); + + Assert.Empty(genericConstraints); + + Assert.Equal(parameters[index].Name, $"propertyValue{i}"); + + Assert.Equal(parameters[index].ParameterType, genericTypeArgs[i]); + } + } + else //single generic argument convention T : T propertyValue + { + var genericTypeArg = genericTypeArgs[0]; + + Assert.Equal(genericTypeArg.Name, "T"); + + var genericConstraints = genericTypeArg.GetTypeInfo().GetGenericParameterConstraints(); + + Assert.Empty(genericConstraints); + + Assert.Equal(parameters[index].Name, "propertyValue"); + + Assert.Equal(genericTypeArg, parameters[index].ParameterType); + + index++; + } + } + + //check for params argument + //params args currently have to be the last argument, and generics don't have params arg + if (!isGeneric && (parameters.Length - index) == 1) + { + var paramsArrayArg = parameters[index]; + + var paramsAttr = parameters[index].GetCustomAttribute(typeof(ParamArrayAttribute), inherit: false); + + Assert.NotNull(paramsAttr); + + Assert.Equal(paramsArrayArg.ParameterType, typeof(object[])); + + Assert.Equal(paramsArrayArg.Name, "propertyValues"); + } + } + } } From 6b3425a4b811db248b0984f35353e2618a7dbc8d Mon Sep 17 00:00:00 2001 From: Joshua Clark Date: Sat, 5 Nov 2016 05:04:24 -0400 Subject: [PATCH 32/53] addressing feedback: changed logger configuration, condensed tests into theory, fixed some comments, add way to bubble up exception messages --- test/Serilog.Tests/Core/LoggerTests.cs | 248 +++++++++++++------------ 1 file changed, 133 insertions(+), 115 deletions(-) diff --git a/test/Serilog.Tests/Core/LoggerTests.cs b/test/Serilog.Tests/Core/LoggerTests.cs index 8373a8f02..7716bc8a1 100644 --- a/test/Serilog.Tests/Core/LoggerTests.cs +++ b/test/Serilog.Tests/Core/LoggerTests.cs @@ -9,6 +9,7 @@ using System.Reflection; using Xunit.Sdk; using System.Text.RegularExpressions; +using System.Text; namespace Serilog.Tests.Core { @@ -120,44 +121,17 @@ public void PropertiesCanBeBound() Assert.Equal("World", property.Value.LiteralValue()); } - [Fact] - public void VerboseMethodsMatchConvention() + [Theory] + [InlineData(LogEventLevel.Verbose)] + [InlineData(LogEventLevel.Debug)] + [InlineData(LogEventLevel.Information)] + [InlineData(LogEventLevel.Warning)] + [InlineData(LogEventLevel.Error)] + [InlineData(LogEventLevel.Fatal)] + public void ValidateConventionForMethodSet(LogEventLevel level) { - ValidateConventionForMethodSet("Verbose"); - } - - [Fact] - public void DebugMethodsMatchConvention() - { - ValidateConventionForMethodSet("Debug"); - } - - [Fact] - public void InformationMethodsMatchConvention() - { - ValidateConventionForMethodSet("Information"); - } - - [Fact] - public void WarningMethodsMatchConvention() - { - ValidateConventionForMethodSet("Warning"); - } - - [Fact] - public void ErrorMethodsMatchConvention() - { - ValidateConventionForMethodSet("Error"); - } - - [Fact] - public void FatalMethodsMatchConvention() - { - ValidateConventionForMethodSet("Fatal"); - } + var setName = level.ToString(); - private void ValidateConventionForMethodSet(string setName) - { var methodSet = typeof(Logger).GetMethods().Where(method => method.Name == setName); var testMethods = typeof(LoggerTests).GetRuntimeMethods() @@ -177,207 +151,251 @@ private void ValidateConventionForMethodSet(string setName) var signatureMatch = false; - foreach (var test in testMethods) + var report = new StringBuilder(); + + foreach (var testMethod in testMethods) { try { - test.Invoke(this, new object[] { method }); + testMethod.Invoke(this, new object[] { method, level }); signatureMatch = true; break; } catch (Exception e) - when (e is TargetInvocationException - && ((TargetInvocationException)e).GetBaseException() is XunitException) + when (e is TargetInvocationException + && (e as TargetInvocationException).GetBaseException() is XunitException) { + var xunitException = (XunitException)(e as TargetInvocationException).GetBaseException(); + + if (xunitException.Data.Contains("IsSignatureAssertionFailure")) + { + report.AppendLine($"{testMethod.Name} Signature Mismatch on: {method} with: {xunitException.Message}"); + } + else + { + report.AppendLine($"{testMethod.Name} Invocation Failure on: {method} with: {xunitException.UserMessage}"); + } + continue; } } - Assert.True(signatureMatch, $"{method} did not match any known convention"); + Assert.True(signatureMatch, $"{method} did not match any known convention\n" + report.ToString()); } } - // Method0 (string messageTemplate) : void - private void ValidateMethod0(MethodInfo method) + void ValidateMethod0(MethodInfo method, LogEventLevel level) { VerifyMethodSignature(method, expectedArgCount: 1); - GetLoggerAndInvoke(method, "message"); + GetLoggerAndInvoke(method, level, "message"); } // Method1 (string messageTemplate, T propertyValue) : void - private void ValidateMethod1(MethodInfo method) + void ValidateMethod1(MethodInfo method, LogEventLevel level) { VerifyMethodSignature(method, isGeneric: true, expectedArgCount: 2); - GetLoggerAndInvokeGeneric(method, new Type[] { typeof(string) }, "message", "value0"); + GetLoggerAndInvokeGeneric(method, level, new Type[] { typeof(string) }, "message", "value0"); } // Method2 (string messageTemplate, T0 propertyValue0, T1 propertyValue1) : void - private void ValidateMethod2(MethodInfo method) + void ValidateMethod2(MethodInfo method, LogEventLevel level) { VerifyMethodSignature(method, isGeneric: true, expectedArgCount: 3); - GetLoggerAndInvokeGeneric(method, new Type[] { typeof(string), typeof(string) }, + GetLoggerAndInvokeGeneric(method, level, new Type[] { typeof(string), typeof(string) }, "message", "value0", "value1"); } // Method3 (string messageTemplate, T0 propertyValue0, T1 propertyValue1, T2 propertyValue2) : void - private void ValidateMethod3(MethodInfo method) + void ValidateMethod3(MethodInfo method, LogEventLevel level) { VerifyMethodSignature(method, isGeneric: true, expectedArgCount: 4); - GetLoggerAndInvokeGeneric(method, new Type[] { typeof(string), typeof(string), typeof(string) }, + GetLoggerAndInvokeGeneric(method, level, new Type[] { typeof(string), typeof(string), typeof(string) }, "message", "value0", "value1", "value2"); } // Method4 (string messageTemplate, params object[] propertyValues) : void - private void ValidateMethod4(MethodInfo method) + void ValidateMethod4(MethodInfo method, LogEventLevel level) { VerifyMethodSignature(method, expectedArgCount: 2); - GetLoggerAndInvoke(method, "message", new object[] { "value0", "value1", "value2" }); + GetLoggerAndInvoke(method, level, "message", new object[] { "value0", "value1", "value2" }); } // Method5 (Exception exception, string messageTemplate) : void - private void ValidateMethod5(MethodInfo method) + void ValidateMethod5(MethodInfo method, LogEventLevel level) { VerifyMethodSignature(method, hasExceptionArg: true, expectedArgCount: 2); - GetLoggerAndInvoke(method, new Exception("test"), "message"); + GetLoggerAndInvoke(method, level, new Exception("test"), "message"); } // Method6 (Exception exception, string messageTemplate, T propertyValue) : void - private void ValidateMethod6(MethodInfo method) + void ValidateMethod6(MethodInfo method, LogEventLevel level) { VerifyMethodSignature(method, hasExceptionArg: true, isGeneric: true, expectedArgCount: 3); - GetLoggerAndInvokeGeneric(method, new Type[] { typeof(string) }, new Exception("test"), "message", "value0"); + GetLoggerAndInvokeGeneric(method, level, new Type[] { typeof(string) }, new Exception("test"), "message", "value0"); } // Method7 (Exception exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1) : void - private void ValidateMethod7(MethodInfo method) + void ValidateMethod7(MethodInfo method, LogEventLevel level) { VerifyMethodSignature(method, hasExceptionArg: true, isGeneric: true, expectedArgCount: 4); - GetLoggerAndInvokeGeneric(method, new Type[] { typeof(string), typeof(string) }, + GetLoggerAndInvokeGeneric(method, level, new Type[] { typeof(string), typeof(string) }, new Exception("test"), "message", "value0", "value1"); } // Method8 (Exception exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1, T2 propertyValue2) : void - private void ValidateMethod8(MethodInfo method) + void ValidateMethod8(MethodInfo method, LogEventLevel level) { VerifyMethodSignature(method, hasExceptionArg: true, isGeneric: true, expectedArgCount: 5); - var typeOfString = typeof(string); - - GetLoggerAndInvokeGeneric(method, new Type[] { typeof(string), typeof(string), typeof(string) }, + GetLoggerAndInvokeGeneric(method, level, new Type[] { typeof(string), typeof(string), typeof(string) }, new Exception("test"), "message", "value0", "value1", "value2"); } // Method9 (Exception exception, string messageTemplate, params object[] propertyValues) : void - private void ValidateMethod9(MethodInfo method) + void ValidateMethod9(MethodInfo method, LogEventLevel level) { VerifyMethodSignature(method, hasExceptionArg: true, expectedArgCount: 3); - GetLoggerAndInvoke(method, new Exception("test"), "message", new object[] { "value0", "value1", "value2" }); + GetLoggerAndInvoke(method, level, new Exception("test"), "message", new object[] { "value0", "value1", "value2" }); } - private static void GetLoggerAndInvoke(MethodInfo method, params object[] parameters) + static void GetLoggerAndInvoke(MethodInfo method, LogEventLevel level, params object[] parameters) { - var logger = new LoggerConfiguration().CreateLogger(); + var sink = new CollectingSink(); + + var logger = new LoggerConfiguration() + .MinimumLevel.Verbose() + .WriteTo.Sink(sink) + .CreateLogger(); method.Invoke(logger, parameters); + + Assert.Equal(1, sink.Events.Count); + + var evt = sink.Events.Single(); + + Assert.Equal(level, evt.Level); } - private static void GetLoggerAndInvokeGeneric(MethodInfo method, Type[] typeArgs, params object[] parameters) + static void GetLoggerAndInvokeGeneric(MethodInfo method, LogEventLevel level, Type[] typeArgs, params object[] parameters) { - var logger = new LoggerConfiguration().CreateLogger(); + var sink = new CollectingSink(); + + var logger = new LoggerConfiguration() + .MinimumLevel.Verbose() + .WriteTo.Sink(sink) + .CreateLogger(); method.MakeGenericMethod(typeArgs).Invoke(logger, parameters); + + Assert.Equal(1, sink.Events.Count); + + var evt = sink.Events.Single(); + + Assert.Equal(level, evt.Level); } // parameters will always be ordered so single evaluation method will work - private static void VerifyMethodSignature(MethodInfo method, bool hasExceptionArg = false, bool isGeneric = false, int expectedArgCount = 1) + static void VerifyMethodSignature(MethodInfo method, bool hasExceptionArg = false, bool isGeneric = false, int expectedArgCount = 1) { - var parameters = method.GetParameters(); + try + { + var parameters = method.GetParameters(); - Assert.Equal(parameters.Length, expectedArgCount); + Assert.Equal(parameters.Length, expectedArgCount); - int index = 0; + int index = 0; - if (hasExceptionArg) //verify exception arg type and name - { - Assert.Equal(parameters[index].ParameterType, typeof(Exception)); + // exceptions always come before messageTemplate string + if (hasExceptionArg) //verify exception argument type and name + { + Assert.Equal(parameters[index].ParameterType, typeof(Exception)); - Assert.Equal(parameters[index].Name, "exception"); + Assert.Equal(parameters[index].Name, "exception"); - index++; - } + index++; + } - //check message template argument - Assert.Equal(parameters[index].ParameterType, typeof(string)); + //check for message template string argument + Assert.Equal(parameters[index].ParameterType, typeof(string)); - Assert.Equal(parameters[index].Name, "messageTemplate"); + Assert.Equal(parameters[index].Name, "messageTemplate"); - index++; + index++; - if (isGeneric) //validate type arguments, generic parameters, and cross-reference - { - Assert.True(method.IsGenericMethod); + if (isGeneric) //validate type arguments, generic parameters, and cross-reference + { + Assert.True(method.IsGenericMethod); - var genericTypeArgs = method.GetGenericArguments(); + var genericTypeArgs = method.GetGenericArguments(); - //multiple generic argument convention T0...Tx : T0 propertyValue0... Tx propertyValueX - if (genericTypeArgs.Length > 1) - { - for (int i = 0; i < genericTypeArgs.Length; i++, index++) + //multiple generic argument convention T0...Tx : T0 propertyValue0... Tx propertyValueX + if (genericTypeArgs.Length > 1) { - Assert.Equal(genericTypeArgs[i].Name, $"T{i}"); + for (int i = 0; i < genericTypeArgs.Length; i++, index++) + { + Assert.Equal(genericTypeArgs[i].Name, $"T{i}"); - var genericConstraints = genericTypeArgs[i].GetTypeInfo().GetGenericParameterConstraints(); + var genericConstraints = genericTypeArgs[i].GetTypeInfo().GetGenericParameterConstraints(); - Assert.Empty(genericConstraints); + Assert.Empty(genericConstraints); - Assert.Equal(parameters[index].Name, $"propertyValue{i}"); + Assert.Equal(parameters[index].Name, $"propertyValue{i}"); - Assert.Equal(parameters[index].ParameterType, genericTypeArgs[i]); + Assert.Equal(parameters[index].ParameterType, genericTypeArgs[i]); + } } - } - else //single generic argument convention T : T propertyValue - { - var genericTypeArg = genericTypeArgs[0]; + else //single generic argument convention T : T propertyValue + { + var genericTypeArg = genericTypeArgs[0]; - Assert.Equal(genericTypeArg.Name, "T"); + Assert.Equal(genericTypeArg.Name, "T"); - var genericConstraints = genericTypeArg.GetTypeInfo().GetGenericParameterConstraints(); + var genericConstraints = genericTypeArg.GetTypeInfo().GetGenericParameterConstraints(); - Assert.Empty(genericConstraints); + Assert.Empty(genericConstraints); - Assert.Equal(parameters[index].Name, "propertyValue"); + Assert.Equal(parameters[index].Name, "propertyValue"); - Assert.Equal(genericTypeArg, parameters[index].ParameterType); + Assert.Equal(genericTypeArg, parameters[index].ParameterType); - index++; + index++; + } } - } - //check for params argument - //params args currently have to be the last argument, and generics don't have params arg - if (!isGeneric && (parameters.Length - index) == 1) - { - var paramsArrayArg = parameters[index]; + //check for params argument: params object[] propertyValues + //params argument currently has to be the last argument, and generic methods don't have params argument + if (!isGeneric && (parameters.Length - index) == 1) + { + var paramsArrayArg = parameters[index]; + + // params array attribute should never have derived/inherited classes + var paramsAttr = parameters[index].GetCustomAttribute(typeof(ParamArrayAttribute), inherit: false); - var paramsAttr = parameters[index].GetCustomAttribute(typeof(ParamArrayAttribute), inherit: false); + Assert.NotNull(paramsAttr); - Assert.NotNull(paramsAttr); + Assert.Equal(paramsArrayArg.ParameterType, typeof(object[])); - Assert.Equal(paramsArrayArg.ParameterType, typeof(object[])); + Assert.Equal(paramsArrayArg.Name, "propertyValues"); + } + } + catch (XunitException e) + { + // mark xunit assertion failures + e.Data.Add("IsSignatureAssertionFailure", true); - Assert.Equal(paramsArrayArg.Name, "propertyValues"); + throw e; } } } From 6264881ad382f35a2012746f5d21d7904fae0220 Mon Sep 17 00:00:00 2001 From: Artur Krajewski Date: Wed, 9 Nov 2016 20:25:48 +0100 Subject: [PATCH 33/53] 'MaximumCollectionLength' -> 'MaximumCollectionCount' Length is kind-of specific to arrays, count is more general, suitable for all IEnumerable. --- .../LoggerDestructuringConfiguration.cs | 26 +++++++++---------- src/Serilog/LoggerConfiguration.cs | 6 ++--- .../Parameters/PropertyValueConverter.cs | 12 ++++----- .../Serilog.Tests/LoggerConfigurationTests.cs | 18 ++++++------- 4 files changed, 31 insertions(+), 31 deletions(-) diff --git a/src/Serilog/Configuration/LoggerDestructuringConfiguration.cs b/src/Serilog/Configuration/LoggerDestructuringConfiguration.cs index e54528a27..54c87ba4d 100644 --- a/src/Serilog/Configuration/LoggerDestructuringConfiguration.cs +++ b/src/Serilog/Configuration/LoggerDestructuringConfiguration.cs @@ -28,28 +28,28 @@ public class LoggerDestructuringConfiguration readonly Action _addScalar; readonly Action _addPolicy; readonly Action _setMaximumDepth; - readonly Action _setMaximumStringLength; - readonly Action _setMaximumCollectionLength; + readonly Action _setMaximumStringCount; + readonly Action _setMaximumCollectionCount; internal LoggerDestructuringConfiguration( LoggerConfiguration loggerConfiguration, Action addScalar, Action addPolicy, Action setMaximumDepth, - Action setMaximumStringLength, - Action setMaximumCollectionLength) + Action setMaximumStringCount, + Action setMaximumCollectionCount) { if (loggerConfiguration == null) throw new ArgumentNullException(nameof(loggerConfiguration)); if (addScalar == null) throw new ArgumentNullException(nameof(addScalar)); if (addPolicy == null) throw new ArgumentNullException(nameof(addPolicy)); - if (setMaximumDepth == null) throw new ArgumentNullException(nameof(setMaximumStringLength)); - if (setMaximumCollectionLength == null) throw new ArgumentNullException(nameof(setMaximumCollectionLength)); + if (setMaximumDepth == null) throw new ArgumentNullException(nameof(setMaximumStringCount)); + if (setMaximumCollectionCount == null) throw new ArgumentNullException(nameof(setMaximumCollectionCount)); _loggerConfiguration = loggerConfiguration; _addScalar = addScalar; _addPolicy = addPolicy; _setMaximumDepth = setMaximumDepth; - _setMaximumStringLength = setMaximumStringLength; - _setMaximumCollectionLength = setMaximumCollectionLength; + _setMaximumStringCount = setMaximumStringCount; + _setMaximumCollectionCount = setMaximumCollectionCount; } /// @@ -170,22 +170,22 @@ public LoggerConfiguration ToMaximumDepth(int maximumDestructuringDepth) public LoggerConfiguration ToMaximumStringLength(int maximumStringLength) { if (maximumStringLength < 2) throw new ArgumentOutOfRangeException(nameof(maximumStringLength), maximumStringLength, "Maximum string length must be at least two."); - _setMaximumStringLength(maximumStringLength); + _setMaximumStringCount(maximumStringLength); return _loggerConfiguration; } /// - /// When destructuring objects, collections be restricted to specified length + /// When destructuring objects, collections be restricted to specified count /// thus avoiding bloating payload. Limit is applied to each collection separately, /// sum of length of collection can exceed limit. /// Applies limit to all including dictionaries. /// /// Configuration object allowing method chaining. /// When passed length is less than 1 - public LoggerConfiguration ToMaximumCollectionLength(int maximumCollectionLength) + public LoggerConfiguration ToMaximumCollectionCount(int maximumCollectionCount) { - if (maximumCollectionLength < 1) throw new ArgumentOutOfRangeException(nameof(maximumCollectionLength), maximumCollectionLength, "Maximum collection length must be at least one."); - _setMaximumCollectionLength(maximumCollectionLength); + if (maximumCollectionCount < 1) throw new ArgumentOutOfRangeException(nameof(maximumCollectionCount), maximumCollectionCount, "Maximum collection length must be at least one."); + _setMaximumCollectionCount(maximumCollectionCount); return _loggerConfiguration; } } diff --git a/src/Serilog/LoggerConfiguration.cs b/src/Serilog/LoggerConfiguration.cs index 25772ca10..99ba02d5a 100644 --- a/src/Serilog/LoggerConfiguration.cs +++ b/src/Serilog/LoggerConfiguration.cs @@ -40,7 +40,7 @@ public class LoggerConfiguration LoggingLevelSwitch _levelSwitch; int _maximumDestructuringDepth = 10; int _maximumStringLength = int.MaxValue; - int _maximumCollectionLength = int.MaxValue; + int _maximumCollectionCount = int.MaxValue; bool _loggerCreated; void ApplyInheritedConfiguration(LoggerConfiguration child) @@ -113,7 +113,7 @@ public LoggerDestructuringConfiguration Destructure _additionalDestructuringPolicies.Add, depth => _maximumDestructuringDepth = depth, length => _maximumStringLength = length, - length => _maximumCollectionLength = length); + count => _maximumCollectionCount = count); } } @@ -157,7 +157,7 @@ public Logger CreateLogger() var converter = new PropertyValueConverter( _maximumDestructuringDepth, _maximumStringLength, - _maximumCollectionLength, + _maximumCollectionCount, _additionalScalarTypes, _additionalDestructuringPolicies, auditing); diff --git a/src/Serilog/Parameters/PropertyValueConverter.cs b/src/Serilog/Parameters/PropertyValueConverter.cs index eba35edb0..43a1d63b5 100755 --- a/src/Serilog/Parameters/PropertyValueConverter.cs +++ b/src/Serilog/Parameters/PropertyValueConverter.cs @@ -47,13 +47,13 @@ partial class PropertyValueConverter : ILogEventPropertyFactory, ILogEventProper readonly IScalarConversionPolicy[] _scalarConversionPolicies; readonly int _maximumDestructuringDepth; readonly int _maximumStringLength; - readonly int _maximumCollectionLength; + readonly int _maximumCollectionCount; readonly bool _propagateExceptions; public PropertyValueConverter( int maximumDestructuringDepth, int maximumStringLength, - int maximumCollectionLength, + int maximumCollectionCount, IEnumerable additionalScalarTypes, IEnumerable additionalDestructuringPolicies, bool propagateExceptions) @@ -62,12 +62,12 @@ public PropertyValueConverter( if (additionalDestructuringPolicies == null) throw new ArgumentNullException(nameof(additionalDestructuringPolicies)); if (maximumDestructuringDepth < 0) throw new ArgumentOutOfRangeException(nameof(maximumDestructuringDepth)); if (maximumStringLength < 2) throw new ArgumentOutOfRangeException(nameof(maximumDestructuringDepth)); - if (maximumCollectionLength < 1) throw new ArgumentOutOfRangeException(nameof(maximumCollectionLength)); + if (maximumCollectionCount < 1) throw new ArgumentOutOfRangeException(nameof(maximumCollectionCount)); _maximumDestructuringDepth = maximumDestructuringDepth; _propagateExceptions = propagateExceptions; _maximumStringLength = maximumStringLength; - _maximumCollectionLength = maximumCollectionLength; + _maximumCollectionCount = maximumCollectionCount; _scalarConversionPolicies = new IScalarConversionPolicy[] { @@ -179,7 +179,7 @@ LogEventPropertyValue CreatePropertyValue(object value, Destructuring destructur var keyProperty = typeInfo.GetDeclaredProperty("Key"); var valueProperty = typeInfo.GetDeclaredProperty("Value"); - return new DictionaryValue(enumerable.Cast().Take(_maximumCollectionLength) + return new DictionaryValue(enumerable.Cast().Take(_maximumCollectionCount) .Select(kvp => new KeyValuePair( (ScalarValue)limiter.CreatePropertyValue(keyProperty.GetValue(kvp), destructuring), limiter.CreatePropertyValue(valueProperty.GetValue(kvp), destructuring))) @@ -187,7 +187,7 @@ LogEventPropertyValue CreatePropertyValue(object value, Destructuring destructur } return new SequenceValue( - enumerable.Cast().Take(_maximumCollectionLength).Select(o => limiter.CreatePropertyValue(o, destructuring))); + enumerable.Cast().Take(_maximumCollectionCount).Select(o => limiter.CreatePropertyValue(o, destructuring))); } if (destructuring == Destructuring.Destructure) diff --git a/test/Serilog.Tests/LoggerConfigurationTests.cs b/test/Serilog.Tests/LoggerConfigurationTests.cs index bce08a650..1aa5891fc 100644 --- a/test/Serilog.Tests/LoggerConfigurationTests.cs +++ b/test/Serilog.Tests/LoggerConfigurationTests.cs @@ -362,33 +362,33 @@ public override string ToString() public void MaximumStringCollectionThrowsForLimitLowerThan1() { var ex = Assert.Throws( - () => new LoggerConfiguration().Destructure.ToMaximumCollectionLength(0)); + () => new LoggerConfiguration().Destructure.ToMaximumCollectionCount(0)); Assert.Equal(0, ex.ActualValue); } [Fact] - public void MaximumCollectionLengthNotEffectiveForArrayAsLongAsLimit() + public void MaximumCollectionCountNotEffectiveForArrayAsLongAsLimit() { var x = new[] { 1, 2, 3 }; - var limitedCollection = LogAndGetAsString(x, conf => conf.Destructure.ToMaximumCollectionLength(3)); + var limitedCollection = LogAndGetAsString(x, conf => conf.Destructure.ToMaximumCollectionCount(3)); Assert.Contains("3", limitedCollection); } [Fact] - public void MaximumCollectionLengthEffectiveForArrayThanLimit() + public void MaximumCollectionCountEffectiveForArrayThanLimit() { var x = new[] { 1, 2, 3, 4 }; - var limitedCollection = LogAndGetAsString(x, conf => conf.Destructure.ToMaximumCollectionLength(3)); + var limitedCollection = LogAndGetAsString(x, conf => conf.Destructure.ToMaximumCollectionCount(3)); Assert.Contains("3", limitedCollection); Assert.DoesNotContain("4", limitedCollection); } [Fact] - public void MaximumCollectionLengthEffectiveForDictionaryWithMoreKeysThanLimit() + public void MaximumCollectionCountEffectiveForDictionaryWithMoreKeysThanLimit() { var x = new Dictionary { @@ -397,14 +397,14 @@ public void MaximumCollectionLengthEffectiveForDictionaryWithMoreKeysThanLimit() {"3", 3} }; - var limitedCollection = LogAndGetAsString(x, conf => conf.Destructure.ToMaximumCollectionLength(2)); + var limitedCollection = LogAndGetAsString(x, conf => conf.Destructure.ToMaximumCollectionCount(2)); Assert.Contains("2", limitedCollection); Assert.DoesNotContain("3", limitedCollection); } [Fact] - public void MaximumCollectionLengthNotEffectiveForDictionaryWithAsManyKeysAsLimit() + public void MaximumCollectionCountNotEffectiveForDictionaryWithAsManyKeysAsLimit() { var x = new Dictionary { @@ -412,7 +412,7 @@ public void MaximumCollectionLengthNotEffectiveForDictionaryWithAsManyKeysAsLimi {"2", 2}, }; - var limitedCollection = LogAndGetAsString(x, conf => conf.Destructure.ToMaximumCollectionLength(2)); + var limitedCollection = LogAndGetAsString(x, conf => conf.Destructure.ToMaximumCollectionCount(2)); Assert.Contains("2", limitedCollection); } From 967339af57693cd7b0fe0f5bd3438b8ca37d35ea Mon Sep 17 00:00:00 2001 From: Artur Krajewski Date: Thu, 10 Nov 2016 09:39:00 +0100 Subject: [PATCH 34/53] Fixed typo from last commit(accidentally changed string related name) --- .../Configuration/LoggerDestructuringConfiguration.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Serilog/Configuration/LoggerDestructuringConfiguration.cs b/src/Serilog/Configuration/LoggerDestructuringConfiguration.cs index 54c87ba4d..a7e786c61 100644 --- a/src/Serilog/Configuration/LoggerDestructuringConfiguration.cs +++ b/src/Serilog/Configuration/LoggerDestructuringConfiguration.cs @@ -28,7 +28,7 @@ public class LoggerDestructuringConfiguration readonly Action _addScalar; readonly Action _addPolicy; readonly Action _setMaximumDepth; - readonly Action _setMaximumStringCount; + readonly Action _setMaximumStringLength; readonly Action _setMaximumCollectionCount; internal LoggerDestructuringConfiguration( @@ -36,19 +36,19 @@ internal LoggerDestructuringConfiguration( Action addScalar, Action addPolicy, Action setMaximumDepth, - Action setMaximumStringCount, + Action setMaximumStringLength, Action setMaximumCollectionCount) { if (loggerConfiguration == null) throw new ArgumentNullException(nameof(loggerConfiguration)); if (addScalar == null) throw new ArgumentNullException(nameof(addScalar)); if (addPolicy == null) throw new ArgumentNullException(nameof(addPolicy)); - if (setMaximumDepth == null) throw new ArgumentNullException(nameof(setMaximumStringCount)); + if (setMaximumDepth == null) throw new ArgumentNullException(nameof(setMaximumStringLength)); if (setMaximumCollectionCount == null) throw new ArgumentNullException(nameof(setMaximumCollectionCount)); _loggerConfiguration = loggerConfiguration; _addScalar = addScalar; _addPolicy = addPolicy; _setMaximumDepth = setMaximumDepth; - _setMaximumStringCount = setMaximumStringCount; + _setMaximumStringLength = setMaximumStringLength; _setMaximumCollectionCount = setMaximumCollectionCount; } @@ -170,7 +170,7 @@ public LoggerConfiguration ToMaximumDepth(int maximumDestructuringDepth) public LoggerConfiguration ToMaximumStringLength(int maximumStringLength) { if (maximumStringLength < 2) throw new ArgumentOutOfRangeException(nameof(maximumStringLength), maximumStringLength, "Maximum string length must be at least two."); - _setMaximumStringCount(maximumStringLength); + _setMaximumStringLength(maximumStringLength); return _loggerConfiguration; } From 7f3c06e7b3c8508c676d5cda2df1195b047249d8 Mon Sep 17 00:00:00 2001 From: Artur Krajewski Date: Thu, 10 Nov 2016 09:40:38 +0100 Subject: [PATCH 35/53] Fixed null-checking in destructuring conf constructor. --- src/Serilog/Configuration/LoggerDestructuringConfiguration.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Serilog/Configuration/LoggerDestructuringConfiguration.cs b/src/Serilog/Configuration/LoggerDestructuringConfiguration.cs index a7e786c61..57ed46622 100644 --- a/src/Serilog/Configuration/LoggerDestructuringConfiguration.cs +++ b/src/Serilog/Configuration/LoggerDestructuringConfiguration.cs @@ -42,7 +42,8 @@ internal LoggerDestructuringConfiguration( if (loggerConfiguration == null) throw new ArgumentNullException(nameof(loggerConfiguration)); if (addScalar == null) throw new ArgumentNullException(nameof(addScalar)); if (addPolicy == null) throw new ArgumentNullException(nameof(addPolicy)); - if (setMaximumDepth == null) throw new ArgumentNullException(nameof(setMaximumStringLength)); + if (setMaximumDepth == null) throw new ArgumentNullException(nameof(setMaximumDepth)); + if (setMaximumStringLength == null) throw new ArgumentNullException(nameof(setMaximumStringLength)); if (setMaximumCollectionCount == null) throw new ArgumentNullException(nameof(setMaximumCollectionCount)); _loggerConfiguration = loggerConfiguration; _addScalar = addScalar; From 42364e9471abdac1bfd403b2f6218869f1a8633d Mon Sep 17 00:00:00 2001 From: Artur Krajewski Date: Thu, 10 Nov 2016 09:44:05 +0100 Subject: [PATCH 36/53] Fixed nameof arg in PropertyValueConverter range checking. --- src/Serilog/Parameters/PropertyValueConverter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Serilog/Parameters/PropertyValueConverter.cs b/src/Serilog/Parameters/PropertyValueConverter.cs index 43a1d63b5..712af6f18 100755 --- a/src/Serilog/Parameters/PropertyValueConverter.cs +++ b/src/Serilog/Parameters/PropertyValueConverter.cs @@ -61,7 +61,7 @@ public PropertyValueConverter( if (additionalScalarTypes == null) throw new ArgumentNullException(nameof(additionalScalarTypes)); if (additionalDestructuringPolicies == null) throw new ArgumentNullException(nameof(additionalDestructuringPolicies)); if (maximumDestructuringDepth < 0) throw new ArgumentOutOfRangeException(nameof(maximumDestructuringDepth)); - if (maximumStringLength < 2) throw new ArgumentOutOfRangeException(nameof(maximumDestructuringDepth)); + if (maximumStringLength < 2) throw new ArgumentOutOfRangeException(nameof(maximumStringLength)); if (maximumCollectionCount < 1) throw new ArgumentOutOfRangeException(nameof(maximumCollectionCount)); _maximumDestructuringDepth = maximumDestructuringDepth; From 80cf2e6ec96afabfd13b25c3b09806fdaa891936 Mon Sep 17 00:00:00 2001 From: Joshua Clark Date: Tue, 15 Nov 2016 17:34:25 -0500 Subject: [PATCH 37/53] Moved convention tests to separate file, and added tests for static Log and ILogger --- test/Serilog.Tests/Core/LoggerTests.cs | 278 ------------- .../MethodOverloadConventionTests.cs | 372 ++++++++++++++++++ 2 files changed, 372 insertions(+), 278 deletions(-) create mode 100644 test/Serilog.Tests/MethodOverloadConventionTests.cs diff --git a/test/Serilog.Tests/Core/LoggerTests.cs b/test/Serilog.Tests/Core/LoggerTests.cs index 7716bc8a1..a92f44052 100644 --- a/test/Serilog.Tests/Core/LoggerTests.cs +++ b/test/Serilog.Tests/Core/LoggerTests.cs @@ -120,283 +120,5 @@ public void PropertiesCanBeBound() Assert.Equal("Name", property.Name); Assert.Equal("World", property.Value.LiteralValue()); } - - [Theory] - [InlineData(LogEventLevel.Verbose)] - [InlineData(LogEventLevel.Debug)] - [InlineData(LogEventLevel.Information)] - [InlineData(LogEventLevel.Warning)] - [InlineData(LogEventLevel.Error)] - [InlineData(LogEventLevel.Fatal)] - public void ValidateConventionForMethodSet(LogEventLevel level) - { - var setName = level.ToString(); - - var methodSet = typeof(Logger).GetMethods().Where(method => method.Name == setName); - - var testMethods = typeof(LoggerTests).GetRuntimeMethods() - .Where(method => Regex.IsMatch(method.Name, "ValidateMethod\\d")); - - foreach (var method in methodSet) - { - Assert.Equal(method.ReturnType, typeof(void)); - - Assert.True(method.IsPublic); - - var messageTemplateAttr = method.GetCustomAttribute(); - - Assert.NotNull(messageTemplateAttr); - - Assert.Equal(messageTemplateAttr.MessageTemplateParameterName, "messageTemplate"); - - var signatureMatch = false; - - var report = new StringBuilder(); - - foreach (var testMethod in testMethods) - { - try - { - testMethod.Invoke(this, new object[] { method, level }); - - signatureMatch = true; - - break; - } - catch (Exception e) - when (e is TargetInvocationException - && (e as TargetInvocationException).GetBaseException() is XunitException) - { - var xunitException = (XunitException)(e as TargetInvocationException).GetBaseException(); - - if (xunitException.Data.Contains("IsSignatureAssertionFailure")) - { - report.AppendLine($"{testMethod.Name} Signature Mismatch on: {method} with: {xunitException.Message}"); - } - else - { - report.AppendLine($"{testMethod.Name} Invocation Failure on: {method} with: {xunitException.UserMessage}"); - } - - continue; - } - } - - Assert.True(signatureMatch, $"{method} did not match any known convention\n" + report.ToString()); - } - } - - // Method0 (string messageTemplate) : void - void ValidateMethod0(MethodInfo method, LogEventLevel level) - { - VerifyMethodSignature(method, expectedArgCount: 1); - - GetLoggerAndInvoke(method, level, "message"); - } - - // Method1 (string messageTemplate, T propertyValue) : void - void ValidateMethod1(MethodInfo method, LogEventLevel level) - { - VerifyMethodSignature(method, isGeneric: true, expectedArgCount: 2); - - GetLoggerAndInvokeGeneric(method, level, new Type[] { typeof(string) }, "message", "value0"); - } - - // Method2 (string messageTemplate, T0 propertyValue0, T1 propertyValue1) : void - void ValidateMethod2(MethodInfo method, LogEventLevel level) - { - VerifyMethodSignature(method, isGeneric: true, expectedArgCount: 3); - - GetLoggerAndInvokeGeneric(method, level, new Type[] { typeof(string), typeof(string) }, - "message", "value0", "value1"); - } - - // Method3 (string messageTemplate, T0 propertyValue0, T1 propertyValue1, T2 propertyValue2) : void - void ValidateMethod3(MethodInfo method, LogEventLevel level) - { - VerifyMethodSignature(method, isGeneric: true, expectedArgCount: 4); - - GetLoggerAndInvokeGeneric(method, level, new Type[] { typeof(string), typeof(string), typeof(string) }, - "message", "value0", "value1", "value2"); - } - - // Method4 (string messageTemplate, params object[] propertyValues) : void - void ValidateMethod4(MethodInfo method, LogEventLevel level) - { - VerifyMethodSignature(method, expectedArgCount: 2); - - GetLoggerAndInvoke(method, level, "message", new object[] { "value0", "value1", "value2" }); - } - - // Method5 (Exception exception, string messageTemplate) : void - void ValidateMethod5(MethodInfo method, LogEventLevel level) - { - VerifyMethodSignature(method, hasExceptionArg: true, expectedArgCount: 2); - - GetLoggerAndInvoke(method, level, new Exception("test"), "message"); - } - - // Method6 (Exception exception, string messageTemplate, T propertyValue) : void - void ValidateMethod6(MethodInfo method, LogEventLevel level) - { - VerifyMethodSignature(method, hasExceptionArg: true, isGeneric: true, expectedArgCount: 3); - - GetLoggerAndInvokeGeneric(method, level, new Type[] { typeof(string) }, new Exception("test"), "message", "value0"); - } - - // Method7 (Exception exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1) : void - void ValidateMethod7(MethodInfo method, LogEventLevel level) - { - VerifyMethodSignature(method, hasExceptionArg: true, isGeneric: true, expectedArgCount: 4); - - GetLoggerAndInvokeGeneric(method, level, new Type[] { typeof(string), typeof(string) }, - new Exception("test"), "message", "value0", "value1"); - } - - // Method8 (Exception exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1, T2 propertyValue2) : void - void ValidateMethod8(MethodInfo method, LogEventLevel level) - { - VerifyMethodSignature(method, hasExceptionArg: true, isGeneric: true, expectedArgCount: 5); - - GetLoggerAndInvokeGeneric(method, level, new Type[] { typeof(string), typeof(string), typeof(string) }, - new Exception("test"), "message", "value0", "value1", "value2"); - } - - // Method9 (Exception exception, string messageTemplate, params object[] propertyValues) : void - void ValidateMethod9(MethodInfo method, LogEventLevel level) - { - VerifyMethodSignature(method, hasExceptionArg: true, expectedArgCount: 3); - - GetLoggerAndInvoke(method, level, new Exception("test"), "message", new object[] { "value0", "value1", "value2" }); - } - - static void GetLoggerAndInvoke(MethodInfo method, LogEventLevel level, params object[] parameters) - { - var sink = new CollectingSink(); - - var logger = new LoggerConfiguration() - .MinimumLevel.Verbose() - .WriteTo.Sink(sink) - .CreateLogger(); - - method.Invoke(logger, parameters); - - Assert.Equal(1, sink.Events.Count); - - var evt = sink.Events.Single(); - - Assert.Equal(level, evt.Level); - } - - static void GetLoggerAndInvokeGeneric(MethodInfo method, LogEventLevel level, Type[] typeArgs, params object[] parameters) - { - var sink = new CollectingSink(); - - var logger = new LoggerConfiguration() - .MinimumLevel.Verbose() - .WriteTo.Sink(sink) - .CreateLogger(); - - method.MakeGenericMethod(typeArgs).Invoke(logger, parameters); - - Assert.Equal(1, sink.Events.Count); - - var evt = sink.Events.Single(); - - Assert.Equal(level, evt.Level); - } - - // parameters will always be ordered so single evaluation method will work - static void VerifyMethodSignature(MethodInfo method, bool hasExceptionArg = false, bool isGeneric = false, int expectedArgCount = 1) - { - try - { - var parameters = method.GetParameters(); - - Assert.Equal(parameters.Length, expectedArgCount); - - int index = 0; - - // exceptions always come before messageTemplate string - if (hasExceptionArg) //verify exception argument type and name - { - Assert.Equal(parameters[index].ParameterType, typeof(Exception)); - - Assert.Equal(parameters[index].Name, "exception"); - - index++; - } - - //check for message template string argument - Assert.Equal(parameters[index].ParameterType, typeof(string)); - - Assert.Equal(parameters[index].Name, "messageTemplate"); - - index++; - - if (isGeneric) //validate type arguments, generic parameters, and cross-reference - { - Assert.True(method.IsGenericMethod); - - var genericTypeArgs = method.GetGenericArguments(); - - //multiple generic argument convention T0...Tx : T0 propertyValue0... Tx propertyValueX - if (genericTypeArgs.Length > 1) - { - for (int i = 0; i < genericTypeArgs.Length; i++, index++) - { - Assert.Equal(genericTypeArgs[i].Name, $"T{i}"); - - var genericConstraints = genericTypeArgs[i].GetTypeInfo().GetGenericParameterConstraints(); - - Assert.Empty(genericConstraints); - - Assert.Equal(parameters[index].Name, $"propertyValue{i}"); - - Assert.Equal(parameters[index].ParameterType, genericTypeArgs[i]); - } - } - else //single generic argument convention T : T propertyValue - { - var genericTypeArg = genericTypeArgs[0]; - - Assert.Equal(genericTypeArg.Name, "T"); - - var genericConstraints = genericTypeArg.GetTypeInfo().GetGenericParameterConstraints(); - - Assert.Empty(genericConstraints); - - Assert.Equal(parameters[index].Name, "propertyValue"); - - Assert.Equal(genericTypeArg, parameters[index].ParameterType); - - index++; - } - } - - //check for params argument: params object[] propertyValues - //params argument currently has to be the last argument, and generic methods don't have params argument - if (!isGeneric && (parameters.Length - index) == 1) - { - var paramsArrayArg = parameters[index]; - - // params array attribute should never have derived/inherited classes - var paramsAttr = parameters[index].GetCustomAttribute(typeof(ParamArrayAttribute), inherit: false); - - Assert.NotNull(paramsAttr); - - Assert.Equal(paramsArrayArg.ParameterType, typeof(object[])); - - Assert.Equal(paramsArrayArg.Name, "propertyValues"); - } - } - catch (XunitException e) - { - // mark xunit assertion failures - e.Data.Add("IsSignatureAssertionFailure", true); - - throw e; - } - } } } diff --git a/test/Serilog.Tests/MethodOverloadConventionTests.cs b/test/Serilog.Tests/MethodOverloadConventionTests.cs new file mode 100644 index 000000000..7603e22d3 --- /dev/null +++ b/test/Serilog.Tests/MethodOverloadConventionTests.cs @@ -0,0 +1,372 @@ +using Serilog.Core; +using Serilog.Events; +using Serilog.Tests.Support; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using Xunit; +using Xunit.Sdk; + +namespace Serilog.Tests +{ + public class MethodOverloadConventionTests + { + const string Write = "Write"; + + //this is used as both the variable name for message template parameter + // and as the argument for the MessageTemplateFormatMethodAttr + const string MessageTemplate = "messageTemplate"; + + [Theory] + [InlineData(Write)] + [InlineData(nameof(LogEventLevel.Verbose))] + [InlineData(nameof(LogEventLevel.Debug))] + [InlineData(nameof(LogEventLevel.Information))] + [InlineData(nameof(LogEventLevel.Warning))] + [InlineData(nameof(LogEventLevel.Error))] + [InlineData(nameof(LogEventLevel.Fatal))] + public void ILoggerValidateConventions(string setName) + { + ValidateConventionForMethodSet(setName, typeof(ILogger)); + } + + [Theory] + [InlineData(Write)] + [InlineData(nameof(LogEventLevel.Verbose))] + [InlineData(nameof(LogEventLevel.Debug))] + [InlineData(nameof(LogEventLevel.Information))] + [InlineData(nameof(LogEventLevel.Warning))] + [InlineData(nameof(LogEventLevel.Error))] + [InlineData(nameof(LogEventLevel.Fatal))] + public void LoggerValidateConventions(string setName) + { + ValidateConventionForMethodSet(setName, typeof(Logger)); + } + + [Theory] + [InlineData(Write)] + [InlineData(nameof(LogEventLevel.Verbose))] + [InlineData(nameof(LogEventLevel.Debug))] + [InlineData(nameof(LogEventLevel.Information))] + [InlineData(nameof(LogEventLevel.Warning))] + [InlineData(nameof(LogEventLevel.Error))] + [InlineData(nameof(LogEventLevel.Fatal))] + public void LogValidateConventions(string setName) + { + ValidateConventionForMethodSet(setName, typeof(Log)); + } + + void ValidateConventionForMethodSet(string setName, Type loggerType) + { + IEnumerable methodSet; + + if (setName == Write) + methodSet = loggerType.GetMethods() + .Where(method => method.Name == setName && method.GetParameters() + .Any(param => param.ParameterType == typeof(string))); + else + methodSet = loggerType.GetMethods() + .Where(method => method.Name == setName); + + var testMethods = typeof(MethodOverloadConventionTests).GetRuntimeMethods() + .Where(method => Regex.IsMatch(method.Name, "ValidateMethod\\d")); + + Assert.Equal(testMethods.Count(), methodSet.Count()); + + foreach (var method in methodSet) + { + Assert.Equal(method.ReturnType, typeof(void)); + + Assert.True(method.IsPublic); + + var messageTemplateAttr = method.GetCustomAttribute(); + + Assert.NotNull(messageTemplateAttr); + + Assert.Equal(messageTemplateAttr.MessageTemplateParameterName, MessageTemplate); + + var signatureMatchAndInvokeSuccess = false; + + var report = new StringBuilder(); + + foreach (var testMethod in testMethods) + { + try + { + testMethod.Invoke(this, new object[] { method }); + + signatureMatchAndInvokeSuccess = true; + + break; + } + catch (TargetInvocationException e) + when (e.GetBaseException() is XunitException) + { + var xunitException = (XunitException)e.GetBaseException(); + + if (xunitException.Data.Contains("IsSignatureAssertionFailure")) + { + report.AppendLine($"{testMethod.Name} Signature Mismatch on: {method} with: {xunitException.Message}"); + } + else + { + report.AppendLine($"{testMethod.Name} Invocation Failure on: {method} with: {xunitException.UserMessage}"); + } + + continue; + } + } + + Assert.True(signatureMatchAndInvokeSuccess, $"{method} did not match any known convention or failed invoke\n" + report.ToString()); + } + } + + // Method0 (string messageTemplate) : void + void ValidateMethod0(MethodInfo method) + { + VerifyMethodSignature(method, expectedArgCount: 1); + + GetLoggerAndInvoke(method, null, "message"); + } + + // Method1 (string messageTemplate, T propertyValue) : void + void ValidateMethod1(MethodInfo method) + { + VerifyMethodSignature(method, isGeneric: true, expectedArgCount: 2); + + GetLoggerAndInvoke(method, + new Type[] { typeof(string) }, "message", "value0"); + } + + // Method2 (string messageTemplate, T0 propertyValue0, T1 propertyValue1) : void + void ValidateMethod2(MethodInfo method) + { + VerifyMethodSignature(method, isGeneric: true, expectedArgCount: 3); + + GetLoggerAndInvoke(method, + new Type[] { typeof(string), typeof(string) }, + "message", "value0", "value1"); + } + + // Method3 (string messageTemplate, T0 propertyValue0, T1 propertyValue1, T2 propertyValue2) : void + void ValidateMethod3(MethodInfo method) + { + VerifyMethodSignature(method, isGeneric: true, expectedArgCount: 4); + + GetLoggerAndInvoke(method, + new Type[] { typeof(string), typeof(string), typeof(string) }, + "message", "value0", "value1", "value2"); + } + + // Method4 (string messageTemplate, params object[] propertyValues) : void + void ValidateMethod4(MethodInfo method) + { + VerifyMethodSignature(method, expectedArgCount: 2); + + GetLoggerAndInvoke(method, null, + "message", new object[] { "value0", "value1", "value2" }); + } + + // Method5 (Exception exception, string messageTemplate) : void + void ValidateMethod5(MethodInfo method) + { + VerifyMethodSignature(method, hasExceptionArg: true, expectedArgCount: 2); + + GetLoggerAndInvoke(method, null, + new Exception("test"), "message"); + } + + // Method6 (Exception exception, string messageTemplate, T propertyValue) : void + void ValidateMethod6(MethodInfo method) + { + VerifyMethodSignature(method, hasExceptionArg: true, isGeneric: true, expectedArgCount: 3); + + GetLoggerAndInvoke(method, + new Type[] { typeof(string) }, + new Exception("test"), "message", "value0"); + } + + // Method7 (Exception exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1) : void + void ValidateMethod7(MethodInfo method) + { + VerifyMethodSignature(method, hasExceptionArg: true, isGeneric: true, expectedArgCount: 4); + + GetLoggerAndInvoke(method, + new Type[] { typeof(string), typeof(string) }, + new Exception("test"), "message", "value0", "value1"); + } + + // Method8 (Exception exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1, T2 propertyValue2) : void + void ValidateMethod8(MethodInfo method) + { + VerifyMethodSignature(method, hasExceptionArg: true, isGeneric: true, expectedArgCount: 5); + + GetLoggerAndInvoke(method, + new Type[] { typeof(string), typeof(string), typeof(string) }, + new Exception("test"), "message", "value0", "value1", "value2"); + } + + // Method9 (Exception exception, string messageTemplate, params object[] propertyValues) : void + void ValidateMethod9(MethodInfo method) + { + VerifyMethodSignature(method, hasExceptionArg: true, expectedArgCount: 3); + + GetLoggerAndInvoke(method, null, + new Exception("test"), "message", new object[] { "value0", "value1", "value2" }); + } + + static void GetLoggerAndInvoke(MethodInfo method, Type[] typeArgs = null, params object[] parameters) + { + var sink = new CollectingSink(); + + var logger = new LoggerConfiguration() + .MinimumLevel.Verbose() + .WriteTo.Sink(sink) + .CreateLogger(); + + LogEventLevel level; + + if (method.Name == Write) + { + level = LogEventLevel.Verbose; + + var paramList = new List() { level }; + + paramList.AddRange(parameters); + + parameters = paramList.ToArray(); + } + else + Assert.True(Enum.TryParse(method.Name, out level)); + + if (method.IsStatic) + { + Log.Logger = logger; + + if (method.IsGenericMethod) + method.MakeGenericMethod(typeArgs).Invoke(null, parameters); + else + method.Invoke(null, parameters); + } + else if (method.IsGenericMethod) + method.MakeGenericMethod(typeArgs).Invoke(logger, parameters); + else + method.Invoke(logger, parameters); + + Assert.Equal(1, sink.Events.Count); + + var evt = sink.Events.Single(); + + Assert.Equal(level, evt.Level); + } + + // parameters will always be ordered so single evaluation method will work + static void VerifyMethodSignature(MethodInfo method, bool hasExceptionArg = false, bool isGeneric = false, int expectedArgCount = 1) + { + try + { + var parameters = method.GetParameters(); + + int index = 0; + + if (method.Name == Write) + { + //write convention methods always have one more parameter, LogEventLevel Arg + expectedArgCount++; + + Assert.Equal(parameters[index].ParameterType, typeof(LogEventLevel)); + + Assert.Equal(parameters[index].Name, "level"); + + index++; + } + + Assert.Equal(parameters.Length, expectedArgCount); + + // exceptions always come before messageTemplate string + if (hasExceptionArg) //verify exception argument type and name + { + Assert.Equal(parameters[index].ParameterType, typeof(Exception)); + + Assert.Equal(parameters[index].Name, "exception"); + + index++; + } + + //check for message template string argument + Assert.Equal(parameters[index].ParameterType, typeof(string)); + + Assert.Equal(parameters[index].Name, MessageTemplate); + + index++; + + if (isGeneric) //validate type arguments, generic parameters, and cross-reference + { + Assert.True(method.IsGenericMethod); + + var genericTypeArgs = method.GetGenericArguments(); + + //multiple generic argument convention T0...Tx : T0 propertyValue0... Tx propertyValueX + if (genericTypeArgs.Length > 1) + { + for (int i = 0; i < genericTypeArgs.Length; i++, index++) + { + Assert.Equal(genericTypeArgs[i].Name, $"T{i}"); + + var genericConstraints = genericTypeArgs[i].GetTypeInfo().GetGenericParameterConstraints(); + + Assert.Empty(genericConstraints); + + Assert.Equal(parameters[index].Name, $"propertyValue{i}"); + + Assert.Equal(parameters[index].ParameterType, genericTypeArgs[i]); + } + } + else //single generic argument convention T : T propertyValue + { + var genericTypeArg = genericTypeArgs[0]; + + Assert.Equal(genericTypeArg.Name, "T"); + + var genericConstraints = genericTypeArg.GetTypeInfo().GetGenericParameterConstraints(); + + Assert.Empty(genericConstraints); + + Assert.Equal(parameters[index].Name, "propertyValue"); + + Assert.Equal(genericTypeArg, parameters[index].ParameterType); + + index++; + } + } + + //check for params argument: params object[] propertyValues + //params argument currently has to be the last argument, and generic methods don't have params argument + if (!isGeneric && (parameters.Length - index) == 1) + { + var paramsArrayArg = parameters[index]; + + // params array attribute should never have derived/inherited classes + var paramsAttr = parameters[index].GetCustomAttribute(typeof(ParamArrayAttribute), inherit: false); + + Assert.NotNull(paramsAttr); + + Assert.Equal(paramsArrayArg.ParameterType, typeof(object[])); + + Assert.Equal(paramsArrayArg.Name, "propertyValues"); + } + } + catch (XunitException e) + { + // mark xunit assertion failures + e.Data.Add("IsSignatureAssertionFailure", true); + + throw e; + } + } + } +} From 3e94c1373a1699d4d451da1308033cadd2e4c125 Mon Sep 17 00:00:00 2001 From: Joshua Clark Date: Thu, 17 Nov 2016 18:47:31 -0500 Subject: [PATCH 38/53] Add tests that touch to static Serilog.Log to their own test collection --- test/Serilog.Tests/LogTests.cs | 1 + test/Serilog.Tests/MethodOverloadConventionTests.cs | 1 + 2 files changed, 2 insertions(+) diff --git a/test/Serilog.Tests/LogTests.cs b/test/Serilog.Tests/LogTests.cs index c2426f213..0ab8716bb 100644 --- a/test/Serilog.Tests/LogTests.cs +++ b/test/Serilog.Tests/LogTests.cs @@ -5,6 +5,7 @@ namespace Serilog.Tests { + [Collection("LogTests")] public class LogTests { [Fact] diff --git a/test/Serilog.Tests/MethodOverloadConventionTests.cs b/test/Serilog.Tests/MethodOverloadConventionTests.cs index 7603e22d3..1af37bb33 100644 --- a/test/Serilog.Tests/MethodOverloadConventionTests.cs +++ b/test/Serilog.Tests/MethodOverloadConventionTests.cs @@ -13,6 +13,7 @@ namespace Serilog.Tests { + [Collection("LogTests")] public class MethodOverloadConventionTests { const string Write = "Write"; From 89a47d30be6c0b3839aba263b99d5722ef61997d Mon Sep 17 00:00:00 2001 From: Joshua Clark Date: Fri, 18 Nov 2016 00:18:53 -0500 Subject: [PATCH 39/53] ILogger API conformance fixes in Serilog.Log --- src/Serilog/Log.cs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/Serilog/Log.cs b/src/Serilog/Log.cs index c5397194f..24834e466 100644 --- a/src/Serilog/Log.cs +++ b/src/Serilog/Log.cs @@ -81,7 +81,7 @@ public static ILogger ForContext(ILogEventEnricher enricher) /// /// Enrichers that apply in the context. /// A logger that will enrich log events as specified. - public static ILogger ForContext(ILogEventEnricher[] enrichers) + public static ILogger ForContext(IEnumerable enrichers) { return Logger.ForContext(enrichers); } @@ -1185,5 +1185,21 @@ public static bool BindMessageTemplate(string messageTemplate, object[] property { return Logger.BindMessageTemplate(messageTemplate, propertyValues, out parsedTemplate, out boundProperties); } + + /// + /// Uses configured scalar conversion and destructuring rules to bind a property value to its captured + /// representation. + /// + /// True if the property could be bound, otherwise false (ILogger + /// The name of the property. Must be non-empty. + /// The property value. + /// If true, the value will be serialized as a structured + /// object if possible; if false, the object will be recorded as a scalar or simple array. + /// The resulting property. + /// methods never throw exceptions). + public static bool BindProperty(string propertyName, object value, bool destructureObjects, out LogEventProperty property) + { + return Logger.BindProperty(propertyName, value, destructureObjects, out property); + } } } From b07de29a1ab6ad58620a856faec6b35fe79b2265 Mon Sep 17 00:00:00 2001 From: Joshua Clark Date: Fri, 18 Nov 2016 13:02:07 -0500 Subject: [PATCH 40/53] revert changes to ForContext(ILogEventEnricher[] enrichers) --- src/Serilog/Log.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Serilog/Log.cs b/src/Serilog/Log.cs index 24834e466..669c47cfb 100644 --- a/src/Serilog/Log.cs +++ b/src/Serilog/Log.cs @@ -81,7 +81,7 @@ public static ILogger ForContext(ILogEventEnricher enricher) /// /// Enrichers that apply in the context. /// A logger that will enrich log events as specified. - public static ILogger ForContext(IEnumerable enrichers) + public static ILogger ForContext(ILogEventEnricher[] enrichers) { return Logger.ForContext(enrichers); } From 844369f97cce55882bad75b3d85b3222e01130ef Mon Sep 17 00:00:00 2001 From: Nicholas Blumhardt Date: Wed, 23 Nov 2016 12:23:01 +1000 Subject: [PATCH 41/53] Disable parallel test execution --- .../Core/LogEventPropertyCapturingTests.cs | 1 - test/Serilog.Tests/Core/LoggerTests.cs | 8 ++--- test/Serilog.Tests/LogTests.cs | 33 ++++++------------- .../MethodOverloadConventionTests.cs | 3 +- .../Support/DelegateDisposable.cs | 24 -------------- 5 files changed, 14 insertions(+), 55 deletions(-) delete mode 100644 test/Serilog.Tests/Support/DelegateDisposable.cs diff --git a/test/Serilog.Tests/Core/LogEventPropertyCapturingTests.cs b/test/Serilog.Tests/Core/LogEventPropertyCapturingTests.cs index 7cb4a3ae7..07c19f093 100644 --- a/test/Serilog.Tests/Core/LogEventPropertyCapturingTests.cs +++ b/test/Serilog.Tests/Core/LogEventPropertyCapturingTests.cs @@ -1,7 +1,6 @@ using System; using System.Linq; using System.Collections.Generic; -using System.IO; using Serilog.Core; using Serilog.Debugging; using Serilog.Events; diff --git a/test/Serilog.Tests/Core/LoggerTests.cs b/test/Serilog.Tests/Core/LoggerTests.cs index a92f44052..eee399e11 100644 --- a/test/Serilog.Tests/Core/LoggerTests.cs +++ b/test/Serilog.Tests/Core/LoggerTests.cs @@ -1,15 +1,13 @@ using System; -using System.IO; using System.Collections.Generic; using System.Linq; using Xunit; using Serilog.Core; using Serilog.Events; using Serilog.Tests.Support; -using System.Reflection; -using Xunit.Sdk; -using System.Text.RegularExpressions; -using System.Text; + +#pragma warning disable Serilog004 // Constant MessageTemplate verifier +#pragma warning disable Serilog003 // Property binding verifier namespace Serilog.Tests.Core { diff --git a/test/Serilog.Tests/LogTests.cs b/test/Serilog.Tests/LogTests.cs index 0ab8716bb..9e98b806e 100644 --- a/test/Serilog.Tests/LogTests.cs +++ b/test/Serilog.Tests/LogTests.cs @@ -1,16 +1,17 @@ -using System; -using Xunit; +using Xunit; using Serilog.Core.Pipeline; using Serilog.Tests.Support; namespace Serilog.Tests { - [Collection("LogTests")] + [Collection("Log.Logger")] public class LogTests { [Fact] public void TheUninitializedLoggerIsSilent() { + // This test depends on being ther first executed from + // the collection. Assert.IsType(Log.Logger); } @@ -18,31 +19,17 @@ public void TheUninitializedLoggerIsSilent() public void DisposesTheLogger() { var disposableLogger = new DisposableLogger(); - using (SwappedLogger(disposableLogger)) - { - Log.CloseAndFlush(); - - Assert.True(disposableLogger.Disposed); - } + Log.Logger = disposableLogger; + Log.CloseAndFlush(); + Assert.True(disposableLogger.Disposed); } [Fact] public void ResetsLoggerToSilentLogger() { - using (SwappedLogger(new DisposableLogger())) - { - Log.CloseAndFlush(); - - Assert.IsType(Log.Logger); - } - } - - private static IDisposable SwappedLogger(ILogger logger) - { - ILogger originalLogger = Log.Logger; - Log.Logger = logger; - - return new DelegateDisposable(() => Log.Logger = originalLogger); + Log.Logger = new DisposableLogger(); + Log.CloseAndFlush(); + Assert.IsType(Log.Logger); } } } diff --git a/test/Serilog.Tests/MethodOverloadConventionTests.cs b/test/Serilog.Tests/MethodOverloadConventionTests.cs index 1af37bb33..8ffe3a592 100644 --- a/test/Serilog.Tests/MethodOverloadConventionTests.cs +++ b/test/Serilog.Tests/MethodOverloadConventionTests.cs @@ -7,13 +7,12 @@ using System.Reflection; using System.Text; using System.Text.RegularExpressions; -using System.Threading.Tasks; using Xunit; using Xunit.Sdk; namespace Serilog.Tests { - [Collection("LogTests")] + [Collection("Log.Logger")] public class MethodOverloadConventionTests { const string Write = "Write"; diff --git a/test/Serilog.Tests/Support/DelegateDisposable.cs b/test/Serilog.Tests/Support/DelegateDisposable.cs deleted file mode 100644 index fbafd3820..000000000 --- a/test/Serilog.Tests/Support/DelegateDisposable.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System; - -namespace Serilog.Tests.Support -{ - public class DelegateDisposable : IDisposable - { - private readonly Action _disposeAction; - private bool _disposed; - - public DelegateDisposable(Action disposeAction) - { - _disposeAction = disposeAction; - } - - public void Dispose() - { - if (_disposed) - return; - - _disposeAction(); - _disposed = true; - } - } -} \ No newline at end of file From 5a19e4ae92fefafea215db39735a3ba2412791ef Mon Sep 17 00:00:00 2001 From: Nicholas Blumhardt Date: Wed, 23 Nov 2016 12:30:33 +1000 Subject: [PATCH 42/53] Add the all-important missing file --- test/Serilog.Tests/Properties/AssemblyInfo.cs | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 test/Serilog.Tests/Properties/AssemblyInfo.cs diff --git a/test/Serilog.Tests/Properties/AssemblyInfo.cs b/test/Serilog.Tests/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..a4bcec543 --- /dev/null +++ b/test/Serilog.Tests/Properties/AssemblyInfo.cs @@ -0,0 +1,3 @@ +using Xunit; + +[assembly: CollectionBehavior(DisableTestParallelization = true)] From 6099d92ccadd8db4a77be2cf7635d9663d7ef82b Mon Sep 17 00:00:00 2001 From: Sergey Komisarchik Date: Thu, 24 Nov 2016 02:12:07 +0300 Subject: [PATCH 43/53] dispose audit sinks --- src/Serilog/LoggerConfiguration.cs | 3 + test/Serilog.Tests/Core/AuditSinkTests.cs | 100 ++++++++++++++++++ .../Serilog.Tests/LoggerConfigurationTests.cs | 66 ------------ 3 files changed, 103 insertions(+), 66 deletions(-) create mode 100644 test/Serilog.Tests/Core/AuditSinkTests.cs diff --git a/src/Serilog/LoggerConfiguration.cs b/src/Serilog/LoggerConfiguration.cs index 99ba02d5a..9aa07ddef 100644 --- a/src/Serilog/LoggerConfiguration.cs +++ b/src/Serilog/LoggerConfiguration.cs @@ -139,6 +139,9 @@ public Logger CreateLogger() { foreach (var disposable in _logEventSinks.OfType()) disposable.Dispose(); + + foreach (var disposable in _auditSinks.OfType()) + disposable.Dispose(); }; ILogEventSink sink = new SafeAggregateSink(_logEventSinks); diff --git a/test/Serilog.Tests/Core/AuditSinkTests.cs b/test/Serilog.Tests/Core/AuditSinkTests.cs new file mode 100644 index 000000000..8aca093d8 --- /dev/null +++ b/test/Serilog.Tests/Core/AuditSinkTests.cs @@ -0,0 +1,100 @@ +using System; +using System.Reflection; +using Serilog.Tests.Support; +using Xunit; + +namespace Serilog.Tests.Core +{ + public class AuditSinkTests + { + [Fact] + public void ExceptionsThrownByAuditSinksArePropagated() + { + var logger = new LoggerConfiguration() + .AuditTo.Sink(new DelegatingSink(e => { throw new Exception("Boom!"); })) + .CreateLogger(); + + Assert.Throws(() => logger.Write(Some.InformationEvent())); + } + + [Fact] + public void ExceptionsThrownByFiltersArePropagatedIfAuditingEnabled() + { + var logger = new LoggerConfiguration() + .AuditTo.Sink(new DelegatingSink(e => { })) + .Filter.ByExcluding(e => { throw new Exception("Boom!"); }) + .CreateLogger(); + + Assert.Throws(() => logger.Write(Some.InformationEvent())); + } + + [Fact] + public void ExceptionsThrownByAuditSinksArePropagatedFromChildLoggers() + { + var logger = new LoggerConfiguration() + .AuditTo.Sink(new DelegatingSink(e => { throw new Exception("Boom!"); })) + .CreateLogger(); + + Assert.Throws(() => logger + .ForContext() + .Write(Some.InformationEvent())); + } + + [Fact] + public void ExceptionsThrownByDestructuringPoliciesArePropagatedIfAuditingEnabled() + { + var logger = new LoggerConfiguration() + .AuditTo.Sink(new CollectingSink()) + .Destructure.ByTransforming(v => { throw new Exception("Boom!"); }) + .CreateLogger(); + + Assert.Throws(() => logger.Information("{@Value}", new Value())); + } + + [Fact] + public void ExceptionsThrownByPropertyAccessorsAreNotPropagated() + { + var logger = new LoggerConfiguration() + .WriteTo.Sink(new CollectingSink()) + .CreateLogger(); + + logger.Information("{@Value}", new ThrowingProperty()); + + Assert.True(true, "No exception reached the caller"); + } + + [Fact] + public void ExceptionsThrownByPropertyAccessorsArePropagatedIfAuditingEnabled() + { + var logger = new LoggerConfiguration() + .AuditTo.Sink(new CollectingSink()) + .CreateLogger(); + + Assert.Throws(() => logger.Information("{@Value}", new ThrowingProperty())); + } + + [Fact] + public void SinkIsDisposedWhenLoggerDisposed() + { + var tracker = new DisposeTrackingSink(); + var logger = new LoggerConfiguration() + .AuditTo.Sink(tracker) + .CreateLogger(); + + logger.Dispose(); + + Assert.True(tracker.IsDisposed); + } + + class Value { } + + class ThrowingProperty + { + // ReSharper disable once UnusedMember.Local + public string Property + { + get { throw new Exception("Boom!"); } + } + } + } +} \ No newline at end of file diff --git a/test/Serilog.Tests/LoggerConfigurationTests.cs b/test/Serilog.Tests/LoggerConfigurationTests.cs index 1aa5891fc..f7bc7c94f 100644 --- a/test/Serilog.Tests/LoggerConfigurationTests.cs +++ b/test/Serilog.Tests/LoggerConfigurationTests.cs @@ -554,39 +554,6 @@ public void ExceptionsThrownByFiltersAreNotPropagated() Assert.True(true, "No exception reached the caller"); } - [Fact] - public void ExceptionsThrownByAuditSinksArePropagated() - { - var logger = new LoggerConfiguration() - .AuditTo.Sink(new DelegatingSink(e => { throw new Exception("Boom!"); })) - .CreateLogger(); - - Assert.Throws(() => logger.Write(Some.InformationEvent())); - } - - [Fact] - public void ExceptionsThrownByFiltersArePropagatedIfAuditingEnabled() - { - var logger = new LoggerConfiguration() - .AuditTo.Sink(new DelegatingSink(e => { })) - .Filter.ByExcluding(e => { throw new Exception("Boom!"); }) - .CreateLogger(); - - Assert.Throws(() => logger.Write(Some.InformationEvent())); - } - - [Fact] - public void ExceptionsThrownByAuditSinksArePropagatedFromChildLoggers() - { - var logger = new LoggerConfiguration() - .AuditTo.Sink(new DelegatingSink(e => { throw new Exception("Boom!"); })) - .CreateLogger(); - - Assert.Throws(() => logger - .ForContext() - .Write(Some.InformationEvent())); - } - class Value { } [Fact] @@ -602,17 +569,6 @@ public void ExceptionsThrownByDestructuringPoliciesAreNotPropagated() Assert.True(true, "No exception reached the caller"); } - [Fact] - public void ExceptionsThrownByDestructuringPoliciesArePropagatedIfAuditingEnabled() - { - var logger = new LoggerConfiguration() - .AuditTo.Sink(new CollectingSink()) - .Destructure.ByTransforming(v => { throw new Exception("Boom!"); }) - .CreateLogger(); - - Assert.Throws(() => logger.Information("{@Value}", new Value())); - } - class ThrowingProperty { // ReSharper disable once UnusedMember.Local @@ -621,27 +577,5 @@ public string Property get { throw new Exception("Boom!"); } } } - - [Fact] - public void ExceptionsThrownByPropertyAccessorsAreNotPropagated() - { - var logger = new LoggerConfiguration() - .WriteTo.Sink(new CollectingSink()) - .CreateLogger(); - - logger.Information("{@Value}", new ThrowingProperty()); - - Assert.True(true, "No exception reached the caller"); - } - - [Fact] - public void ExceptionsThrownByPropertyAccessorsArePropagatedIfAuditingEnabled() - { - var logger = new LoggerConfiguration() - .AuditTo.Sink(new CollectingSink()) - .CreateLogger(); - - Assert.Throws(() => logger.Information("{@Value}", new ThrowingProperty())); - } } } From d79f0392c3c2291b0062be8e0cbc674fa2f7472d Mon Sep 17 00:00:00 2001 From: Sergey Komisarchik Date: Thu, 24 Nov 2016 02:34:50 +0300 Subject: [PATCH 44/53] cosmetics on disposing audit sinks --- src/Serilog/LoggerConfiguration.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/Serilog/LoggerConfiguration.cs b/src/Serilog/LoggerConfiguration.cs index 9aa07ddef..bc39b5e4b 100644 --- a/src/Serilog/LoggerConfiguration.cs +++ b/src/Serilog/LoggerConfiguration.cs @@ -137,10 +137,7 @@ public Logger CreateLogger() Action dispose = () => { - foreach (var disposable in _logEventSinks.OfType()) - disposable.Dispose(); - - foreach (var disposable in _auditSinks.OfType()) + foreach (var disposable in _logEventSinks.Concat(_auditSinks).OfType()) disposable.Dispose(); }; From ada1df031be1402f394304e83580059da6405c20 Mon Sep 17 00:00:00 2001 From: Joshua Clark Date: Wed, 23 Nov 2016 22:53:39 -0500 Subject: [PATCH 45/53] Added tests for the rest of ILogger and add support for SilentLogger --- .../MethodOverloadConventionTests.cs | 1116 ++++++++++++----- 1 file changed, 807 insertions(+), 309 deletions(-) diff --git a/test/Serilog.Tests/MethodOverloadConventionTests.cs b/test/Serilog.Tests/MethodOverloadConventionTests.cs index 8ffe3a592..71fc84808 100644 --- a/test/Serilog.Tests/MethodOverloadConventionTests.cs +++ b/test/Serilog.Tests/MethodOverloadConventionTests.cs @@ -1,6 +1,7 @@ using Serilog.Core; using Serilog.Events; using Serilog.Tests.Support; +using Serilog.Core.Pipeline; using System; using System.Collections.Generic; using System.Linq; @@ -12,361 +13,858 @@ namespace Serilog.Tests { + /// + /// The goal of these tests is to test API conformance, + /// against classes that implement the ILogger interface + /// [Collection("Log.Logger")] public class MethodOverloadConventionTests { - const string Write = "Write"; - - //this is used as both the variable name for message template parameter - // and as the argument for the MessageTemplateFormatMethodAttr - const string MessageTemplate = "messageTemplate"; - - [Theory] - [InlineData(Write)] - [InlineData(nameof(LogEventLevel.Verbose))] - [InlineData(nameof(LogEventLevel.Debug))] - [InlineData(nameof(LogEventLevel.Information))] - [InlineData(nameof(LogEventLevel.Warning))] - [InlineData(nameof(LogEventLevel.Error))] - [InlineData(nameof(LogEventLevel.Fatal))] - public void ILoggerValidateConventions(string setName) - { - ValidateConventionForMethodSet(setName, typeof(ILogger)); - } - - [Theory] - [InlineData(Write)] - [InlineData(nameof(LogEventLevel.Verbose))] - [InlineData(nameof(LogEventLevel.Debug))] - [InlineData(nameof(LogEventLevel.Information))] - [InlineData(nameof(LogEventLevel.Warning))] - [InlineData(nameof(LogEventLevel.Error))] - [InlineData(nameof(LogEventLevel.Fatal))] - public void LoggerValidateConventions(string setName) - { - ValidateConventionForMethodSet(setName, typeof(Logger)); - } - - [Theory] - [InlineData(Write)] - [InlineData(nameof(LogEventLevel.Verbose))] - [InlineData(nameof(LogEventLevel.Debug))] - [InlineData(nameof(LogEventLevel.Information))] - [InlineData(nameof(LogEventLevel.Warning))] - [InlineData(nameof(LogEventLevel.Error))] - [InlineData(nameof(LogEventLevel.Fatal))] - public void LogValidateConventions(string setName) - { - ValidateConventionForMethodSet(setName, typeof(Log)); - } - - void ValidateConventionForMethodSet(string setName, Type loggerType) - { - IEnumerable methodSet; - - if (setName == Write) - methodSet = loggerType.GetMethods() - .Where(method => method.Name == setName && method.GetParameters() - .Any(param => param.ParameterType == typeof(string))); - else - methodSet = loggerType.GetMethods() - .Where(method => method.Name == setName); - - var testMethods = typeof(MethodOverloadConventionTests).GetRuntimeMethods() - .Where(method => Regex.IsMatch(method.Name, "ValidateMethod\\d")); - - Assert.Equal(testMethods.Count(), methodSet.Count()); - - foreach (var method in methodSet) - { - Assert.Equal(method.ReturnType, typeof(void)); - - Assert.True(method.IsPublic); - - var messageTemplateAttr = method.GetCustomAttribute(); - - Assert.NotNull(messageTemplateAttr); - - Assert.Equal(messageTemplateAttr.MessageTemplateParameterName, MessageTemplate); - - var signatureMatchAndInvokeSuccess = false; - - var report = new StringBuilder(); - - foreach (var testMethod in testMethods) - { - try - { - testMethod.Invoke(this, new object[] { method }); - - signatureMatchAndInvokeSuccess = true; - - break; - } - catch (TargetInvocationException e) - when (e.GetBaseException() is XunitException) - { - var xunitException = (XunitException)e.GetBaseException(); - - if (xunitException.Data.Contains("IsSignatureAssertionFailure")) - { - report.AppendLine($"{testMethod.Name} Signature Mismatch on: {method} with: {xunitException.Message}"); - } - else - { - report.AppendLine($"{testMethod.Name} Invocation Failure on: {method} with: {xunitException.UserMessage}"); - } - - continue; - } - } - - Assert.True(signatureMatchAndInvokeSuccess, $"{method} did not match any known convention or failed invoke\n" + report.ToString()); - } - } - - // Method0 (string messageTemplate) : void - void ValidateMethod0(MethodInfo method) - { - VerifyMethodSignature(method, expectedArgCount: 1); - - GetLoggerAndInvoke(method, null, "message"); - } - - // Method1 (string messageTemplate, T propertyValue) : void - void ValidateMethod1(MethodInfo method) - { - VerifyMethodSignature(method, isGeneric: true, expectedArgCount: 2); - - GetLoggerAndInvoke(method, - new Type[] { typeof(string) }, "message", "value0"); - } - - // Method2 (string messageTemplate, T0 propertyValue0, T1 propertyValue1) : void - void ValidateMethod2(MethodInfo method) - { - VerifyMethodSignature(method, isGeneric: true, expectedArgCount: 3); - - GetLoggerAndInvoke(method, - new Type[] { typeof(string), typeof(string) }, - "message", "value0", "value1"); - } - - // Method3 (string messageTemplate, T0 propertyValue0, T1 propertyValue1, T2 propertyValue2) : void - void ValidateMethod3(MethodInfo method) - { - VerifyMethodSignature(method, isGeneric: true, expectedArgCount: 4); - - GetLoggerAndInvoke(method, - new Type[] { typeof(string), typeof(string), typeof(string) }, - "message", "value0", "value1", "value2"); - } - - // Method4 (string messageTemplate, params object[] propertyValues) : void - void ValidateMethod4(MethodInfo method) - { - VerifyMethodSignature(method, expectedArgCount: 2); - - GetLoggerAndInvoke(method, null, - "message", new object[] { "value0", "value1", "value2" }); - } - - // Method5 (Exception exception, string messageTemplate) : void - void ValidateMethod5(MethodInfo method) - { - VerifyMethodSignature(method, hasExceptionArg: true, expectedArgCount: 2); - - GetLoggerAndInvoke(method, null, - new Exception("test"), "message"); - } - - // Method6 (Exception exception, string messageTemplate, T propertyValue) : void - void ValidateMethod6(MethodInfo method) - { - VerifyMethodSignature(method, hasExceptionArg: true, isGeneric: true, expectedArgCount: 3); - - GetLoggerAndInvoke(method, - new Type[] { typeof(string) }, - new Exception("test"), "message", "value0"); - } - - // Method7 (Exception exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1) : void - void ValidateMethod7(MethodInfo method) - { - VerifyMethodSignature(method, hasExceptionArg: true, isGeneric: true, expectedArgCount: 4); - - GetLoggerAndInvoke(method, - new Type[] { typeof(string), typeof(string) }, - new Exception("test"), "message", "value0", "value1"); - } - - // Method8 (Exception exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1, T2 propertyValue2) : void - void ValidateMethod8(MethodInfo method) - { - VerifyMethodSignature(method, hasExceptionArg: true, isGeneric: true, expectedArgCount: 5); - - GetLoggerAndInvoke(method, - new Type[] { typeof(string), typeof(string), typeof(string) }, - new Exception("test"), "message", "value0", "value1", "value2"); - } + const string Write = "Write"; + + //this is used as both the variable name for message template parameter + // and as the argument for the MessageTemplateFormatMethodAttr + const string MessageTemplate = "messageTemplate"; + + [Theory] + [InlineData(Write)] + [InlineData(nameof(LogEventLevel.Verbose))] + [InlineData(nameof(LogEventLevel.Debug))] + [InlineData(nameof(LogEventLevel.Information))] + [InlineData(nameof(LogEventLevel.Warning))] + [InlineData(nameof(LogEventLevel.Error))] + [InlineData(nameof(LogEventLevel.Fatal))] + public void ILoggerValidateConventions(string setName) + { + ValidateConventionForMethodSet(setName, typeof(ILogger)); + } + + [Theory] + [InlineData(Write)] + [InlineData(nameof(LogEventLevel.Verbose))] + [InlineData(nameof(LogEventLevel.Debug))] + [InlineData(nameof(LogEventLevel.Information))] + [InlineData(nameof(LogEventLevel.Warning))] + [InlineData(nameof(LogEventLevel.Error))] + [InlineData(nameof(LogEventLevel.Fatal))] + public void LoggerValidateConventions(string setName) + { + ValidateConventionForMethodSet(setName, typeof(Logger)); + } + + [Theory] + [InlineData(Write)] + [InlineData(nameof(LogEventLevel.Verbose))] + [InlineData(nameof(LogEventLevel.Debug))] + [InlineData(nameof(LogEventLevel.Information))] + [InlineData(nameof(LogEventLevel.Warning))] + [InlineData(nameof(LogEventLevel.Error))] + [InlineData(nameof(LogEventLevel.Fatal))] + public void LogValidateConventions(string setName) + { + ValidateConventionForMethodSet(setName, typeof(Log)); + } + + [Theory] + [InlineData(Write)] + [InlineData(nameof(LogEventLevel.Verbose))] + [InlineData(nameof(LogEventLevel.Debug))] + [InlineData(nameof(LogEventLevel.Information))] + [InlineData(nameof(LogEventLevel.Warning))] + [InlineData(nameof(LogEventLevel.Error))] + [InlineData(nameof(LogEventLevel.Fatal))] + public void SilentLoggerValidateConventions(string setName) + { + ValidateConventionForMethodSet(setName, typeof(SilentLogger), + checkMesgTempAttr: false, testInvokeResults: false); + } + + [Theory] + [InlineData(typeof(SilentLogger))] + [InlineData(typeof(Logger))] + [InlineData(typeof(Log))] + [InlineData(typeof(ILogger))] + public void ValidateWriteEventLogMethods(Type loggerType) + { + var methods = loggerType.GetMethods() + .Where(method => method.Name == Write && method.GetParameters() + .Any(param => param.ParameterType == typeof(LogEvent))); + + Assert.Single(methods); + + var writeMethod = methods.Single(); + + Assert.True(writeMethod.IsPublic); + + Assert.Equal(writeMethod.ReturnType, typeof(void)); + + LogEventLevel level = LogEventLevel.Information; + + CollectingSink sink; + + var logger = GetLogger(loggerType, out sink); + + InvokeMethod(writeMethod, logger, new object[] { Some.LogEvent(DateTimeOffset.Now, level) }); + + //handle silent logger special case i.e. no result validation + if (loggerType == typeof(SilentLogger)) + return; + else + TestResults(level, sink); + } + + [Theory] + [InlineData(typeof(SilentLogger))] + [InlineData(typeof(Logger))] + [InlineData(typeof(Log))] + [InlineData(typeof(ILogger))] + public void ValidateForContextMethods(Type loggerType) + { + var methodSet = loggerType.GetMethods().Where(method => method.Name == "ForContext"); + + var testMethods = typeof(MethodOverloadConventionTests).GetRuntimeMethods() + .Where(method => Regex.IsMatch(method.Name, "ForContextMethod\\d")); + + Assert.Equal(testMethods.Count(), methodSet.Count()); + + foreach (var method in methodSet) + { + Assert.Equal(method.ReturnType, typeof(ILogger)); + + Assert.True(method.IsPublic); + + var signatureMatchAndInvokeSuccess = false; + + var report = new StringBuilder(); + + foreach (var testMethod in testMethods) + { + try + { + testMethod.Invoke(this, new object[] { method }); + + signatureMatchAndInvokeSuccess = true; + + break; + } + catch (TargetInvocationException e) + when (e.GetBaseException() is XunitException) + { + var xunitException = (XunitException)e.GetBaseException(); + + if (xunitException.Data.Contains("IsSignatureAssertionFailure")) + { + report.AppendLine($"{testMethod.Name} Signature Mismatch on: {method} with: {xunitException.Message}"); + } + else + { + report.AppendLine($"{testMethod.Name} Invocation Failure on: {method} with: {xunitException.UserMessage}"); + } + + continue; + } + } + + Assert.True(signatureMatchAndInvokeSuccess, $"{method} did not match any known method or failed invoke\n" + report.ToString()); + } + } + + [Theory] + [InlineData(typeof(SilentLogger))] + [InlineData(typeof(Logger))] + [InlineData(typeof(Log))] + [InlineData(typeof(ILogger))] + public void ValidateBindMessageTemplateMethods(Type loggerType) + { + var method = loggerType.GetMethod("BindMessageTemplate"); - // Method9 (Exception exception, string messageTemplate, params object[] propertyValues) : void - void ValidateMethod9(MethodInfo method) - { - VerifyMethodSignature(method, hasExceptionArg: true, expectedArgCount: 3); + Assert.Equal(method.ReturnType, typeof(bool)); + Assert.True(method.IsPublic); - GetLoggerAndInvoke(method, null, - new Exception("test"), "message", new object[] { "value0", "value1", "value2" }); - } + var parameters = method.GetParameters(); + int index = 0; - static void GetLoggerAndInvoke(MethodInfo method, Type[] typeArgs = null, params object[] parameters) - { - var sink = new CollectingSink(); + Assert.Equal(parameters[index].Name, "messageTemplate"); + Assert.Equal(parameters[index].ParameterType, typeof(string)); + index++; - var logger = new LoggerConfiguration() - .MinimumLevel.Verbose() - .WriteTo.Sink(sink) - .CreateLogger(); + Assert.Equal(parameters[index].Name, "propertyValues"); + Assert.Equal(parameters[index].ParameterType, typeof(object[])); + index++; - LogEventLevel level; + Assert.Equal(parameters[index].Name, "parsedTemplate"); + Assert.Equal(parameters[index].ParameterType, typeof(MessageTemplate).MakeByRefType()); + Assert.True(parameters[index].IsOut); + index++; - if (method.Name == Write) - { - level = LogEventLevel.Verbose; + Assert.Equal(parameters[index].Name, "boundProperties"); + Assert.Equal(parameters[index].ParameterType, typeof(IEnumerable).MakeByRefType()); + Assert.True(parameters[index].IsOut); + index++; - var paramList = new List() { level }; + var logger = GetLogger(loggerType); - paramList.AddRange(parameters); + var args = new object[] + { + "Processed {value0}, {value1}", new object[] { "value0", "value1" }, null, null + }; - parameters = paramList.ToArray(); - } - else - Assert.True(Enum.TryParse(method.Name, out level)); + var result = InvokeMethod(method, logger, args); - if (method.IsStatic) - { - Log.Logger = logger; + Assert.IsType(typeof(bool), result); - if (method.IsGenericMethod) - method.MakeGenericMethod(typeArgs).Invoke(null, parameters); - else - method.Invoke(null, parameters); - } - else if (method.IsGenericMethod) - method.MakeGenericMethod(typeArgs).Invoke(logger, parameters); - else - method.Invoke(logger, parameters); + if (loggerType == typeof(SilentLogger)) + Assert.False(result as bool?); + else + Assert.True(result as bool?); + } - Assert.Equal(1, sink.Events.Count); + [Theory] + [InlineData(typeof(SilentLogger))] + [InlineData(typeof(Logger))] + [InlineData(typeof(Log))] + [InlineData(typeof(ILogger))] + public void ValidateBindPropertyMethods(Type loggerType) + { + var method = loggerType.GetMethod("BindProperty"); - var evt = sink.Events.Single(); + Assert.Equal(method.ReturnType, typeof(bool)); + Assert.True(method.IsPublic); - Assert.Equal(level, evt.Level); - } + var parameters = method.GetParameters(); + int index = 0; - // parameters will always be ordered so single evaluation method will work - static void VerifyMethodSignature(MethodInfo method, bool hasExceptionArg = false, bool isGeneric = false, int expectedArgCount = 1) - { - try - { - var parameters = method.GetParameters(); + Assert.Equal(parameters[index].Name, "propertyName"); + Assert.Equal(parameters[index].ParameterType, typeof(string)); + index++; - int index = 0; + Assert.Equal(parameters[index].Name, "value"); + Assert.Equal(parameters[index].ParameterType, typeof(object)); + index++; - if (method.Name == Write) - { - //write convention methods always have one more parameter, LogEventLevel Arg - expectedArgCount++; + Assert.Equal(parameters[index].Name, "destructureObjects"); + Assert.Equal(parameters[index].ParameterType, typeof(bool)); + index++; - Assert.Equal(parameters[index].ParameterType, typeof(LogEventLevel)); + Assert.Equal(parameters[index].Name, "property"); + Assert.Equal(parameters[index].ParameterType, typeof(LogEventProperty).MakeByRefType()); + Assert.True(parameters[index].IsOut); - Assert.Equal(parameters[index].Name, "level"); + var logger = GetLogger(loggerType); - index++; - } + var args = new object[] + { + "SomeString", "someString", false, null + }; - Assert.Equal(parameters.Length, expectedArgCount); + var result = InvokeMethod(method, logger, args); - // exceptions always come before messageTemplate string - if (hasExceptionArg) //verify exception argument type and name - { - Assert.Equal(parameters[index].ParameterType, typeof(Exception)); + Assert.IsType(typeof(bool), result); - Assert.Equal(parameters[index].Name, "exception"); + if (loggerType == typeof(SilentLogger)) + Assert.False(result as bool?); + else + Assert.True(result as bool?); + } - index++; - } + //public ILogger ForContext(ILogEventEnricher enricher) + void ForContextMethod0(MethodInfo method) + { + try + { + var parameters = method.GetParameters(); - //check for message template string argument - Assert.Equal(parameters[index].ParameterType, typeof(string)); + Assert.Single(parameters); - Assert.Equal(parameters[index].Name, MessageTemplate); + var parameter = parameters.Single(); - index++; + Assert.Equal(parameter.Name, "enricher"); - if (isGeneric) //validate type arguments, generic parameters, and cross-reference - { - Assert.True(method.IsGenericMethod); + Assert.Equal(parameter.ParameterType, typeof(ILogEventEnricher)); + } + catch (XunitException e) + { + e.Data.Add("IsSignatureAssertionFailure", true); - var genericTypeArgs = method.GetGenericArguments(); + throw e; + } - //multiple generic argument convention T0...Tx : T0 propertyValue0... Tx propertyValueX - if (genericTypeArgs.Length > 1) - { - for (int i = 0; i < genericTypeArgs.Length; i++, index++) - { - Assert.Equal(genericTypeArgs[i].Name, $"T{i}"); + var logger = GetLogger(method.DeclaringType); - var genericConstraints = genericTypeArgs[i].GetTypeInfo().GetGenericParameterConstraints(); + var logEnricher = new TestDummies.DummyThreadIdEnricher(); - Assert.Empty(genericConstraints); + var enrichedLogger = InvokeMethod(method, logger, new object[] { logEnricher }); - Assert.Equal(parameters[index].Name, $"propertyValue{i}"); + Assert.NotNull(enrichedLogger); - Assert.Equal(parameters[index].ParameterType, genericTypeArgs[i]); - } - } - else //single generic argument convention T : T propertyValue - { - var genericTypeArg = genericTypeArgs[0]; + Assert.True(enrichedLogger is ILogger); + } - Assert.Equal(genericTypeArg.Name, "T"); + //public ILogger ForContext(ILogEventEnricher[] enricher) + void ForContextMethod1(MethodInfo method) + { + try + { + var parameters = method.GetParameters(); - var genericConstraints = genericTypeArg.GetTypeInfo().GetGenericParameterConstraints(); + Assert.Single(parameters); - Assert.Empty(genericConstraints); + var parameter = parameters.Single(); - Assert.Equal(parameters[index].Name, "propertyValue"); + Assert.Equal(parameter.Name, "enrichers"); - Assert.Equal(genericTypeArg, parameters[index].ParameterType); + Assert.True(parameter.ParameterType == typeof(IEnumerable) + || parameter.ParameterType == typeof(ILogEventEnricher[])); + } + catch (XunitException e) + { + e.Data.Add("IsSignatureAssertionFailure", true); - index++; - } - } + throw e; + } - //check for params argument: params object[] propertyValues - //params argument currently has to be the last argument, and generic methods don't have params argument - if (!isGeneric && (parameters.Length - index) == 1) - { - var paramsArrayArg = parameters[index]; + var logger = GetLogger(method.DeclaringType); - // params array attribute should never have derived/inherited classes - var paramsAttr = parameters[index].GetCustomAttribute(typeof(ParamArrayAttribute), inherit: false); + var logEnricher = new TestDummies.DummyThreadIdEnricher(); - Assert.NotNull(paramsAttr); + var enrichedLogger = InvokeMethod(method, logger, + new object[] { new ILogEventEnricher[] { logEnricher, logEnricher } }); - Assert.Equal(paramsArrayArg.ParameterType, typeof(object[])); + Assert.NotNull(enrichedLogger); - Assert.Equal(paramsArrayArg.Name, "propertyValues"); - } - } - catch (XunitException e) - { - // mark xunit assertion failures - e.Data.Add("IsSignatureAssertionFailure", true); + Assert.True(enrichedLogger is ILogger); + } - throw e; - } - } - } -} + //public ILogger ForContext(string propertyName, object value, bool destructureObjects) + void ForContextMethod2(MethodInfo method) + { + try + { + var parameters = method.GetParameters(); + Assert.Equal(parameters.Length, 3); + + int index = 0; + + Assert.Equal(parameters[index].Name, "propertyName"); + Assert.Equal(parameters[index].ParameterType, typeof(string)); + index++; + + Assert.Equal(parameters[index].Name, "value"); + Assert.Equal(parameters[index].ParameterType, typeof(object)); + index++; + + Assert.Equal(parameters[index].Name, "destructureObjects"); + Assert.Equal(parameters[index].ParameterType, typeof(bool)); + Assert.True(parameters[index].IsOptional); + } + catch (XunitException e) + { + e.Data.Add("IsSignatureAssertionFailure", true); + + throw e; + } + + var logger = GetLogger(method.DeclaringType); + + var propertyName = "SomeString"; + var propertyValue = "someString"; + + var enrichedLogger = InvokeMethod(method, logger, new object[] { propertyName, propertyValue, false }); + + Assert.NotNull(enrichedLogger); + + Assert.True(enrichedLogger is ILogger); + } + + //public ILogger ForContext() + void ForContextMethod3(MethodInfo method) + { + try + { + Assert.True(method.IsGenericMethod); + + var genericArgs = method.GetGenericArguments(); + + Assert.Single(genericArgs); + + var genericArg = genericArgs.Single(); + + Assert.Equal(genericArg.Name, "TSource"); + } + catch (XunitException e) + { + e.Data.Add("IsSignatureAssertionFailure", true); + + throw e; + } + + var logger = GetLogger(method.DeclaringType); + + var enrichedLogger = InvokeMethod(method, logger, null, new Type[] { typeof(object) }); + + Assert.NotNull(enrichedLogger); + + Assert.True(enrichedLogger is ILogger); + } + + //public ILogger ForContext(Type source) + void ForContextMethod4(MethodInfo method) + { + try + { + var args = method.GetParameters(); + + Assert.Single(args); + + var arg = args.Single(); + + Assert.Equal(arg.Name, "source"); + + Assert.Equal(arg.ParameterType, typeof(Type)); + } + catch (XunitException e) + { + e.Data.Add("IsSignatureAssertionFailure", true); + + throw e; + } + + var logger = GetLogger(method.DeclaringType); + + var enrichedLogger = InvokeMethod(method, logger, new object[] { typeof(object) }); + + Assert.NotNull(enrichedLogger); + + Assert.True(enrichedLogger is ILogger); + } + + void ValidateConventionForMethodSet( + string setName, + Type loggerType, + bool checkMesgTempAttr = true, + bool testInvokeResults = true) + { + IEnumerable methodSet; + + if (setName == Write) + methodSet = loggerType.GetMethods() + .Where(method => method.Name == setName && method.GetParameters() + .Any(param => param.ParameterType == typeof(string))); + else + methodSet = loggerType.GetMethods() + .Where(method => method.Name == setName); + + var testMethods = typeof(MethodOverloadConventionTests).GetRuntimeMethods() + .Where(method => Regex.IsMatch(method.Name, "ValidateMethod\\d")); + + Assert.Equal(testMethods.Count(), methodSet.Count()); + + foreach (var method in methodSet) + { + Assert.Equal(method.ReturnType, typeof(void)); + + Assert.True(method.IsPublic); + + if (checkMesgTempAttr) + { + var messageTemplateAttr = method.GetCustomAttribute(); + + Assert.NotNull(messageTemplateAttr); + + Assert.Equal(messageTemplateAttr.MessageTemplateParameterName, MessageTemplate); + } + + var signatureMatchAndInvokeSuccess = false; + + var report = new StringBuilder(); + + foreach (var testMethod in testMethods) + { + try + { + Action invokeTestMethod = null; + + if (testInvokeResults) + invokeTestMethod = InvokeConventionMethodAndTest; + else + invokeTestMethod = InvokeConventionMethod; + + testMethod.Invoke(this, new object[] { method, invokeTestMethod }); + + signatureMatchAndInvokeSuccess = true; + + break; + } + catch (TargetInvocationException e) + when (e.GetBaseException() is XunitException) + { + var xunitException = (XunitException)e.GetBaseException(); + + if (xunitException.Data.Contains("IsSignatureAssertionFailure")) + { + report.AppendLine($"{testMethod.Name} Signature Mismatch on: {method} with: {xunitException.Message}"); + } + else + { + report.AppendLine($"{testMethod.Name} Invocation Failure on: {method} with: {xunitException.UserMessage}"); + } + + continue; + } + } + + Assert.True(signatureMatchAndInvokeSuccess, $"{method} did not match any known convention or failed invoke\n" + report.ToString()); + } + } + + // Method0 (string messageTemplate) : void + void ValidateMethod0(MethodInfo method, Action invokeMethod) + { + VerifyMethodSignature(method, expectedArgCount: 1); + + var parameters = new object[] { "message" }; + + invokeMethod(method, null, parameters); + } + + // Method1 (string messageTemplate, T propertyValue) : void + void ValidateMethod1(MethodInfo method, Action invokeMethod) + { + VerifyMethodSignature(method, isGeneric: true, expectedArgCount: 2); + + var typeArgs = new Type[] { typeof(string) }; + + var parameters = new object[] { "message", "value0" }; + + invokeMethod(method, typeArgs, parameters); + } + + // Method2 (string messageTemplate, T0 propertyValue0, T1 propertyValue1) : void + void ValidateMethod2(MethodInfo method, Action invokeMethod) + { + VerifyMethodSignature(method, isGeneric: true, expectedArgCount: 3); + + var typeArgs = new Type[] { typeof(string), typeof(string) }; + + var parameters = new object[] + { + "Processed {value0}, {value1}", "value0", "value1" + }; + + invokeMethod(method, typeArgs, parameters); + } + + // Method3 (string messageTemplate, T0 propertyValue0, T1 propertyValue1, T2 propertyValue2) : void + void ValidateMethod3(MethodInfo method, Action invokeMethod) + { + VerifyMethodSignature(method, isGeneric: true, expectedArgCount: 4); + + var typeArgs = new Type[] { typeof(string), typeof(string), typeof(string) }; + + var parameters = new object[] + { + "Processed {value0}, {value1}, {value2}", "value0", "value1", "value2" + }; + + invokeMethod(method, typeArgs, parameters); + } + + // Method4 (string messageTemplate, params object[] propertyValues) : void + void ValidateMethod4(MethodInfo method, Action invokeMethod) + { + VerifyMethodSignature(method, expectedArgCount: 2); + + var parameters = new object[] + { + "Processed {value0}, {value1}, {value2}", new object[] { "value0", "value1", "value2" } + }; + + invokeMethod(method, null, parameters); + } + + // Method5 (Exception exception, string messageTemplate) : void + void ValidateMethod5(MethodInfo method, Action invokeMethod) + { + VerifyMethodSignature(method, hasExceptionArg: true, expectedArgCount: 2); + + var parameters = new object[] { new Exception("test"), "message" }; + + invokeMethod(method, null, parameters); + } + + // Method6 (Exception exception, string messageTemplate, T propertyValue) : void + void ValidateMethod6(MethodInfo method, Action invokeMethod) + { + VerifyMethodSignature(method, hasExceptionArg: true, isGeneric: true, expectedArgCount: 3); + + var typeArgs = new Type[] { typeof(string) }; + + var parameters = new object[] + { + new Exception("test"), "Processed {value0}", "value0" + }; + + invokeMethod(method, typeArgs, parameters); + } + + // Method7 (Exception exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1) : void + void ValidateMethod7(MethodInfo method, Action invokeMethod) + { + VerifyMethodSignature(method, hasExceptionArg: true, isGeneric: true, expectedArgCount: 4); + + var typeArgs = new Type[] { typeof(string), typeof(string) }; + + var parameters = new object[] + { + new Exception("test"), "Processed {value0}, {value1}", "value0", "value1" + }; + + invokeMethod(method, typeArgs, parameters); + } + + // Method8 (Exception exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1, T2 propertyValue2) : void + void ValidateMethod8(MethodInfo method, Action invokeMethod) + { + VerifyMethodSignature(method, hasExceptionArg: true, isGeneric: true, expectedArgCount: 5); + + var typeArgs = new Type[] { typeof(string), typeof(string), typeof(string) }; + + var parameters = new object[] + { + new Exception("test"), "Processed {value0}, {value1}, {value2}", "value0", "value1", "value2" + }; + + invokeMethod(method, typeArgs, parameters); + } + + // Method9 (Exception exception, string messageTemplate, params object[] propertyValues) : void + void ValidateMethod9(MethodInfo method, Action invokeMethod) + { + VerifyMethodSignature(method, hasExceptionArg: true, expectedArgCount: 3); + + object[] parameters = new object[] + { + new Exception("test"), "Processed {value0}, {value1}, {value2}", + new object[] { "value0", "value1", "value2" } + }; + + invokeMethod(method, null, parameters); + } + + //primarily meant for testing silent logger + static void InvokeConventionMethod( + MethodInfo method, + Type[] typeArgs, + object[] parameters, + out LogEventLevel level, + out CollectingSink sink) + { + var logger = GetLogger(method.DeclaringType, out sink); + + if (method.Name == Write) + { + level = LogEventLevel.Verbose; + + var paramList = new List() { level }; + + paramList.AddRange(parameters); + + parameters = paramList.ToArray(); + } + else + Assert.True(Enum.TryParse(method.Name, out level)); + + InvokeMethod(method, logger, parameters, typeArgs); + } + + static void InvokeConventionMethod(MethodInfo method, Type[] typeArgs, object[] parameters) + { + CollectingSink sink; + + LogEventLevel level; + + InvokeConventionMethod(method, typeArgs, parameters, out level, out sink); + } + + static void InvokeConventionMethodAndTest(MethodInfo method, Type[] typeArgs, object[] parameters) + { + CollectingSink sink; + + LogEventLevel level; + + InvokeConventionMethod(method, typeArgs, parameters, out level, out sink); + + TestResults(level, sink); + } + + // parameters will always be ordered so single evaluation method will work + static void VerifyMethodSignature(MethodInfo method, bool hasExceptionArg = false, bool isGeneric = false, int expectedArgCount = 1) + { + try + { + var parameters = method.GetParameters(); + + int index = 0; + + if (method.Name == Write) + { + //write convention methods always have one more parameter, LogEventLevel Arg + expectedArgCount++; + + Assert.Equal(parameters[index].ParameterType, typeof(LogEventLevel)); + + Assert.Equal(parameters[index].Name, "level"); + + index++; + } + + Assert.Equal(parameters.Length, expectedArgCount); + + // exceptions always come before messageTemplate string + if (hasExceptionArg) //verify exception argument type and name + { + Assert.Equal(parameters[index].ParameterType, typeof(Exception)); + + Assert.Equal(parameters[index].Name, "exception"); + + index++; + } + + //check for message template string argument + Assert.Equal(parameters[index].ParameterType, typeof(string)); + + Assert.Equal(parameters[index].Name, MessageTemplate); + + index++; + + if (isGeneric) //validate type arguments, generic parameters, and cross-reference + { + Assert.True(method.IsGenericMethod); + + var genericTypeArgs = method.GetGenericArguments(); + + //multiple generic argument convention T0...Tx : T0 propertyValue0... Tx propertyValueX + if (genericTypeArgs.Length > 1) + { + for (int i = 0; i < genericTypeArgs.Length; i++, index++) + { + Assert.Equal(genericTypeArgs[i].Name, $"T{i}"); + + var genericConstraints = genericTypeArgs[i].GetTypeInfo().GetGenericParameterConstraints(); + + Assert.Empty(genericConstraints); + + Assert.Equal(parameters[index].Name, $"propertyValue{i}"); + + Assert.Equal(parameters[index].ParameterType, genericTypeArgs[i]); + } + } + else //single generic argument convention T : T propertyValue + { + var genericTypeArg = genericTypeArgs[0]; + + Assert.Equal(genericTypeArg.Name, "T"); + + var genericConstraints = genericTypeArg.GetTypeInfo().GetGenericParameterConstraints(); + + Assert.Empty(genericConstraints); + + Assert.Equal(parameters[index].Name, "propertyValue"); + + Assert.Equal(genericTypeArg, parameters[index].ParameterType); + + index++; + } + } + + //check for params argument: params object[] propertyValues + //params argument currently has to be the last argument, and generic methods don't have params argument + if (!isGeneric && (parameters.Length - index) == 1) + { + var paramsArrayArg = parameters[index]; + + // params array attribute should never have derived/inherited classes + var paramsAttr = parameters[index].GetCustomAttribute(typeof(ParamArrayAttribute), inherit: false); + + Assert.NotNull(paramsAttr); + + Assert.Equal(paramsArrayArg.ParameterType, typeof(object[])); + + Assert.Equal(paramsArrayArg.Name, "propertyValues"); + } + } + catch (XunitException e) + { + // mark xunit assertion failures + e.Data.Add("IsSignatureAssertionFailure", true); + + throw e; + } + } + + static object InvokeMethod( + MethodInfo method, + ILogger instance, + object[] parameters, + Type[] typeArgs = null) + { + if (method.IsStatic) + { + if (method.IsGenericMethod) + return method.MakeGenericMethod(typeArgs).Invoke(null, parameters); + else + return method.Invoke(null, parameters); + } + else if (method.IsGenericMethod) + return method.MakeGenericMethod(typeArgs).Invoke(instance, parameters); + else + return method.Invoke(instance, parameters); + } + + static void TestResults(LogEventLevel level, CollectingSink results) + { + Assert.Equal(1, results.Events.Count); + + var evt = results.Events.Single(); + + Assert.Equal(level, evt.Level); + } + + static ILogger GetLogger(Type loggerType) + { + CollectingSink sink; + + return GetLogger(loggerType, out sink); + } + + static ILogger GetLogger(Type loggerType, out CollectingSink sink) + { + sink = null; + + if (loggerType == typeof(Logger) || loggerType == typeof(ILogger)) + { + sink = new CollectingSink(); + + return new LoggerConfiguration() + .MinimumLevel.Verbose() + .WriteTo.Sink(sink) + .CreateLogger(); + } + else if (loggerType == typeof(Log)) + { + sink = new CollectingSink(); + + Log.CloseAndFlush(); + + Log.Logger = new LoggerConfiguration() + .MinimumLevel.Verbose() + .WriteTo.Sink(sink) + .CreateLogger(); + + return null; + } + else + return new SilentLogger(); + } + } +} \ No newline at end of file From eb2b2b27aad65415e3fffb616cf7eafa0a8c1651 Mon Sep 17 00:00:00 2001 From: Joshua Clark Date: Wed, 23 Nov 2016 23:00:47 -0500 Subject: [PATCH 46/53] added message template attribute check --- test/Serilog.Tests/MethodOverloadConventionTests.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/Serilog.Tests/MethodOverloadConventionTests.cs b/test/Serilog.Tests/MethodOverloadConventionTests.cs index 71fc84808..164a87241 100644 --- a/test/Serilog.Tests/MethodOverloadConventionTests.cs +++ b/test/Serilog.Tests/MethodOverloadConventionTests.cs @@ -181,6 +181,12 @@ public void ValidateBindMessageTemplateMethods(Type loggerType) Assert.Equal(method.ReturnType, typeof(bool)); Assert.True(method.IsPublic); + var messageTemplateAttr = method.GetCustomAttribute(); + + Assert.NotNull(messageTemplateAttr); + + Assert.Equal(messageTemplateAttr.MessageTemplateParameterName, MessageTemplate); + var parameters = method.GetParameters(); int index = 0; From 108d21c6f604101795fe3e9a43be6bf788262273 Mon Sep 17 00:00:00 2001 From: Joshua Clark Date: Fri, 25 Nov 2016 00:30:17 -0500 Subject: [PATCH 47/53] Added test for IsEnabled methods, added additional method invokes to eliminate some partials and misses --- .../MethodOverloadConventionTests.cs | 110 +++++++++++++++--- 1 file changed, 91 insertions(+), 19 deletions(-) diff --git a/test/Serilog.Tests/MethodOverloadConventionTests.cs b/test/Serilog.Tests/MethodOverloadConventionTests.cs index 164a87241..20ff57c9f 100644 --- a/test/Serilog.Tests/MethodOverloadConventionTests.cs +++ b/test/Serilog.Tests/MethodOverloadConventionTests.cs @@ -219,10 +219,17 @@ public void ValidateBindMessageTemplateMethods(Type loggerType) Assert.IsType(typeof(bool), result); + //silentlogger is always false if (loggerType == typeof(SilentLogger)) - Assert.False(result as bool?); - else - Assert.True(result as bool?); + return; + + Assert.True(result as bool?); + + //test null arg path + var falseResult = InvokeMethod(method, logger, new object[] { null, null, null, null }); + + Assert.IsType(typeof(bool), falseResult); + Assert.False(falseResult as bool?); } [Theory] @@ -267,10 +274,58 @@ public void ValidateBindPropertyMethods(Type loggerType) Assert.IsType(typeof(bool), result); + //silentlogger will always be false if (loggerType == typeof(SilentLogger)) - Assert.False(result as bool?); - else - Assert.True(result as bool?); + return; + + Assert.True(result as bool?); + + //test null arg path/ invalid property name + var falseResult = InvokeMethod(method, logger, new object[] { " ", null, false, null }); + + Assert.IsType(typeof(bool), falseResult); + Assert.False(falseResult as bool?); + } + + [Theory] + [InlineData(typeof(SilentLogger))] + [InlineData(typeof(Logger))] + [InlineData(typeof(Log))] + [InlineData(typeof(ILogger))] + public void ValidateIsEnabledMethods(Type loggerType) + { + var method = loggerType.GetMethod("IsEnabled"); + + Assert.True(method.IsPublic); + Assert.Equal(method.ReturnType, typeof(bool)); + + var parameters = method.GetParameters(); + + Assert.Single(parameters); + + var parameter = parameters.Single(); + + Assert.Equal(parameter.Name, "level"); + Assert.Equal(parameter.ParameterType, typeof(LogEventLevel)); + + CollectingSink sink; + + var logger = GetLogger(loggerType, out sink, LogEventLevel.Information); + + var falseResult = InvokeMethod(method, logger, new object[] { LogEventLevel.Verbose }); + + Assert.IsType(typeof(bool), falseResult); + Assert.False(falseResult as bool?); + + var trueResult = InvokeMethod(method, logger, new object[] { LogEventLevel.Warning }); + + Assert.IsType(typeof(bool), trueResult); + + //return as silentlogger will always be false + if (loggerType == typeof(SilentLogger)) + return; + + Assert.True(trueResult as bool?); } //public ILogger ForContext(ILogEventEnricher enricher) @@ -285,7 +340,6 @@ void ForContextMethod0(MethodInfo method) var parameter = parameters.Single(); Assert.Equal(parameter.Name, "enricher"); - Assert.Equal(parameter.ParameterType, typeof(ILogEventEnricher)); } catch (XunitException e) @@ -301,9 +355,7 @@ void ForContextMethod0(MethodInfo method) var enrichedLogger = InvokeMethod(method, logger, new object[] { logEnricher }); - Assert.NotNull(enrichedLogger); - - Assert.True(enrichedLogger is ILogger); + TestForContextResult(method, logger, normalResult: enrichedLogger); } //public ILogger ForContext(ILogEventEnricher[] enricher) @@ -336,9 +388,7 @@ void ForContextMethod1(MethodInfo method) var enrichedLogger = InvokeMethod(method, logger, new object[] { new ILogEventEnricher[] { logEnricher, logEnricher } }); - Assert.NotNull(enrichedLogger); - - Assert.True(enrichedLogger is ILogger); + TestForContextResult(method, logger, normalResult: enrichedLogger); } //public ILogger ForContext(string propertyName, object value, bool destructureObjects) @@ -439,9 +489,29 @@ void ForContextMethod4(MethodInfo method) var enrichedLogger = InvokeMethod(method, logger, new object[] { typeof(object) }); - Assert.NotNull(enrichedLogger); + TestForContextResult(method, logger, normalResult: enrichedLogger); + } - Assert.True(enrichedLogger is ILogger); + void TestForContextResult(MethodInfo method, ILogger logger, object normalResult) + { + Assert.NotNull(normalResult); + + Assert.True(normalResult is ILogger); + + if (method.DeclaringType == typeof(SilentLogger)) + return; + + Assert.NotSame(logger, normalResult); + + //if invoked with null args it should return the same instance + var sameLogger = InvokeMethod(method, logger, new object[] { null }); + + Assert.NotNull(sameLogger); + + if (method.DeclaringType == typeof(Log)) + Assert.Same(Log.Logger, sameLogger); + else + Assert.Same(logger, sameLogger); } void ValidateConventionForMethodSet( @@ -843,7 +913,7 @@ static ILogger GetLogger(Type loggerType) return GetLogger(loggerType, out sink); } - static ILogger GetLogger(Type loggerType, out CollectingSink sink) + static ILogger GetLogger(Type loggerType, out CollectingSink sink, LogEventLevel level = LogEventLevel.Verbose) { sink = null; @@ -852,7 +922,7 @@ static ILogger GetLogger(Type loggerType, out CollectingSink sink) sink = new CollectingSink(); return new LoggerConfiguration() - .MinimumLevel.Verbose() + .MinimumLevel.Is(level) .WriteTo.Sink(sink) .CreateLogger(); } @@ -863,14 +933,16 @@ static ILogger GetLogger(Type loggerType, out CollectingSink sink) Log.CloseAndFlush(); Log.Logger = new LoggerConfiguration() - .MinimumLevel.Verbose() + .MinimumLevel.Is(level) .WriteTo.Sink(sink) .CreateLogger(); return null; } - else + else if (loggerType == typeof(SilentLogger)) return new SilentLogger(); + else + throw new ArgumentException($"Logger Type of {loggerType} is not supported"); } } } \ No newline at end of file From 1e2ecf1adbc1eb698087ad2896547c2676288ca1 Mon Sep 17 00:00:00 2001 From: Joshua Clark Date: Sat, 26 Nov 2016 21:17:07 -0500 Subject: [PATCH 48/53] addressing some partials and misses along with some code clean up --- .../MethodOverloadConventionTests.cs | 50 ++++++++----------- 1 file changed, 22 insertions(+), 28 deletions(-) diff --git a/test/Serilog.Tests/MethodOverloadConventionTests.cs b/test/Serilog.Tests/MethodOverloadConventionTests.cs index 20ff57c9f..c634f46e0 100644 --- a/test/Serilog.Tests/MethodOverloadConventionTests.cs +++ b/test/Serilog.Tests/MethodOverloadConventionTests.cs @@ -95,7 +95,6 @@ public void ValidateWriteEventLogMethods(Type loggerType) var writeMethod = methods.Single(); Assert.True(writeMethod.IsPublic); - Assert.Equal(writeMethod.ReturnType, typeof(void)); LogEventLevel level = LogEventLevel.Information; @@ -110,7 +109,7 @@ public void ValidateWriteEventLogMethods(Type loggerType) if (loggerType == typeof(SilentLogger)) return; else - TestResults(level, sink); + EvaluateSingleResult(level, sink); } [Theory] @@ -130,7 +129,6 @@ public void ValidateForContextMethods(Type loggerType) foreach (var method in methodSet) { Assert.Equal(method.ReturnType, typeof(ILogger)); - Assert.True(method.IsPublic); var signatureMatchAndInvokeSuccess = false; @@ -184,7 +182,6 @@ public void ValidateBindMessageTemplateMethods(Type loggerType) var messageTemplateAttr = method.GetCustomAttribute(); Assert.NotNull(messageTemplateAttr); - Assert.Equal(messageTemplateAttr.MessageTemplateParameterName, MessageTemplate); var parameters = method.GetParameters(); @@ -428,8 +425,24 @@ void ForContextMethod2(MethodInfo method) var enrichedLogger = InvokeMethod(method, logger, new object[] { propertyName, propertyValue, false }); Assert.NotNull(enrichedLogger); - Assert.True(enrichedLogger is ILogger); + + //silentlogger will always return itself + if (method.DeclaringType == typeof(SilentLogger)) + return; + + Assert.NotSame(logger, enrichedLogger); + + //invalid args path + var sameLogger = InvokeMethod(method, logger, new object[] { null, null, false }); + + Assert.NotNull(sameLogger); + Assert.True(sameLogger is ILogger); + + if (method.DeclaringType == typeof(Log)) + Assert.Same(Log.Logger, sameLogger); + else + Assert.Same(logger, sameLogger); } //public ILogger ForContext() @@ -459,7 +472,6 @@ void ForContextMethod3(MethodInfo method) var enrichedLogger = InvokeMethod(method, logger, null, new Type[] { typeof(object) }); Assert.NotNull(enrichedLogger); - Assert.True(enrichedLogger is ILogger); } @@ -475,7 +487,6 @@ void ForContextMethod4(MethodInfo method) var arg = args.Single(); Assert.Equal(arg.Name, "source"); - Assert.Equal(arg.ParameterType, typeof(Type)); } catch (XunitException e) @@ -495,7 +506,6 @@ void ForContextMethod4(MethodInfo method) void TestForContextResult(MethodInfo method, ILogger logger, object normalResult) { Assert.NotNull(normalResult); - Assert.True(normalResult is ILogger); if (method.DeclaringType == typeof(SilentLogger)) @@ -538,7 +548,6 @@ void ValidateConventionForMethodSet( foreach (var method in methodSet) { Assert.Equal(method.ReturnType, typeof(void)); - Assert.True(method.IsPublic); if (checkMesgTempAttr) @@ -546,7 +555,6 @@ void ValidateConventionForMethodSet( var messageTemplateAttr = method.GetCustomAttribute(); Assert.NotNull(messageTemplateAttr); - Assert.Equal(messageTemplateAttr.MessageTemplateParameterName, MessageTemplate); } @@ -739,7 +747,7 @@ static void InvokeConventionMethod( if (method.Name == Write) { - level = LogEventLevel.Verbose; + level = LogEventLevel.Information; var paramList = new List() { level }; @@ -756,7 +764,6 @@ static void InvokeConventionMethod( static void InvokeConventionMethod(MethodInfo method, Type[] typeArgs, object[] parameters) { CollectingSink sink; - LogEventLevel level; InvokeConventionMethod(method, typeArgs, parameters, out level, out sink); @@ -765,12 +772,11 @@ static void InvokeConventionMethod(MethodInfo method, Type[] typeArgs, object[] static void InvokeConventionMethodAndTest(MethodInfo method, Type[] typeArgs, object[] parameters) { CollectingSink sink; - LogEventLevel level; InvokeConventionMethod(method, typeArgs, parameters, out level, out sink); - TestResults(level, sink); + EvaluateSingleResult(level, sink); } // parameters will always be ordered so single evaluation method will work @@ -788,9 +794,7 @@ static void VerifyMethodSignature(MethodInfo method, bool hasExceptionArg = fals expectedArgCount++; Assert.Equal(parameters[index].ParameterType, typeof(LogEventLevel)); - Assert.Equal(parameters[index].Name, "level"); - index++; } @@ -800,17 +804,13 @@ static void VerifyMethodSignature(MethodInfo method, bool hasExceptionArg = fals if (hasExceptionArg) //verify exception argument type and name { Assert.Equal(parameters[index].ParameterType, typeof(Exception)); - Assert.Equal(parameters[index].Name, "exception"); - index++; } //check for message template string argument Assert.Equal(parameters[index].ParameterType, typeof(string)); - Assert.Equal(parameters[index].Name, MessageTemplate); - index++; if (isGeneric) //validate type arguments, generic parameters, and cross-reference @@ -829,9 +829,7 @@ static void VerifyMethodSignature(MethodInfo method, bool hasExceptionArg = fals var genericConstraints = genericTypeArgs[i].GetTypeInfo().GetGenericParameterConstraints(); Assert.Empty(genericConstraints); - Assert.Equal(parameters[index].Name, $"propertyValue{i}"); - Assert.Equal(parameters[index].ParameterType, genericTypeArgs[i]); } } @@ -844,11 +842,8 @@ static void VerifyMethodSignature(MethodInfo method, bool hasExceptionArg = fals var genericConstraints = genericTypeArg.GetTypeInfo().GetGenericParameterConstraints(); Assert.Empty(genericConstraints); - Assert.Equal(parameters[index].Name, "propertyValue"); - Assert.Equal(genericTypeArg, parameters[index].ParameterType); - index++; } } @@ -863,9 +858,7 @@ static void VerifyMethodSignature(MethodInfo method, bool hasExceptionArg = fals var paramsAttr = parameters[index].GetCustomAttribute(typeof(ParamArrayAttribute), inherit: false); Assert.NotNull(paramsAttr); - Assert.Equal(paramsArrayArg.ParameterType, typeof(object[])); - Assert.Equal(paramsArrayArg.Name, "propertyValues"); } } @@ -897,8 +890,9 @@ static object InvokeMethod( return method.Invoke(instance, parameters); } - static void TestResults(LogEventLevel level, CollectingSink results) + static void EvaluateSingleResult(LogEventLevel level, CollectingSink results) { + //evaluate single log event Assert.Equal(1, results.Events.Count); var evt = results.Events.Single(); From 8070720866a87d1f3b4b89fa73fa27468af2cbde Mon Sep 17 00:00:00 2001 From: Sergey Komisarchik Date: Tue, 29 Nov 2016 03:50:45 +0300 Subject: [PATCH 49/53] Include AssemblyInformationalVersion attribute to the with commit hash --- Build.ps1 | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Build.ps1 b/Build.ps1 index 7c5a85f6a..fc5d8a20e 100644 --- a/Build.ps1 +++ b/Build.ps1 @@ -12,6 +12,8 @@ if(Test-Path .\artifacts) { $branch = @{ $true = $env:APPVEYOR_REPO_BRANCH; $false = $(git symbolic-ref --short -q HEAD) }[$env:APPVEYOR_REPO_BRANCH -ne $NULL]; $revision = @{ $true = "{0:00000}" -f [convert]::ToInt32("0" + $env:APPVEYOR_BUILD_NUMBER, 10); $false = "local" }[$env:APPVEYOR_BUILD_NUMBER -ne $NULL]; $suffix = @{ $true = ""; $false = "$($branch.Substring(0, [math]::Min(10,$branch.Length)))-$revision"}[$branch -eq "master" -and $revision -ne "local"] +$commitHash = $(git rev-parse --short HEAD) +$buildSuffix = @{ $true = "$($suffix)-$($commitHash)"; $false = "$($branch)-$($commitHash)" }[$suffix -ne ""] echo "build: Version suffix is $suffix" @@ -20,7 +22,8 @@ foreach ($src in ls src/*) { echo "build: Packaging project in $src" - & dotnet pack -c Release -o ..\..\artifacts --version-suffix=$suffix + & dotnet build -c Release --version-suffix=$buildSuffix + & dotnet pack -c Release -o ..\..\artifacts --version-suffix=$suffix --no-build if($LASTEXITCODE -ne 0) { exit 1 } Pop-Location From 06f2f95ae756219a998649d4ad4194ac5f5bd319 Mon Sep 17 00:00:00 2001 From: Sergey Komisarchik Date: Tue, 29 Nov 2016 07:49:08 +0300 Subject: [PATCH 50/53] log $buildSuffix --- Build.ps1 | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Build.ps1 b/Build.ps1 index fc5d8a20e..55db40f54 100644 --- a/Build.ps1 +++ b/Build.ps1 @@ -15,7 +15,8 @@ $suffix = @{ $true = ""; $false = "$($branch.Substring(0, [math]::Min(10,$branch $commitHash = $(git rev-parse --short HEAD) $buildSuffix = @{ $true = "$($suffix)-$($commitHash)"; $false = "$($branch)-$($commitHash)" }[$suffix -ne ""] -echo "build: Version suffix is $suffix" +echo "build: Package version suffix is $suffix" +echo "build: Build version suffix is $buildSuffix" foreach ($src in ls src/*) { Push-Location $src From 79cb2b1877114f670816a1879f0b3fe0f9a25460 Mon Sep 17 00:00:00 2001 From: Nicholas Blumhardt Date: Tue, 20 Dec 2016 14:42:41 +1000 Subject: [PATCH 51/53] Stack Overflow is two words [Skip CI] --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 476abb42c..8db74f24a 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -#Serilog [![Build status](https://ci.appveyor.com/api/projects/status/b9rm3l7kduryjgcj/branch/master?svg=true)](https://ci.appveyor.com/project/serilog/serilog/branch/master) [![NuGet Version](http://img.shields.io/nuget/v/Serilog.svg?style=flat)](https://www.nuget.org/packages/Serilog/) [![Rager Releases](http://rager.io/badge.svg?url=https%3A%2F%2Fwww.nuget.org%2Fpackages%2FSerilog%2F)](http://rager.io/projects/search?badge=1&query=nuget.org/packages/Serilog/) [![Join the chat at https://gitter.im/serilog/serilog](https://img.shields.io/gitter/room/serilog/serilog.svg)](https://gitter.im/serilog/serilog) [![Stack Overflow](https://img.shields.io/badge/stackoverflow-serilog-orange.svg)](http://stackoverflow.com/questions/tagged/serilog) +#Serilog [![Build status](https://ci.appveyor.com/api/projects/status/b9rm3l7kduryjgcj/branch/master?svg=true)](https://ci.appveyor.com/project/serilog/serilog/branch/master) [![NuGet Version](http://img.shields.io/nuget/v/Serilog.svg?style=flat)](https://www.nuget.org/packages/Serilog/) [![Rager Releases](http://rager.io/badge.svg?url=https%3A%2F%2Fwww.nuget.org%2Fpackages%2FSerilog%2F)](http://rager.io/projects/search?badge=1&query=nuget.org/packages/Serilog/) [![Join the chat at https://gitter.im/serilog/serilog](https://img.shields.io/gitter/room/serilog/serilog.svg)](https://gitter.im/serilog/serilog) [![Stack Overflow](https://img.shields.io/badge/stack%20overflow-serilog-orange.svg)](http://stackoverflow.com/questions/tagged/serilog) Serilog is a diagnostic logging library for .NET applications. It is easy to set up, has a clean API, and runs on all recent .NET platforms. While it's useful even in the simplest applications, Serilog's support for structured logging shines when instrumenting complex, distributed, and asynchronous applications and systems. From c06519962178e6ee953d003fb10e9dc10e235f35 Mon Sep 17 00:00:00 2001 From: Nicholas Blumhardt Date: Wed, 4 Jan 2017 16:16:47 +1000 Subject: [PATCH 52/53] Refactor key-value settings to consistently apply audit-to, write-to, filter and enrich configuration methods --- .../CallableConfigurationMethodFinder.cs | 52 +++++ .../KeyValuePairs/KeyValuePairSettings.cs | 196 +++++------------- .../KeyValuePairs/SettingValueConversions.cs | 76 +++++++ .../CallableConfigurationMethodFinderTests.cs | 30 +++ .../Settings/KeyValuePairSettingsTests.cs | 69 +----- .../Settings/SettingValueConversionsTests.cs | 53 +++++ 6 files changed, 263 insertions(+), 213 deletions(-) create mode 100644 src/Serilog/Settings/KeyValuePairs/CallableConfigurationMethodFinder.cs create mode 100644 src/Serilog/Settings/KeyValuePairs/SettingValueConversions.cs create mode 100644 test/Serilog.Tests/Settings/CallableConfigurationMethodFinderTests.cs create mode 100644 test/Serilog.Tests/Settings/SettingValueConversionsTests.cs diff --git a/src/Serilog/Settings/KeyValuePairs/CallableConfigurationMethodFinder.cs b/src/Serilog/Settings/KeyValuePairs/CallableConfigurationMethodFinder.cs new file mode 100644 index 000000000..7660b33bd --- /dev/null +++ b/src/Serilog/Settings/KeyValuePairs/CallableConfigurationMethodFinder.cs @@ -0,0 +1,52 @@ +// Copyright 2013-2015 Serilog Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using Serilog.Configuration; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Runtime.CompilerServices; + +namespace Serilog.Settings.KeyValuePairs +{ + static class CallableConfigurationMethodFinder + { + internal static LoggerConfiguration FromLogContext(LoggerEnrichmentConfiguration loggerEnrichmentConfiguration) + { + return loggerEnrichmentConfiguration.FromLogContext(); + } + + static readonly MethodInfo SurrogateFromLogContextConfigurationMethod = typeof(CallableConfigurationMethodFinder).GetTypeInfo().DeclaredMethods.Single(m => m.Name == nameof(FromLogContext)); + + internal static IList FindConfigurationMethods(IEnumerable configurationAssemblies, Type configType) + { + var methods = configurationAssemblies + .SelectMany(a => a.ExportedTypes + .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 == configType) + .ToList(); + + // Unlike the other configuration methods, FromLogContext is an instance method rather than an extension. This + // hack ensures we find it. + if (configType == typeof(LoggerEnrichmentConfiguration)) + methods.Add(SurrogateFromLogContextConfigurationMethod); + + return methods; + } + } +} diff --git a/src/Serilog/Settings/KeyValuePairs/KeyValuePairSettings.cs b/src/Serilog/Settings/KeyValuePairs/KeyValuePairSettings.cs index 49e113895..e53ad2a33 100644 --- a/src/Serilog/Settings/KeyValuePairs/KeyValuePairSettings.cs +++ b/src/Serilog/Settings/KeyValuePairs/KeyValuePairSettings.cs @@ -16,7 +16,6 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; -using System.Runtime.CompilerServices; using System.Text.RegularExpressions; using Serilog.Configuration; using Serilog.Events; @@ -31,22 +30,39 @@ class KeyValuePairSettings : ILoggerSettings const string MinimumLevelDirective = "minimum-level"; const string EnrichWithDirective = "enrich"; const string EnrichWithPropertyDirective = "enrich:with-property"; + const string FilterDirective = "filter"; const string UsingDirectiveFullFormPrefix = "using:"; - const string EnrichWithEventEnricherPrefix = "enrich:"; const string EnrichWithPropertyDirectivePrefix = "enrich:with-property:"; const string MinimumLevelOverrideDirectivePrefix = "minimum-level:override:"; - const string AuditOrWriteToDirectiveRegex = @"^(?audit-to|write-to):(?[A-Za-z0-9]*)(\.(?[A-Za-z0-9]*)){0,1}$"; + const string CallableDirectiveRegex = @"^(?audit-to|write-to|enrich|filter):(?[A-Za-z0-9]*)(\.(?[A-Za-z0-9]*)){0,1}$"; - readonly string[] _supportedDirectives = + static readonly string[] _supportedDirectives = { UsingDirective, AuditToDirective, WriteToDirective, MinimumLevelDirective, EnrichWithPropertyDirective, - EnrichWithDirective + EnrichWithDirective, + FilterDirective + }; + + static readonly Dictionary CallableDirectiveReceiverTypes = new Dictionary + { + ["audit-to"] = typeof(LoggerAuditSinkConfiguration), + ["write-to"] = typeof(LoggerSinkConfiguration), + ["enrich"] = typeof(LoggerEnrichmentConfiguration), + ["filter"] = typeof(LoggerFilterConfiguration) + }; + + static readonly Dictionary> CallableDirectiveReceivers = new Dictionary> + { + [typeof(LoggerAuditSinkConfiguration)] = lc => lc.AuditTo, + [typeof(LoggerSinkConfiguration)] = lc => lc.WriteTo, + [typeof(LoggerEnrichmentConfiguration)] = lc => lc.Enrich, + [typeof(LoggerFilterConfiguration)] = lc => lc.Filter }; readonly Dictionary _settings; @@ -90,65 +106,41 @@ public void Configure(LoggerConfiguration loggerConfiguration) } } - var splitWriteTo = new Regex(AuditOrWriteToDirectiveRegex); + var matchCallables = new Regex(CallableDirectiveRegex); - var sinkDirectives = (from wt in directives - where splitWriteTo.IsMatch(wt.Key) - let match = splitWriteTo.Match(wt.Key) - select new + var callableDirectives = (from wt in directives + where matchCallables.IsMatch(wt.Key) + let match = matchCallables.Match(wt.Key) + select new { - Directive = match.Groups["directive"].Value, - Call = new MethodArgumentValue + ReceiverType = CallableDirectiveReceiverTypes[match.Groups["directive"].Value], + Call = new ConfigurationMethodCall { - Method = match.Groups["method"].Value, - Argument = match.Groups["argument"].Value, + MethodName = match.Groups["method"].Value, + ArgumentName = match.Groups["argument"].Value, Value = wt.Value } - }).ToList(); - - 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(); + }).ToList(); - if (sinkDirectives.Any() || eventEnricherDirectives.Any()) + if (callableDirectives.Any()) { var configurationAssemblies = LoadConfigurationAssemblies(directives); - var writeToDirectives = sinkDirectives - .Where(d => d.Directive == WriteToDirective) - .Select(d => d.Call) - .GroupBy(call => call.Method) - .ToList(); - - if (writeToDirectives.Any()) + foreach (var receiverGroup in callableDirectives.GroupBy(d => d.ReceiverType)) { - ApplyDirectives(writeToDirectives, FindWriteToSinkConfigurationMethods(configurationAssemblies), loggerConfiguration.WriteTo); - } - - var auditToDirectives = sinkDirectives - .Where(d => d.Directive == AuditToDirective) - .Select(d => d.Call) - .GroupBy(call => call.Method) - .ToList(); + var methods = CallableConfigurationMethodFinder.FindConfigurationMethods(configurationAssemblies, receiverGroup.Key); - if (auditToDirectives.Any()) - { - ApplyDirectives(auditToDirectives, FindAuditToSinkConfigurationMethods(configurationAssemblies), loggerConfiguration.AuditTo); - } + var calls = receiverGroup + .Select(d => d.Call) + .GroupBy(call => call.MethodName) + .ToList(); - if (eventEnricherDirectives.Any()) - { - ApplyDirectives(eventEnricherDirectives, FindEventEnricherConfigurationMethods(configurationAssemblies), loggerConfiguration.Enrich); + ApplyDirectives(calls, methods, CallableDirectiveReceivers[receiverGroup.Key](loggerConfiguration)); } } } - static void ApplyDirectives(List> directives, IList configurationMethods, object loggerConfigMethod) + static void ApplyDirectives(List> directives, IList configurationMethods, object loggerConfigMethod) { foreach (var directiveInfo in directives) { @@ -158,8 +150,8 @@ static void ApplyDirectives(List> directi { 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(); + let directive = directiveInfo.FirstOrDefault(s => s.ArgumentName == p.Name) + select directive == null ? p.DefaultValue : SettingValueConversions.ConvertToType(directive.Value, p.ParameterType)).ToList(); call.Insert(0, loggerConfigMethod); @@ -168,12 +160,12 @@ static void ApplyDirectives(List> directi } } - internal static MethodInfo SelectConfigurationMethod(IEnumerable candidateMethods, string name, IEnumerable suppliedArgumentValues) + internal static MethodInfo SelectConfigurationMethod(IEnumerable candidateMethods, string name, IEnumerable suppliedArgumentValues) { return candidateMethods .Where(m => m.Name == name && - m.GetParameters().Skip(1).All(p => p.HasDefaultValue || suppliedArgumentValues.Any(s => s.Argument == p.Name))) - .OrderByDescending(m => m.GetParameters().Count(p => suppliedArgumentValues.Any(s => s.Argument == p.Name))) + m.GetParameters().Skip(1).All(p => p.HasDefaultValue || suppliedArgumentValues.Any(s => s.ArgumentName == p.Name))) + .OrderByDescending(m => m.GetParameters().Count(p => suppliedArgumentValues.Any(s => s.ArgumentName == p.Name))) .FirstOrDefault(); } @@ -193,102 +185,10 @@ internal static IEnumerable LoadConfigurationAssemblies(Dictionary> ExtendedTypeConversions = new Dictionary> - { - { typeof(Uri), s => new Uri(s) }, - { typeof(TimeSpan), s => TimeSpan.Parse(s) } - }; - - internal static object ConvertToType(string value, Type toType) - { - var toTypeInfo = toType.GetTypeInfo(); - if (toTypeInfo.IsGenericType && toType.GetGenericTypeDefinition() == typeof(Nullable<>)) - { - if (string.IsNullOrEmpty(value)) - return null; - - // unwrap Nullable<> type since we're not handling null situations - toType = toTypeInfo.GenericTypeArguments[0]; - toTypeInfo = toType.GetTypeInfo(); - } - - if (toTypeInfo.IsEnum) - return Enum.Parse(toType, value); - - var convertor = ExtendedTypeConversions - .Where(t => t.Key.GetTypeInfo().IsAssignableFrom(toTypeInfo)) - .Select(t => t.Value) - .FirstOrDefault(); - - if (convertor != null) - return convertor(value); - - if (toTypeInfo.IsInterface && !string.IsNullOrWhiteSpace(value)) - { - var type = Type.GetType(value.Trim(), throwOnError: false); - if (type != null) - { - var ctor = type.GetTypeInfo().DeclaredConstructors.FirstOrDefault(ci => - { - var parameters = ci.GetParameters(); - return parameters.Length == 0 || parameters.All(pi => pi.HasDefaultValue); - }); - - if (ctor == null) - throw new InvalidOperationException($"A default constructor was not found on {type.FullName}."); - - var call = ctor.GetParameters().Select(pi => pi.DefaultValue).ToArray(); - return ctor.Invoke(call); - } - } - - return Convert.ChangeType(value, toType); - } - - internal static IList FindWriteToSinkConfigurationMethods(IEnumerable configurationAssemblies) - { - return FindConfigurationMethods(configurationAssemblies, typeof(LoggerSinkConfiguration)); - } - - internal static IList FindAuditToSinkConfigurationMethods(IEnumerable configurationAssemblies) - { - return FindConfigurationMethods(configurationAssemblies, typeof(LoggerAuditSinkConfiguration)); - } - - // Unlike the other configuration methods, FromLogContext is an instance method rather than an extension. - - internal static LoggerConfiguration FromLogContext(LoggerEnrichmentConfiguration loggerEnrichmentConfiguration) - { - return loggerEnrichmentConfiguration.FromLogContext(); - } - - static readonly MethodInfo SurrogateFromLogContextConfigurationMethod = typeof(KeyValuePairSettings).GetTypeInfo().DeclaredMethods.Single(m => m.Name == "FromLogContext"); - - internal static IList FindEventEnricherConfigurationMethods(IEnumerable configurationAssemblies) - { - var found = FindConfigurationMethods(configurationAssemblies, typeof(LoggerEnrichmentConfiguration)); - if (configurationAssemblies.Contains(typeof(LoggerEnrichmentConfiguration).GetTypeInfo().Assembly)) - found.Add(SurrogateFromLogContextConfigurationMethod); - - return found; - } - - internal static IList FindConfigurationMethods(IEnumerable configurationAssemblies, Type configType) - { - return configurationAssemblies - .SelectMany(a => a.ExportedTypes - .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 == configType) - .ToList(); - } - - internal class MethodArgumentValue + internal class ConfigurationMethodCall { - public string Method { get; set; } - public string Argument { get; set; } + public string MethodName { get; set; } + public string ArgumentName { get; set; } public string Value { get; set; } } } diff --git a/src/Serilog/Settings/KeyValuePairs/SettingValueConversions.cs b/src/Serilog/Settings/KeyValuePairs/SettingValueConversions.cs new file mode 100644 index 000000000..519fea315 --- /dev/null +++ b/src/Serilog/Settings/KeyValuePairs/SettingValueConversions.cs @@ -0,0 +1,76 @@ +// Copyright 2013-2015 Serilog Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +namespace Serilog.Settings.KeyValuePairs +{ + class SettingValueConversions + { + static Dictionary> ExtendedTypeConversions = new Dictionary> + { + { typeof(Uri), s => new Uri(s) }, + { typeof(TimeSpan), s => TimeSpan.Parse(s) } + }; + + public static object ConvertToType(string value, Type toType) + { + var toTypeInfo = toType.GetTypeInfo(); + if (toTypeInfo.IsGenericType && toType.GetGenericTypeDefinition() == typeof(Nullable<>)) + { + if (string.IsNullOrEmpty(value)) + return null; + + // unwrap Nullable<> type since we're not handling null situations + toType = toTypeInfo.GenericTypeArguments[0]; + toTypeInfo = toType.GetTypeInfo(); + } + + if (toTypeInfo.IsEnum) + return Enum.Parse(toType, value); + + var convertor = ExtendedTypeConversions + .Where(t => t.Key.GetTypeInfo().IsAssignableFrom(toTypeInfo)) + .Select(t => t.Value) + .FirstOrDefault(); + + if (convertor != null) + return convertor(value); + + if (toTypeInfo.IsInterface && !string.IsNullOrWhiteSpace(value)) + { + var type = Type.GetType(value.Trim(), throwOnError: false); + if (type != null) + { + var ctor = type.GetTypeInfo().DeclaredConstructors.FirstOrDefault(ci => + { + var parameters = ci.GetParameters(); + return parameters.Length == 0 || parameters.All(pi => pi.HasDefaultValue); + }); + + if (ctor == null) + throw new InvalidOperationException($"A default constructor was not found on {type.FullName}."); + + var call = ctor.GetParameters().Select(pi => pi.DefaultValue).ToArray(); + return ctor.Invoke(call); + } + } + + return Convert.ChangeType(value, toType); + } + } +} diff --git a/test/Serilog.Tests/Settings/CallableConfigurationMethodFinderTests.cs b/test/Serilog.Tests/Settings/CallableConfigurationMethodFinderTests.cs new file mode 100644 index 000000000..7febd4e58 --- /dev/null +++ b/test/Serilog.Tests/Settings/CallableConfigurationMethodFinderTests.cs @@ -0,0 +1,30 @@ +using Serilog.Configuration; +using Serilog.Settings.KeyValuePairs; +using System.Linq; +using System.Reflection; +using TestDummies; +using Xunit; + +namespace Serilog.Tests.Settings +{ + public class CallableConfigurationMethodFinderTests + { + [Fact] + public void FindsEnricherSpecificConfigurationMethods() + { + var eventEnrichers = CallableConfigurationMethodFinder + .FindConfigurationMethods(new[] + { + typeof(Log).GetTypeInfo().Assembly, + typeof(DummyThreadIdEnricher).GetTypeInfo().Assembly + }, typeof(LoggerEnrichmentConfiguration)) + .Select(m => m.Name) + .Distinct() + .ToList(); + + + Assert.True(eventEnrichers.Contains("FromLogContext")); + Assert.True(eventEnrichers.Contains("WithDummyThreadId")); + } + } +} diff --git a/test/Serilog.Tests/Settings/KeyValuePairSettingsTests.cs b/test/Serilog.Tests/Settings/KeyValuePairSettingsTests.cs index b19af9824..7b57984e8 100644 --- a/test/Serilog.Tests/Settings/KeyValuePairSettingsTests.cs +++ b/test/Serilog.Tests/Settings/KeyValuePairSettingsTests.cs @@ -17,41 +17,6 @@ namespace Serilog.Tests.Settings { public class KeyValuePairSettingsTests { - [Fact] - public void ConvertibleValuesConvertToTIfTargetIsNullable() - { - var result = (int?)KeyValuePairSettings.ConvertToType("3", typeof(int?)); - Assert.True(result == 3); - } - - [Fact] - public void NullValuesConvertToNullIfTargetIsNullable() - { - var result = (int?)KeyValuePairSettings.ConvertToType(null, typeof(int?)); - Assert.True(result == null); - } - - [Fact] - public void EmptyStringValuesConvertToNullIfTargetIsNullable() - { - var result = (int?)KeyValuePairSettings.ConvertToType("", typeof(int?)); - Assert.True(result == null); - } - - [Fact] - public void ValuesConvertToNullableTimeSpan() - { - var result = (System.TimeSpan?)KeyValuePairSettings.ConvertToType("00:01:00", typeof(System.TimeSpan?)); - Assert.Equal(System.TimeSpan.FromMinutes(1), result); - } - - [Fact] - public void ValuesConvertToEnumMembers() - { - var result = (LogEventLevel)KeyValuePairSettings.ConvertToType("Information", typeof(LogEventLevel)); - Assert.Equal(LogEventLevel.Information, result); - } - [Fact] public void FindsConfigurationAssemblies() { @@ -61,24 +26,6 @@ public void FindsConfigurationAssemblies() Assert.Equal(1, configurationAssemblies.Count); } - [Fact] - public void FindsEventEnrichersWithinAnAssembly() - { - var eventEnrichers = KeyValuePairSettings - .FindEventEnricherConfigurationMethods(new[] - { - typeof(Log).GetTypeInfo().Assembly, - typeof(DummyThreadIdEnricher).GetTypeInfo().Assembly - }) - .Select(m => m.Name) - .Distinct() - .ToList(); - - - Assert.True(eventEnrichers.Contains("FromLogContext")); - Assert.True(eventEnrichers.Contains("WithDummyThreadId")); - } - [Fact] public void PropertyEnrichmentIsApplied() { @@ -97,14 +44,6 @@ public void PropertyEnrichmentIsApplied() Assert.Equal("Test", evt.Properties["App"].LiteralValue()); } - - [Fact] - public void StringValuesConvertToDefaultInstancesIfTargetIsInterface() - { - var result = (object)KeyValuePairSettings.ConvertToType("Serilog.Formatting.Json.JsonFormatter", typeof(ITextFormatter)); - Assert.IsType(result); - } - [Fact] public void CallableMethodsAreSelected() { @@ -112,7 +51,7 @@ public void CallableMethodsAreSelected() Assert.Equal(2, options.Count(mi => mi.Name == "DummyRollingFile")); var suppliedArguments = new[] { - new KeyValuePairSettings.MethodArgumentValue { Method = "DummyRollingFile", Argument = "pathFormat", Value = "C:\\" }, + new KeyValuePairSettings.ConfigurationMethodCall { MethodName = "DummyRollingFile", ArgumentName = "pathFormat", Value = "C:\\" }, }; var selected = KeyValuePairSettings.SelectConfigurationMethod(options, "DummyRollingFile", suppliedArguments); @@ -126,8 +65,8 @@ public void MethodsAreSelectedBasedOnCountOfMatchedArguments() Assert.Equal(2, options.Count(mi => mi.Name == "DummyRollingFile")); var suppliedArguments = new[] { - new KeyValuePairSettings.MethodArgumentValue { Method = "DummyRollingFile", Argument = "pathFormat", Value = "C:\\" }, - new KeyValuePairSettings.MethodArgumentValue { Method = "DummyRollingFile", Argument = "formatter", Value = "SomeFormatter, SomeAssembly" } + new KeyValuePairSettings.ConfigurationMethodCall { MethodName = "DummyRollingFile", ArgumentName = "pathFormat", Value = "C:\\" }, + new KeyValuePairSettings.ConfigurationMethodCall { MethodName = "DummyRollingFile", ArgumentName = "formatter", Value = "SomeFormatter, SomeAssembly" } }; var selected = KeyValuePairSettings.SelectConfigurationMethod(options, "DummyRollingFile", suppliedArguments); @@ -214,4 +153,4 @@ public void TestMinimumLevelOverrides() { Assert.NotNull(evt); } } -} \ No newline at end of file +} diff --git a/test/Serilog.Tests/Settings/SettingValueConversionsTests.cs b/test/Serilog.Tests/Settings/SettingValueConversionsTests.cs new file mode 100644 index 000000000..0fca5ec32 --- /dev/null +++ b/test/Serilog.Tests/Settings/SettingValueConversionsTests.cs @@ -0,0 +1,53 @@ +using Serilog.Events; +using Serilog.Formatting; +using Serilog.Formatting.Json; +using Serilog.Settings.KeyValuePairs; +using Xunit; + +namespace Serilog.Tests.Settings +{ + public class SettingValueConversionsTests + { + [Fact] + public void ConvertibleValuesConvertToTIfTargetIsNullable() + { + var result = (int?)SettingValueConversions.ConvertToType("3", typeof(int?)); + Assert.True(result == 3); + } + + [Fact] + public void NullValuesConvertToNullIfTargetIsNullable() + { + var result = (int?)SettingValueConversions.ConvertToType(null, typeof(int?)); + Assert.True(result == null); + } + + [Fact] + public void EmptyStringValuesConvertToNullIfTargetIsNullable() + { + var result = (int?)SettingValueConversions.ConvertToType("", typeof(int?)); + Assert.True(result == null); + } + + [Fact] + public void ValuesConvertToNullableTimeSpan() + { + var result = (System.TimeSpan?)SettingValueConversions.ConvertToType("00:01:00", typeof(System.TimeSpan?)); + Assert.Equal(System.TimeSpan.FromMinutes(1), result); + } + + [Fact] + public void ValuesConvertToEnumMembers() + { + var result = (LogEventLevel)SettingValueConversions.ConvertToType("Information", typeof(LogEventLevel)); + Assert.Equal(LogEventLevel.Information, result); + } + + [Fact] + public void StringValuesConvertToDefaultInstancesIfTargetIsInterface() + { + var result = (object)SettingValueConversions.ConvertToType("Serilog.Formatting.Json.JsonFormatter", typeof(ITextFormatter)); + Assert.IsType(result); + } + } +} From c57477606c0c0292f51af20fb2783a4392d94b60 Mon Sep 17 00:00:00 2001 From: Matthew Erbs Date: Sat, 28 Jan 2017 13:49:04 +1000 Subject: [PATCH 53/53] Changed to use Travis OSX example from dotnet/docs. --- .travis.yml | 47 ++++++++--------------------------------------- 1 file changed, 8 insertions(+), 39 deletions(-) diff --git a/.travis.yml b/.travis.yml index abdf1132a..562d018d5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,45 +1,14 @@ language: csharp -#dotnet cli require Ubuntu 14.04 -sudo: required -dist: trusty - -#dotnet cli require OSX 10.10 -osx_image: xcode7.1 - -addons: - apt: - packages: - - gettext - - libcurl4-openssl-dev - - libicu-dev - - libssl-dev - - libunwind8 - - zlib1g - -os: - - linux - -env: - matrix: - - CLI_VERSION=1.0.0-preview2-003121 - - CLI_VERSION=Latest - matrix: - allow_failures: - - env: CLI_VERSION=Latest - -before_install: - - if test "$TRAVIS_OS_NAME" == "osx"; then brew update; brew install openssl; brew link --force openssl; fi - # Download script to install dotnet cli - - if test "$CLI_OBTAIN_URL" == ""; then export CLI_OBTAIN_URL="https://raw.githubusercontent.com/dotnet/cli/rel/1.0.0-preview2/scripts/obtain/dotnet-install.sh"; fi - - curl -L --create-dirs $CLI_OBTAIN_URL -o ./scripts/obtain/install.sh - - find ./scripts -name "*.sh" -exec chmod +x {} \; - - export DOTNET_INSTALL_DIR="$PWD/.dotnetcli" - # use bash to workaround bug https://github.com/dotnet/cli/issues/1725 - - sudo bash ./scripts/obtain/install.sh --channel "preview" --version "$CLI_VERSION" --install-dir "$DOTNET_INSTALL_DIR" --no-path - # add dotnet to PATH - - export PATH="$DOTNET_INSTALL_DIR:$PATH" + include: + - os: linux # Ubuntu 14.04 + dist: trusty + sudo: required + dotnet: 1.0.0-preview2-003121 + - os: osx # OSX 10.11 + osx_image: xcode7.2 + dotnet: 1.0.0-preview2-003121 script: - ./build.sh \ No newline at end of file