zoukankan      html  css  js  c++  java
  • 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.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<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 系统进行配置,分为日志级别过滤和日志提供者两种配置,而下一章则会分析一下日志的过滤原理。

  • 相关阅读:
    eclipse javaWeb项目如何引入jar包
    Unity3D 批量图片资源导入设置
    WaitForTargetFPS
    自适应分辨率
    UnityException: Texture is not readable
    Unity bundle的制作和使用
    Unity3D之Assetbundle
    Unity使用外部版本控制SVN
    AssetBundle机制相关资料收集
    Assetbundle的杂七杂八
  • 原文地址:https://www.cnblogs.com/RainingNight/p/asp-net-core-logging-configure.html
Copyright © 2011-2022 走看看