ASP.NET Core 源碼學習之 Logging「2」:Configure
在上一章中,我們對 ASP.NET Logging 系統做了一個整體的介紹,而在本章中則開始從最基本的配置開始,逐步深入到源碼當中去。
默認配置在 ASP.NET Core 2.0 中,對默認配置做了很大的簡化,並把一些基本配置移動到了程序的入口點 Program
類中,更加簡潔。
public class Program
{
public static void Main(string[] args)
{
BuildWebHost(args).Run;
}
public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>
.Build;
}
如上,可以看到基本的配置都放到了 CreateDefaultBuilder
方法中,而WebHost
則在MetaPackages中,提供了一些簡化方法。
public static IWebHostBuilder CreateDefaultBuilder(string[] args)
{
var builder = new WebHostBuilder
.UseKestrel
.UseContentRoot(Directory.GetCurrentDirectory)
.ConfigureAppConfiguration((hostingContext, config) =>
{
var env = hostingContext.HostingEnvironment;
config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true);
if (env.IsDevelopment)
{
var appAssembly = Assembly.Load(new AssemblyName(env.ApplicationName));
if (appAssembly != null)
{
config.AddUserSecrets(appAssembly, optional: true);
}
}
config.AddEnvironmentVariables;
if (args != null)
{
config.AddCommandLine(args);
}
})
.ConfigureLogging((hostingContext, logging) =>
{
logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging"));
logging.AddConsole;
logging.AddDebug;
})
.UseIISIntegration
.UseDefaultServiceProvider((context, options) =>
{
options.ValidateScopes = context.HostingEnvironment.IsDevelopment;
});
return builder;
}
如上可以看到一些我們在 1.0 中非常熟悉的代碼,而 ConfigureLogging
則是IWebHostBuilder
類的一個擴展方法:
public static IWebHostBuilder ConfigureLogging(this IWebHostBuilder hostBuilder, Action<WebHostBuilderContext, ILoggingBuilder> configureLogging)
{
return hostBuilder.ConfigureServices((context, collection) => collection.AddLogging(builder => configureLogging(context, builder)));
}
而 AddLogging
則是 Logging 系統的入口點,是由Microsoft.Extensions.Logging
所提供的擴展方法:
public static IServiceCollection AddLogging(this IServiceCollection services, Action<ILoggingBuilder> configure)
{
if (services == null)
{
throw new ArgumentNullException(nameof(services));
}
services.AddOptions;
services.TryAdd(ServiceDescriptor.Singleton<ILoggerFactory, LoggerFactory>);
services.TryAdd(ServiceDescriptor.Singleton(typeof(ILogger<>), typeof(Logger<>)));
services.TryAddEnumerable(ServiceDescriptor.Singleton<IConfigureOptions<LoggerFilterOptions>>(
new DefaultLoggerLevelConfigureOptions(LogLevel.Information)));
configure(new LoggingBuilder(services));
return services;
}
首先註冊了 Logging 系統基本服務的默認實現,用來激活 Logging 系統,然後創建 LoggingBuilder
對象,而後一系列對日誌系統的配置,都是調用的該對象的擴展方法。
internal class LoggingBuilder : ILoggingBuilder
{
public LoggingBuilder(IServiceCollection services)
{
Services = services;
}
public IServiceCollection Services { get; }
}
現在回頭看看 CreateDefaultBuilder
方法中通過ConfigureLogging
來對日誌系統所做的默認配置。
AddConfiguration
該方法是對日誌系統的一個全局配置:
logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging"));
public static ILoggingBuilder AddConfiguration(this ILoggingBuilder builder, IConfiguration configuration)
{
builder.Services.AddSingleton<IConfigureOptions<LoggerFilterOptions>>(new LoggerFilterConfigureOptions(configuration));
builder.Services.AddSingleton<IOptionsChangeTokenSource<LoggerFilterOptions>>(new ConfigurationChangeTokenSource<LoggerFilterOptions>(configuration));
return builder;
}
首先使用 Options模式註冊了一個LoggerFilterOptions
:
public class LoggerFilterOptions
{
public LogLevel MinLevel { get; set; }
public IList<LoggerFilterRule> Rules { get; } = new List<LoggerFilterRule>;
}
public class LoggerFilterRule
{
...
public string ProviderName { get; }
public string CategoryName { get; }
public LogLevel? LogLevel { get; }
public Func<string, string, LogLevel, bool> Filter { get; }
....
}
而默認實現 LoggerFilterConfigureOptions
的邏輯很簡單,就是從配置文件中讀取LogLevel
的配置:
internal class LoggerFilterConfigureOptions : IConfigureOptions<LoggerFilterOptions>
{
...
private void LoadDefaultConfigValues(LoggerFilterOptions options)
{
if (_configuration == null)
{
return;
}
foreach (var configurationSection in _configuration.GetChildren)
{
if (configurationSection.Key == "LogLevel")
{
// Load global category defaults
LoadRules(options, configurationSection, null);
}
else
{
var logLevelSection = configurationSection.GetSection("LogLevel");
if (logLevelSection != null)
{
// Load logger specific rules
var logger = configurationSection.Key;
LoadRules(options, logLevelSection, logger);
}
}
}
}
private void LoadRules(LoggerFilterOptions options, IConfigurationSection configurationSection, string logger)
{
foreach (var section in configurationSection.AsEnumerable(true))
{
if (TryGetSwitch(section.Value, out var level))
{
var category = section.Key;
if (category == "Default")
{
category = null;
}
var newRule = new LoggerFilterRule(logger, category, level, null);
options.Rules.Add(newRule);
}
}
}
...
}
通過代碼,我們可以清楚的知道,我們的配置文件應該按如下格式來定義
{
"Logging": {
"LogLevel": { // 表示全局
"Default": "Warning" // 不指定CategoryName,應用於所有Category
},
"Console":{ // 指定 ProviderName,僅針對於 ConsoleProvider
"Default": "Warning",
"Microsoft": "Error" // 指定CategoryName為Microsoft的日誌級別為Error
}
}
}
而 IOptionsChangeTokenSource是對上面IConfigureOptions的一個補充,為我們獲取OptionsMonitor注入了必要的服務,更多關於 Options 的介紹可以看我之前文章IOptionsMonitor。
而在 Logging 系統中,也是通過注入 IOptionsMonitor<LoggerFilterOptions>
來使用LoggerFilterOptions的:
public LoggerFactory(IEnumerable<ILoggerProvider> providers, IOptionsMonitor<LoggerFilterOptions> filterOption)
{
_providerRegistrations = providers.Select(provider => new ProviderRegistration { Provider = provider }).ToList;
_changeTokenRegistration = filterOption.OnChange(RefreshFilters);
RefreshFilters(filterOption.CurrentValue);
}
AddConsole
上面我們提到,在配置文件中可以指定針對某個 Provider 的配置,而 AddConsole
則是用來添加一個 Console 類型的 Provider,用來將日誌記錄到控制台中:
public static ILoggingBuilder AddConsole(this ILoggingBuilder builder)
{
builder.Services.AddSingleton<ILoggerProvider, ConsoleLoggerProvider>;
return builder;
}
public static ILoggingBuilder AddConsole(this ILoggingBuilder builder, Action<ConsoleLoggerOptions> configure)
{
if (configure == null)
{
throw new ArgumentNullException(nameof(configure));
}
builder.AddConsole;
builder.Services.Configure(configure);
return builder;
}
以上代碼在 Microsoft.Extensions.Logging.ConsolePackage 中,首先提供了ILoggerProvider
的注入方法,用來啟用控制台的日誌記錄功能,而且還提供了一個方法重載,用來指定針對 ConsoleProvider 的配置。
而 AddDebug 與 AddConsole 類似,只不過是把日誌輸出在 Debug 窗口中。
更多關於 Provider 的配置,會在以後再詳細探索。
自定義配置
上面介紹了 ASP.NET Core 中對日誌系統的默認配置,那麼如果我們想再添加一些其它配置應該怎麼做呢?
在 1.0 時代,我們通過是在 Startup 類中的 Configure 方法中,注入 ILoggerFactory
來進行配置,當然,在 2.0 中我們仍然可以這樣做,但是更加推薦的做法是在 Program 入口方法中進行配置,而 Configure 方法通過是對一些中間件的配置。
我們可以直接使用上面介紹過的 ConfigureLogging
擴展方法來添加我們自己的配置:
public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args).ConfigureLogging(build =>
{
build.AddFilter(f => f == LogLevel.Debug);
build.AddEventSourceLogger;
})
.UseStartup<Startup>
.Build;
我們添加了一個 EventSource Provider,並且使用了 AddFilter
擴展方法對日誌的過濾進行配置。而AddFilter的作用類似於 前面介紹的AddConfiguration,只是把配置方式從配置文件變成了代碼。
public static class FilterLoggingBuilderExtensions
{
// 具有多個重載,此處省略
public static ILoggingBuilder AddFilter(this ILoggingBuilder builder, Func<string, string, LogLevel, bool> filter) =>
builder.ConfigureFilter(options => options.AddFilter(filter));
private static ILoggingBuilder ConfigureFilter(this ILoggingBuilder builder, Action<LoggerFilterOptions> configureOptions)
{
builder.Services.Configure(configureOptions);
return builder;
}
}
可以看到,最終也是對 ConfigureOptions
的配置,而後執行的配置會覆蓋之前配置的。
本章從 Logging 系統的起始點入手,詳細分析了如何對 Logging 系統進行配置,分為日誌級別過濾和日誌提供者兩種配置,而下一章則會分析一下日誌的過濾原理。
※如何恢復未釋放租約的HDFS文件
※C中調用HttpWebRequest類中Get/Post請求無故失效的詭異問題
※Android面試題目之四: 歸併排序
TAG:科技優家 |
※網關 Spring-Cloud-Gateway 源碼解析——路由之RouteDefinitionLocator一覽
※React Native BackHandler exitApp 源碼分析
※RocketMQ 源碼學習 2 : Namesrv
※從JDK源碼看StringBuffer
※JDK 源碼閱讀 : DirectByteBuffer
※閱讀Android源碼BitmapFactory
※HikariCP源碼分析之leakDetectionThreshold及實戰解決Spark/Scala連接池泄漏
※MFC TabSheet 源碼
※JDK源碼閱讀:InterruptibleChannel 與可中斷 IO
※Apache Storm流計算模型 及WordCount源碼實踐
※RocketMQ 源碼學習 3 :Remoting 模塊
※Selenium3源碼之common package篇
※Execotors、Future、Callable和FutureTask的使用與源碼分析
※AtomicInteger 源碼解析
※druid-spring-boot-starter源碼解析
※JDK 源碼閱讀 Reference
※Flutter圖片緩存 Image.network源碼分析
※Rasa Core源碼之Policy訓練
※分散式共享 Session之SpringSession 源碼細節
※Istio技術與實踐02:源碼解析之Istio on Kubernetes 統一服務發現