一、前言
在項目的開發維護階段,有時候我們關注的問題不僅僅在於功能的實現,甚至需要關注系統發布上線后遇到的問題能否及時的查找並解決。所以我們需要有一個好的解決方案來及時的定位錯誤的根源並做出正確及時的修復,這樣才能不影響系統正常的運行狀態。

這個時候我們發現,其實在asp.net core中已經內置了日志系統,並提供了各種內置和第三方日志記錄提供程序的日志記錄接口,在進行應用開發中,可以進行統一配置,並且利用第三方日志框架相結合,更加有效的實現日志記錄。所以在這個系列中,主要是對內置日志記錄系統的學習,以及后續使用第三方日志框架集成我們需要的日志系統。
二、說明
在這一篇中主要是對日志記錄的配置進行說明,從開始配置日志,以及后續使用配置進行日志處理。
在新建項目成功之后,我們都會看到一個命名為appsettings.json
配置,打開一看,短短的幾行配置,
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
然后啟動運行的時候,程序會在調試面板和控制台中分別輸出顯示來源如下:
在控制台中:
在調試面板中:
這里的日志配置,在系統中到底都起來什么作用?讓我們來一探究竟吧!
三、開始
3.1 默認配置
我們查看源代碼發現,在程序的入口點中發現,在初始化時候,通過CreateDefaultBuilder
方法來實現日志記錄的默認配置。
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 內置的日志接口中,實現了多種內置的日志提供器,除了上面默認實現的
Console
、Debug
和EventSource
,還包括下面的這幾個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
對象時,為每個提供程序選擇一個規則。
四、問題
雖然在這一節中只是對日志記錄的配置進行了說明,但是在后續中也會對日志內部的核心運行機制進行說明介紹。所以,在這一篇中留下幾個疑問
- 日志記錄的輸出可以在哪里查看?而又由什么實現決定的呢?
- 如何管理輸出不同的日志呢?都有哪些方式呢?
以上的這些內容,會在下一篇進行介紹說明。
好了,今天的日志配置內容就說到這里了,希望能給大家在使用Core開發項目中對日志系統有進一步的認識。