當前位置:
首頁 > 知識 > ASP.NET Core 源碼學習之 Logging「2」:Configure

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

而 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 統一服務發現