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 系統進行配置,分為日志級別過濾和日志提供者兩種配置,而下一章則會分析一下日志的過濾原理。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM