在上一章中,我们对 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() .Build();}
如上,可以看到基本的配置都放到了 CreateDefaultBuilder
方法中,而 WebHost
则在 中,提供了一些简化方法。
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, ActionconfigureLogging){ return hostBuilder.ConfigureServices((context, collection) => collection.AddLogging(builder => configureLogging(context, builder)));}
而 AddLogging
则是 Logging 系统的入口点,是由 Microsoft.Extensions.Logging
所提供的扩展方法:
public static IServiceCollection AddLogging(this IServiceCollection services, Actionconfigure){ if (services == null) { throw new ArgumentNullException(nameof(services)); } services.AddOptions(); services.TryAdd(ServiceDescriptor.Singleton ()); services.TryAdd(ServiceDescriptor.Singleton(typeof(ILogger<>), typeof(Logger<>))); services.TryAddEnumerable(ServiceDescriptor.Singleton >( 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>(new LoggerFilterConfigureOptions(configuration)); builder.Services.AddSingleton >(new ConfigurationChangeTokenSource (configuration)); return builder;}
首先使用 Options 模式注册了一个 LoggerFilterOptions
:
public class LoggerFilterOptions{ public LogLevel MinLevel { get; set; } public IListRules { get; } = new List ();}public class LoggerFilterRule{ ... public string ProviderName { get; } public string CategoryName { get; } public LogLevel? LogLevel { get; } public Func Filter { get; } ....}
而默认实现 LoggerFilterConfigureOptions
的逻辑很简单,就是从配置文件中读取 LogLevel
的配置:
internal class LoggerFilterConfigureOptions : IConfigureOptions{ ... 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 的介绍可以看我之前文章 。
而在 Logging 系统中,也是通过注入 IOptionsMonitor<LoggerFilterOptions>
来使用 LoggerFilterOptions 的:
public LoggerFactory(IEnumerableproviders, IOptionsMonitor 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(); return builder;}public static ILoggingBuilder AddConsole(this ILoggingBuilder builder, Action configure){ if (configure == null) { throw new ArgumentNullException(nameof(configure)); } builder.AddConsole(); builder.Services.Configure(configure); return builder;}
以上代码在 Microsoft.Extensions.Logging.Console Package 中,首先提供了 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() .Build();
我们添加了一个 EventSource Provider,并且使用了 AddFilter
扩展方法对日志的过滤进行配置。而 AddFilter 的作用类似于 前面介绍的 AddConfiguration,只是把配置方式从配置文件变成了代码。
public static class FilterLoggingBuilderExtensions{ // 具有多个重载,此处省略 public static ILoggingBuilder AddFilter(this ILoggingBuilder builder, Funcfilter) => builder.ConfigureFilter(options => options.AddFilter(filter)); private static ILoggingBuilder ConfigureFilter(this ILoggingBuilder builder, Action configureOptions) { builder.Services.Configure(configureOptions); return builder; }}
可以看到,最终也是对 ConfigureOptions
的配置,而后执行的配置会覆盖之前配置的。
总结
本章从 Logging 系统的起始点入手,详细分析了如何对 Logging 系统进行配置,分为日志级别过滤和日志提供者两种配置,而下一章则会分析一下日志的过滤原理。