基於.NetCore3.1系列 —— 日誌記錄之日誌配置揭祕

1、前言

在項目的開發維護階段,有時候咱們關注的問題不只僅在於功能的實現,甚至須要關注系統發佈上線後遇到的問題可否及時的查找並解決。因此咱們須要有一個好的解決方案來及時的定位錯誤的根源並作出正確及時的修復,這樣才能不影響系統正常的運行狀態。git

logging

這個時候咱們發現,其實在asp.net core中已經內置了日誌系統,並提供了各類內置和第三方日誌記錄提供程序的日誌記錄接口,在進行應用開發中,能夠進行統一配置,而且利用第三方日誌框架相結合,更加有效的實現日誌記錄。因此在這個系列中,主要是對內置日誌記錄系統的學習,以及後續使用第三方日誌框架集成咱們須要的日誌系統。程序員

2、說明

在這一篇中主要是對日誌記錄的配置進行說明,從開始配置日誌,以及後續使用配置進行日誌處理。github

在新建項目成功以後,咱們都會看到一個命名爲appsettings.json配置,打開一看,短短的幾行配置,web

"Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },

而後啓動運行的時候,程序會在調試面板和控制檯中分別輸出顯示來源以下:json

在控制檯中:c#

logging

在調試面板中:windows

img

這裏的日誌配置,在系統中到底都起來什麼做用?讓咱們來一探究竟吧!api

3、開始

3.1 默認配置

咱們查看源代碼發現,在程序的入口點中發現,在初始化時候,經過CreateDefaultBuilder方法來實現日誌記錄的默認配置。app

public class Program
{
    public static void Main(string[] args)
    {
        CreateHostBuilder(args).Build().Run();
    }

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder.UseStartup<Startup>();
            });
}

因此下面咱們看一下CreateDefaultBuilder在源碼中都對日誌作了哪些默認配置?框架

public static IHostBuilder CreateDefaultBuilder(string[] args)
        {
            var builder = new HostBuilder();

            builder.UseContentRoot(Directory.GetCurrentDirectory());
            builder.ConfigureHostConfiguration(config =>
            {
                config.AddEnvironmentVariables(prefix: "DOTNET_");
                if (args != null)
                {
                    config.AddCommandLine(args);
                }
            });
            builder.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() && !string.IsNullOrEmpty(env.ApplicationName))
                {
                    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) =>
            {
                var isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
                if (isWindows)
                {
                    logging.AddFilter<EventLogLoggerProvider>(level => level >= LogLevel.Warning);
                } logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging"));
                logging.AddConsole();
                logging.AddDebug();
                logging.AddEventSourceLogger();
                if (isWindows)
                {
                    logging.AddEventLog();
                }
            })
            .UseDefaultServiceProvider((context, options) =>
            {
                var isDevelopment = context.HostingEnvironment.IsDevelopment();
                options.ValidateScopes = isDevelopment;
                options.ValidateOnBuild = isDevelopment;
            });
            return builder;
        }

經過上面這一段源碼咱們能夠看到一個命名爲ConfigureLogging的對象,咱們根據命名的意思大體能夠看出,這是一個配置日誌的方法,繼續查看ConfigureLogging源碼

public static IHostBuilder ConfigureLogging(this IHostBuilder hostBuilder, Action<HostBuilderContext, ILoggingBuilder> configureLogging)
    {
        return hostBuilder.ConfigureServices((context, collection) => collection.AddLogging(builder => configureLogging(context, builder)));
    }

經過IServiceCollection註冊服務集合容器,將日誌服務添加到這個服務容器,使用AddLogging方法實現對日誌服務的註冊。

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;
    }

經過AddLogging添加到服務集合容器,先經過添加所需的配置AddOptions,經過注入的方式實現默認的ILoggerFactory,ILogger ( 這個會在後續的篇章中進行說明),再後經過LoggingBuilder完成日誌對象的建立,

public interface ILoggingBuilder
{
    IServiceCollection Services { get; }
}
internal class LoggingBuilder : ILoggingBuilder
{
    public LoggingBuilder(IServiceCollection services)
    {
    Services = services;
    }

    public IServiceCollection Services { get; }
}

對日誌系統的配置,用於提供程序的接口,ILoggingBuilder後面能夠對該對象進行拓展使用。

經過以上的流程CreateDefaultBuilder方法,實現對預先配置的默認值初始化,所以也發現了其中的ConfigureLogging也是其中要進行默認初始化的值,也就是系統默認的日誌配置。

單獨把ConfigureLogging這一塊的源碼拎出來再看看:

.ConfigureLogging((hostingContext, logging) =>
        {
            var isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
            if (isWindows)
            {
                logging.AddFilter<EventLogLoggerProvider>(level => level >= LogLevel.Warning);
            }
            logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging"));
            logging.AddConsole();
            logging.AddDebug();
            logging.AddEventSourceLogger();

            if (isWindows)
            {
                logging.AddEventLog();
            }
        })

在asp.net core啓動中,根據操做系統平臺適應不一樣的服務,在windows服務中,將EventLogLoggerProvider的默認值設置爲警告或者更高的級別。

