diff --git a/.gitignore b/.gitignore index bba82e6fb..c88ba5dc8 100644 --- a/.gitignore +++ b/.gitignore @@ -331,3 +331,4 @@ ASALocalRun/ # MFractors (Xamarin productivity tool) working folder .mfractor/ +*.orig diff --git a/BuildNativeUWP.ps1 b/BuildNativeUWP.ps1 index 3def3da7a..8e1993440 100644 --- a/BuildNativeUWP.ps1 +++ b/BuildNativeUWP.ps1 @@ -5,7 +5,7 @@ param( echo "build: running .NET Native toolchain for UWP test project" -$msbuild = $(cmd /c where /R 'C:\Program Files (x86)\Microsoft Visual Studio\2017' msbuild.exe | select -First 1) +$msbuild = $(cmd /c where /R 'C:\Program Files (x86)\Microsoft Visual Studio\2019' msbuild.exe | select -First 1) if ($customLogger) { & $msbuild $proj /logger:"$customLogger" /verbosity:minimal /t:Restore diff --git a/Serilog.sln b/Serilog.sln index 6475f21a2..67033eadc 100644 --- a/Serilog.sln +++ b/Serilog.sln @@ -8,8 +8,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "assets", "assets", "{E9D1B5 appveyor.yml = appveyor.yml Build.ps1 = Build.ps1 build.sh = build.sh + BuildNativeUWP.ps1 = BuildNativeUWP.ps1 CHANGES.md = CHANGES.md Directory.Build.props = Directory.Build.props + global.json = global.json NuGet.config = NuGet.config README.md = README.md run_perf_tests.sh = run_perf_tests.sh diff --git a/Serilog.sln.DotSettings b/Serilog.sln.DotSettings index 33cc3008c..fcce79117 100644 --- a/Serilog.sln.DotSettings +++ b/Serilog.sln.DotSettings @@ -543,6 +543,7 @@ II.2.12 <HandlesEvent /> True True True + True True True True @@ -556,4 +557,9 @@ II.2.12 <HandlesEvent /> <data /> - <data><IncludeFilters /><ExcludeFilters /></data> \ No newline at end of file + <data><IncludeFilters /><ExcludeFilters /></data> + True + True + True + True + True \ No newline at end of file diff --git a/Setup.ps1 b/Setup.ps1 new file mode 100644 index 000000000..83d7a6723 --- /dev/null +++ b/Setup.ps1 @@ -0,0 +1,10 @@ +$ErrorActionPreference = "Stop" + +$RequiredDotnetVersion = $(cat ./global.json | convertfrom-json).sdk.version + +mkdir "./build" +Invoke-WebRequest "https://dot.net/v1/dotnet-install.ps1" -OutFile "./build/installcli.ps1" +& ./build/installcli.ps1 -InstallDir "$pwd/.dotnetcli" -NoPath -Version $RequiredDotnetVersion +if ($LASTEXITCODE) { exit 1 } + +$env:Path = "$pwd/.dotnetcli;$env:Path" diff --git a/appveyor.yml b/appveyor.yml index 744f9b71c..a0b5ef1ab 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,10 +1,12 @@ version: '{build}' skip_tags: true image: - - Visual Studio 2017 - - Ubuntu + - Visual Studio 2019 + - Ubuntu1804 configuration: Release test: off +install: + - ps: ./Setup.ps1 build_script: - ps: ./Build.ps1 - ps: ./BuildNativeUWP.ps1 @@ -12,7 +14,9 @@ for: - matrix: only: - - image: Ubuntu + - image: Ubuntu1804 + install: + - sh setup.sh build_script: - sh build.sh artifacts: diff --git a/build.sh b/build.sh index 573c706eb..9dcb67856 100755 --- a/build.sh +++ b/build.sh @@ -1,6 +1,9 @@ #!/bin/bash set -e + +export PATH=$HOME/.dotnetcli:$PATH + dotnet --info dotnet --list-sdks dotnet restore @@ -9,13 +12,14 @@ echo "🤖 Attempting to build..." for path in src/**/*.csproj; do dotnet build -f netstandard1.0 -c Release ${path} dotnet build -f netstandard1.3 -c Release ${path} + dotnet build -f netstandard2.0 -c Release ${path} done echo "🤖 Running tests..." for path in test/*.Tests/*.csproj; do - dotnet test -f netcoreapp2.0 -c Release ${path} + dotnet test -f netcoreapp2.2 -c Release ${path} done for path in test/*.PerformanceTests/*.PerformanceTests.csproj; do - dotnet build -f netcoreapp2.0 -c Release ${path} + dotnet build -f netcoreapp2.2 -c Release ${path} done diff --git a/global.json b/global.json index 815be4bfb..3580810ef 100644 --- a/global.json +++ b/global.json @@ -1,5 +1,5 @@ { "sdk": { - "version": "2.1.4" + "version": "2.2.300" } } diff --git a/setup.sh b/setup.sh new file mode 100644 index 000000000..b2c156b18 --- /dev/null +++ b/setup.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +sudo apt-get update && sudo apt-get install -y --no-install-recommends jq + +RequiredDotnetVersion=$(jq -r '.sdk.version' global.json) + +curl https://dot.net/v1/dotnet-install.sh -sSf | bash -s -- --install-dir $HOME/.dotnetcli --no-path --version $RequiredDotnetVersion diff --git a/src/Serilog/Capturing/MessageTemplateProcessor.cs b/src/Serilog/Capturing/MessageTemplateProcessor.cs index 974f6fe8a..596f09995 100644 --- a/src/Serilog/Capturing/MessageTemplateProcessor.cs +++ b/src/Serilog/Capturing/MessageTemplateProcessor.cs @@ -12,7 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -using System.Collections.Generic; using Serilog.Core; using Serilog.Core.Pipeline; using Serilog.Events; @@ -32,7 +31,7 @@ public MessageTemplateProcessor(PropertyValueConverter propertyValueConverter) _propertyBinder = new PropertyBinder(_propertyValueConverter); } - public void Process(string messageTemplate, object[] messageTemplateParameters, out MessageTemplate parsedTemplate, out IEnumerable properties) + public void Process(string messageTemplate, object[] messageTemplateParameters, out MessageTemplate parsedTemplate, out EventProperty[] properties) { parsedTemplate = _parser.Parse(messageTemplate); properties = _propertyBinder.ConstructProperties(parsedTemplate, messageTemplateParameters); @@ -42,5 +41,10 @@ public LogEventProperty CreateProperty(string name, object value, bool destructu { return _propertyValueConverter.CreateProperty(name, value, destructureObjects); } + + public LogEventPropertyValue CreatePropertyValue(object value, bool destructureObjects = false) + { + return _propertyValueConverter.CreatePropertyValue(value, destructureObjects); + } } } diff --git a/src/Serilog/Capturing/PropertyBinder.cs b/src/Serilog/Capturing/PropertyBinder.cs index 03613e0f1..0b41ada1e 100644 --- a/src/Serilog/Capturing/PropertyBinder.cs +++ b/src/Serilog/Capturing/PropertyBinder.cs @@ -13,8 +13,6 @@ // limitations under the License. using System; -using System.Collections.Generic; -using System.Linq; using Serilog.Debugging; using Serilog.Events; using Serilog.Parsing; @@ -26,7 +24,7 @@ class PropertyBinder { readonly PropertyValueConverter _valueConverter; - static readonly LogEventProperty[] NoProperties = new LogEventProperty[0]; + static readonly EventProperty[] NoProperties = new EventProperty[0]; public PropertyBinder(PropertyValueConverter valueConverter) { @@ -41,7 +39,7 @@ public PropertyBinder(PropertyValueConverter valueConverter) /// represented in the message template. /// A list of properties; if the template is malformed then /// this will be empty. - public IEnumerable ConstructProperties(MessageTemplate messageTemplate, object[] messageTemplateParameters) + public EventProperty[] ConstructProperties(MessageTemplate messageTemplate, object[] messageTemplateParameters) { if (messageTemplateParameters == null || messageTemplateParameters.Length == 0) { @@ -57,14 +55,14 @@ public IEnumerable ConstructProperties(MessageTemplate message return ConstructNamedProperties(messageTemplate, messageTemplateParameters); } - IEnumerable ConstructPositionalProperties(MessageTemplate template, object[] messageTemplateParameters) + EventProperty[] ConstructPositionalProperties(MessageTemplate template, object[] messageTemplateParameters) { var positionalProperties = template.PositionalProperties; if (positionalProperties.Length != messageTemplateParameters.Length) SelfLog.WriteLine("Positional property count does not match parameter count: {0}", template); - var result = new LogEventProperty[messageTemplateParameters.Length]; + var result = new EventProperty[messageTemplateParameters.Length]; foreach (var property in positionalProperties) { if (property.TryGetPositionalValue(out int position)) @@ -79,7 +77,7 @@ IEnumerable ConstructPositionalProperties(MessageTemplate temp var next = 0; for (var i = 0; i < result.Length; ++i) { - if (result[i] != null) + if (!result[i].Equals(EventProperty.None)) { result[next] = result[i]; ++next; @@ -92,11 +90,11 @@ IEnumerable ConstructPositionalProperties(MessageTemplate temp return result; } - IEnumerable ConstructNamedProperties(MessageTemplate template, object[] messageTemplateParameters) + EventProperty[] ConstructNamedProperties(MessageTemplate template, object[] messageTemplateParameters) { var namedProperties = template.NamedProperties; if (namedProperties == null) - return Enumerable.Empty(); + return NoProperties; var matchedRun = namedProperties.Length; if (namedProperties.Length != messageTemplateParameters.Length) @@ -105,7 +103,7 @@ IEnumerable ConstructNamedProperties(MessageTemplate template, SelfLog.WriteLine("Named property count does not match parameter count: {0}", template); } - var result = new LogEventProperty[messageTemplateParameters.Length]; + var result = new EventProperty[messageTemplateParameters.Length]; for (var i = 0; i < matchedRun; ++i) { var property = template.NamedProperties[i]; @@ -116,14 +114,14 @@ IEnumerable ConstructNamedProperties(MessageTemplate template, for (var i = matchedRun; i < messageTemplateParameters.Length; ++i) { var value = _valueConverter.CreatePropertyValue(messageTemplateParameters[i]); - result[i] = new LogEventProperty("__" + i, value); + result[i] = new EventProperty("__" + i, value); } return result; } - LogEventProperty ConstructProperty(PropertyToken propertyToken, object value) + EventProperty ConstructProperty(PropertyToken propertyToken, object value) { - return new LogEventProperty( + return new EventProperty( propertyToken.PropertyName, _valueConverter.CreatePropertyValue(value, propertyToken.Destructuring)); } diff --git a/src/Serilog/Capturing/PropertyValueConverter.cs b/src/Serilog/Capturing/PropertyValueConverter.cs index 61606285e..0638afaf0 100644 --- a/src/Serilog/Capturing/PropertyValueConverter.cs +++ b/src/Serilog/Capturing/PropertyValueConverter.cs @@ -133,9 +133,6 @@ LogEventPropertyValue CreatePropertyValue(object value, Destructuring destructur return Stringify(value); } - var valueType = value.GetType(); - DepthLimiter.SetCurrentDepth(depth); - if (destructuring == Destructuring.Destructure) { if (value is string stringValue) @@ -144,12 +141,17 @@ LogEventPropertyValue CreatePropertyValue(object value, Destructuring destructur } } + if (value is string) + return new ScalarValue(value); + foreach (var scalarConversionPolicy in _scalarConversionPolicies) { if (scalarConversionPolicy.TryConvertToScalar(value, out var converted)) return converted; } + DepthLimiter.SetCurrentDepth(depth); + if (destructuring == Destructuring.Destructure) { foreach (var destructuringPolicy in _destructuringPolicies) @@ -159,6 +161,8 @@ LogEventPropertyValue CreatePropertyValue(object value, Destructuring destructur } } + var valueType = value.GetType(); + if (TryConvertEnumerable(value, destructuring, valueType, out var enumerableResult)) return enumerableResult; diff --git a/src/Serilog/Core/Enrichers/FixedPropertyEnricher.cs b/src/Serilog/Core/Enrichers/FixedPropertyEnricher.cs index 16df1c427..c9847f71c 100644 --- a/src/Serilog/Core/Enrichers/FixedPropertyEnricher.cs +++ b/src/Serilog/Core/Enrichers/FixedPropertyEnricher.cs @@ -19,17 +19,18 @@ namespace Serilog.Core.Enrichers { class FixedPropertyEnricher : ILogEventEnricher { - readonly LogEventProperty _logEventProperty; + readonly EventProperty _eventProperty; - public FixedPropertyEnricher(LogEventProperty logEventProperty) + public FixedPropertyEnricher(in EventProperty eventProperty) { - _logEventProperty = logEventProperty ?? throw new ArgumentNullException(nameof(logEventProperty)); + if (eventProperty.Equals(EventProperty.None)) throw new ArgumentNullException(nameof(eventProperty)); + _eventProperty = eventProperty; } public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory) { if (logEvent == null) throw new ArgumentNullException(nameof(logEvent)); - logEvent.AddPropertyIfAbsent(_logEventProperty); + logEvent.AddPropertyIfAbsent(_eventProperty); } } -} \ No newline at end of file +} diff --git a/src/Serilog/Core/Logger.cs b/src/Serilog/Core/Logger.cs index 1a1ae9dc1..d6e500e8c 100644 --- a/src/Serilog/Core/Logger.cs +++ b/src/Serilog/Core/Logger.cs @@ -14,6 +14,7 @@ using System; using System.Collections.Generic; +using System.Linq; using Serilog.Capturing; using Serilog.Core.Enrichers; using Serilog.Core.Pipeline; @@ -32,6 +33,7 @@ namespace Serilog.Core public sealed class Logger : ILogger, ILogEventSink, IDisposable { static readonly object[] NoPropertyValues = new object[0]; + static readonly LogEventProperty[] NoProperties = new LogEventProperty[0]; readonly MessageTemplateProcessor _messageTemplateProcessor; readonly ILogEventSink _sink; @@ -141,10 +143,9 @@ public ILogger ForContext(string propertyName, object value, bool destructureObj } // It'd be nice to do the destructuring lazily, but unfortunately `value` may be mutated between - // now and the first log event written... - // A future optimization opportunity may be to implement ILogEventEnricher on LogEventProperty to - // remove one more allocation. - var enricher = new FixedPropertyEnricher(_messageTemplateProcessor.CreateProperty(propertyName, value, destructureObjects)); + // now and the first log event written. + var propertyValue = _messageTemplateProcessor.CreatePropertyValue(value, destructureObjects); + var enricher = new FixedPropertyEnricher(new EventProperty(propertyName, propertyValue)); var minimumLevel = _minimumLevel; var levelSwitch = _levelSwitch; @@ -367,7 +368,7 @@ public void Write(LogEventLevel level, Exception exception, string messageTempla propertyValues.GetType() != typeof(object[])) propertyValues = new object[] { propertyValues }; - _messageTemplateProcessor.Process(messageTemplate, propertyValues, out MessageTemplate parsedTemplate, out IEnumerable boundProperties); + _messageTemplateProcessor.Process(messageTemplate, propertyValues, out MessageTemplate parsedTemplate, out var boundProperties); var logEvent = new LogEvent(DateTimeOffset.Now, level, exception, parsedTemplate, boundProperties); Dispatch(logEvent); @@ -1333,7 +1334,11 @@ public bool BindMessageTemplate(string messageTemplate, object[] propertyValues, return false; } - _messageTemplateProcessor.Process(messageTemplate, propertyValues, out parsedTemplate, out boundProperties); + _messageTemplateProcessor.Process(messageTemplate, propertyValues, out parsedTemplate, out var boundEventProperties); + boundProperties = boundEventProperties.Length == 0 ? + NoProperties : + boundEventProperties.Select(p => new LogEventProperty(p)); + return true; } diff --git a/src/Serilog/Core/Sinks/SecondaryLoggerSink.cs b/src/Serilog/Core/Sinks/SecondaryLoggerSink.cs index 4e854c065..5285489c8 100644 --- a/src/Serilog/Core/Sinks/SecondaryLoggerSink.cs +++ b/src/Serilog/Core/Sinks/SecondaryLoggerSink.cs @@ -40,13 +40,7 @@ public void Emit(LogEvent logEvent) { if (logEvent == null) throw new ArgumentNullException(nameof(logEvent)); - var copy = new LogEvent( - logEvent.Timestamp, - logEvent.Level, - logEvent.Exception, - logEvent.MessageTemplate, - logEvent.Properties.Select(p => new LogEventProperty(p.Key, p.Value))); - + var copy = logEvent.Copy(); _logger.Write(copy); } diff --git a/src/Serilog/Events/EventProperty.cs b/src/Serilog/Events/EventProperty.cs new file mode 100644 index 000000000..ce16b5bfc --- /dev/null +++ b/src/Serilog/Events/EventProperty.cs @@ -0,0 +1,98 @@ +// Copyright 2019 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; + +namespace Serilog.Events +{ + /// + /// A property associated with a . + /// + /// This type is currently internal, while we consider future directions for the logging pipeline, but should end up public + /// in future. + readonly struct EventProperty + { + /// + /// No property. + /// + public static EventProperty None = default; + + /// + /// The name of the property. + /// + public string Name { get; } + + /// + /// The value of the property. + /// + public LogEventPropertyValue Value { get; } + + /// + /// Construct a with the specified name and value. + /// + /// The name of the property. + /// The value of the property. + /// + /// + public EventProperty(string name, LogEventPropertyValue value) + { + EnsureValid(name, value); + + Name = name; + Value = value; + } + + /// + /// Permit deconstruction of the property into a name/value pair. + /// + /// The name of the property. + /// The value of the property. + public void Deconstruct(out string name, out LogEventPropertyValue value) + { + name = Name; + value = Value; + } + + static void EnsureValid(string name, LogEventPropertyValue value) + { + if (value == null) throw new ArgumentNullException(nameof(value)); + if (!LogEventProperty.IsValidName(name)) + throw new ArgumentException("Property name is not valid."); + } + + /// + public override bool Equals(object obj) + { + return obj is EventProperty other && Equals(other); + } + + /// Indicates whether this instance and a specified are equal. + /// The to compare with the current instance. + /// + /// if and this instance represent the same value; otherwise, . + public bool Equals(EventProperty other) + { + return string.Equals(Name, other.Name) && Equals(Value, other.Value); + } + + /// + public override int GetHashCode() + { + unchecked + { + return ((Name != null ? Name.GetHashCode() : 0) * 397) ^ (Value != null ? Value.GetHashCode() : 0); + } + } + } +} diff --git a/src/Serilog/Events/LogEvent.cs b/src/Serilog/Events/LogEvent.cs index 1906960f7..45e0056bd 100644 --- a/src/Serilog/Events/LogEvent.cs +++ b/src/Serilog/Events/LogEvent.cs @@ -25,6 +25,15 @@ public class LogEvent { readonly Dictionary _properties; + LogEvent(DateTimeOffset timestamp, LogEventLevel level, Exception exception, MessageTemplate messageTemplate, Dictionary properties) + { + Timestamp = timestamp; + Level = level; + Exception = exception; + MessageTemplate = messageTemplate ?? throw new ArgumentNullException(nameof(messageTemplate)); + _properties = properties ?? throw new ArgumentNullException(nameof(properties)); + } + /// /// Construct a new . /// @@ -34,16 +43,26 @@ public class LogEvent /// The message template describing the event. /// Properties associated with the event, including those presented in . public LogEvent(DateTimeOffset timestamp, LogEventLevel level, Exception exception, MessageTemplate messageTemplate, IEnumerable properties) + : this(timestamp, level, exception, messageTemplate, new Dictionary()) { - if (messageTemplate == null) throw new ArgumentNullException(nameof(messageTemplate)); if (properties == null) throw new ArgumentNullException(nameof(properties)); - Timestamp = timestamp; - Level = level; - Exception = exception; - MessageTemplate = messageTemplate; - _properties = new Dictionary(); - foreach (var p in properties) - AddOrUpdateProperty(p); + foreach (var property in properties) + AddOrUpdateProperty(property); + } + + /// + /// Construct a new . + /// + /// The time at which the event occurred. + /// The level of the event. + /// An exception associated with the event, or null. + /// The message template describing the event. + /// Properties associated with the event, including those presented in . + internal LogEvent(DateTimeOffset timestamp, LogEventLevel level, Exception exception, MessageTemplate messageTemplate, EventProperty[] properties) + : this(timestamp, level, exception, messageTemplate, new Dictionary(properties?.Length ?? throw new ArgumentNullException(nameof(properties)))) + { + for (var i = 0; i < properties.Length; ++i) + _properties[properties[i].Name] = properties[i].Value; } /// @@ -103,6 +122,17 @@ public void AddOrUpdateProperty(LogEventProperty property) _properties[property.Name] = property.Value; } + /// + /// Add a property to the event if not already present, otherwise, update its value. + /// + /// The property to add or update. + /// + internal void AddOrUpdateProperty(in EventProperty property) + { + if (property.Equals(EventProperty.None)) throw new ArgumentNullException(nameof(property)); + _properties[property.Name] = property.Value; + } + /// /// Add a property to the event if not already present. /// @@ -115,6 +145,18 @@ public void AddPropertyIfAbsent(LogEventProperty property) _properties.Add(property.Name, property.Value); } + /// + /// Add a property to the event if not already present. + /// + /// The property to add. + /// + internal void AddPropertyIfAbsent(in EventProperty property) + { + if (property.Equals(EventProperty.None)) throw new ArgumentNullException(nameof(property)); + if (!_properties.ContainsKey(property.Name)) + _properties.Add(property.Name, property.Value); + } + /// /// Remove a property from the event, if present. Otherwise no action /// is performed. @@ -124,5 +166,19 @@ public void RemovePropertyIfPresent(string propertyName) { _properties.Remove(propertyName); } + + internal LogEvent Copy() + { + var properties = new Dictionary(Properties.Count); + foreach (var key in _properties.Keys) + properties.Add(key, _properties[key]); + + return new LogEvent( + Timestamp, + Level, + Exception, + MessageTemplate, + properties); + } } -} \ No newline at end of file +} diff --git a/src/Serilog/Events/LogEventProperty.cs b/src/Serilog/Events/LogEventProperty.cs index 8bea942fa..f5cbc9027 100644 --- a/src/Serilog/Events/LogEventProperty.cs +++ b/src/Serilog/Events/LogEventProperty.cs @@ -38,6 +38,19 @@ public LogEventProperty(string name, LogEventPropertyValue value) Value = value; } + /// + /// Construct a from an existing instance. + /// + /// The existing property. + /// + /// + internal LogEventProperty(EventProperty property) + { + if (property.Equals(EventProperty.None)) throw new ArgumentNullException(nameof(property)); + Name = property.Name; + Value = property.Value; + } + /// /// The name of the property. /// diff --git a/test/Serilog.PerformanceTests/BindingBenchmark.cs b/test/Serilog.PerformanceTests/BindingBenchmark.cs new file mode 100644 index 000000000..857665351 --- /dev/null +++ b/test/Serilog.PerformanceTests/BindingBenchmark.cs @@ -0,0 +1,75 @@ +// Copyright 2013-2016 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 BenchmarkDotNet.Attributes; +using System; +using System.Collections.Generic; +using System.Linq; +using Serilog.Events; +using Serilog.Parsing; +using Serilog.PerformanceTests.Support; + +namespace Serilog.PerformanceTests +{ + /// + /// Tests the cost of writing through the logging pipeline. + /// + [MemoryDiagnoser] + public class BindingBenchmark + { + const string + MT0 = "Zero", + MT1 = "Zero{A}one", + MT5 = "Zero{A}one{B}two{C}three{D}four{E}five"; + + ILogger _log; + object[] _zero, _one, _five; + + [GlobalSetup] + public void Setup() + { + _log = new LoggerConfiguration() + .WriteTo.Sink(new NullSink()) + .CreateLogger(); + + _zero = new object[0]; + _one = new object[] { 1 }; + _five = new object[] { 1, 2, 3, 4, 5 }; + } + + // The benchmarks run p.Count() to force enumeration; this will be representative of how the API + // is consumed (there's not much point benchmarking time to return a lazy enumerator). + + [Benchmark(Baseline = true)] + public (MessageTemplate, int) BindZero() + { + _log.BindMessageTemplate(MT0, _zero, out var mt, out var p); + return (mt, p.Count()); + } + + [Benchmark] + public (MessageTemplate, int) BindOne() + { + _log.BindMessageTemplate(MT1, _one, out var mt, out var p); + return (mt, p.Count()); + } + + [Benchmark] + public (MessageTemplate, int) BindFive() + { + _log.BindMessageTemplate(MT5, _five, out var mt, out var p); + return (mt, p.Count()); + } + } +} diff --git a/test/Serilog.PerformanceTests/Harness.cs b/test/Serilog.PerformanceTests/Harness.cs index 081987f65..14b3ba963 100644 --- a/test/Serilog.PerformanceTests/Harness.cs +++ b/test/Serilog.PerformanceTests/Harness.cs @@ -17,17 +17,22 @@ namespace Serilog.PerformanceTests { + /// + /// Wrappers that make it easy to run benchmark suites through the dotnet test runner. + /// + /// + /// dotnet test -c Release -f netcoreapp2.2 --filter "FullyQualifiedName=Serilog.PerformanceTests.Harness.Allocations" + /// public class Harness { - // dotnet test -c Release -f net46 --filter "FullyQualifiedName=Serilog.PerformanceTests.Harness.AllocationsBenchmark" [Fact] - public void AllocationsBenchmark() + public void Allocations() { BenchmarkRunner.Run(); } [Fact] - public void MessageTemplateCacheBenchmark() + public void MessageTemplateCache() { BenchmarkRunner.Run(); BenchmarkRunner.Run(); @@ -74,11 +79,17 @@ public void OutputTemplateRendering() { BenchmarkRunner.Run(); } - + [Fact] - public void MessageTemplateRenderingBenchmark() + public void MessageTemplateRendering() { BenchmarkRunner.Run(); } + + [Fact] + public void Binding() + { + BenchmarkRunner.Run(); + } } } diff --git a/test/Serilog.PerformanceTests/LevelControlBenchmark.cs b/test/Serilog.PerformanceTests/LevelControlBenchmark.cs index 4c6ae2849..1740c1dad 100644 --- a/test/Serilog.PerformanceTests/LevelControlBenchmark.cs +++ b/test/Serilog.PerformanceTests/LevelControlBenchmark.cs @@ -13,7 +13,7 @@ public class LevelControlBenchmark ILogger _off, _levelSwitchOff, _minLevel, _levelSwitch; readonly LogEvent _event = Some.InformationEvent(); - [Setup] + [GlobalSetup] public void Setup() { _off = new LoggerConfiguration() diff --git a/test/Serilog.PerformanceTests/LogContextEnrichmentBenchmark.cs b/test/Serilog.PerformanceTests/LogContextEnrichmentBenchmark.cs index df0a7f7e2..8f3d03f76 100644 --- a/test/Serilog.PerformanceTests/LogContextEnrichmentBenchmark.cs +++ b/test/Serilog.PerformanceTests/LogContextEnrichmentBenchmark.cs @@ -10,7 +10,7 @@ public class LogContextEnrichmentBenchmark ILogger _bare, _enriched; readonly LogEvent _event = Some.InformationEvent(); - [Setup] + [GlobalSetup] public void Setup() { _bare = new LoggerConfiguration() diff --git a/test/Serilog.PerformanceTests/MessageTemplateCacheBenchmark/MessageTemplateCacheBenchmark_Cached.cs b/test/Serilog.PerformanceTests/MessageTemplateCacheBenchmark/MessageTemplateCacheBenchmark_Cached.cs index 2382cec6c..b9300ae9f 100644 --- a/test/Serilog.PerformanceTests/MessageTemplateCacheBenchmark/MessageTemplateCacheBenchmark_Cached.cs +++ b/test/Serilog.PerformanceTests/MessageTemplateCacheBenchmark/MessageTemplateCacheBenchmark_Cached.cs @@ -25,7 +25,7 @@ public class MessageTemplateCacheBenchmark_Cached [Params(1, -1)] public int MaxDegreeOfParallelism { get; set; } - [Setup] + [GlobalSetup] public void Setup() { _templateList = Enumerable.Range(0, Items).Select(x => $"{DefaultOutputTemplate}_{Guid.NewGuid()}").ToList(); diff --git a/test/Serilog.PerformanceTests/MessageTemplateCacheBenchmark/MessageTemplateCacheBenchmark_Leaking.cs b/test/Serilog.PerformanceTests/MessageTemplateCacheBenchmark/MessageTemplateCacheBenchmark_Leaking.cs index 4acae343a..af8534649 100644 --- a/test/Serilog.PerformanceTests/MessageTemplateCacheBenchmark/MessageTemplateCacheBenchmark_Leaking.cs +++ b/test/Serilog.PerformanceTests/MessageTemplateCacheBenchmark/MessageTemplateCacheBenchmark_Leaking.cs @@ -25,7 +25,7 @@ public class MessageTemplateCacheBenchmark_Leaking [Params(1, -1)] public int MaxDegreeOfParallelism { get; set; } - [Setup] + [GlobalSetup] public void Setup() { _templateList = Enumerable.Range(0, Items).Select(x => $"{DefaultOutputTemplate}_{Guid.NewGuid()}").ToList(); diff --git a/test/Serilog.PerformanceTests/MessageTemplateParsingBenchmark.cs b/test/Serilog.PerformanceTests/MessageTemplateParsingBenchmark.cs index 02e5ab13e..18742cff8 100644 --- a/test/Serilog.PerformanceTests/MessageTemplateParsingBenchmark.cs +++ b/test/Serilog.PerformanceTests/MessageTemplateParsingBenchmark.cs @@ -11,7 +11,7 @@ public class MessageTemplateParsingBenchmark MessageTemplateParser _parser; const string _DefaultConsoleOutputTemplate = "{Timestamp:yyyy-MM-dd HH:mm:ss} [{Level}] {Message}{NewLine}{Exception}"; - [Setup] + [GlobalSetup] public void Setup() { _parser = new MessageTemplateParser(); diff --git a/test/Serilog.PerformanceTests/NestedLoggerCreationBenchmark.cs b/test/Serilog.PerformanceTests/NestedLoggerCreationBenchmark.cs index 1c170d366..1d71e386c 100644 --- a/test/Serilog.PerformanceTests/NestedLoggerCreationBenchmark.cs +++ b/test/Serilog.PerformanceTests/NestedLoggerCreationBenchmark.cs @@ -6,11 +6,12 @@ namespace Serilog.PerformanceTests /// /// Tests the cost creating a nested logger. /// + [MemoryDiagnoser] public class NestedLoggerCreationBenchmark { ILogger log; - [Setup] + [GlobalSetup] public void Setup() { log = new LoggerConfiguration() @@ -18,6 +19,12 @@ public void Setup() .CreateLogger(); } + [Benchmark] + public void ForContextInt() + { + log.ForContext("Number", 1); + } + [Benchmark] public void ForContextString() { @@ -30,4 +37,4 @@ public void ForContextType() log.ForContext(); } } -} \ No newline at end of file +} diff --git a/test/Serilog.PerformanceTests/NestedLoggerLatencyBenchmark.cs b/test/Serilog.PerformanceTests/NestedLoggerLatencyBenchmark.cs index 292472ecf..e5d23edff 100644 --- a/test/Serilog.PerformanceTests/NestedLoggerLatencyBenchmark.cs +++ b/test/Serilog.PerformanceTests/NestedLoggerLatencyBenchmark.cs @@ -12,7 +12,7 @@ public class NestedLoggerLatencyBenchmark ILogger _log, _nested; readonly LogEvent _event = Some.InformationEvent(); - [Setup] + [GlobalSetup] public void Setup() { _log = new LoggerConfiguration() diff --git a/test/Serilog.PerformanceTests/PipelineBenchmark.cs b/test/Serilog.PerformanceTests/PipelineBenchmark.cs index 631237377..47001b257 100644 --- a/test/Serilog.PerformanceTests/PipelineBenchmark.cs +++ b/test/Serilog.PerformanceTests/PipelineBenchmark.cs @@ -26,7 +26,7 @@ public class PipelineBenchmark ILogger _log; Exception _exception; - [Setup] + [GlobalSetup] public void Setup() { _exception = new Exception("An Error"); diff --git a/test/Serilog.PerformanceTests/Serilog.PerformanceTests.csproj b/test/Serilog.PerformanceTests/Serilog.PerformanceTests.csproj index 03cb721d6..03551f55a 100644 --- a/test/Serilog.PerformanceTests/Serilog.PerformanceTests.csproj +++ b/test/Serilog.PerformanceTests/Serilog.PerformanceTests.csproj @@ -1,6 +1,6 @@  - netcoreapp2.0;net46 + netcoreapp2.2;net4.7.2 Serilog.PerformanceTests ../../assets/Serilog.snk true @@ -17,8 +17,8 @@ - - - + + + \ No newline at end of file diff --git a/test/Serilog.Tests/Context/LogContextTests.cs b/test/Serilog.Tests/Context/LogContextTests.cs index 7c7f8f3c2..dd3521826 100644 --- a/test/Serilog.Tests/Context/LogContextTests.cs +++ b/test/Serilog.Tests/Context/LogContextTests.cs @@ -342,9 +342,9 @@ public async Task DisconnectRemoteObjectsAfterCrossDomainCallsOnDispose() await Task.Delay(200); - // This is intermittently 2 or 3, depending on the moods of the test runner; + // This is intermittently 2 or 3 (now, 4), depending on the moods of the test runner; // I think "at least two" is what we're concerned about, here. - Assert.InRange(tracker.DisconnectCount, 2, 3); + Assert.InRange(tracker.DisconnectCount, 2, 4); void CallFromRemote() { } } diff --git a/test/Serilog.Tests/Core/LogEventPropertyCapturingTests.cs b/test/Serilog.Tests/Core/LogEventPropertyCapturingTests.cs index 192440e58..3a750b1fb 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, 1000, Enumerable.Empty(), Enumerable.Empty(), false)); - return binder.ConstructProperties(mt, properties); + return binder.ConstructProperties(mt, properties).Select(p => new LogEventProperty(p.Name, p.Value)); } } } diff --git a/test/Serilog.Tests/Serilog.Tests.csproj b/test/Serilog.Tests/Serilog.Tests.csproj index 9ab406ea7..d1947d4ed 100644 --- a/test/Serilog.Tests/Serilog.Tests.csproj +++ b/test/Serilog.Tests/Serilog.Tests.csproj @@ -1,6 +1,6 @@  - netcoreapp2.0;net452;net46 + netcoreapp2.2;net452;net46 Serilog.Tests ../../assets/Serilog.snk true @@ -17,8 +17,8 @@ - - + + diff --git a/test/Serilog.UwpTests/Serilog.UwpTests.csproj b/test/Serilog.UwpTests/Serilog.UwpTests.csproj index 956847f18..eff70092f 100644 --- a/test/Serilog.UwpTests/Serilog.UwpTests.csproj +++ b/test/Serilog.UwpTests/Serilog.UwpTests.csproj @@ -11,8 +11,8 @@ Serilog.UwpTests en-US UAP - 10.0.10586.0 - 10.0.10586.0 + 10.0.17134.0 + 10.0.17134.0 14 512 {A5A43C5B-DE2A-4C0C-9213-0A381AF9435A};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} @@ -132,10 +132,10 @@ 6.1.5 - 2.3.1 + 2.4.1 - 2.3.1 + 2.4.1