* Serilog support
This commit is contained in:
Hananiel Sarella 2019-03-21 23:14:38 -04:00 коммит произвёл GitHub
Родитель 0f958ce840
Коммит 5e51521a93
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
12 изменённых файлов: 1084 добавлений и 0 удалений

Просмотреть файл

@ -27,6 +27,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
stylecop.json = stylecop.json
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.Extensions.Logging.SerilogDynamicLogger", "src\Steeltoe.Extensions.Logging.SerilogDynamicLogger\Steeltoe.Extensions.Logging.SerilogDynamicLogger.csproj", "{C4F8B0B3-86C8-45AA-88EB-A18570B2754E}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.Extensions.Logging.SerilogDynamicLogger.Test", "test\Steeltoe.Extensions.Logging.SerilogDynamicLogger.Test\Steeltoe.Extensions.Logging.SerilogDynamicLogger.Test.csproj", "{DF66D833-EAC4-4C84-A80F-74CFBD2BF78F}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -41,6 +45,14 @@ Global
{CBD7C37D-6CDC-4058-9B68-68EDC87837FD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CBD7C37D-6CDC-4058-9B68-68EDC87837FD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CBD7C37D-6CDC-4058-9B68-68EDC87837FD}.Release|Any CPU.Build.0 = Release|Any CPU
{C4F8B0B3-86C8-45AA-88EB-A18570B2754E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C4F8B0B3-86C8-45AA-88EB-A18570B2754E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C4F8B0B3-86C8-45AA-88EB-A18570B2754E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C4F8B0B3-86C8-45AA-88EB-A18570B2754E}.Release|Any CPU.Build.0 = Release|Any CPU
{DF66D833-EAC4-4C84-A80F-74CFBD2BF78F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DF66D833-EAC4-4C84-A80F-74CFBD2BF78F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DF66D833-EAC4-4C84-A80F-74CFBD2BF78F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DF66D833-EAC4-4C84-A80F-74CFBD2BF78F}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -48,6 +60,8 @@ Global
GlobalSection(NestedProjects) = preSolution
{BCF90E1F-ADD2-42A2-8601-AB7A5C9FAFC4} = {26E1A61B-FA79-41BF-9C3B-5A04299F356A}
{CBD7C37D-6CDC-4058-9B68-68EDC87837FD} = {EEF15943-96DB-44B5-BA4A-1D11171AD74F}
{C4F8B0B3-86C8-45AA-88EB-A18570B2754E} = {26E1A61B-FA79-41BF-9C3B-5A04299F356A}
{DF66D833-EAC4-4C84-A80F-74CFBD2BF78F} = {EEF15943-96DB-44B5-BA4A-1D11171AD74F}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {F03D9805-6C7E-411E-A8D5-191ABE8D2F66}

Просмотреть файл

@ -0,0 +1,21 @@
// Copyright 2017 the original author or authors.
//
// 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.
namespace Steeltoe.Extensions.Logging.SerilogDynamicLogger
{
public interface ISerilogOptions
{
MinimumLevel MinimumLevel { get; set; }
}
}

Просмотреть файл

@ -0,0 +1,44 @@
// Copyright 2017 the original author or authors.
//
// 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.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Configuration;
using Microsoft.Extensions.Logging.Console;
using Microsoft.Extensions.Options;
using System.Linq;
namespace Steeltoe.Extensions.Logging.SerilogDynamicLogger
{
public static class SerilogBuilderExtensions
{
/// <summary>
/// Add Steeltoe logger wrapped in a <see cref="IDynamicLoggerProvider"/> that supports
/// dynamically controlling the minimum log level via management endpoints
/// </summary>
/// <param name="builder">The <see cref="ILoggingBuilder"/> for configuring the LoggerFactory </param>
/// <returns>The configured <see cref="ILoggingBuilder"/></returns>
public static ILoggingBuilder AddSerilogDynamicConsole(this ILoggingBuilder builder)
{
builder.AddFilter<DynamicLoggerProvider>(null, LogLevel.Trace);
builder.Services.AddSingleton<ISerilogOptions, SerilogOptions>();
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<ILoggerProvider, SerilogDynamicProvider>());
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<IOptionsChangeTokenSource<ConsoleLoggerOptions>, LoggerProviderOptionsChangeTokenSource<ConsoleLoggerOptions, ConsoleLoggerProvider>>());
builder.Services.AddSingleton<IDynamicLoggerProvider>((p) => p.GetServices<ILoggerProvider>().OfType<IDynamicLoggerProvider>().SingleOrDefault());
return builder;
}
}
}

Просмотреть файл