AddConfiguration : 添加系統日誌的全局配置。

在配置中,能夠根據提供的不一樣類型程序來針對實現日誌記錄的輸出方式。而這裏默認實現的AddConsole()AddDebug() 和AddEventSourceLogger()分別是將日誌輸出到控制檯、調試窗口中,以及提供寫入事件源。

AddConsole : 添加控制檯到工廠方法中,用來將日誌記錄到控制檯中。

AddDebug : 添加Debug窗口到工廠方法中,用來將日誌記錄到窗口中。

說明:asp.net core 內置的日誌接口中,實現了多種內置的日誌提供器,除了上面默認實現的ConsoleDebugEventSource,還包括下面的這幾個

EventLog :

TraceSource

AzureAppServicesFile

AzureAppServicesBlob

ApplicationInsights

還記得上面提到的appsettings.json配置嗎?在這裏,咱們來看看

{
  "Logging": {
    "LogLevel": {
      "Default": "Debug",
      "Microsoft": "Information"
    },
    "Console": {
      "LogLevel": {
        "Default": "Debug",
        "System": "Warning"
      }
    }
  }
}

AddConfiguration中,

logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging"));

獲取配置文件的Logging數據,實現全局配置,

public static ILoggingBuilder AddConfiguration(this ILoggingBuilder builder, IConfiguration configuration)
        {
            builder.AddConfiguration();
            builder.Services.AddSingleton<IConfigureOptions<LoggerFilterOptions>>(new LoggerFilterConfigureOptions(configuration));
            builder.Services.AddSingleton<IOptionsChangeTokenSource<LoggerFilterOptions>>(new ConfigurationChangeTokenSource<LoggerFilterOptions>(configuration));
            builder.Services.AddSingleton(new LoggingConfiguration(configuration));
            return builder;
        }
internal class LoggerFilterConfigureOptions : IConfigureOptions<LoggerFilterOptions>
{
    private const string LogLevelKey = "LogLevel";
    private const string DefaultCategory = "Default";
    private readonly IConfiguration _configuration;

    public LoggerFilterConfigureOptions(IConfiguration configuration)
    {
        _configuration = configuration;
    }

    public void Configure(LoggerFilterOptions options)
    {
        LoadDefaultConfigValues(options);
    }

    private void LoadDefaultConfigValues(LoggerFilterOptions options)
    {
        if (_configuration == null)
        {
            return;
        }

        options.CaptureScopes = _configuration.GetValue(nameof(options.CaptureScopes), options.CaptureScopes);

        foreach (var configurationSection in _configuration.GetChildren())
        {
            if (configurationSection.Key.Equals(LogLevelKey, StringComparison.OrdinalIgnoreCase))
            {
                // Load global category defaults
                LoadRules(options, configurationSection, null);
            }
            else
            {
                var logLevelSection = configurationSection.GetSection(LogLevelKey);
                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.Equals(DefaultCategory, StringComparison.OrdinalIgnoreCase))
                {
                    category = null;
                }
                var newRule = new LoggerFilterRule(logger, category, level, null);
                options.Rules.Add(newRule);
            }
        }
    }
}

以上是AddConfiguration實現的總體流程源碼,默認註冊實現LoggerFilterConfigureOptions對配置數據的讀取,其中定義的 LogLevelKey = "LogLevel"DefaultCategory = "Default" 默認字符串,以此來獲取默認全局配置數據。

在默認配置的文本格式appsettings.json中,Logging屬性能夠具備LogLevel和日誌提供程序屬性。Logging 下的 LogLevel 屬性指定了用於記錄所選類別的最低級別。在本例中, Microsoft 類別在 Information 級別記錄,其餘均在 Debug 級別記錄。

日誌級別說明:每個日誌都有指定的日誌級別值,日誌級別判斷指示嚴重性或重要性。使用日誌等級能夠很好的過濾想要的日誌,記錄日誌記錄問題的同時,甚至爲咱們提供很是詳細的日誌信息。

LogLevel 嚴重性:Trace < Debug < Information < Warning < Error < Critical < None。

日誌級別 經常使用場景
Trace = 0 記錄一些對程序員調試問題有幫助的信息, 其中可能包含一些敏感信息, 因此應該避免在 生產環境中啓用Trace日誌,所以不該該用於生產環境。默認應禁用。
Debug = 1 記錄一些在開發和調試階段有用的短時變 量(Short-term usefulness), 因此除非爲了臨時排除生產環境的 故障,開發人員應該儘可能避免在生產環境中啓用Debug日誌,默認狀況下這是最詳細的日誌。
Information = 2 記錄跟蹤應用程序的一些流程, 例如,記錄當前api請求的url。
Warning = 3 記錄應用程序中發生出現錯誤或其它致使程序中止的流程異常信息。 這些信息中可能包含錯誤消息或者錯誤產生的條件, 可供後續調查,例如, 文件未找到
Error = 4 記錄應用程序中某個操做產生的錯誤和異常信息。這些消息應該指明當前活動或操做(好比當前的 HTTP 請求),而不是應用程序範圍的故障。
Critical = 5 記錄一些須要馬上修復,急需被關注的問題,應當記錄關鍵級別的日誌。例如數據丟失,磁盤空間不足等。

