From d7717f4ad1a5157f7a02c5c446c904f2f1fe3324 Mon Sep 17 00:00:00 2001 From: Rolf Kristensen Date: Mon, 1 Apr 2019 23:59:01 +0200 Subject: [PATCH] Load JSON based NLog config (e.g. declared in appsettings.json) + BeginScopeParser performance improvements (#263) * Load NLog config from appsettings.json using NLogLoggingConfiguration * Update NLog.Extensions.Logging.csproj * refactor --- NLog.Extensions.Logging.sln | 1 + build.ps1 | 4 +- .../ConsoleExample/ConsoleExample.csproj | 1 - examples/NetCore2/ConsoleExample/Program.cs | 3 + .../NetCore2/ConsoleExample/appsettings.json | 38 ++++ .../Config/NLogLoggingConfiguration.cs | 112 ++++++++++ .../Internal/StringExtensions.cs | 13 ++ .../Logging/NLogBeginScopeParser.cs | 199 ++++++++++-------- .../Logging/NLogLogger.cs | 2 +- .../Logging/NLogLoggerProvider.cs | 8 +- .../NLog.Extensions.Logging.csproj | 10 +- ...NLog.Extensions.Logging.csproj.DotSettings | 6 + .../ConfigSettingLayoutRendererTests.cs | 2 +- .../NLog.Extensions.Logging.Tests.csproj | 4 + .../NLogLoggingConfigurationTests.cs | 69 ++++++ 15 files changed, 365 insertions(+), 107 deletions(-) create mode 100644 src/NLog.Extensions.Logging/Config/NLogLoggingConfiguration.cs create mode 100644 src/NLog.Extensions.Logging/Internal/StringExtensions.cs create mode 100644 src/NLog.Extensions.Logging/NLog.Extensions.Logging.csproj.DotSettings create mode 100644 test/NLog.Extensions.Logging.Tests/NLogLoggingConfigurationTests.cs diff --git a/NLog.Extensions.Logging.sln b/NLog.Extensions.Logging.sln index cf14af65..631b3ddf 100644 --- a/NLog.Extensions.Logging.sln +++ b/NLog.Extensions.Logging.sln @@ -8,6 +8,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution ProjectSection(SolutionItems) = preProject appveyor.yml = appveyor.yml build.ps1 = build.ps1 + CHANGELOG.MD = CHANGELOG.MD README.md = README.md EndProjectSection EndProject diff --git a/build.ps1 b/build.ps1 index 90751251..b9abb9e0 100644 --- a/build.ps1 +++ b/build.ps1 @@ -2,8 +2,8 @@ # creates NuGet package at \artifacts dotnet --version -$versionPrefix = "1.4.0" -$versionSuffix = "" +$versionPrefix = "1.5.0" +$versionSuffix = "rc1" $versionFile = $versionPrefix + "." + ${env:APPVEYOR_BUILD_NUMBER} $versionProduct = $versionPrefix; if (-Not $versionSuffix.Equals("")) diff --git a/examples/NetCore2/ConsoleExample/ConsoleExample.csproj b/examples/NetCore2/ConsoleExample/ConsoleExample.csproj index b86064c9..c5eefde4 100644 --- a/examples/NetCore2/ConsoleExample/ConsoleExample.csproj +++ b/examples/NetCore2/ConsoleExample/ConsoleExample.csproj @@ -11,7 +11,6 @@ - diff --git a/examples/NetCore2/ConsoleExample/Program.cs b/examples/NetCore2/ConsoleExample/Program.cs index 8eda3269..931557a2 100644 --- a/examples/NetCore2/ConsoleExample/Program.cs +++ b/examples/NetCore2/ConsoleExample/Program.cs @@ -12,6 +12,7 @@ internal class Program private static void Main() { var logger = LogManager.GetCurrentClassLogger(); + try { var config = new ConfigurationBuilder() @@ -19,6 +20,8 @@ private static void Main() .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) .Build(); + LogManager.Configuration = new NLogLoggingConfiguration(config.GetSection("NLog")); + var servicesProvider = BuildDi(config); using (servicesProvider as IDisposable) { diff --git a/examples/NetCore2/ConsoleExample/appsettings.json b/examples/NetCore2/ConsoleExample/appsettings.json index daabc235..352cad2f 100644 --- a/examples/NetCore2/ConsoleExample/appsettings.json +++ b/examples/NetCore2/ConsoleExample/appsettings.json @@ -5,5 +5,43 @@ "ParseMessageTemplates": true, "CaptureMessageProperties": true } + }, + "NLog": { + "autoreload": true, + "internalLogLevel": "Info", + "internalLogFile": "c:/temp/console-example-internal.log", + "throwConfigExceptions": true, + "targets": { + "console": { + "type": "Console", + "layout": "${date}|${level:uppercase=true}|${message} ${exception:format=tostring}|${logger}|${all-event-properties}" + }, + "file": { + "type": "AsyncWrapper", + "target": { + "wrappedFile": { + "type": "File", + "fileName": "c:/temp/console-example.log", + "layout": { + "type": "JsonLayout", + "Attributes": [ + { "name": "timestamp", "layout": "${date:format=o}" }, + { "name": "level", "layout": "${level}" }, + { "name": "logger", "layout": "${logger}" }, + { "name": "message", "layout": "${message:raw=true}" }, + { "name": "properties", "encode": false, "layout": { "type": "JsonLayout", "includeallproperties": "true" } } + ] + } + } + } + } + }, + "rules": [ + { + "logger": "*", + "minLevel": "Trace", + "writeTo": "File,Console" + } + ] } } \ No newline at end of file diff --git a/src/NLog.Extensions.Logging/Config/NLogLoggingConfiguration.cs b/src/NLog.Extensions.Logging/Config/NLogLoggingConfiguration.cs new file mode 100644 index 00000000..cc170164 --- /dev/null +++ b/src/NLog.Extensions.Logging/Config/NLogLoggingConfiguration.cs @@ -0,0 +1,112 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.Extensions.Configuration; +using NLog.Config; + +namespace NLog.Extensions.Logging +{ + /// + /// Configures NLog through Microsoft Extension Configuration section (Ex from appsettings.json) + /// + public class NLogLoggingConfiguration : LoggingConfigurationParser + { + private readonly Action _reloadConfiguration; + + /// + /// Initializes a new instance of the class. + /// + /// Configuration section to be read + public NLogLoggingConfiguration(IConfigurationSection nlogConfig) + : this(nlogConfig, LogManager.LogFactory) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Configuration section to be read + /// The to which to apply any applicable configuration values. + public NLogLoggingConfiguration(IConfigurationSection nlogConfig, LogFactory logFactory) + : base(logFactory) + { + _reloadConfiguration = (state) => LoadConfigurationSection((IConfigurationSection)state, true); + LoadConfigurationSection(nlogConfig, null); + } + + private void LoadConfigurationSection(IConfigurationSection nlogConfig, bool? autoReload) + { + var configElement = new LoggingConfigurationElement(nlogConfig, true); + LoadConfig(configElement, null); + if (autoReload ?? configElement.AutoReload) + { + nlogConfig.GetReloadToken().RegisterChangeCallback(_reloadConfiguration, nlogConfig); + } + } + + private class LoggingConfigurationElement : ILoggingConfigurationElement + { + readonly IConfigurationSection _configurationSection; + readonly string _nameOverride; + + public bool AutoReload { get; } + + public LoggingConfigurationElement(IConfigurationSection configurationSection, bool topElement, string nameOverride = null) + { + _configurationSection = configurationSection; + _nameOverride = nameOverride; + if (topElement && bool.TryParse(configurationSection["autoreload"], out var autoreload)) + { + AutoReload = autoreload; + } + } + + public string Name => _nameOverride ?? _configurationSection.Key; + + public IEnumerable> Values + { + get + { + var children = _configurationSection.GetChildren(); + foreach (var child in children) + { + if (!child.GetChildren().Any()) + yield return new KeyValuePair(child.Key, child.Value); + } + if (_nameOverride != null) + yield return new KeyValuePair("name", _configurationSection.Key); + } + } + + public IEnumerable Children + { + get + { + var children = _configurationSection.GetChildren(); + foreach (var child in children) + { + var firstChildValue = child?.GetChildren()?.FirstOrDefault(); + if (firstChildValue == null) + { + continue; + } + + if (_nameOverride == "target" && child.Key.EqualsOrdinalIgnoreCase("target") && child.GetChildren().Count() == 1) + { + yield return new LoggingConfigurationElement(firstChildValue, false, "target"); + } + else + { + string nameOverride = null; + if (_configurationSection.Key.EqualsOrdinalIgnoreCase("targets")) + { + nameOverride = "target"; + } + yield return new LoggingConfigurationElement(child, false, nameOverride); + } + } + } + } + } + } +} diff --git a/src/NLog.Extensions.Logging/Internal/StringExtensions.cs b/src/NLog.Extensions.Logging/Internal/StringExtensions.cs new file mode 100644 index 00000000..7dae8783 --- /dev/null +++ b/src/NLog.Extensions.Logging/Internal/StringExtensions.cs @@ -0,0 +1,13 @@ +using System; +using System.Linq; + +namespace NLog.Extensions.Logging +{ + internal static class StringExtensions + { + internal static bool EqualsOrdinalIgnoreCase(this string text, string text2) + { + return string.Equals(text, text2, StringComparison.OrdinalIgnoreCase); + } + } +} diff --git a/src/NLog.Extensions.Logging/Logging/NLogBeginScopeParser.cs b/src/NLog.Extensions.Logging/Logging/NLogBeginScopeParser.cs index 78407347..903674b9 100644 --- a/src/NLog.Extensions.Logging/Logging/NLogBeginScopeParser.cs +++ b/src/NLog.Extensions.Logging/Logging/NLogBeginScopeParser.cs @@ -1,18 +1,24 @@ using System; +using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Linq.Expressions; using System.Reflection; using NLog.Common; namespace NLog.Extensions.Logging { + using ExtractorDictionary = ConcurrentDictionary, Func>>; + /// /// Converts Microsoft Extension Logging BeginScope into NLog NestedDiagnosticsLogicalContext + MappedDiagnosticsLogicalContext /// internal class NLogBeginScopeParser { private readonly NLogProviderOptions _options; - private readonly ConcurrentDictionary, Func>> _scopeStateExtractors = new ConcurrentDictionary, Func>>(); + + private readonly ExtractorDictionary _scopeStateExtractors = + new ExtractorDictionary(); public NLogBeginScopeParser(NLogProviderOptions options) { @@ -30,15 +36,15 @@ public IDisposable ParseBeginScope(T state) if (!(state is string)) { - if (state is System.Collections.IEnumerable scopePropertyCollection) + if (state is IEnumerable scopePropertyCollection) + { return ScopeProperties.CaptureScopeProperties(scopePropertyCollection, _scopeStateExtractors); - else - return ScopeProperties.CaptureScopeProperty(state, _scopeStateExtractors); - } - else - { - return NestedDiagnosticsLogicalContext.Push(state); + } + + return ScopeProperties.CaptureScopeProperty(state, _scopeStateExtractors); } + + return NestedDiagnosticsLogicalContext.Push(state); } return CreateDiagnosticLogicalContext(state); @@ -48,15 +54,7 @@ public static IDisposable CreateDiagnosticLogicalContext(T state) { try { -#if NETSTANDARD return NestedDiagnosticsLogicalContext.Push(state); // AsyncLocal has no requirement to be Serializable -#else - // TODO Add support for Net46 in NLog (AsyncLocal), then we only have to do this check for legacy Net451 (CallContext) - if (state?.GetType().IsSerializable ?? true) - return NestedDiagnosticsLogicalContext.Push(state); - else - return NestedDiagnosticsLogicalContext.Push(state.ToString()); // Support HostingLogScope, ActionLogScope, FormattedLogValues and others -#endif } catch (Exception ex) { @@ -65,87 +63,138 @@ public static IDisposable CreateDiagnosticLogicalContext(T state) } } +#pragma warning disable S3881 // "IDisposable" should be implemented correctly - no recources, but dispose pattern private class ScopeProperties : IDisposable +#pragma warning restore S3881 // "IDisposable" should be implemented correctly { - Stack _properties; + private readonly IDisposable _mldcScope; + private readonly IDisposable _ndlcScope; - /// - /// Properties, never null and lazy init - /// - Stack Properties => _properties ?? (_properties = new Stack()); + private ScopeProperties(IDisposable ndlcScope, IDisposable mldcScope) + { + _ndlcScope = ndlcScope; + _mldcScope = mldcScope; + } - public ScopeProperties(int initialCapacity = 0) + public void Dispose() { - if (initialCapacity > 0) - _properties = new Stack(initialCapacity); + try + { + _mldcScope?.Dispose(); + } + catch (Exception ex) + { + InternalLogger.Debug(ex, "Exception in BeginScope dispose MappedDiagnosticsLogicalContext"); + } + + try + { + _ndlcScope?.Dispose(); + } + catch (Exception ex) + { + InternalLogger.Debug(ex, "Exception in BeginScope dispose NestedDiagnosticsLogicalContext"); + } + } + + private static IDisposable CreateScopeProperties(object scopeObject, IReadOnlyList> propertyList) + { + if (propertyList?.Count > 0) + { + return new ScopeProperties(CreateDiagnosticLogicalContext(scopeObject), MappedDiagnosticsLogicalContext.SetScoped(propertyList)); + } + + return CreateDiagnosticLogicalContext(scopeObject); } - public static ScopeProperties CaptureScopeProperties(IReadOnlyList> scopePropertyList) + public static IDisposable CaptureScopeProperties(IReadOnlyList> scopePropertyList) { - ScopeProperties scope = new ScopeProperties(scopePropertyList.Count + 1); + if (scopePropertyList.Count == 0 || scopePropertyList[scopePropertyList.Count - 1].Key != NLogLogger.OriginalFormatPropertyName) + { + return CreateScopeProperties(scopePropertyList, scopePropertyList); + } - for (int i = 0; i < scopePropertyList.Count; ++i) + var propertyList = new List>(scopePropertyList.Count - 1); + for (var i = 0; i < scopePropertyList.Count; ++i) { var property = scopePropertyList[i]; if (i == scopePropertyList.Count - 1 && i > 0 && property.Key == NLogLogger.OriginalFormatPropertyName) - continue; // Handle BeginScope("Hello {World}", "Earth") + { + continue; // Handle BeginScope("Hello {World}", "Earth") + } - scope.AddProperty(property.Key, property.Value); + propertyList.Add(property); } - scope.AddDispose(CreateDiagnosticLogicalContext(scopePropertyList)); - return scope; + return CreateScopeProperties(scopePropertyList, propertyList); } - public static ScopeProperties CaptureScopeProperties(System.Collections.IEnumerable scopePropertyCollection, ConcurrentDictionary, Func>> stateExractor) + public static IDisposable CaptureScopeProperties(IEnumerable scopePropertyCollection, ExtractorDictionary stateExractor) { - ScopeProperties scope = new ScopeProperties(); + List> propertyList = null; var keyValueExtractor = default(KeyValuePair, Func>); foreach (var property in scopePropertyCollection) { if (property == null) + { + break; + } + + if (keyValueExtractor.Key == null && !TryLookupExtractor(stateExractor, property.GetType(), out keyValueExtractor)) + { break; + } - if (keyValueExtractor.Key == null) + var propertyValue = TryParseKeyValueProperty(keyValueExtractor, property); + if (!propertyValue.HasValue) { - if (!TryLookupExtractor(stateExractor, property.GetType(), out keyValueExtractor)) - break; + continue; } - AddKeyValueProperty(scope, keyValueExtractor, property); + propertyList = propertyList ?? new List>(); + propertyList.Add(propertyValue.Value); } - scope.AddDispose(CreateDiagnosticLogicalContext(scopePropertyCollection)); - return scope; + return CreateScopeProperties(scopePropertyCollection, propertyList); } - public static IDisposable CaptureScopeProperty(TState scopeProperty, ConcurrentDictionary, Func>> stateExractor) + public static IDisposable CaptureScopeProperty(TState scopeProperty, ExtractorDictionary stateExractor) { if (!TryLookupExtractor(stateExractor, scopeProperty.GetType(), out var keyValueExtractor)) + { return CreateDiagnosticLogicalContext(scopeProperty); + } + + var propertyValue = TryParseKeyValueProperty(keyValueExtractor, scopeProperty); + if (!propertyValue.HasValue) + { + return CreateDiagnosticLogicalContext(scopeProperty); + } - var scope = new ScopeProperties(); - AddKeyValueProperty(scope, keyValueExtractor, scopeProperty); - scope.AddDispose(CreateDiagnosticLogicalContext(scopeProperty)); - return scope; + return new ScopeProperties(CreateDiagnosticLogicalContext(scopeProperty), MappedDiagnosticsLogicalContext.SetScoped(propertyValue.Value.Key, propertyValue.Value.Value)); } - private static void AddKeyValueProperty(ScopeProperties scope, KeyValuePair, Func> keyValueExtractor, object property) + private static KeyValuePair? TryParseKeyValueProperty(KeyValuePair, Func> keyValueExtractor, object property) { + string propertyName = null; + try { var propertyKey = keyValueExtractor.Key.Invoke(property); + propertyName = propertyKey?.ToString() ?? string.Empty; var propertyValue = keyValueExtractor.Value.Invoke(property); - scope.AddProperty(propertyKey?.ToString() ?? string.Empty, propertyValue); + return new KeyValuePair(propertyName, propertyValue); } catch (Exception ex) { - InternalLogger.Debug(ex, "Exception in BeginScope add property"); + InternalLogger.Debug(ex, "Exception in BeginScope add property {0}", propertyName); + return null; } } - private static bool TryLookupExtractor(ConcurrentDictionary, Func>> stateExractor, Type propertyType, out KeyValuePair, Func> keyValueExtractor) + private static bool TryLookupExtractor(ExtractorDictionary stateExractor, Type propertyType, + out KeyValuePair, Func> keyValueExtractor) { if (!stateExractor.TryGetValue(propertyType, out keyValueExtractor)) { @@ -172,63 +221,35 @@ private static bool TryBuildExtractor(Type propertyType, out KeyValuePair)) + { return false; + } var keyPropertyInfo = itemType.GetDeclaredProperty("Key"); var valuePropertyInfo = itemType.GetDeclaredProperty("Value"); if (valuePropertyInfo == null || keyPropertyInfo == null) + { return false; + } - var keyValuePairObjParam = System.Linq.Expressions.Expression.Parameter(typeof(object), "pair"); - var keyValuePairTypeParam = System.Linq.Expressions.Expression.Convert(keyValuePairObjParam, propertyType); + var keyValuePairObjParam = Expression.Parameter(typeof(object), "pair"); + var keyValuePairTypeParam = Expression.Convert(keyValuePairObjParam, propertyType); - var propertyKeyAccess = System.Linq.Expressions.Expression.Property(keyValuePairTypeParam, keyPropertyInfo); - var propertyKeyAccessObj = System.Linq.Expressions.Expression.Convert(propertyKeyAccess, typeof(object)); - var propertyKeyLambda = System.Linq.Expressions.Expression.Lambda>(propertyKeyAccessObj, keyValuePairObjParam).Compile(); + var propertyKeyAccess = Expression.Property(keyValuePairTypeParam, keyPropertyInfo); + var propertyKeyAccessObj = Expression.Convert(propertyKeyAccess, typeof(object)); + var propertyKeyLambda = Expression.Lambda>(propertyKeyAccessObj, keyValuePairObjParam).Compile(); - var propertyValueAccess = System.Linq.Expressions.Expression.Property(keyValuePairTypeParam, valuePropertyInfo); - var propertyValueLambda = System.Linq.Expressions.Expression.Lambda>(propertyValueAccess, keyValuePairObjParam).Compile(); + var propertyValueAccess = Expression.Property(keyValuePairTypeParam, valuePropertyInfo); + var propertyValueLambda = Expression.Lambda>(propertyValueAccess, keyValuePairObjParam).Compile(); keyValueExtractor = new KeyValuePair, Func>(propertyKeyLambda, propertyValueLambda); return true; } - public void AddDispose(IDisposable disposable) - { - if (disposable != null) - Properties.Push(disposable); - } - - public void AddProperty(string key, object value) - { - AddDispose(MappedDiagnosticsLogicalContext.SetScoped(key, value)); - } - - public void Dispose() - { - var properties = _properties; - if (properties != null) - { - IDisposable property = null; - while (properties.Count > 0) - { - try - { - property = properties.Pop(); - property.Dispose(); - } - catch (Exception ex) - { - InternalLogger.Debug(ex, "Exception in BeginScope dispose property {0}", property); - } - } - } - } - public override string ToString() { - return (_properties?.Count > 0 ? _properties.Peek()?.ToString() : null) ?? base.ToString(); + return _ndlcScope?.ToString() ?? base.ToString(); } } } -} +} \ No newline at end of file diff --git a/src/NLog.Extensions.Logging/Logging/NLogLogger.cs b/src/NLog.Extensions.Logging/Logging/NLogLogger.cs index 46b9b986..5bc588b9 100644 --- a/src/NLog.Extensions.Logging/Logging/NLogLogger.cs +++ b/src/NLog.Extensions.Logging/Logging/NLogLogger.cs @@ -441,7 +441,7 @@ public IDisposable BeginScope(TState state) return NullScope.Instance; } - if (_beginScopeParser != null) + if (_options.CaptureMessageProperties) { return _beginScopeParser.ParseBeginScope(state) ?? NullScope.Instance; } diff --git a/src/NLog.Extensions.Logging/Logging/NLogLoggerProvider.cs b/src/NLog.Extensions.Logging/Logging/NLogLoggerProvider.cs index 86b92ca6..5e36d1ce 100644 --- a/src/NLog.Extensions.Logging/Logging/NLogLoggerProvider.cs +++ b/src/NLog.Extensions.Logging/Logging/NLogLoggerProvider.cs @@ -15,7 +15,7 @@ namespace NLog.Extensions.Logging #endif public class NLogLoggerProvider : Microsoft.Extensions.Logging.ILoggerProvider { - private NLogBeginScopeParser _beginScopeParser; + private readonly NLogBeginScopeParser _beginScopeParser; /// /// NLog options @@ -64,10 +64,7 @@ public NLogLoggerProvider(NLogProviderOptions options, LogFactory logFactory) /// New Logger public Microsoft.Extensions.Logging.ILogger CreateLogger(string name) { - var beginScopeParser = ((Options?.CaptureMessageProperties ?? true) && (Options?.IncludeScopes ?? true)) - ? (_beginScopeParser ?? System.Threading.Interlocked.CompareExchange(ref _beginScopeParser, new NLogBeginScopeParser(Options), null)) - : null; - return new NLogLogger(LogFactory.GetLogger(name), Options, beginScopeParser); + return new NLogLogger(LogFactory.GetLogger(name), Options, _beginScopeParser); } /// @@ -98,6 +95,7 @@ private static void RegisterHiddenAssembliesForCallSite() { InternalLogger.Debug("Hide assemblies for callsite"); LogManager.AddHiddenAssembly(typeof(NLogLoggerProvider).GetTypeInfo().Assembly); + NLog.Config.ConfigurationItemFactory.Default.RegisterItemsFromAssembly(typeof(NLogLoggerProvider).GetTypeInfo().Assembly); #if !NETCORE1_0 var allAssemblies = AppDomain.CurrentDomain.GetAssemblies(); diff --git a/src/NLog.Extensions.Logging/NLog.Extensions.Logging.csproj b/src/NLog.Extensions.Logging/NLog.Extensions.Logging.csproj index 3978eb4b..02f54712 100644 --- a/src/NLog.Extensions.Logging/NLog.Extensions.Logging.csproj +++ b/src/NLog.Extensions.Logging/NLog.Extensions.Logging.csproj @@ -18,13 +18,7 @@ For ASP.NET Core, use NLog.Web.AspNetCore: https://www.nuget.org/packages/NLog.W NLog;Microsoft.Extensions.Logging;log;logfiles;netcore -- Updated to Microsoft.Extensions.Logging ver. 2.1.0 -- ${configsetting} layout renderer now depends on AddNLog-call to provide IConfiguration-interface -- NLogLoggerProvider options now binds to IConfiguration (Using section Logging:NLog) -- Now builds snupkg packages - -Full changelog: https://github.com/NLog/NLog.Extensions.Logging/blob/master/CHANGELOG.MD - +See changelog: https://github.com/NLog/NLog.Extensions.Logging/blob/master/CHANGELOG.MD https://github.com/NLog/NLog.Extensions.Logging https://github.com/NLog/NLog.Extensions.Logging/blob/master/LICENSE @@ -62,7 +56,7 @@ Full changelog: https://github.com/NLog/NLog.Extensions.Logging/blob/master/CHAN $(DefineConstants);NETSTANDARD - + diff --git a/src/NLog.Extensions.Logging/NLog.Extensions.Logging.csproj.DotSettings b/src/NLog.Extensions.Logging/NLog.Extensions.Logging.csproj.DotSettings new file mode 100644 index 00000000..c83f823b --- /dev/null +++ b/src/NLog.Extensions.Logging/NLog.Extensions.Logging.csproj.DotSettings @@ -0,0 +1,6 @@ + + True + True + True + True + True \ No newline at end of file diff --git a/test/NLog.Extensions.Logging.Tests/ConfigSettingLayoutRendererTests.cs b/test/NLog.Extensions.Logging.Tests/ConfigSettingLayoutRendererTests.cs index cfe3972f..9e0a0f8e 100644 --- a/test/NLog.Extensions.Logging.Tests/ConfigSettingLayoutRendererTests.cs +++ b/test/NLog.Extensions.Logging.Tests/ConfigSettingLayoutRendererTests.cs @@ -15,7 +15,7 @@ public void ConfigSettingFallbackDefaultLookup() Assert.Equal("MyTableName", result); } -#if NETSTANDARD2_0 || NET461 +#if !NETCORE1_0 [Fact] public void ConfigSettingGlobalConfigLookup() { diff --git a/test/NLog.Extensions.Logging.Tests/NLog.Extensions.Logging.Tests.csproj b/test/NLog.Extensions.Logging.Tests/NLog.Extensions.Logging.Tests.csproj index 29ae213d..5f520c7b 100644 --- a/test/NLog.Extensions.Logging.Tests/NLog.Extensions.Logging.Tests.csproj +++ b/test/NLog.Extensions.Logging.Tests/NLog.Extensions.Logging.Tests.csproj @@ -14,6 +14,10 @@ true + + $(DefineConstants);NETCORE1_0 + + diff --git a/test/NLog.Extensions.Logging.Tests/NLogLoggingConfigurationTests.cs b/test/NLog.Extensions.Logging.Tests/NLogLoggingConfigurationTests.cs new file mode 100644 index 00000000..0ee27934 --- /dev/null +++ b/test/NLog.Extensions.Logging.Tests/NLogLoggingConfigurationTests.cs @@ -0,0 +1,69 @@ +using System.Collections.Generic; +using System.Linq; +using Microsoft.Extensions.Configuration; +using NLog.Targets; +using NLog.Targets.Wrappers; +using Xunit; + +namespace NLog.Extensions.Logging.Tests +{ + public class NLogLoggingConfigurationTests + { +#if !NETCORE1_0 + [Fact] + public void LoadSimpleConfig() + { + var memoryConfig = CreateMemoryConfigConsoleTargetAndRule(); + + memoryConfig["NLog:Targets:file:type"] = "File"; + memoryConfig["NLog:Targets:file:fileName"] = "hello.txt"; + memoryConfig["NLog:Targets:console:type"] = "Console"; + + var logConfig = CreateNLogLoggingConfigurationWithNLogSection(memoryConfig); + + Assert.Single(logConfig.LoggingRules); + Assert.Equal(2, logConfig.LoggingRules[0].Targets.Count); + Assert.Equal(2, logConfig.AllTargets.Count); + Assert.Single(logConfig.AllTargets.Where(t => t is FileTarget)); + Assert.Single(logConfig.AllTargets.Where(t => t is ConsoleTarget)); + } + + [Fact] + public void LoadWrapperConfig() + { + var memoryConfig = CreateMemoryConfigConsoleTargetAndRule(); + memoryConfig["NLog:Targets:file:type"] = "AsyncWrapper"; + memoryConfig["NLog:Targets:file:target:wrappedFile:type"] = "File"; + memoryConfig["NLog:Targets:file:target:wrappedFile:fileName"] = "hello.txt"; + + var logConfig = CreateNLogLoggingConfigurationWithNLogSection(memoryConfig); + + Assert.Single(logConfig.LoggingRules); + Assert.Equal(2, logConfig.LoggingRules[0].Targets.Count); + Assert.Equal(3, logConfig.AllTargets.Count); + Assert.Single(logConfig.AllTargets.Where(t => t is AsyncTargetWrapper)); + Assert.Single(logConfig.AllTargets.Where(t => t is FileTarget)); + Assert.Single(logConfig.AllTargets.Where(t => t is ConsoleTarget)); + } + + private static NLogLoggingConfiguration CreateNLogLoggingConfigurationWithNLogSection(IDictionary memoryConfig) + { + var configuration = new ConfigurationBuilder().AddInMemoryCollection(memoryConfig).Build(); + var logFactory = new LogFactory(); + var logConfig = new NLogLoggingConfiguration(configuration.GetSection("NLog"), logFactory); + return logConfig; + } + + private static Dictionary CreateMemoryConfigConsoleTargetAndRule() + { + var memoryConfig = new Dictionary(); + memoryConfig["NLog:Rules:0:logger"] = "*"; + memoryConfig["NLog:Rules:0:minLevel"] = "Trace"; + memoryConfig["NLog:Rules:0:writeTo"] = "File,Console"; + memoryConfig["NLog:Targets:console:type"] = "Console"; + + return memoryConfig; + } +#endif + } +}