@ -0,0 +1,199 @@
// Copyright 2017 the original author or authors.
//
// 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.Configuration;
using Microsoft.Extensions.Logging;
using Serilog.AspNetCore;
using Serilog.Core;
using Serilog.Events;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using static Serilog.ConfigurationLoggerConfigurationExtensions;
namespace Steeltoe.Extensions.Logging.SerilogDynamicLogger
{
/// <summary>
/// A wrapper for the <see cref="Serilog.Logger"/> to dynamically set log levels
/// </summary>
public class SerilogDynamicProvider : IDynamicLoggerProvider
{
private Logger _globalLogger;
private ISerilogOptions _serilogOptions;
private ConcurrentDictionary<string, ILogger> _loggers = new ConcurrentDictionary<string, ILogger>();
private ConcurrentDictionary<string, LoggingLevelSwitch> _loggerSwitches = new ConcurrentDictionary<string, LoggingLevelSwitch>();
/// <summary>
/// Initializes a new instance of the <see cref="SerilogDynamicProvider"/> class.
/// Any Serilog settings can be passed in the IConfiguration as needed.
/// </summary>
/// <param name="configuration">Serilog readable <see cref="IConfiguration"/></param>
/// <param name="options">Subset of Serilog options managed by wrapper<see cref="ISerilogOptions"/></param>
public SerilogDynamicProvider(IConfiguration configuration, ISerilogOptions options = null)
{
if (configuration == null)
{
throw new ArgumentNullException(nameof(configuration));
}
_serilogOptions = options ?? new SerilogOptions(configuration);
// Add a level switch that controls the "Default" level at the root
var levelSwitch = new LoggingLevelSwitch(_serilogOptions.MinimumLevel.Default);
_loggerSwitches.GetOrAdd("Default", levelSwitch);
// Add a global logger that will be the root of all other added loggers
_globalLogger = new Serilog.LoggerConfiguration()
.MinimumLevel.ControlledBy(levelSwitch)
.ReadFrom.Configuration(configuration)
.CreateLogger();
}
public ILogger CreateLogger(string categoryName)
{
LogEventLevel eventLevel = _serilogOptions.MinimumLevel.Default;
foreach (var overrideOption in _serilogOptions.MinimumLevel.Override)
{
if (categoryName.StartsWith(overrideOption.Key))
{
eventLevel = overrideOption.Value;
}
}
// Chain new loggers to the global loggers with its own switch
// taking into accound any "Overrides"
var levelSwitch = new LoggingLevelSwitch(eventLevel);
_loggerSwitches.GetOrAdd(categoryName, levelSwitch);
var serilogger = new Serilog.LoggerConfiguration()
.MinimumLevel.ControlledBy(levelSwitch)
.WriteTo.Logger(_globalLogger)
.CreateLogger();
var factory = new SerilogLoggerFactory(serilogger, true);
return _loggers.GetOrAdd(categoryName, factory.CreateLogger(categoryName));
}
public void Dispose()
{
_globalLogger.Dispose();
_loggerSwitches = null;
_loggers = null;
}
public ICollection<ILoggerConfiguration> GetLoggerConfigurations()
{
var results = new Dictionary<string, ILoggerConfiguration>();
// get the default first
LogLevel configuredDefault = GetConfiguredLevel("Default") ?? LogLevel.None;
LogLevel effectiveDefault = GetEffectiveLevel("Default");
results.Add("Default", new LoggerConfiguration("Default", configuredDefault, effectiveDefault));
// then get all running loggers
foreach (var logger in _loggers)
{
foreach (var name in GetKeyPrefixes(logger.Key))
{
if (name != "Default")
{
LogLevel? configured = GetConfiguredLevel(name);
LogLevel effective = GetEffectiveLevel(logger.Key);
var config = new LoggerConfiguration(name, configured, effective);
if (results.ContainsKey(name))
{
if (!results[name].Equals(config))
{
throw new InvalidProgramException("Shouldn't happen");
}
}
results[name] = config;
}
}
}
return results.Values;
}
public void SetLogLevel(string category, LogLevel? level)
{
var filteredPairs = _loggerSwitches.Where(kvp => kvp.Key.StartsWith(category));
foreach (var kvp in filteredPairs)
{
var currentLevel = level ?? GetConfiguredLevel(kvp.Key);
if (currentLevel != null)
{
kvp.Value.MinimumLevel = ToSerilogLevel(currentLevel);
}
}
}
private LogLevel? GetConfiguredLevel(string name)
{
LogLevel? returnValue = null;
if (name == "Default")
{
returnValue = (LogLevel)_serilogOptions.MinimumLevel.Default;
}
else
{
var overrides = _serilogOptions.MinimumLevel.Override;
if (overrides != null
&& overrides.ContainsKey(name)
&& overrides.TryGetValue(name, out LogEventLevel configuredLevel))
{
returnValue = (LogLevel)configuredLevel;
}
}
return returnValue;
}
private LogLevel GetEffectiveLevel(string name)
{
LoggingLevelSwitch levelSwitch;
_loggerSwitches.TryGetValue(name, out levelSwitch);
return (LogLevel)levelSwitch.MinimumLevel;
}
private IEnumerable<string> GetKeyPrefixes(string name)
{
while (!string.IsNullOrEmpty(name))
{
yield return name;
var lastIndexOfDot = name.LastIndexOf('.');
if (lastIndexOfDot == -1)
{
yield return "Default";
break;
}
name = name.Substring(0, lastIndexOfDot);
}
}
private LogEventLevel ToSerilogLevel(LogLevel? level)
{
if (level == null || level == LogLevel.None)
{
return LogEventLevel.Fatal;
}
return (LogEventLevel)level;
}
}
}