日誌級別只須要簡單的經過 AddFilter 對日誌的過濾級別配置一下就好了。同時也能夠經過自定義在

Logging.{providername}.LogLevel 中指定了級別,則這些級別將重寫 Logging.LogLevel 中設置的全部內容。(在下文自定義中說明)

由此能夠看出,日誌記錄提供程序配置由一個或多個配置提供程序提供,如文件格式(系統自帶的appsettings.json)或者經過(已安裝或已建立的)自定義提供程序(下文會說明自定義方式)。

3.2 自定義配置

看完了上面實現的默認配置以後,咱們也清楚了能夠修改默認配置實現不一樣等級日誌的輸出,所以,咱們也能夠經過自定義的方式,對默認配置的修改,實現咱們想要的日誌記錄方式。

能夠經過自行選擇添加提供程序來替換默認配置的提供的程序。這樣就實現自定義。自定義的方式有不少,好比

3.2.1 代碼添加提供程序

調用ClearProviders,清除默認以後,可添加所需的提供程序。以下:

public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args) //能夠看出在使用模板建立項目的時候,默認添加了控制檯和調試日誌組件,並從appsettings.json中讀取配置。
        .ConfigureLogging((hostingContext, logging) =>
        {
            logging.ClearProviders(); //去掉默認添加的日誌提供程序
            //添加控制檯輸出
            logging.AddConsole();
            //添加調試輸出
            logging.AddDebug();
        })
        .ConfigureWebHostDefaults(webBuilder =>
        {  
            webBuilder.UseStartup<Startup>();
        });
}

由上能夠發現咱們能夠經過在入口程序中直接對添加ConfigureLogging(在上文中源碼能夠看出)拓展方法來實現咱們的自定義配置。

3.2.2 代碼添加過濾器

過濾器AddFilter,添加過濾規則,能夠爲不一樣的日誌提供者指定不一樣的過濾器,實現有效的自定義日誌的輸出。以下代碼:

.ConfigureLogging(logging =>
    logging.AddFilter("System", LogLevel.Debug)
           .AddFilter<DebugLoggerProvider>("Microsoft", LogLevel.Trace))

添加指定了全局的過濾器,做用於全部日誌提供者,示例中的第二個 AddFilter 使用類型名稱來指定調試提供程序。 第一個 AddFilter 應用於所有提供程序,由於它未指定提供程序類型。

這裏的AddFilter其實於以前讀取配置文件信息添加配置AddConfiguration的做用類似,只是從配置文件的邏輯改爲了以代碼的方式實現過濾篩選,到最終也是對ConfigureOptions 的配置。

3.2.3 配置文件自定義

ASP.NET Core默認會從appSetting.json中的Logging屬性讀取日誌的配置(固然你也能夠從其餘文件中讀取配置),這裏設置了不一樣的日誌提供器產生的最低的日誌級別,配置樣例以下。

{
  "Logging": {
    "Debug": {
      "LogLevel": {
        "Default": "Information"
      }
    },
    "Console": {
      "LogLevel": {
        "Microsoft.AspNetCore.Mvc.Razor.Internal": "Warning",
        "Microsoft.AspNetCore.Mvc.Razor.Razor": "Debug",
        "Microsoft.AspNetCore.Mvc.Razor": "Error",
        "Default": "Information"
      }
    },
    "LogLevel": {
      "Default": "Debug"
    }
  }
}

此 JSON 將建立 6 條篩選規則:Debug中1 條用於調試提供程序,Console中 4 條用於控制檯提供程序,最後一條LogLevel 用於全部提供程序。 建立 ILogger 對象時,爲每一個提供程序選擇一個規則。

4、問題

雖然在這一節中只是對日誌記錄的配置進行了說明,可是在後續中也會對日誌內部的核心運行機制進行說明介紹。因此,在這一篇中留下幾個疑問

  1. 日誌記錄的輸出能夠在哪裏查看?而又由什麼實現決定的呢?
  2. 如何管理輸出不一樣的日誌呢?都有哪些方式呢?

以上的這些內容,會在下一篇進行介紹說明。

好了,今天的日誌配置內容就說到這裏了,但願能給你們在使用Core開發項目中對日誌系統有進一步的認識。

5、總結

  1. 本篇主要是對net core3.1中內置的系統日誌進行配置使用,不論是基於默認配置的輸出方式,仍是自定義形式的配置,都是爲了有效的輸出日誌記錄,便於咱們查找發現問題。
  2. 關於日誌配置,其實都是在對ConfigureOptions的配置,只是在形式上是直接讀取配置文件或經過代碼的方式實現自定義來實現日誌配置。
  3. 後續會對內置的日誌系統進一步說明,以及內部運行的主要核心機制。
  4. 若是有不對的或不理解的地方,但願你們能夠多多指正,提出問題,一塊兒討論,不斷學習,共同進步。
  5. 官方源碼參考資料
相關文章
相關標籤/搜索