diff --git a/appveyor.yml b/appveyor.yml index 0f0fb46..dc593a6 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,6 +1,6 @@ version: '{build}' skip_tags: true -image: Visual Studio 2017 Preview +image: Visual Studio 2019 configuration: Release install: - ps: mkdir -Force ".\build\" | Out-Null @@ -16,7 +16,7 @@ artifacts: deploy: - provider: NuGet api_key: - secure: bd9z4P73oltOXudAjPehwp9iDKsPtC+HbgshOrSgoyQKr5xVK+bxJQngrDJkHdY8 + secure: N59tiJECUYpip6tEn0xvdmDAEiP9SIzyLEFLpwiigm/8WhJvBNs13QxzT1/3/JW/ skip_symbols: true on: branch: /^(master|dev)$/ diff --git a/build.sh b/build.sh index dc51fc0..6d1ff38 100644 --- a/build.sh +++ b/build.sh @@ -3,7 +3,6 @@ dotnet --info dotnet restore for path in src/**/*.csproj; do - dotnet build -f netstandard1.3 -c Release ${path} dotnet build -f netstandard2.0 -c Release ${path} done diff --git a/global.json b/global.json index 73bdd84..6b64107 100644 --- a/global.json +++ b/global.json @@ -1,5 +1,5 @@ { "sdk": { - "version": "2.0.0-preview2-006497" + "version": "2.2.103" } } diff --git a/samples/Sample/Program.cs b/samples/Sample/Program.cs index 7f95bfa..03014b7 100644 --- a/samples/Sample/Program.cs +++ b/samples/Sample/Program.cs @@ -2,6 +2,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Serilog; +using Serilog.Extensions.Logging; namespace Sample { @@ -9,19 +10,33 @@ public class Program { public static void Main(string[] args) { + // Creating a `LoggerProviderCollection` lets Serilog optionally write + // events through other dynamically-added MEL ILoggerProviders. + var providers = new LoggerProviderCollection(); + Log.Logger = new LoggerConfiguration() .MinimumLevel.Debug() - .WriteTo.LiterateConsole() + .WriteTo.Console() + .WriteTo.Providers(providers) .CreateLogger(); - var services = new ServiceCollection() - .AddLogging(builder => - { - builder.AddSerilog(); - }); + var services = new ServiceCollection(); + + services.AddSingleton(providers); + services.AddSingleton(sc => + { + var providerCollection = sc.GetService(); + var factory = new SerilogLoggerFactory(null, true, providerCollection); + + foreach (var provider in sc.GetServices()) + factory.AddProvider(provider); + + return factory; + }); + + services.AddLogging(l => l.AddConsole()); var serviceProvider = services.BuildServiceProvider(); - // getting the logger using the class's name is conventional var logger = serviceProvider.GetRequiredService>(); var startTime = DateTimeOffset.UtcNow; @@ -57,6 +72,8 @@ public static void Main(string[] args) logger.LogInformation("{Result,-10:l}{StartTime,15:l}{EndTime,15:l}{Duration,15:l}", "RESULT", "START TIME", "END TIME", "DURATION(ms)"); logger.LogInformation("{Result,-10:l}{StartTime,15:l}{EndTime,15:l}{Duration,15:l}", "------", "----- ----", "--- ----", "------------"); logger.LogInformation("{Result,-10:l}{StartTime,15:mm:s tt}{EndTime,15:mm:s tt}{Duration,15}", "SUCCESS", startTime, endTime, (endTime - startTime).TotalMilliseconds); + + serviceProvider.Dispose(); } } } diff --git a/samples/Sample/Sample.csproj b/samples/Sample/Sample.csproj index 5c34526..c557b8a 100644 --- a/samples/Sample/Sample.csproj +++ b/samples/Sample/Sample.csproj @@ -1,7 +1,7 @@  - net461;netcoreapp2.0 + netcoreapp2.0 Sample Exe Sample @@ -14,7 +14,8 @@ - + + \ No newline at end of file diff --git a/serilog-extensions-logging.sln b/serilog-extensions-logging.sln index 80abb55..c53414c 100644 --- a/serilog-extensions-logging.sln +++ b/serilog-extensions-logging.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.26730.10 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29209.62 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{A1893BD1-333D-4DFE-A0F0-DDBB2FE526E0}" EndProject @@ -24,6 +24,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "assets", "assets", "{9C21B9 assets\Serilog.snk = assets\Serilog.snk EndProjectSection EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Serilog.Extensions.Logging.Benchmarks", "test\Serilog.Extensions.Logging.Benchmarks\Serilog.Extensions.Logging.Benchmarks.csproj", "{6D5986FF-EECD-4E75-8BC6-A5F78AB549B2}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -42,6 +44,10 @@ Global {65357FBC-9BC4-466D-B621-1C3A19BC2A78}.Debug|Any CPU.Build.0 = Debug|Any CPU {65357FBC-9BC4-466D-B621-1C3A19BC2A78}.Release|Any CPU.ActiveCfg = Release|Any CPU {65357FBC-9BC4-466D-B621-1C3A19BC2A78}.Release|Any CPU.Build.0 = Release|Any CPU + {6D5986FF-EECD-4E75-8BC6-A5F78AB549B2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6D5986FF-EECD-4E75-8BC6-A5F78AB549B2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6D5986FF-EECD-4E75-8BC6-A5F78AB549B2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6D5986FF-EECD-4E75-8BC6-A5F78AB549B2}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -50,6 +56,7 @@ Global {903CD13A-D54B-4CEC-A55F-E22AE3D93B3B} = {A1893BD1-333D-4DFE-A0F0-DDBB2FE526E0} {37EADF84-5E41-4224-A194-1E3299DCD0B8} = {E30F638E-BBBE-4AD1-93CE-48CC69CFEFE1} {65357FBC-9BC4-466D-B621-1C3A19BC2A78} = {F2407211-6043-439C-8E06-3641634332E7} + {6D5986FF-EECD-4E75-8BC6-A5F78AB549B2} = {E30F638E-BBBE-4AD1-93CE-48CC69CFEFE1} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {811E61C5-3871-4633-AFAE-B35B619C8A10} diff --git a/serilog-extensions-logging.sln.DotSettings b/serilog-extensions-logging.sln.DotSettings new file mode 100644 index 0000000..4009815 --- /dev/null +++ b/serilog-extensions-logging.sln.DotSettings @@ -0,0 +1,11 @@ + + True + True + True + True + True + True + True + True + True + True \ No newline at end of file diff --git a/src/Serilog.Extensions.Logging/Extensions/Logging/LevelConvert.cs b/src/Serilog.Extensions.Logging/Extensions/Logging/LevelConvert.cs new file mode 100644 index 0000000..58354e0 --- /dev/null +++ b/src/Serilog.Extensions.Logging/Extensions/Logging/LevelConvert.cs @@ -0,0 +1,81 @@ +// 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 Microsoft.Extensions.Logging; +using Serilog.Events; + +// ReSharper disable RedundantCaseLabel + +namespace Serilog.Extensions.Logging +{ + /// + /// Converts between Serilog and Microsoft.Extensions.Logging level enum values. + /// + public static class LevelConvert + { + /// + /// Convert to the equivalent Serilog . + /// + /// A Microsoft.Extensions.Logging . + /// The Serilog equivalent of . + /// The value has no Serilog equivalent. It is mapped to + /// as the closest approximation, but this has entirely + /// different semantics. + public static LogEventLevel ToSerilogLevel(LogLevel logLevel) + { + switch (logLevel) + { + case LogLevel.None: + case LogLevel.Critical: + return LogEventLevel.Fatal; + case LogLevel.Error: + return LogEventLevel.Error; + case LogLevel.Warning: + return LogEventLevel.Warning; + case LogLevel.Information: + return LogEventLevel.Information; + case LogLevel.Debug: + return LogEventLevel.Debug; + case LogLevel.Trace: + default: + return LogEventLevel.Verbose; + } + } + + /// + /// Convert to the equivalent Microsoft.Extensions.Logging . + /// + /// A Serilog . + /// The Microsoft.Extensions.Logging equivalent of . + public static LogLevel ToExtensionsLevel(LogEventLevel logEventLevel) + { + switch (logEventLevel) + { + case LogEventLevel.Fatal: + return LogLevel.Critical; + case LogEventLevel.Error: + return LogLevel.Error; + case LogEventLevel.Warning: + return LogLevel.Warning; + case LogEventLevel.Information: + return LogLevel.Information; + case LogEventLevel.Debug: + return LogLevel.Debug; + case LogEventLevel.Verbose: + default: + return LogLevel.Trace; + } + } + } +} diff --git a/src/Serilog.Extensions.Logging/Extensions/Logging/LoggerProviderCollection.cs b/src/Serilog.Extensions.Logging/Extensions/Logging/LoggerProviderCollection.cs new file mode 100644 index 0000000..6ecb000 --- /dev/null +++ b/src/Serilog.Extensions.Logging/Extensions/Logging/LoggerProviderCollection.cs @@ -0,0 +1,67 @@ +// 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; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using Microsoft.Extensions.Logging; + +namespace Serilog.Extensions.Logging +{ + /// + /// A dynamically-modifiable collection of s. + /// + public class LoggerProviderCollection : IDisposable + { + volatile ILoggerProvider[] _providers = new ILoggerProvider[0]; + + /// + /// Add to the collection. + /// + /// A logger provider. + public void AddProvider(ILoggerProvider provider) + { + if (provider == null) throw new ArgumentNullException(nameof(provider)); + + var existing = _providers; + var added = existing.Concat(new[] {provider}).ToArray(); + +#pragma warning disable 420 // ref to a volatile field + while (Interlocked.CompareExchange(ref _providers, added, existing) != existing) +#pragma warning restore 420 + { + existing = _providers; + added = existing.Concat(new[] { provider }).ToArray(); + } + } + + /// + /// Get the currently-active providers. + /// + /// + /// If the collection has been disposed, we'll leave the individual + /// providers with the job of throwing . + /// + public IEnumerable Providers => _providers; + + /// + public void Dispose() + { + foreach (var provider in _providers) + provider.Dispose(); + } + } +} diff --git a/src/Serilog.Extensions.Logging/Extensions/Logging/LoggerProviderCollectionSink.cs b/src/Serilog.Extensions.Logging/Extensions/Logging/LoggerProviderCollectionSink.cs new file mode 100644 index 0000000..94838ea --- /dev/null +++ b/src/Serilog.Extensions.Logging/Extensions/Logging/LoggerProviderCollectionSink.cs @@ -0,0 +1,62 @@ +// 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; +using Serilog.Core; +using Serilog.Events; + +namespace Serilog.Extensions.Logging +{ + class LoggerProviderCollectionSink : ILogEventSink, IDisposable + { + readonly LoggerProviderCollection _providers; + + public LoggerProviderCollectionSink(LoggerProviderCollection providers) + { + _providers = providers ?? throw new ArgumentNullException(nameof(providers)); + } + + public void Emit(LogEvent logEvent) + { + string categoryName = null; + + if (logEvent.Properties.TryGetValue("SourceContext", out var sourceContextProperty) && + sourceContextProperty is ScalarValue sourceContextValue && + sourceContextValue.Value is string sourceContext) + { + categoryName = sourceContext; + } + + var level = LevelConvert.ToExtensionsLevel(logEvent.Level); + var slv = new SerilogLogValues(logEvent.MessageTemplate, logEvent.Properties); + + foreach (var provider in _providers.Providers) + { + var logger = provider.CreateLogger(categoryName); + + logger.Log( + level, + default, + slv, + logEvent.Exception, + (s, e) => s.ToString()); + } + } + + public void Dispose() + { + _providers.Dispose(); + } + } +} diff --git a/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLogValues.cs b/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLogValues.cs new file mode 100644 index 0000000..c6f8058 --- /dev/null +++ b/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLogValues.cs @@ -0,0 +1,62 @@ +// 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 Serilog.Events; +using System; +using System.Collections; +using System.Collections.Generic; + +namespace Serilog.Extensions.Logging +{ + readonly struct SerilogLogValues : IReadOnlyList> + { + // Note, this struct is only used in a very limited context internally, so we ignore + // the possibility of fields being null via the default struct initialization. + + private readonly MessageTemplate _messageTemplate; + private readonly IReadOnlyDictionary _properties; + private readonly KeyValuePair[] _values; + + public SerilogLogValues(MessageTemplate messageTemplate, IReadOnlyDictionary properties) + { + _messageTemplate = messageTemplate ?? throw new ArgumentNullException(nameof(messageTemplate)); + + // The dictionary is needed for rendering through the message template + _properties = properties ?? throw new ArgumentNullException(nameof(properties)); + + // The array is needed because the IReadOnlyList interface expects indexed access + _values = new KeyValuePair[_properties.Count + 1]; + var i = 0; + foreach (var p in properties) + { + _values[i] = new KeyValuePair(p.Key, (p.Value is ScalarValue sv) ? sv.Value : p.Value); + ++i; + } + _values[i] = new KeyValuePair("{OriginalFormat}", _messageTemplate.Text); + } + + public KeyValuePair this[int index] + { + get => _values[index]; + } + + public int Count => _properties.Count + 1; + + public IEnumerator> GetEnumerator() => ((IEnumerable>)_values).GetEnumerator(); + + public override string ToString() => _messageTemplate.Render(_properties); + + IEnumerator IEnumerable.GetEnumerator() => _values.GetEnumerator(); + } +} diff --git a/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLogger.cs b/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLogger.cs index 5dff16c..89b9f93 100644 --- a/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLogger.cs +++ b/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLogger.cs @@ -4,6 +4,7 @@ using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; +using System.Linq; using Serilog.Core; using Serilog.Events; using FrameworkLogger = Microsoft.Extensions.Logging.ILogger; @@ -17,15 +18,19 @@ class SerilogLogger : FrameworkLogger readonly SerilogLoggerProvider _provider; readonly ILogger _logger; - static readonly MessageTemplateParser _messageTemplateParser = new MessageTemplateParser(); + static readonly MessageTemplateParser MessageTemplateParser = new MessageTemplateParser(); + + // It's rare to see large event ids, as they are category-specific + static readonly LogEventProperty[] LowEventIdValues = Enumerable.Range(0, 48) + .Select(n => new LogEventProperty("Id", new ScalarValue(n))) + .ToArray(); public SerilogLogger( SerilogLoggerProvider provider, ILogger logger = null, string name = null) { - if (provider == null) throw new ArgumentNullException(nameof(provider)); - _provider = provider; + _provider = provider ?? throw new ArgumentNullException(nameof(provider)); _logger = logger; // If a logger was passed, the provider has already added itself as an enricher @@ -39,7 +44,7 @@ public SerilogLogger( public bool IsEnabled(LogLevel logLevel) { - return _logger.IsEnabled(ConvertLevel(logLevel)); + return _logger.IsEnabled(LevelConvert.ToSerilogLevel(logLevel)); } public IDisposable BeginScope(TState state) @@ -49,7 +54,7 @@ public IDisposable BeginScope(TState state) public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) { - var level = ConvertLevel(logLevel); + var level = LevelConvert.ToSerilogLevel(logLevel); if (!_logger.IsEnabled(level)) { return; @@ -60,25 +65,22 @@ public void Log(LogLevel logLevel, EventId eventId, TState state, Except var properties = new List(); - var structure = state as IEnumerable>; - if (structure != null) + if (state is IEnumerable> structure) { foreach (var property in structure) { - if (property.Key == SerilogLoggerProvider.OriginalFormatPropertyName && property.Value is string) + if (property.Key == SerilogLoggerProvider.OriginalFormatPropertyName && property.Value is string value) { - messageTemplate = (string)property.Value; + messageTemplate = value; } else if (property.Key.StartsWith("@")) { - LogEventProperty destructured; - if (logger.BindProperty(property.Key.Substring(1), property.Value, true, out destructured)) + if (logger.BindProperty(property.Key.Substring(1), property.Value, true, out var destructured)) properties.Add(destructured); } else { - LogEventProperty bound; - if (logger.BindProperty(property.Key, property.Value, false, out bound)) + if (logger.BindProperty(property.Key, property.Value, false, out var bound)) properties.Add(bound); } } @@ -89,8 +91,7 @@ public void Log(LogLevel logLevel, EventId eventId, TState state, Except if (messageTemplate == null && !stateTypeInfo.IsGenericType) { messageTemplate = "{" + stateType.Name + ":l}"; - LogEventProperty stateTypeProperty; - if (logger.BindProperty(stateType.Name, AsLoggableValue(state, formatter), false, out stateTypeProperty)) + if (logger.BindProperty(stateType.Name, AsLoggableValue(state, formatter), false, out var stateTypeProperty)) properties.Add(stateTypeProperty); } } @@ -111,8 +112,7 @@ public void Log(LogLevel logLevel, EventId eventId, TState state, Except if (propertyName != null) { - LogEventProperty property; - if (logger.BindProperty(propertyName, AsLoggableValue(state, formatter), false, out property)) + if (logger.BindProperty(propertyName, AsLoggableValue(state, formatter), false, out var property)) properties.Add(property); } } @@ -120,7 +120,7 @@ public void Log(LogLevel logLevel, EventId eventId, TState state, Except if (eventId.Id != 0 || eventId.Name != null) properties.Add(CreateEventIdProperty(eventId)); - var parsedTemplate = _messageTemplateParser.Parse(messageTemplate ?? ""); + var parsedTemplate = MessageTemplateParser.Parse(messageTemplate ?? ""); var evt = new LogEvent(DateTimeOffset.Now, level, exception, parsedTemplate, properties); logger.Write(evt); } @@ -133,34 +133,17 @@ static object AsLoggableValue(TState state, Func(2); if (eventId.Id != 0) { - properties.Add(new LogEventProperty("Id", new ScalarValue(eventId.Id))); + if (eventId.Id < LowEventIdValues.Length) + // Avoid some allocations + properties.Add(LowEventIdValues[eventId.Id]); + else + properties.Add(new LogEventProperty("Id", new ScalarValue(eventId.Id))); } if (eventId.Name != null) diff --git a/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLoggerFactory.cs b/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLoggerFactory.cs new file mode 100644 index 0000000..cf25f06 --- /dev/null +++ b/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLoggerFactory.cs @@ -0,0 +1,76 @@ +// 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; +using Microsoft.Extensions.Logging; +using Serilog.Debugging; + +namespace Serilog.Extensions.Logging +{ + /// + /// A complete Serilog-backed implementation of the .NET Core logging infrastructure. + /// + public class SerilogLoggerFactory : ILoggerFactory + { + readonly LoggerProviderCollection _providerCollection; + readonly SerilogLoggerProvider _provider; + + /// + /// Initializes a new instance of the class. + /// + /// The Serilog logger; if not supplied, the static will be used. + /// When true, dispose when the framework disposes the provider. If the + /// logger is not specified but is true, the method will be + /// called on the static class instead. + /// A , for use with WriteTo.Providers(). + public SerilogLoggerFactory(ILogger logger = null, bool dispose = false, LoggerProviderCollection providerCollection = null) + { + _provider = new SerilogLoggerProvider(logger, dispose); + _providerCollection = providerCollection; + } + + /// + /// Disposes the provider. + /// + public void Dispose() + { + _provider.Dispose(); + } + + /// + /// Creates a new instance. + /// + /// The category name for messages produced by the logger. + /// + /// The . + /// + public Microsoft.Extensions.Logging.ILogger CreateLogger(string categoryName) + { + return _provider.CreateLogger(categoryName); + } + + /// + /// Adds an to the logging system. + /// + /// The . + public void AddProvider(ILoggerProvider provider) + { + if (provider == null) throw new ArgumentNullException(nameof(provider)); + if (_providerCollection != null) + _providerCollection.AddProvider(provider); + else + SelfLog.WriteLine("Ignoring added logger provider {0}", provider); + } + } +} diff --git a/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLoggerProvider.cs b/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLoggerProvider.cs index 0a8910e..f4daf8a 100644 --- a/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLoggerProvider.cs +++ b/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLoggerProvider.cs @@ -2,17 +2,12 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -#if ASYNCLOCAL -using System.Threading; -#else -using System.Runtime.Remoting; -using System.Runtime.Remoting.Messaging; -#endif using Microsoft.Extensions.Logging; using Serilog.Core; using Serilog.Events; using FrameworkLogger = Microsoft.Extensions.Logging.ILogger; using System.Collections.Generic; +using System.Threading; using Serilog.Context; namespace Serilog.Extensions.Logging @@ -20,9 +15,7 @@ namespace Serilog.Extensions.Logging /// /// An that pipes events through Serilog. /// -#if LOGGING_BUILDER [ProviderAlias("Serilog")] -#endif public class SerilogLoggerProvider : ILoggerProvider, ILogEventEnricher { internal const string OriginalFormatPropertyName = "{OriginalFormat}"; @@ -57,7 +50,7 @@ public FrameworkLogger CreateLogger(string name) return new SerilogLogger(this, _logger, name); } - /// + /// public IDisposable BeginScope(T state) { if (CurrentScope != null) @@ -66,7 +59,7 @@ public IDisposable BeginScope(T state) // The outermost scope pushes and pops the Serilog `LogContext` - once // this enricher is on the stack, the `CurrentScope` property takes care // of the rest of the `BeginScope()` stack. - var popSerilogContext = LogContext.PushProperties(this); + var popSerilogContext = LogContext.Push(this); return new SerilogLoggerScope(this, state, popSerilogContext); } @@ -93,36 +86,13 @@ public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory) } } -#if ASYNCLOCAL readonly AsyncLocal _value = new AsyncLocal(); internal SerilogLoggerScope CurrentScope { - get - { - return _value.Value; - } - set - { - _value.Value = value; - } - } -#else - readonly string _currentScopeKey = nameof(SerilogLoggerScope) + "#" + Guid.NewGuid().ToString("n"); - - internal SerilogLoggerScope CurrentScope - { - get - { - var objectHandle = CallContext.LogicalGetData(_currentScopeKey) as ObjectHandle; - return objectHandle?.Unwrap() as SerilogLoggerScope; - } - set - { - CallContext.LogicalSetData(_currentScopeKey, new ObjectHandle(value)); - } + get => _value.Value; + set => _value.Value = value; } -#endif /// public void Dispose() diff --git a/src/Serilog.Extensions.Logging/LoggerSinkConfigurationExtensions.cs b/src/Serilog.Extensions.Logging/LoggerSinkConfigurationExtensions.cs new file mode 100644 index 0000000..85064db --- /dev/null +++ b/src/Serilog.Extensions.Logging/LoggerSinkConfigurationExtensions.cs @@ -0,0 +1,49 @@ +// 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; +using Serilog.Configuration; +using Serilog.Core; +using Serilog.Events; +using Serilog.Extensions.Logging; + +namespace Serilog +{ + /// + /// Extensions for . + /// + public static class LoggerSinkConfigurationExtensions + { + /// + /// Write Serilog events to the providers in . + /// + /// The `WriteTo` object. + /// A to write events to. + /// The minimum level for + /// events passed through the sink. Ignored when is specified. + /// A switch allowing the pass-through minimum level + /// to be changed at runtime. + /// A to allow method chaining. + public static LoggerConfiguration Providers( + this LoggerSinkConfiguration configuration, + LoggerProviderCollection providers, + LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum, + LoggingLevelSwitch levelSwitch = null) + { + if (configuration == null) throw new ArgumentNullException(nameof(configuration)); + if (providers == null) throw new ArgumentNullException(nameof(providers)); + return configuration.Sink(new LoggerProviderCollectionSink(providers), restrictedToMinimumLevel, levelSwitch); + } + } +} diff --git a/src/Serilog.Extensions.Logging/Serilog.Extensions.Logging.csproj b/src/Serilog.Extensions.Logging/Serilog.Extensions.Logging.csproj index 3501e2c..479cb20 100644 --- a/src/Serilog.Extensions.Logging/Serilog.Extensions.Logging.csproj +++ b/src/Serilog.Extensions.Logging/Serilog.Extensions.Logging.csproj @@ -2,9 +2,9 @@ Low-level Serilog provider for Microsoft.Extensions.Logging - 2.0.4 + 3.0.0 Microsoft;Serilog Contributors - net45;net46;net461;netstandard1.3;netstandard2.0 + netstandard2.0 true true Serilog.Extensions.Logging @@ -20,34 +20,12 @@ git false Serilog + 7.3 - - - - - - - - + - - $(DefineConstants);ASYNCLOCAL - - - - $(DefineConstants);ASYNCLOCAL;LOGGING_BUILDER - - - - $(DefineConstants);ASYNCLOCAL - - - - $(DefineConstants);ASYNCLOCAL;LOGGING_BUILDER - - diff --git a/src/Serilog.Extensions.Logging/SerilogLoggingBuilderExtensions.cs b/src/Serilog.Extensions.Logging/SerilogLoggingBuilderExtensions.cs index d7a3f5f..27bb6dd 100644 --- a/src/Serilog.Extensions.Logging/SerilogLoggingBuilderExtensions.cs +++ b/src/Serilog.Extensions.Logging/SerilogLoggingBuilderExtensions.cs @@ -12,8 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -#if LOGGING_BUILDER - using System; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -54,5 +52,3 @@ public static ILoggingBuilder AddSerilog(this ILoggingBuilder builder, ILogger l } } } - -#endif // LOGGING_BUILDER diff --git a/test/Serilog.Extensions.Logging.Benchmarks/LogEventConstructionBenchmark.cs b/test/Serilog.Extensions.Logging.Benchmarks/LogEventConstructionBenchmark.cs new file mode 100644 index 0000000..51cae2e --- /dev/null +++ b/test/Serilog.Extensions.Logging.Benchmarks/LogEventConstructionBenchmark.cs @@ -0,0 +1,103 @@ +// 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; +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Running; +using Microsoft.Extensions.Logging; +using IMelLogger = Microsoft.Extensions.Logging.ILogger; +using Serilog.Events; +using Serilog.Extensions.Logging.Benchmarks.Support; +using Xunit; + +namespace Serilog.Extensions.Logging.Benchmarks +{ + [MemoryDiagnoser] + public class LogEventConstructionBenchmark + { + readonly IMelLogger _melLogger; + readonly ILogger _serilogContextualLogger; + readonly CapturingSink _sink; + const int LowId = 10, HighId = 101; + const string Template = "This is an event"; + + public LogEventConstructionBenchmark() + { + _sink = new CapturingSink(); + var underlyingLogger = new LoggerConfiguration().WriteTo.Sink(_sink).CreateLogger(); + _serilogContextualLogger = underlyingLogger.ForContext(); + _melLogger = new SerilogLoggerProvider(underlyingLogger).CreateLogger(GetType().FullName); + } + + static void VerifyEventId(LogEvent evt, int? expectedId) + { + if (evt == null) throw new ArgumentNullException(nameof(evt)); + if (expectedId == null) + { + Assert.False(evt.Properties.TryGetValue("EventId", out _)); + } + else + { + Assert.True(evt.Properties.TryGetValue("EventId", out var eventIdValue)); + var structure = Assert.IsType(eventIdValue); + var idValue = Assert.Single(structure.Properties, p => p.Name == "Id")?.Value; + var scalar = Assert.IsType(idValue); + Assert.Equal(expectedId.Value, scalar.Value); + } + } + + [Fact] + public void Verify() + { + VerifyEventId(Native(), null); + VerifyEventId(NoId(), null); + VerifyEventId(LowNumbered(), LowId); + VerifyEventId(HighNumbered(), HighId); + } + + [Fact] + public void Benchmark() + { + BenchmarkRunner.Run(); + } + + [Benchmark(Baseline = true)] + public LogEvent Native() + { + _serilogContextualLogger.Information(Template); + return _sink.Collect(); + } + + [Benchmark] + public LogEvent NoId() + { + _melLogger.LogInformation(Template); + return _sink.Collect(); + } + + [Benchmark] + public LogEvent LowNumbered() + { + _melLogger.LogInformation(LowId, Template); + return _sink.Collect(); + } + + [Benchmark] + public LogEvent HighNumbered() + { + _melLogger.LogInformation(HighId, Template); + return _sink.Collect(); + } + } +} diff --git a/test/Serilog.Extensions.Logging.Benchmarks/Serilog.Extensions.Logging.Benchmarks.csproj b/test/Serilog.Extensions.Logging.Benchmarks/Serilog.Extensions.Logging.Benchmarks.csproj new file mode 100644 index 0000000..7840f21 --- /dev/null +++ b/test/Serilog.Extensions.Logging.Benchmarks/Serilog.Extensions.Logging.Benchmarks.csproj @@ -0,0 +1,26 @@ + + + + netcoreapp2.2 + true + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + diff --git a/test/Serilog.Extensions.Logging.Benchmarks/Support/CapturingSink.cs b/test/Serilog.Extensions.Logging.Benchmarks/Support/CapturingSink.cs new file mode 100644 index 0000000..3913561 --- /dev/null +++ b/test/Serilog.Extensions.Logging.Benchmarks/Support/CapturingSink.cs @@ -0,0 +1,36 @@ +// 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 Serilog.Core; +using Serilog.Events; + +namespace Serilog.Extensions.Logging.Benchmarks.Support +{ + class CapturingSink : ILogEventSink + { + LogEvent _emitted; + + public void Emit(LogEvent logEvent) + { + _emitted = logEvent; + } + + public LogEvent Collect() + { + var collected = _emitted; + _emitted = null; + return collected; + } + } +} diff --git a/test/Serilog.Extensions.Logging.Tests/Serilog.Extensions.Logging.Tests.csproj b/test/Serilog.Extensions.Logging.Tests/Serilog.Extensions.Logging.Tests.csproj index 54eee14..e1a144a 100644 --- a/test/Serilog.Extensions.Logging.Tests/Serilog.Extensions.Logging.Tests.csproj +++ b/test/Serilog.Extensions.Logging.Tests/Serilog.Extensions.Logging.Tests.csproj @@ -1,15 +1,13 @@ - + - netcoreapp1.1;netcoreapp2.0;net46;net461 + netcoreapp2.0;net472 Serilog.Extensions.Logging.Tests ../../assets/Serilog.snk true true Serilog.Extensions.Logging.Tests true - $(PackageTargetFallback);dnxcore50;portable-net45+win8 - 1.0.4 @@ -22,9 +20,4 @@ - - - - - diff --git a/test/Serilog.Extensions.Logging.Tests/SerilogLogValuesTests.cs b/test/Serilog.Extensions.Logging.Tests/SerilogLogValuesTests.cs new file mode 100644 index 0000000..d5a777e --- /dev/null +++ b/test/Serilog.Extensions.Logging.Tests/SerilogLogValuesTests.cs @@ -0,0 +1,53 @@ +using Serilog.Events; +using Serilog.Parsing; +using System.Collections.Generic; +using System.Linq; +using Xunit; + +namespace Serilog.Extensions.Logging.Tests +{ + public class SerilogLogValuesTests + { + [Fact] + public void OriginalFormatIsExposed() + { + const string format = "Hello, {Name}!"; + var mt = new MessageTemplateParser().Parse(format); + var lv = new SerilogLogValues(mt, new Dictionary()); + var kvp = lv.Single(); + Assert.Equal("{OriginalFormat}", kvp.Key); + Assert.Equal(format, kvp.Value); + } + + [Fact] + public void ScalarPropertiesAreSimplified() + { + const string name = "Scalar"; + var scalar = 15; + var lv = new SerilogLogValues(MessageTemplate.Empty, new Dictionary { [name] = new ScalarValue(scalar) }); + var kvp = lv.Single(p => p.Key == name); + var sv = Assert.IsType(kvp.Value); + Assert.Equal(scalar, sv); + } + + [Fact] + public void NonscalarPropertiesAreWrapped() + { + const string name = "Sequence"; + var seq = new SequenceValue(Enumerable.Empty()); + var lv = new SerilogLogValues(MessageTemplate.Empty, new Dictionary { [name] = seq }); + var kvp = lv.Single(p => p.Key == name); + var sv = Assert.IsType(kvp.Value); + Assert.Equal(seq, sv); + } + + [Fact] + public void MessageTemplatesAreRendered() + { + const string format = "Hello, {Name}!"; + var mt = new MessageTemplateParser().Parse(format); + var lv = new SerilogLogValues(mt, new Dictionary { ["Name"] = new ScalarValue("World") }); + Assert.Equal("Hello, \"World\"!", lv.ToString()); + } + } +} diff --git a/test/Serilog.Extensions.Logging.Tests/SerilogLoggerTests.cs b/test/Serilog.Extensions.Logging.Tests/SerilogLoggerTests.cs index e57d4a8..d2d5bac 100644 --- a/test/Serilog.Extensions.Logging.Tests/SerilogLoggerTests.cs +++ b/test/Serilog.Extensions.Logging.Tests/SerilogLoggerTests.cs @@ -9,63 +9,35 @@ using System.IO; using System.Linq; using Serilog.Debugging; -using Serilog.Framework.Logging.Tests.Support; +using Serilog.Extensions.Logging.Tests.Support; using Xunit; -namespace Serilog.Extensions.Logging.Test +namespace Serilog.Extensions.Logging.Tests { public class SerilogLoggerTest { - private const string Name = "test"; - private const string TestMessage = "This is a test"; + const string Name = "test"; + const string TestMessage = "This is a test"; - private Tuple SetUp(LogLevel logLevel) + static Tuple SetUp(LogLevel logLevel) { var sink = new SerilogSink(); - var config = new LoggerConfiguration() - .WriteTo.Sink(sink); + var serilogLogger = new LoggerConfiguration() + .WriteTo.Sink(sink) + .MinimumLevel.Is(LevelConvert.ToSerilogLevel(logLevel)) + .CreateLogger(); - SetMinLevel(config, logLevel); - - var provider = new SerilogLoggerProvider(config.CreateLogger()); + var provider = new SerilogLoggerProvider(serilogLogger); var logger = (SerilogLogger)provider.CreateLogger(Name); return new Tuple(logger, sink); } - private void SetMinLevel(LoggerConfiguration serilog, LogLevel logLevel) - { - serilog.MinimumLevel.Is(MapLevel(logLevel)); - } - - private LogEventLevel MapLevel(LogLevel logLevel) - { - switch (logLevel) - { - case LogLevel.Trace: - return LogEventLevel.Verbose; - case LogLevel.Debug: - return LogEventLevel.Debug; - case LogLevel.Information: - return LogEventLevel.Information; - case LogLevel.Warning: - return LogEventLevel.Warning; - case LogLevel.Error: - return LogEventLevel.Error; - case LogLevel.Critical: - return LogEventLevel.Fatal; - default: - return LogEventLevel.Verbose; - } - } - [Fact] public void LogsWhenNullFilterGiven() { - var t = SetUp(LogLevel.Trace); - var logger = t.Item1; - var sink = t.Item2; + var (logger, sink) = SetUp(LogLevel.Trace); logger.Log(LogLevel.Information, 0, TestMessage, null, null); @@ -75,9 +47,7 @@ public void LogsWhenNullFilterGiven() [Fact] public void LogsCorrectLevel() { - var t = SetUp(LogLevel.Trace); - var logger = t.Item1; - var sink = t.Item2; + var (logger, sink) = SetUp(LogLevel.Trace); logger.Log(LogLevel.Trace, 0, TestMessage, null, null); logger.Log(LogLevel.Debug, 0, TestMessage, null, null); @@ -134,9 +104,7 @@ public void LogsCorrectLevel() [InlineData(LogLevel.Critical, LogLevel.Critical, 1)] public void LogsWhenEnabled(LogLevel minLevel, LogLevel logLevel, int expected) { - var t = SetUp(minLevel); - var logger = t.Item1; - var sink = t.Item2; + var (logger, sink) = SetUp(minLevel); logger.Log(logLevel, 0, TestMessage, null, null); @@ -146,9 +114,7 @@ public void LogsWhenEnabled(LogLevel minLevel, LogLevel logLevel, int expected) [Fact] public void LogsCorrectMessage() { - var t = SetUp(LogLevel.Trace); - var logger = t.Item1; - var sink = t.Item2; + var (logger, sink) = SetUp(LogLevel.Trace); logger.Log(LogLevel.Information, 0, null, null, null); logger.Log(LogLevel.Information, 0, TestMessage, null, null); @@ -171,9 +137,7 @@ public void LogsCorrectMessage() [Fact] public void CarriesException() { - var t = SetUp(LogLevel.Trace); - var logger = t.Item1; - var sink = t.Item2; + var (logger, sink) = SetUp(LogLevel.Trace); var exception = new Exception(); @@ -186,9 +150,7 @@ public void CarriesException() [Fact] public void SingleScopeProperty() { - var t = SetUp(LogLevel.Trace); - var logger = t.Item1; - var sink = t.Item2; + var (logger, sink) = SetUp(LogLevel.Trace); using (logger.BeginScope(new FoodScope("pizza"))) { @@ -203,9 +165,7 @@ public void SingleScopeProperty() [Fact] public void NestedScopeSameProperty() { - var t = SetUp(LogLevel.Trace); - var logger = t.Item1; - var sink = t.Item2; + var (logger, sink) = SetUp(LogLevel.Trace); using (logger.BeginScope(new FoodScope("avocado"))) { @@ -224,9 +184,7 @@ public void NestedScopeSameProperty() [Fact] public void NestedScopesDifferentProperties() { - var t = SetUp(LogLevel.Trace); - var logger = t.Item1; - var sink = t.Item2; + var (logger, sink) = SetUp(LogLevel.Trace); using (logger.BeginScope(new FoodScope("spaghetti"))) { @@ -249,9 +207,7 @@ public void CarriesMessageTemplateProperties() var selfLog = new StringWriter(); SelfLog.Enable(selfLog); - var t = SetUp(LogLevel.Trace); - var logger = t.Item1; - var sink = t.Item2; + var (logger, sink) = SetUp(LogLevel.Trace); logger.LogInformation("Hello, {Recipient}", "World"); @@ -266,11 +222,9 @@ public void CarriesMessageTemplateProperties() [Fact] public void CarriesEventIdIfNonzero() { - var t = SetUp(LogLevel.Trace); - var logger = t.Item1; - var sink = t.Item2; + var (logger, sink) = SetUp(LogLevel.Trace); - int expected = 42; + const int expected = 42; logger.Log(LogLevel.Information, expected, "Test", null, null); @@ -302,9 +256,7 @@ public void WhenDisposeIsTrueProvidedLoggerIsDisposed() [Fact] public void BeginScopeDestructuresObjectsWhenDestructurerIsUsedInMessageTemplate() { - var t = SetUp(LogLevel.Trace); - var logger = t.Item1; - var sink = t.Item2; + var (logger, sink) = SetUp(LogLevel.Trace); using (logger.BeginScope("{@Person}", new Person { FirstName = "John", LastName = "Smith" })) { @@ -324,9 +276,7 @@ public void BeginScopeDestructuresObjectsWhenDestructurerIsUsedInMessageTemplate [Fact] public void BeginScopeDestructuresObjectsWhenDestructurerIsUsedInDictionary() { - var t = SetUp(LogLevel.Trace); - var logger = t.Item1; - var sink = t.Item2; + var (logger, sink) = SetUp(LogLevel.Trace); using (logger.BeginScope(new Dictionary {{ "@Person", new Person { FirstName = "John", LastName = "Smith" }}})) { @@ -346,9 +296,7 @@ public void BeginScopeDestructuresObjectsWhenDestructurerIsUsedInDictionary() [Fact] public void BeginScopeDoesNotModifyKeyWhenDestructurerIsNotUsedInMessageTemplate() { - var t = SetUp(LogLevel.Trace); - var logger = t.Item1; - var sink = t.Item2; + var (logger, sink) = SetUp(LogLevel.Trace); using (logger.BeginScope("{FirstName}", "John")) { @@ -362,9 +310,7 @@ public void BeginScopeDoesNotModifyKeyWhenDestructurerIsNotUsedInMessageTemplate [Fact] public void BeginScopeDoesNotModifyKeyWhenDestructurerIsNotUsedInDictionary() { - var t = SetUp(LogLevel.Trace); - var logger = t.Item1; - var sink = t.Item2; + var (logger, sink) = SetUp(LogLevel.Trace); using (logger.BeginScope(new Dictionary { { "FirstName", "John"}})) { @@ -378,9 +324,7 @@ public void BeginScopeDoesNotModifyKeyWhenDestructurerIsNotUsedInDictionary() [Fact] public void NamedScopesAreCaptured() { - var t = SetUp(LogLevel.Trace); - var logger = t.Item1; - var sink = t.Item2; + var (logger, sink) = SetUp(LogLevel.Trace); using (logger.BeginScope("Outer")) using (logger.BeginScope("Inner")) @@ -390,16 +334,15 @@ public void NamedScopesAreCaptured() Assert.Equal(1, sink.Writes.Count); - LogEventPropertyValue scopeValue; - Assert.True(sink.Writes[0].Properties.TryGetValue(SerilogLoggerProvider.ScopePropertyName, out scopeValue)); - - var items = (scopeValue as SequenceValue)?.Elements.Select(e => ((ScalarValue)e).Value).Cast().ToArray(); + Assert.True(sink.Writes[0].Properties.TryGetValue(SerilogLoggerProvider.ScopePropertyName, out var scopeValue)); + var sequence = Assert.IsType(scopeValue); + var items = sequence.Elements.Select(e => Assert.IsType(e).Value).Cast().ToArray(); Assert.Equal(2, items.Length); Assert.Equal("Outer", items[0]); Assert.Equal("Inner", items[1]); } - private class FoodScope : IEnumerable> + class FoodScope : IEnumerable> { readonly string _name; @@ -419,7 +362,7 @@ IEnumerator IEnumerable.GetEnumerator() } } - private class LuckyScope : IEnumerable> + class LuckyScope : IEnumerable> { readonly int _luckyNumber; @@ -439,10 +382,29 @@ IEnumerator IEnumerable.GetEnumerator() } } - private class Person + class Person { + // ReSharper disable once UnusedAutoPropertyAccessor.Local public string FirstName { get; set; } + + // ReSharper disable once UnusedAutoPropertyAccessor.Local public string LastName { get; set; } } + + [Theory] + [InlineData(1)] + [InlineData(10)] + [InlineData(48)] + [InlineData(100)] + public void LowAndHighNumberedEventIdsAreMapped(int id) + { + var orig = new EventId(id, "test"); + var mapped = SerilogLogger.CreateEventIdProperty(orig); + var value = Assert.IsType(mapped.Value); + Assert.Equal(2, value.Properties.Count); + var idValue = value.Properties.Single(p => p.Name == "Id").Value; + var scalar = Assert.IsType(idValue); + Assert.Equal(id, scalar.Value); + } } -} \ No newline at end of file +} diff --git a/test/Serilog.Extensions.Logging.Tests/Support/DisposeTrackingLogger.cs b/test/Serilog.Extensions.Logging.Tests/Support/DisposeTrackingLogger.cs index a967fef..d833c0c 100644 --- a/test/Serilog.Extensions.Logging.Tests/Support/DisposeTrackingLogger.cs +++ b/test/Serilog.Extensions.Logging.Tests/Support/DisposeTrackingLogger.cs @@ -3,7 +3,7 @@ using Serilog.Core; using Serilog.Events; -namespace Serilog.Framework.Logging.Tests.Support +namespace Serilog.Extensions.Logging.Tests.Support { public class DisposeTrackingLogger : ILogger, IDisposable { diff --git a/test/Serilog.Extensions.Logging.Tests/Support/SerilogSink.cs b/test/Serilog.Extensions.Logging.Tests/Support/SerilogSink.cs index a0f6384..cd7d5d7 100644 --- a/test/Serilog.Extensions.Logging.Tests/Support/SerilogSink.cs +++ b/test/Serilog.Extensions.Logging.Tests/Support/SerilogSink.cs @@ -5,7 +5,7 @@ using Serilog.Core; using Serilog.Events; -namespace Serilog.Extensions.Logging.Test +namespace Serilog.Extensions.Logging.Tests.Support { public class SerilogSink : ILogEventSink {