Просмотреть файл

@ -0,0 +1,57 @@
// Copyright 2017 the original author or authors.
//
// 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.Configuration;
using Serilog.Events;
using System.Collections.Generic;
namespace Steeltoe.Extensions.Logging.SerilogDynamicLogger
{
/// <summary>
/// Implements a subset of the Serilog Options needed for SerilogDynamicProvider
/// </summary>
public class SerilogOptions : ISerilogOptions
{
private const string CONFIG_PATH = "Serilog";
/// <summary>
/// This controls the root logger (and the "Default") and
/// limits the verbosity of all other overrides to this setting
/// </summary>
public MinimumLevel MinimumLevel { get; set; }
public SerilogOptions(IConfiguration configuration)
{
var section = configuration.GetSection(CONFIG_PATH);
section.Bind(this);
if (MinimumLevel == null)
{
MinimumLevel = new MinimumLevel()
{
Default = LogEventLevel.Verbose, // Set root to verbose to have sub loggers work at all levels
Override = new Dictionary<string, LogEventLevel>()
};
}
}
}
#pragma warning disable SA1402 // File may only contain a single class
public class MinimumLevel
#pragma warning restore SA1402 // File may only contain a single class
{
public LogEventLevel Default { get; set; }
public Dictionary<string, LogEventLevel> Override { get; set; }
}
}

Просмотреть файл

@ -0,0 +1,44 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\config\versions.props" />
<PropertyGroup>
<Description>Steeltoe Console Logger</Description>
<Authors>Pivotal;hsarella</Authors>
<VersionPrefix>$(SteeltoeVersion)</VersionPrefix>
<VersionSuffix>$(VersionSuffix)</VersionSuffix>
<TargetFramework>netstandard2.0</TargetFramework>
<AssemblyName>Steeltoe.Extensions.Logging.SerilogDynamicLogger</AssemblyName>
<PackageId>Steeltoe.Extensions.Logging.SerilogDynamicLogger</PackageId>
<PackageTags>Spring Cloud;Logging;Management</PackageTags>
<PackageIconUrl>https://steeltoe.io/images/transparent.png</PackageIconUrl>
<PackageProjectUrl>https://steeltoe.io</PackageProjectUrl>
<PackageLicenseUrl>https://www.apache.org/licenses/LICENSE-2.0</PackageLicenseUrl>
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<NoWarn>SA1101;SA1124;SA1201;SA1309;SA1310;SA1401;SA1600;SA1652;1591</NoWarn>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging" Version="2.2.0" />
<PackageReference Include="Serilog.AspNetCore" Version="2.1.1" />
<PackageReference Include="Serilog.Filters.Expressions" Version="2.0.0" />
<PackageReference Include="Serilog.Settings.Configuration" Version="3.0.1" />
<PackageReference Include="Serilog.Sinks.Console" Version="3.1.1" />
<PackageReference Include="StyleCop.Analyzers" Version="1.0.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<AdditionalFiles Include="..\..\stylecop.json">
<Link>stylecop.json</Link>
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</AdditionalFiles>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Steeltoe.Extensions.Logging.DynamicLogger\Steeltoe.Extensions.Logging.DynamicLogger.csproj" />
</ItemGroup>
</Project>

Просмотреть файл

@ -0,0 +1,43 @@
// Copyright 2017 the original author or authors.
//
// 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.IO;
namespace Steeltoe.Extensions.Logging.SerilogDynamicLogger.Test
{
internal class ConsoleOutputBorrower : IDisposable
{
private StringWriter _borrowedOutput;
private TextWriter _originalOutput;
public ConsoleOutputBorrower()
{
_borrowedOutput = new StringWriter();
_originalOutput = Console.Out;
Console.SetOut(_borrowedOutput);
}
public override string ToString()
{
return _borrowedOutput.ToString();
}
public void Dispose()
{
Console.SetOut(_originalOutput);
_borrowedOutput.Dispose();
}
}
}

Просмотреть файл

