ASP.NET Coreソース学習のLogging[2]:Configure

10519 ワード

前章では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は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でよく知られているコードがいくつか見られますが、ConfigureLoggingIWebHostBuilderクラスの拡張方法です.
public static IWebHostBuilder ConfigureLogging(this IWebHostBuilder hostBuilder, Action 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 configure)
{
    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;
}

まず、ロギングシステムの基本サービスのデフォルトインプリメンテーションを登録し、ロギングシステムをアクティブにし、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 IList Rules { 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についての詳細は、私の前の記事IOptionsMonitorを参照してください.
ロギングシステムでは、IOptionsMonitorを注入することによってロギングFilterOptionsを使用しています.
public LoggerFactory(IEnumerable providers, IOptionsMonitor filterOption)
{
    _providerRegistrations = providers.Select(provider => new ProviderRegistration { Provider = provider }).ToList();
    _changeTokenRegistration = filterOption.OnChange(RefreshFilters);
    RefreshFilters(filterOption.CurrentValue);
}

AddConsole


前述したように、プロファイルではProviderの構成を指定できますが、AddConsoleはコンソールにログを記録するためにコンソールタイプの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クラスのコンフィギュレーションメソッドにILoggerFactoryを注入して構成することができました.もちろん、2.0ではそうすることができますが、Programエントリメソッドで構成することがより推奨され、コンフィギュレーションメソッドはミドルウェアの構成です.
上記のConfigureLogging拡張方法を使用して、独自の構成を追加できます.
public static IWebHost BuildWebHost(string[] args) =>
    WebHost.CreateDefaultBuilder(args).ConfigureLogging(build =>
    {
        build.AddFilter(f => f == LogLevel.Debug);
        build.AddEventSourceLogger();
    })
    .UseStartup()
    .Build();

EventSourceProviderを追加し、AddFilter拡張メソッドを使用してログのフィルタリングを構成しました.AddFilterの役割は、前述したAddConfigurationに似ていますが、プロファイルからコードに構成方法を変更しただけです.
public static class FilterLoggingBuilderExtensions
{
    //       ,    

    public static ILoggingBuilder AddFilter(this ILoggingBuilder builder, Func filter) =>
        builder.ConfigureFilter(options => options.AddFilter(filter));

    private static ILoggingBuilder ConfigureFilter(this ILoggingBuilder builder, Action configureOptions)
    {
        builder.Services.Configure(configureOptions);
        return builder;
    }
}

最終的にはConfigureOptionsの構成であり、その後実行される構成は前の構成を上書きすることがわかる.

まとめ


本章では、ログレベルフィルタリングとログプロバイダの2つの構成に分けて、ログのフィルタリング原理を分析します.