diff --git a/.travis.yml b/.travis.yml index 9d00bbe4..02fac129 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,10 +18,13 @@ install: # Restore dependencies - dotnet restore src/NLog.Extensions.Logging - dotnet restore src/NLog.Extensions.Hosting + - dotnet restore src/NLog.Extensions.Configuration - dotnet restore test/NLog.Extensions.Hosting.Tests - dotnet restore test/NLog.Extensions.Logging.Tests + - dotnet restore test/NLog.Extensions.Configuration.Tests script: # Run tests - dotnet test test/NLog.Extensions.Hosting.Tests --configuration Release --framework netcoreapp2.0 - dotnet test test/NLog.Extensions.Logging.Tests --configuration Release --framework netcoreapp2.0 + - dotnet test test/NLog.Extensions.Configuration.Tests --configuration Release --framework netcoreapp2.0 diff --git a/NLog.Extensions.Logging.sln b/NLog.Extensions.Logging.sln index 62e12c90..21a650a4 100644 --- a/NLog.Extensions.Logging.sln +++ b/NLog.Extensions.Logging.sln @@ -20,6 +20,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NLog.Extensions.Logging.Tes EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HostingExample", "examples\NetCore2\HostingExample\HostingExample.csproj", "{07D358DF-D77A-434B-B034-95785DF7106F}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NLog.Extensions.Configuration", "src\NLog.Extensions.Configuration\NLog.Extensions.Configuration.csproj", "{AE82D026-CE85-48CC-BFFE-2D5C1556CC2B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NLog.Extensions.Configuration.Tests", "test\NLog.Extensions.Configuration.Tests\NLog.Extensions.Configuration.Tests.csproj", "{78A9081B-066B-4B34-BBD7-764D53CE4AA3}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -50,6 +54,14 @@ Global {07D358DF-D77A-434B-B034-95785DF7106F}.Debug|Any CPU.Build.0 = Debug|Any CPU {07D358DF-D77A-434B-B034-95785DF7106F}.Release|Any CPU.ActiveCfg = Release|Any CPU {07D358DF-D77A-434B-B034-95785DF7106F}.Release|Any CPU.Build.0 = Release|Any CPU + {AE82D026-CE85-48CC-BFFE-2D5C1556CC2B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AE82D026-CE85-48CC-BFFE-2D5C1556CC2B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AE82D026-CE85-48CC-BFFE-2D5C1556CC2B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AE82D026-CE85-48CC-BFFE-2D5C1556CC2B}.Release|Any CPU.Build.0 = Release|Any CPU + {78A9081B-066B-4B34-BBD7-764D53CE4AA3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {78A9081B-066B-4B34-BBD7-764D53CE4AA3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {78A9081B-066B-4B34-BBD7-764D53CE4AA3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {78A9081B-066B-4B34-BBD7-764D53CE4AA3}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -61,6 +73,8 @@ Global {0DC000BA-2DF8-48E5-A7BC-D76CB9D3FC61} = {FBD2E07B-F25B-4D2F-AEF6-6D1E10F1E523} {DC42BF57-6316-4FCA-AD33-48FFDAFB4712} = {FBD2E07B-F25B-4D2F-AEF6-6D1E10F1E523} {07D358DF-D77A-434B-B034-95785DF7106F} = {BD106966-02BE-4137-B9DC-4ECE56B4C204} + {AE82D026-CE85-48CC-BFFE-2D5C1556CC2B} = {C21FD102-21B1-46DB-AD62-86692558AD01} + {78A9081B-066B-4B34-BBD7-764D53CE4AA3} = {FBD2E07B-F25B-4D2F-AEF6-6D1E10F1E523} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {46DF0C22-7B6A-4A64-BC63-7B2F6A14F334} diff --git a/appveyor.yml b/appveyor.yml index ca90b067..0f48a4ab 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -23,8 +23,9 @@ artifacts: test_script: - nuget.exe install OpenCover -ExcludeVersion -DependencyVersion Ignore - OpenCover\tools\OpenCover.Console.exe -register:user -target:"C:/Program Files/dotnet/dotnet.exe" -targetargs:"test -f netcoreapp1.1 NLog.Extensions.Logging.Tests" -filter:"+[NLog.Extensions.Logging]* +[NLog.Extensions.Hosting]* -[NLog.Extensions.Logging.Tests]* -[NLog.Extensions.Hosting.Tests]*" -output:"coverage.xml" -oldstyle -targetdir:"test" - - OpenCover\tools\OpenCover.Console.exe -register:user -mergeoutput -target:"C:/Program Files/dotnet/dotnet.exe" -targetargs:"test -f netcoreapp2.0 NLog.Extensions.Logging.Tests" -filter:"+[NLog.Extensions.Logging]* +[NLog.Extensions.Hosting]* -[NLog.Extensions.Logging.Tests]* -[NLog.Extensions.Hosting.Tests]*" -output:"coverage.xml" -oldstyle -targetdir:"test" + - OpenCover\tools\OpenCover.Console.exe -register:user -mergeoutput -target:"C:/Program Files/dotnet/dotnet.exe" -targetargs:"test -f netcoreapp2.0 NLog.Extensions.Logging.Tests" -filter:"+[NLog.Extensions.Logging]* -[NLog.Extensions.Logging.Tests]*" -output:"coverage.xml" -oldstyle -targetdir:"test" - OpenCover\tools\OpenCover.Console.exe -register:user -mergeoutput -target:"C:/Program Files/dotnet/dotnet.exe" -targetargs:"test -f netcoreapp2.0 NLog.Extensions.Hosting.Tests" -filter:"+[NLog.Extensions.Logging]* +[NLog.Extensions.Hosting]* -[NLog.Extensions.Logging.Tests]* -[NLog.Extensions.Hosting.Tests]*" -output:"coverage.xml" -oldstyle -targetdir:"test" + - OpenCover\tools\OpenCover.Console.exe -register:user -mergeoutput -target:"C:/Program Files/dotnet/dotnet.exe" -targetargs:"test -f netcoreapp2.0 NLog.Extensions.Configuration.Tests" -filter:"+[NLog.Extensions.Logging]* +[NLog.Extensions.Hosting]* +[NLog.Extensions.Configuration]* -[NLog.Extensions.Logging.Tests]* -[NLog.Extensions.Hosting.Tests]* -[NLog.Extensions.Configuration.Tests]*" -output:"coverage.xml" -oldstyle -targetdir:"test" - pip install codecov - codecov -f "coverage.xml" - ps: .\run-sonar.ps1 diff --git a/build.ps1 b/build.ps1 index 16b0a697..cbc00a74 100644 --- a/build.ps1 +++ b/build.ps1 @@ -17,6 +17,10 @@ dotnet restore .\src\NLog.Extensions.Hosting\ if (-Not $LastExitCode -eq 0) { exit $LastExitCode } +dotnet restore .\src\NLog.Extensions.Configuration\ +if (-Not $LastExitCode -eq 0) + { exit $LastExitCode } + msbuild /t:Pack .\src\NLog.Extensions.Logging\ /p:targetFrameworks='"net451;net461;netstandard1.3;netstandard1.5;netstandard2.0"' /p:VersionPrefix=$versionPrefix /p:VersionSuffix=$versionSuffix /p:FileVersion=$versionFile /p:ProductVersion=$versionProduct /p:Configuration=Release /p:IncludeSymbols=true /p:PackageOutputPath=..\..\artifacts /verbosity:minimal if (-Not $LastExitCode -eq 0) { exit $LastExitCode } @@ -25,4 +29,9 @@ msbuild /t:Pack .\src\NLog.Extensions.Hosting\ /p:targetFrameworks='"netstandard if (-Not $LastExitCode -eq 0) { exit $LastExitCode } +msbuild /t:Pack .\src\NLog.Extensions.Configuration\ /p:targetFrameworks='"net451;net461;netstandard1.3;netstandard1.5;netstandard2.0"' /p:VersionPrefix=$versionPrefix /p:VersionSuffix=$versionSuffix /p:FileVersion=$versionFile /p:ProductVersion=$versionProduct /p:Configuration=Release /p:IncludeSymbols=true /p:PackageOutputPath=..\..\artifacts /verbosity:minimal +if (-Not $LastExitCode -eq 0) + { exit $LastExitCode } + + exit $LastExitCode diff --git a/src/NLog.Extensions.Configuration/ConfigSettingLayoutRenderer.cs b/src/NLog.Extensions.Configuration/ConfigSettingLayoutRenderer.cs new file mode 100644 index 00000000..5cf22a37 --- /dev/null +++ b/src/NLog.Extensions.Configuration/ConfigSettingLayoutRenderer.cs @@ -0,0 +1,184 @@ +using System; +using System.Collections.Generic; +using Microsoft.Extensions.Configuration; +using NLog.Config; +using NLog.Layouts; +using NLog.LayoutRenderers; +using System.Text; + +namespace NLog.Extensions.Configuration +{ + /// + /// Layout renderer that can lookup values from Microsoft Extension Configuration Container (json, xml, ini) + /// + /// Not to be confused with NLog.AppConfig that includes ${appsetting} + /// + /// Example: appsettings.json + /// { + /// "Mode":"Prod", + /// "Options":{ + /// "StorageConnectionString":"UseDevelopmentStorage=true", + /// } + /// } + /// + /// Config Setting Lookup: + /// ${configsetting:name=Mode} = "Prod" + /// ${configsetting:name=Options.StorageConnectionString} = "UseDevelopmentStorage=true" + /// ${configsetting:name=Options.TableName:default=MyTable} = "MyTable" + /// + /// Config Setting Lookup Cached: + /// ${configsetting:cached=True:name=Mode} + /// + [LayoutRenderer("configsetting")] + public class ConfigSettingLayoutRenderer : LayoutRenderer + { + internal IConfigurationRoot _configurationRoot; + + private static readonly Dictionary> _cachedConfigFiles = new Dictionary>(); + + /// + /// Global Configuration Container. Used if has default value + /// + public static IConfiguration DefaultConfiguration { get; set; } + + /// + /// Name of the setting + /// + [RequiredParameter] + [DefaultParameter] + public string Name { get => _name; set => _name = value?.Replace(".", ":"); } + private string _name; + + /// + /// The default value to render if the setting value is null. + /// + public string Default { get; set; } + + /// + /// Configuration FileName (Multiple filenames can be split using '|' pipe-character) + /// + /// Relative paths are automatically prefixed with ${basedir} + public Layout FileName { get; set; } = DefaultFileName; + private const string DefaultFileName = "appsettings.json|appsettings.${environment:variable=ASPNETCORE_ENVIRONMENT}.json"; + + /// + protected override void InitializeLayoutRenderer() + { + _configurationRoot = null; + base.InitializeLayoutRenderer(); + } + + /// + protected override void CloseLayoutRenderer() + { + _configurationRoot = null; + base.CloseLayoutRenderer(); + } + + /// + protected override void Append(StringBuilder builder, LogEventInfo logEvent) + { + if (string.IsNullOrEmpty(_name)) + return; + + string value = null; + var configurationRoot = TryGetConfigurationRoot(); + if (configurationRoot != null) + { + value = configurationRoot[_name]; + } + + builder.Append(value ?? Default); + } + + private IConfiguration TryGetConfigurationRoot() + { + if (DefaultConfiguration != null) + { + var simpleLayout = FileName as SimpleLayout; + if (simpleLayout == null || string.IsNullOrEmpty(simpleLayout.Text) || ReferenceEquals(simpleLayout.Text, DefaultFileName)) + { + if (_configurationRoot != null) + _configurationRoot = null; + return DefaultConfiguration; + } + } + + if (_configurationRoot != null) + return _configurationRoot; + + var fileNames = FileName?.Render(LogEventInfo.CreateNullEvent()); + if (!string.IsNullOrEmpty(fileNames)) + { + return _configurationRoot = LoadFileConfiguration(fileNames); + } + + return null; + } + + private IConfigurationRoot LoadFileConfiguration(string fileNames) + { + lock (_cachedConfigFiles) + { + if (_cachedConfigFiles.TryGetValue(fileNames, out var wearkConfigRoot) && wearkConfigRoot.TryGetTarget(out var configRoot)) + { + return configRoot; + } + else + { + configRoot = BuildConfigurationRoot(fileNames); + _cachedConfigFiles[fileNames] = new WeakReference(configRoot); + return configRoot; + } + } + } + + private static IConfigurationRoot BuildConfigurationRoot(string fileNames) + { + var configBuilder = new ConfigurationBuilder(); + string baseDir = null; + foreach (var fileName in fileNames.Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries)) + { + var fullPath = fileName; + if (!System.IO.Path.IsPathRooted(fullPath)) + { + if (baseDir == null) + baseDir = new BaseDirLayoutRenderer().Render(LogEventInfo.CreateNullEvent()) ?? string.Empty; + fullPath = System.IO.Path.Combine(baseDir, fileName); + } + + AddFileConfiguration(configBuilder, fullPath); + } + + return configBuilder.Build(); + } + + private static void AddFileConfiguration(ConfigurationBuilder configBuilder, string fullPath) + { + if (System.IO.File.Exists(fullPath)) + { + // NOTE! Decided not to monitor for changes, as it would require access to dipose this monitoring again + if (string.Equals(System.IO.Path.GetExtension(fullPath), ".json", StringComparison.OrdinalIgnoreCase)) + { + configBuilder.AddJsonFile(fullPath); + } + else if (string.Equals(System.IO.Path.GetExtension(fullPath), ".xml", StringComparison.OrdinalIgnoreCase)) + { + configBuilder.AddXmlFile(fullPath); + } + else if (string.Equals(System.IO.Path.GetExtension(fullPath), ".ini", StringComparison.OrdinalIgnoreCase)) + { + configBuilder.AddIniFile(fullPath); + } + else + { + Common.InternalLogger.Info("configSetting - Skipping FileName with unknown file-extension: {0}", fullPath); + } + } + else + { + Common.InternalLogger.Info("configSetting - Skipping FileName as file doesnt't exists: {0}", fullPath); + } + } + } +} diff --git a/src/NLog.Extensions.Configuration/NLog.Extensions.Configuration.csproj b/src/NLog.Extensions.Configuration/NLog.Extensions.Configuration.csproj new file mode 100644 index 00000000..8828889d --- /dev/null +++ b/src/NLog.Extensions.Configuration/NLog.Extensions.Configuration.csproj @@ -0,0 +1,89 @@ + + + + net451;net461;netstandard1.3;netstandard1.5;netstandard2.0 + full + true + true + true + + NLog.Extensions.Configuration v$(ProductVersion) + $(ProductVersion) + + NLog + Julian Verdurmen;CoCo Lin + NLog extensions for Microsoft.Extensions.Configuration + https://github.com/NLog/NLog.Extensions.Logging + https://github.com/NLog/NLog.Extensions.Logging/blob/master/LICENSE + https://nlog-project.org/NConfig.png + https://github.com/NLog/NLog.Extensions.Logging.git + git + NLog;Microsoft.Extensions.Configuration;log;logfiles;netcore + 1.0: Initial release + + + {0A5EC30A-2DC6-4EB3-BF1E-2E82BA83220F} + true + 1.0.0.0 + ..\NLog.snk + true + + + + NLog.Extensions.Configuration for .NET Framework 4.6.1 + true + + + NLog.Extensions.Configuration for .NET Framework 4.5.1 + true + + + NLog.Extensions.Configuration for NetStandard 1.3 + + + NLog.Extensions.Configuration for NetStandard 1.5 + + + NLog.Extensions.v for NetStandard 2.0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/NLog.Extensions.Configuration/Properties/AssemblyInfo.cs b/src/NLog.Extensions.Configuration/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..3958956b --- /dev/null +++ b/src/NLog.Extensions.Configuration/Properties/AssemblyInfo.cs @@ -0,0 +1,6 @@ +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +[assembly: ComVisible(false)] + +[assembly: InternalsVisibleTo("NLog.Extensions.Configuration.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100772391E63C104728ADCF18E2390474262559FA7F34A4215848F43288CDE875DCC92A06222E9BE0592B211FF74ADBB5D21A7AAB5522B540B1735F2F03279221056FEDBE7E534073DABEE9DB48F8ECEBCF1DC98A95576E45CBEFF5FE7C4842859451AB2DAE7A8370F1B2F7A529D2CA210E3E844D973523D73D193DF6C17F1314A6")] \ No newline at end of file diff --git a/test/NLog.Extensions.Configuration.Tests/ConfigSettingLayoutRendererTests.cs b/test/NLog.Extensions.Configuration.Tests/ConfigSettingLayoutRendererTests.cs new file mode 100644 index 00000000..5639a139 --- /dev/null +++ b/test/NLog.Extensions.Configuration.Tests/ConfigSettingLayoutRendererTests.cs @@ -0,0 +1,62 @@ +using System.Collections.Generic; +using Microsoft.Extensions.Configuration; +using Xunit; + +namespace NLog.Extensions.Configuration.Tests +{ + public class ConfigSettingLayoutRendererTests + { + [Fact] + public void ConfigSettingSimpleLookup() + { + ConfigSettingLayoutRenderer.DefaultConfiguration = null; + var layoutRenderer = new ConfigSettingLayoutRenderer() { Name = "Mode" }; + var result = layoutRenderer.Render(LogEventInfo.CreateNullEvent()); + Assert.Equal("Prod", result); + } + + [Fact] + public void ConfigSettingOptimizedLookup() + { + ConfigSettingLayoutRenderer.DefaultConfiguration = null; + var layoutRenderer1 = new ConfigSettingLayoutRenderer() { Name = "Mode" }; + var result1 = layoutRenderer1.Render(LogEventInfo.CreateNullEvent()); + Assert.Equal("Prod", result1); + + var layoutRenderer2 = new ConfigSettingLayoutRenderer() { Name = "Options.SqlConnectionString" }; + var result2 = layoutRenderer2.Render(LogEventInfo.CreateNullEvent()); + Assert.Equal("YourProdStorageConnectionString", result2); + + Assert.Same(layoutRenderer1._configurationRoot, layoutRenderer2._configurationRoot); + } + + [Fact] + public void ConfigSettingFallbackDefaultLookup() + { + ConfigSettingLayoutRenderer.DefaultConfiguration = null; + var layoutRenderer = new ConfigSettingLayoutRenderer() { Name = "Options.TableName", Default = "MyTableName" }; + var result = layoutRenderer.Render(LogEventInfo.CreateNullEvent()); + Assert.Equal("MyTableName", result); + } + + [Fact] + public void ConfigSettingCustomeFileNameLookup() + { + ConfigSettingLayoutRenderer.DefaultConfiguration = null; + var layoutRenderer = new ConfigSettingLayoutRenderer() { Name = "Mode", FileName = "appsettings.json" }; + var result = layoutRenderer.Render(LogEventInfo.CreateNullEvent()); + Assert.Equal("Prod", result); + } + + [Fact] + public void ConfigSettingGlobalConfigLookup() + { + var memoryConfig = new Dictionary(); + memoryConfig["Mode"] = "Test"; + ConfigSettingLayoutRenderer.DefaultConfiguration = new ConfigurationBuilder().AddInMemoryCollection(memoryConfig).Build(); + var layoutRenderer = new ConfigSettingLayoutRenderer() { Name = "Mode", FileName = "" }; + var result = layoutRenderer.Render(LogEventInfo.CreateNullEvent()); + Assert.Equal("Test", result); + } + } +} diff --git a/test/NLog.Extensions.Configuration.Tests/NLog.Extensions.Configuration.Tests.csproj b/test/NLog.Extensions.Configuration.Tests/NLog.Extensions.Configuration.Tests.csproj new file mode 100644 index 00000000..74638da3 --- /dev/null +++ b/test/NLog.Extensions.Configuration.Tests/NLog.Extensions.Configuration.Tests.csproj @@ -0,0 +1,33 @@ + + + PackageReference + + netcoreapp2.0 + Library + false + full + true + + true + false + ..\..\src\NLog.snk + true + + + + + + + + + + + + + PreserveNewest + + + Always + + + \ No newline at end of file diff --git a/test/NLog.Extensions.Configuration.Tests/Properties/AssemblyInfo.cs b/test/NLog.Extensions.Configuration.Tests/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..97f073b7 --- /dev/null +++ b/test/NLog.Extensions.Configuration.Tests/Properties/AssemblyInfo.cs @@ -0,0 +1,14 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +[assembly: Xunit.CollectionBehavior(DisableTestParallelization = true)] \ No newline at end of file diff --git a/test/NLog.Extensions.Configuration.Tests/appsettings.json b/test/NLog.Extensions.Configuration.Tests/appsettings.json new file mode 100644 index 00000000..944de05a --- /dev/null +++ b/test/NLog.Extensions.Configuration.Tests/appsettings.json @@ -0,0 +1,7 @@ +{ + "Mode": "Prod", + "Options": { + "SqlConnectionString": "YourProdStorageConnectionString", + "Language": "English" + } +} \ No newline at end of file