@ -0,0 +1,63 @@
// Copyright 2017 the original author or authors.
//
// 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.Configuration;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Console;
using Serilog.Events;
using System.Collections.Generic;
using Xunit;
namespace Steeltoe.Extensions.Logging.SerilogDynamicLogger.Test
{
public class SerilogDynamicLoggerConfigurationExtensionsTest
{
[Fact]
public void SerilogOptions_Set_Correctly()
{
// arrange
var appSettings = new Dictionary<string, string>
{
{ "Serilog:MinimumLevel:Default", "Error" },
{ "Serilog:MinimumLevel:Override:Microsoft", "Warning" },
{ "Serilog:MinimumLevel:Override:Steeltoe.Extensions", "Verbose" },
{ "Serilog:MinimumLevel:Override:Steeltoe", "Information" }
};
var builder = new ConfigurationBuilder().AddInMemoryCollection(appSettings);
// act
var serilogOptions = new SerilogOptions(builder.Build());
// assert
Assert.Equal(LogEventLevel.Error, serilogOptions.MinimumLevel.Default);
Assert.Equal(LogEventLevel.Warning, serilogOptions.MinimumLevel.Override["Microsoft"]);
Assert.Equal(LogEventLevel.Information, serilogOptions.MinimumLevel.Override["Steeltoe"]);
Assert.Equal(LogEventLevel.Verbose, serilogOptions.MinimumLevel.Override["Steeltoe.Extensions"]);
}
[Fact]
public void SerilogOptions_NoError_When_NotConfigured()
{
// arrange
var appSettings = new Dictionary<string, string>();
var builder = new ConfigurationBuilder().AddInMemoryCollection(appSettings);
// act
var serilogOptions = new SerilogOptions(builder.Build());
// assert
Assert.Equal(LogEventLevel.Verbose, serilogOptions.MinimumLevel.Default);
}
}
}

Просмотреть файл

