Skip to content

Commit

Permalink
Load NLog config from appsettings.json using NLogLoggingConfiguration
Browse files Browse the repository at this point in the history
  • Loading branch information
snakefoot committed Apr 1, 2019
1 parent 2460b35 commit 6c8c197
Show file tree
Hide file tree
Showing 13 changed files with 282 additions and 78 deletions.
1 change: 1 addition & 0 deletions NLog.Extensions.Logging.sln
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions build.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -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(""))
Expand Down
1 change: 0 additions & 1 deletion examples/NetCore2/ConsoleExample/ConsoleExample.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.1.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="2.1.0" />
<PackageReference Include="NLog.Schema" Version="4.5.11" />
</ItemGroup>

<ItemGroup>
Expand Down
3 changes: 3 additions & 0 deletions examples/NetCore2/ConsoleExample/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,16 @@ internal class Program
private static void Main()
{
var logger = LogManager.GetCurrentClassLogger();

try
{
var config = new ConfigurationBuilder()
.SetBasePath(System.IO.Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.Build();

LogManager.Configuration = new NLogLoggingConfiguration(config.GetSection("NLog"));

var servicesProvider = BuildDi(config);
using (servicesProvider as IDisposable)
{
Expand Down
38 changes: 38 additions & 0 deletions examples/NetCore2/ConsoleExample/appsettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
]
}
}
108 changes: 108 additions & 0 deletions src/NLog.Extensions.Logging/Config/NLogLoggingConfiguration.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Extensions.Configuration;
using NLog.Config;

namespace NLog.Extensions.Logging
{
/// <summary>
/// Configures NLog through Microsoft Extension Configuration section (Ex from appsettings.json)
/// </summary>
public class NLogLoggingConfiguration : LoggingConfigurationParser
{
private readonly Action<Object> _reloadConfiguration;

private class LoggingConfigurationElement : ILoggingConfigurationElement
{
readonly IConfigurationSection _configurationSection;
readonly string _nameOverride;

public bool AutoReload { get; private set; }

public LoggingConfigurationElement(IConfigurationSection configurationSection, bool topElement, string nameOverride = null)
{
_configurationSection = configurationSection;
_nameOverride = nameOverride;
if (topElement && bool.TryParse(configurationSection["autoreload"], out bool autoreload))
{
AutoReload = autoreload;
}
}

public string Name => _nameOverride ?? _configurationSection.Key;

public IEnumerable<KeyValuePair<string, string>> Values
{
get
{
var children = _configurationSection.GetChildren();
foreach (var child in children)
{
if (!child.GetChildren().Any())
yield return new KeyValuePair<string, string>(child.Key, child.Value);
}
if (_nameOverride != null)
yield return new KeyValuePair<string, string>("name", _configurationSection.Key);
}
}

public IEnumerable<ILoggingConfigurationElement> Children
{
get
{
var children = _configurationSection.GetChildren();
foreach (var child in children)
{
var firstChildValue = child?.GetChildren()?.FirstOrDefault();
if (firstChildValue != null)
{
if (_nameOverride == "target" && string.Equals(child.Key, "target", StringComparison.OrdinalIgnoreCase) && child.GetChildren().Count() == 1)
{
yield return new LoggingConfigurationElement(firstChildValue, false, "target");
}
else
{
string nameOverride = null;
if (string.Equals(_configurationSection.Key, "targets", StringComparison.OrdinalIgnoreCase))
nameOverride = "target";
yield return new LoggingConfigurationElement(child, false, nameOverride);
}
}
}
}
}
}

/// <summary>
/// Initializes a new instance of the <see cref="NLogLoggingConfiguration"/> class.
/// </summary>
/// <param name="nlogConfig">Configuration section to be read</param>
public NLogLoggingConfiguration(IConfigurationSection nlogConfig)
: this(nlogConfig, NLog.LogManager.LogFactory)
{
}

/// <summary>
/// Initializes a new instance of the <see cref="NLogLoggingConfiguration"/> class.
/// </summary>
/// <param name="nlogConfig">Configuration section to be read</param>
/// <param name="logFactory">The <see cref="LogFactory"/> to which to apply any applicable configuration values.</param>
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);
}
}
}
}
129 changes: 63 additions & 66 deletions src/NLog.Extensions.Logging/Logging/NLogBeginScopeParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,15 +48,7 @@ public static IDisposable CreateDiagnosticLogicalContext<T>(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)
{
Expand All @@ -67,39 +59,47 @@ public static IDisposable CreateDiagnosticLogicalContext<T>(T state)

private class ScopeProperties : IDisposable
{
Stack<IDisposable> _properties;

/// <summary>
/// Properties, never null and lazy init
/// </summary>
Stack<IDisposable> Properties => _properties ?? (_properties = new Stack<IDisposable>());
private readonly IDisposable _ndlcScope;
private readonly IDisposable _mldcScope;

public ScopeProperties(int initialCapacity = 0)
public ScopeProperties(IDisposable ndlcScope, IDisposable mldcScope)
{
if (initialCapacity > 0)
_properties = new Stack<IDisposable>(initialCapacity);
_ndlcScope = ndlcScope;
_mldcScope = mldcScope;
}

public static ScopeProperties CaptureScopeProperties(IReadOnlyList<KeyValuePair<string, object>> scopePropertyList)
private static IDisposable CreateScopeProperties(object scopeObject, IReadOnlyList<KeyValuePair<string, object>> propertyList)
{
ScopeProperties scope = new ScopeProperties(scopePropertyList.Count + 1);
if (propertyList?.Count > 0)
return new ScopeProperties(CreateDiagnosticLogicalContext(scopeObject), MappedDiagnosticsLogicalContext.SetScoped(propertyList));
else
return CreateDiagnosticLogicalContext(scopeObject);
}

for (int i = 0; i < scopePropertyList.Count; ++i)
public static IDisposable CaptureScopeProperties(IReadOnlyList<KeyValuePair<string, object>> scopePropertyList)
{
if (scopePropertyList.Count == 0 || scopePropertyList[scopePropertyList.Count - 1].Key != NLogLogger.OriginalFormatPropertyName)
{
var property = scopePropertyList[i];
if (i == scopePropertyList.Count - 1 && i > 0 && property.Key == NLogLogger.OriginalFormatPropertyName)
continue; // Handle BeginScope("Hello {World}", "Earth")

scope.AddProperty(property.Key, property.Value);
return CreateScopeProperties(scopePropertyList, scopePropertyList);
}
else
{
List<KeyValuePair<string, object>> propertyList = new List<KeyValuePair<string, object>>(scopePropertyList.Count - 1);
for (int 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")

scope.AddDispose(CreateDiagnosticLogicalContext(scopePropertyList));
return scope;
propertyList.Add(property);
}
return CreateScopeProperties(scopePropertyList, propertyList);
}
}

public static ScopeProperties CaptureScopeProperties(System.Collections.IEnumerable scopePropertyCollection, ConcurrentDictionary<Type, KeyValuePair<Func<object, object>, Func<object, object>>> stateExractor)
public static IDisposable CaptureScopeProperties(System.Collections.IEnumerable scopePropertyCollection, ConcurrentDictionary<Type, KeyValuePair<Func<object, object>, Func<object, object>>> stateExractor)
{
ScopeProperties scope = new ScopeProperties();
List<KeyValuePair<string, object>> propertyList = null;

var keyValueExtractor = default(KeyValuePair<Func<object, object>, Func<object, object>>);
foreach (var property in scopePropertyCollection)
Expand All @@ -113,35 +113,44 @@ public static ScopeProperties CaptureScopeProperties(System.Collections.IEnumera
break;
}

AddKeyValueProperty(scope, keyValueExtractor, property);
var propertyValue = TryParseKeyValueProperty(keyValueExtractor, property);
if (!propertyValue.HasValue)
continue;

propertyList = propertyList ?? new List<KeyValuePair<string, object>>();
propertyList.Add(propertyValue.Value);
}

scope.AddDispose(CreateDiagnosticLogicalContext(scopePropertyCollection));
return scope;
return CreateScopeProperties(scopePropertyCollection, propertyList);
}

public static IDisposable CaptureScopeProperty<TState>(TState scopeProperty, ConcurrentDictionary<Type, KeyValuePair<Func<object, object>, Func<object, object>>> stateExractor)
{
if (!TryLookupExtractor(stateExractor, scopeProperty.GetType(), out var keyValueExtractor))
return CreateDiagnosticLogicalContext(scopeProperty);

var scope = new ScopeProperties();
AddKeyValueProperty(scope, keyValueExtractor, scopeProperty);
scope.AddDispose(CreateDiagnosticLogicalContext(scopeProperty));
return scope;
var propertyValue = TryParseKeyValueProperty(keyValueExtractor, scopeProperty);
if (!propertyValue.HasValue)
return CreateDiagnosticLogicalContext(scopeProperty);

return new ScopeProperties(CreateDiagnosticLogicalContext(scopeProperty), MappedDiagnosticsLogicalContext.SetScoped(propertyValue.Value.Key, propertyValue.Value.Value));
}

private static void AddKeyValueProperty(ScopeProperties scope, KeyValuePair<Func<object, object>, Func<object, object>> keyValueExtractor, object property)
private static KeyValuePair<string,object>? TryParseKeyValueProperty(KeyValuePair<Func<object, object>, Func<object, object>> 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<string, object>(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;
}
}

Expand Down Expand Up @@ -193,41 +202,29 @@ private static bool TryBuildExtractor(Type propertyType, out KeyValuePair<Func<o
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)
try
{
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);
}
}
_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");
}
}

public override string ToString()
{
return (_properties?.Count > 0 ? _properties.Peek()?.ToString() : null) ?? base.ToString();
return _ndlcScope?.ToString() ?? base.ToString();
}
}
}
Expand Down
Loading

0 comments on commit 6c8c197

Please sign in to comment.