@ -0,0 +1,375 @@
// Copyright 2017 the original author or authors.
//
// 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.Configuration;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Console;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using Xunit;
namespace Steeltoe.Extensions.Logging.SerilogDynamicLogger.Test
{
public class SerilogDynamicLoggerProviderTest
{
[Fact]
public void Create_CreatesCorrectLogger()
{
var provider = new SerilogDynamicProvider(GetConfiguration());
LoggerFactory fac = new LoggerFactory();
fac.AddProvider(provider);
ILogger logger = fac.CreateLogger(typeof(A.B.C.D.TestClass));
Assert.NotNull(logger);
Assert.True(logger.IsEnabled(LogLevel.Information));
Assert.False(logger.IsEnabled(LogLevel.Debug));
}
[Fact]
public void SetLogLevel_UpdatesLogger()
{
var provider = new SerilogDynamicProvider(GetConfiguration());
LoggerFactory fac = new LoggerFactory();
fac.AddProvider(provider);
ILogger logger = fac.CreateLogger(typeof(A.B.C.D.TestClass));
Assert.NotNull(logger);
Assert.True(logger.IsEnabled(LogLevel.Critical));
Assert.True(logger.IsEnabled(LogLevel.Error));
Assert.True(logger.IsEnabled(LogLevel.Warning));
Assert.True(logger.IsEnabled(LogLevel.Information));
Assert.False(logger.IsEnabled(LogLevel.Debug));
provider.SetLogLevel("A", LogLevel.Debug);
Assert.True(logger.IsEnabled(LogLevel.Information));
Assert.True(logger.IsEnabled(LogLevel.Debug));
provider.SetLogLevel("A", LogLevel.Information);
Assert.True(logger.IsEnabled(LogLevel.Information));
Assert.False(logger.IsEnabled(LogLevel.Debug));
}
[Fact]
public void SetLogLevel_UpdatesNamespaceDescendants()
{
// arrange (A* should log at Information)
var provider = new SerilogDynamicProvider(GetConfiguration());
// act I: with original setup
var childLogger = provider.CreateLogger("A.B.C");
var configurations = provider.GetLoggerConfigurations();
var tierOneNamespace = configurations.First(n => n.Name == "A");
// assert I: base namespace is in the response, correctly
Assert.Equal(LogLevel.Information, tierOneNamespace.EffectiveLevel);
configurations = provider.GetLoggerConfigurations();
// act II: set A.B* to log at Trace
provider.SetLogLevel("A.B", LogLevel.Trace);
configurations = provider.GetLoggerConfigurations();
tierOneNamespace = configurations.First(n => n.Name == "A");
var tierTwoNamespace = configurations.First(n => n.Name == "A.B");
// assert II: since Serilog doesnt expose filters, we can only change at the granularity of configured filters
Assert.Equal(LogLevel.Trace, tierOneNamespace.EffectiveLevel);
Assert.Equal(LogLevel.Trace, tierTwoNamespace.EffectiveLevel);
Assert.True(childLogger.IsEnabled(LogLevel.Trace));
// act III: set A to something else, make sure it inherits down
provider.SetLogLevel("A", LogLevel.Error);
configurations = provider.GetLoggerConfigurations();
tierOneNamespace = configurations.First(n => n.Name == "A");
tierTwoNamespace = configurations.First(n => n.Name == "A.B");
var grandchildLogger = provider.CreateLogger("A.B.C.D");
// assert again
Assert.Equal(LogLevel.Error, tierOneNamespace.EffectiveLevel);
Assert.Equal(LogLevel.Error, tierTwoNamespace.EffectiveLevel);
Assert.False(childLogger.IsEnabled(LogLevel.Warning));
Assert.False(grandchildLogger.IsEnabled(LogLevel.Debug));
}
[Fact]
public void SetLogLevel_Can_Reset_to_Default()
{
// arrange (A* should log at Information)
var provider = new SerilogDynamicProvider(GetConfiguration());
// act I: with original setup
var firstLogger = provider.CreateLogger("A.B.C");
var configurations = provider.GetLoggerConfigurations();
var tierOneNamespace = configurations.First(n => n.Name == "A");
// assert I: base namespace is in the response, correctly
Assert.Equal(LogLevel.Information, tierOneNamespace.EffectiveLevel);
// act II: set A.B* to log at Trace
provider.SetLogLevel("A.B", LogLevel.Trace);
configurations = provider.GetLoggerConfigurations();
tierOneNamespace = configurations.First(n => n.Name == "A");
var tierTwoNamespace = configurations.First(n => n.Name == "A.B");
// assert II: since Serilog doesnt expose filters, we can only change at the granularity of configured overrides
Assert.Equal(LogLevel.Trace, tierOneNamespace.EffectiveLevel);
Assert.Equal(LogLevel.Trace, tierTwoNamespace.EffectiveLevel);
Assert.True(firstLogger.IsEnabled(LogLevel.Trace));
// act III: reset A.B
provider.SetLogLevel("A.B", null);
configurations = provider.GetLoggerConfigurations();
tierOneNamespace = configurations.First(n => n.Name == "A");
tierTwoNamespace = configurations.First(n => n.Name == "A.B");
var secondLogger = provider.CreateLogger("A.B.C.D");
// assert again
Assert.Equal(LogLevel.Information, tierOneNamespace.EffectiveLevel);
Assert.Equal(LogLevel.Information, tierTwoNamespace.EffectiveLevel);
Assert.True(firstLogger.IsEnabled(LogLevel.Information));
Assert.True(secondLogger.IsEnabled(LogLevel.Information));
}
[Fact]
public void GetLoggerConfigurations_ReturnsExpected()
{
var provider = new SerilogDynamicProvider(GetConfiguration());
LoggerFactory fac = new LoggerFactory();
fac.AddProvider(provider);
ILogger logger = fac.CreateLogger(typeof(A.B.C.D.TestClass));
var logConfig = provider.GetLoggerConfigurations();
Assert.Equal(6, logConfig.Count);
Assert.Contains(new LoggerConfiguration("Default", LogLevel.Trace, LogLevel.Trace), logConfig);
Assert.Contains(new LoggerConfiguration("A.B.C.D.TestClass", null, LogLevel.Information), logConfig);
Assert.Contains(new LoggerConfiguration("A.B.C.D", null, LogLevel.Information), logConfig);
Assert.Contains(new LoggerConfiguration("A.B.C", LogLevel.Information, LogLevel.Information), logConfig);
Assert.Contains(new LoggerConfiguration("A.B", null, LogLevel.Information), logConfig);
Assert.Contains(new LoggerConfiguration("A", LogLevel.Information, LogLevel.Information), logConfig);
}
[Fact]
public void GetLoggerConfigurations_ReturnsExpected_After_SetLogLevel()
{
// arrange
var provider = new SerilogDynamicProvider(GetConfiguration());
LoggerFactory fac = new LoggerFactory();
fac.AddProvider(provider);
// act I
ILogger logger = fac.CreateLogger(typeof(A.B.C.D.TestClass));
var logConfig = provider.GetLoggerConfigurations();
// assert I
Assert.Equal(6, logConfig.Count);
Assert.Contains(new LoggerConfiguration("Default", LogLevel.Trace, LogLevel.Trace), logConfig);
Assert.Contains(new LoggerConfiguration("A.B.C.D.TestClass", null, LogLevel.Information), logConfig);
Assert.Contains(new LoggerConfiguration("A.B.C.D", null, LogLevel.Information), logConfig);
Assert.Contains(new LoggerConfiguration("A.B.C", LogLevel.Information, LogLevel.Information), logConfig);
Assert.Contains(new LoggerConfiguration("A.B", null, LogLevel.Information), logConfig);
Assert.Contains(new LoggerConfiguration("A", LogLevel.Information, LogLevel.Information), logConfig);
// act II
provider.SetLogLevel("A.B", LogLevel.Trace);
logConfig = provider.GetLoggerConfigurations();
// assert II
Assert.Equal(6, logConfig.Count);
Assert.Contains(new LoggerConfiguration("Default", LogLevel.Trace, LogLevel.Trace), logConfig);
Assert.Contains(new LoggerConfiguration("A.B.C.D.TestClass", null, LogLevel.Trace), logConfig);
Assert.Contains(new LoggerConfiguration("A.B.C.D", null, LogLevel.Trace), logConfig);
Assert.Contains(new LoggerConfiguration("A.B.C", LogLevel.Information, LogLevel.Trace), logConfig);
Assert.Contains(new LoggerConfiguration("A.B", null, LogLevel.Trace), logConfig);
Assert.Contains(new LoggerConfiguration("A", LogLevel.Information, LogLevel.Trace), logConfig);
}
[Fact]
public void SetLogLevel_Works_OnDefault()
{
// arrange
var provider = new SerilogDynamicProvider(GetConfiguration());
LoggerFactory fac = new LoggerFactory();
fac.AddProvider(provider);
var originalLogConfig = provider.GetLoggerConfigurations();
// act
provider.SetLogLevel("Default", LogLevel.Information);
var updatedLogConfig = provider.GetLoggerConfigurations();
// assert
Assert.Contains(new LoggerConfiguration("Default", LogLevel.Trace, LogLevel.Trace), originalLogConfig);
Assert.Contains(new LoggerConfiguration("Default", LogLevel.Trace, LogLevel.Information), updatedLogConfig);
}
[Fact]
public void ResetLogLevel_Works_OnDefault()
{
// arrange
var provider = new SerilogDynamicProvider(GetConfiguration());
LoggerFactory fac = new LoggerFactory();
fac.AddProvider(provider);
var originalLogConfig = provider.GetLoggerConfigurations();
// act
provider.SetLogLevel("Default", LogLevel.Information);
var updatedLogConfig = provider.GetLoggerConfigurations();
provider.SetLogLevel("Default", null);
var resetConfig = provider.GetLoggerConfigurations();
// assert
Assert.Contains(new LoggerConfiguration("Default", LogLevel.Trace, LogLevel.Trace), originalLogConfig);
Assert.Contains(new LoggerConfiguration("Default", LogLevel.Trace, LogLevel.Information), updatedLogConfig);
Assert.Contains(new LoggerConfiguration("Default", LogLevel.Trace, LogLevel.Trace), resetConfig);
}
[Fact]
public void LoggerLogs_At_Configured_Setting()
{
// arrange
var provider = new SerilogDynamicProvider(GetConfiguration());
LoggerFactory fac = new LoggerFactory();
fac.AddProvider(provider);
ILogger logger = fac.CreateLogger(typeof(A.B.C.D.TestClass));
// act I - log at all levels, expect Info and above to work
using (var unConsole = new ConsoleOutputBorrower())
{
WriteLogEntries(logger);
// pause the thread to allow the logging to happen
Thread.Sleep(100);
var logged = unConsole.ToString();
// assert I
Assert.Contains("Critical message", logged);
Assert.Contains("Error message", logged);
Assert.Contains("Warning message", logged);
Assert.Contains("Informational message", logged);
Assert.DoesNotContain("Debug message", logged);
Assert.DoesNotContain("Trace message", logged);
}
// act II - adjust rules, expect Error and above to work
provider.SetLogLevel("A.B.C.D", LogLevel.Error);
using (var unConsole = new ConsoleOutputBorrower())
{
WriteLogEntries(logger);
// pause the thread to allow the logging to happen
Thread.Sleep(100);
var logged2 = unConsole.ToString();
// assert II
Assert.Contains("Critical message", logged2);
Assert.Contains("Error message", logged2);
Assert.DoesNotContain("Warning message", logged2);
Assert.DoesNotContain("Informational message", logged2);
Assert.DoesNotContain("Debug message", logged2);
Assert.DoesNotContain("Trace message", logged2);
}
// act III - adjust rules, expect Trace and above to work
provider.SetLogLevel("A", LogLevel.Trace);
using (var unConsole = new ConsoleOutputBorrower())
{
WriteLogEntries(logger);
// pause the thread to allow the logging to happen
Thread.Sleep(100);
var logged3 = unConsole.ToString();
// assert III
Assert.Contains("Critical message", logged3);
Assert.Contains("Error message", logged3);
Assert.Contains("Warning message", logged3);
Assert.Contains("Informational message", logged3);
Assert.Contains("Debug message", logged3);
Assert.Contains("Trace message", logged3);
}
// act IV - adjust rules, expect nothing to work
provider.SetLogLevel("A", LogLevel.None);
using (var unConsole = new ConsoleOutputBorrower())
{
WriteLogEntries(logger);
// pause the thread to allow the logging to happen
Thread.Sleep(100);
var logged4 = unConsole.ToString();
// assert IV
Assert.Contains("Critical message", logged4); // Serilog lowest level is Fatal == Critical
Assert.DoesNotContain("Error message", logged4);
Assert.DoesNotContain("Warning message", logged4);
Assert.DoesNotContain("Informational message", logged4);
Assert.DoesNotContain("Debug message", logged4);
Assert.DoesNotContain("Trace message", logged4);
}
// act V - reset the rules, expect Info and above to work
provider.SetLogLevel("A", LogLevel.Information); // Only works with serilog for configured values
using (var unConsole = new ConsoleOutputBorrower())
{
WriteLogEntries(logger);
// pause the thread to allow the logging to happen
Thread.Sleep(100);
var logged5 = unConsole.ToString();
// assert V
Assert.NotNull(provider.GetLoggerConfigurations().First(c => c.Name == "A"));
Assert.Contains("Critical message", logged5);
Assert.Contains("Error message", logged5);
Assert.Contains("Warning message", logged5);
Assert.Contains("Informational message", logged5);
Assert.DoesNotContain("Debug message", logged5);
Assert.DoesNotContain("Trace message", logged5);
}
}
private void WriteLogEntries(ILogger logger)
{
logger.LogCritical("Critical message");
logger.LogError("Error message");
logger.LogWarning("Warning message");
logger.LogInformation("Informational message");
logger.LogDebug("Debug message");
logger.LogTrace("Trace message");
}
private IConfiguration GetConfiguration()
{
var appSettings = new Dictionary<string, string>
{
{ "Serilog:MinimumLevel:Default", "Verbose" }, // Sets level of root logger so has to be higher than any sub logger
{ "Serilog:MinimumLevel:Override:Microsoft", "Warning" },
{ "Serilog:MinimumLevel:Override:Steeltoe.Extensions", "Verbose" },
{ "Serilog:MinimumLevel:Override:Steeltoe", "Information" },
{ "Serilog:MinimumLevel:Override:A", "Information" },
{ "Serilog:MinimumLevel:Override:A.B.C", "Information" },
{ "Serilog:WriteTo:Name", "Console" },
};
var builder = new ConfigurationBuilder().AddInMemoryCollection(appSettings);
return builder.Build();
}
}
}

Просмотреть файл

@ -0,0 +1,166 @@
// Copyright 2017 the original author or authors.
//
// 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.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Console;
using Microsoft.Extensions.Options;
using System.Collections.Generic;
using System.Linq;
using Xunit;
namespace Steeltoe.Extensions.Logging.SerilogDynamicLogger.Test
{
public class SerilogDynamicLoggingBuilderTest
{
private static Dictionary<string, string> appsettings = new Dictionary<string, string>()
{
{ "Serilog:MinimumLevel:Default", "Verbose" }, // Sets level of root logger so has to be higher than any sub logger
{ "Serilog:MinimumLevel:Override:Microsoft", "Warning" },
{ "Serilog:MinimumLevel:Override:Steeltoe.Extensions", "Verbose" },
{ "Serilog:MinimumLevel:Override:Steeltoe", "Information" },
{ "Serilog:MinimumLevel:Override:A", "Information" },
{ "Serilog:MinimumLevel:Override:A.B.C.D", "Fatal" },
{ "Serilog:WriteTo:Name", "Console" },
};
[Fact]
public void OnlyApplicableFilters_AreApplied()
{
// arrange
var appsettings = new Dictionary<string, string>()
{
["Logging:IncludeScopes"] = "false",
["Logging:LogLevel:Default"] = "Information",
["Logging:foo:LogLevel:A.B.C.D.TestClass"] = "None"
};
var configuration = new ConfigurationBuilder().AddInMemoryCollection(appsettings).Build();
var services = new ServiceCollection()
.AddSingleton<IConfiguration>(configuration)
.AddLogging(builder =>
{
builder.AddSerilogDynamicConsole();
})
.BuildServiceProvider();
// act
var logger = services.GetService(typeof(ILogger<A.B.C.D.TestClass>)) as ILogger<A.B.C.D.TestClass>;
// assert
Assert.NotNull(logger);
Assert.True(logger.IsEnabled(LogLevel.Information), "Information level should be enabled");
Assert.False(logger.IsEnabled(LogLevel.Debug), "Debug level should NOT be enabled");
}
[Fact]
public void DynamicLevelSetting_WorksWith_ConsoleFilters()
{
// arrange
var configuration = new ConfigurationBuilder().AddInMemoryCollection(appsettings).Build();
var services = new ServiceCollection()
.AddSingleton<IConfiguration>(configuration)
.AddLogging(builder =>
{
builder.AddSerilogDynamicConsole();
})
.BuildServiceProvider();
// act
var logger = services.GetService(typeof(ILogger<A.B.C.D.TestClass>)) as ILogger<A.B.C.D.TestClass>;
// assert
Assert.NotNull(logger);
Assert.True(logger.IsEnabled(LogLevel.Critical), "Critical level should be enabled");
Assert.False(logger.IsEnabled(LogLevel.Error), "Error level should NOT be enabled");
Assert.False(logger.IsEnabled(LogLevel.Warning), "Warning level should NOT be enabled");
Assert.False(logger.IsEnabled(LogLevel.Debug), "Debug level should NOT be enabled");
Assert.False(logger.IsEnabled(LogLevel.Trace), "Trace level should NOT be enabled yet");
// change the log level and confirm it worked
var provider = services.GetRequiredService(typeof(ILoggerProvider)) as SerilogDynamicProvider;
provider.SetLogLevel("A.B.C.D", LogLevel.Trace);
var levels = provider.GetLoggerConfigurations().Where(c => c.Name.StartsWith("A.B.C.D"))
.Select(x => x.EffectiveLevel);
Assert.NotNull(levels);
Assert.True(levels.All(x => x == LogLevel.Trace));
}
[Fact]
public void AddDynamicConsole_AddsAllLoggerProviders()
{
// arrange
var configuration = new ConfigurationBuilder().AddInMemoryCollection(appsettings).Build();
var services = new ServiceCollection()
.AddSingleton<IConfiguration>(configuration)
.AddLogging(builder =>
{
builder.AddSerilogDynamicConsole();
}).BuildServiceProvider();
// act
var dlogProvider = services.GetService<IDynamicLoggerProvider>();
var logProviders = services.GetServices<ILoggerProvider>();
// assert
Assert.NotNull(dlogProvider);
Assert.NotEmpty(logProviders);
Assert.Single(logProviders);
Assert.IsType<SerilogDynamicProvider>(logProviders.SingleOrDefault());
}
[Fact]
public void AddDynamicConsole_AddsLoggerProvider_DisposeTwiceSucceeds()
{
// arrange
var configuration = new ConfigurationBuilder().AddInMemoryCollection(appsettings).Build();
var services = new ServiceCollection()
.AddSingleton<IConfiguration>(configuration)
.AddLogging(builder =>
{
builder.AddSerilogDynamicConsole();
}).BuildServiceProvider();
// act
var dlogProvider = services.GetService<IDynamicLoggerProvider>();
var logProviders = services.GetServices<ILoggerProvider>();
// assert
services.Dispose();
dlogProvider.Dispose();
}
[Fact]
public void AddDynamicConsole_WithConfigurationParam_AddsServices()
{
// arrange
var configuration = new ConfigurationBuilder().AddInMemoryCollection(appsettings).Build();
var services = new ServiceCollection()
.AddSingleton<IConfiguration>(configuration)
.AddLogging(builder => builder.AddSerilogDynamicConsole())
.BuildServiceProvider();
// act
var dlogProvider = services.GetService<IDynamicLoggerProvider>();
var logProviders = services.GetServices<ILoggerProvider>();
// assert
Assert.NotNull(dlogProvider);
Assert.NotEmpty(logProviders);
Assert.Single(logProviders);
Assert.IsType<SerilogDynamicProvider>(logProviders.SingleOrDefault());
}
}
}

Просмотреть файл

@ -0,0 +1,38 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\versions.props" />
<PropertyGroup>
<TargetFrameworks>net461;netcoreapp2.1</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<None Update="xunit.runner.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="$(AspNetCoreMvcTestVersion)" />
<PackageReference Include="Microsoft.AspNetCore.TestHost" Version="$(AspNetCoreTestVersion)" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="$(TestSdkVersion)" />
<PackageReference Include="xunit" Version="$(XunitVersion)" />
<PackageReference Include="xunit.runner.visualstudio" Version="$(XunitStudioVersion)" />
<DotNetCliToolReference Include="dotnet-xunit" Version="$(XunitVersion)" />
<PackageReference Include="StyleCop.Analyzers" Version="$(StyleCopVersion)">
<PrivateAssets>All</PrivateAssets>
</PackageReference>
</ItemGroup>
<PropertyGroup>
<NoWarn>SA1101;SA1124;SA1201;SA1309;SA1310;SA1401;SA1600;SA1652;1591</NoWarn>
</PropertyGroup>
<ItemGroup>
<AdditionalFiles Include="..\..\stylecop.json">
<Link>stylecop.json</Link>
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</AdditionalFiles>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Steeltoe.Extensions.Logging.SerilogDynamicLogger\Steeltoe.Extensions.Logging.SerilogDynamicLogger.csproj" />
</ItemGroup>
</Project>

Просмотреть файл

@ -0,0 +1,20 @@
// Copyright 2017 the original author or authors.
//
// 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.
namespace A.B.C.D
{
public class TestClass
{
}
}