針對控制台的ILogger實現類型為ConsoleLogger,對應的ILoggerProvider實現類型為ConsoleLoggerProvider,這兩個類型都定義在 NuGet包“Microsoft.Extensions.Logging.Console”中。ConsoleLogger要將一條日志輸出到控制台上,首選要解決的是格式化的問題,具體來說是如何將日志消息的內容荷載和元數據(類別、等級和事件ID等)格式化成呈現在控制台上的文本。針對日志的格式化由ConsoleFormatter對象來完成。(本篇提供的實例已經匯總到《ASP.NET Core 6框架揭秘-實例演示版》)
[S901]SimpleConsoleFormatter格式化器(源代碼)
[S902]SystemdConsoleFormatter格式化器(源代碼)
[S903]JsonConsoleFormatter格式化器(源代碼)
[S904]改變ConsoleLogger的標准輸出和錯誤輸出(源代碼)
[S905]自定義控制台日志的格式化器(源代碼)
[S901]SimpleConsoleFormatter格式化器
下面了代碼演示了如何使用SimpleConsoleFormatter來格式化控制台輸出的日志。我們利用命令行參數控制是否采用單行文本輸出和着色方案。在調用ILoggingBuiler接口的AddConsole擴展方法對ConsoleLoggerProvider對象進行注冊之后,我們接着調用它的AddSimpleConsole擴展方法將SimpleConsoleFormatter作為格式化器,並利用作為參數的Action<SimpleConsoleFormatterOptions>委托根據命令行參數的對SimpleConsoleFormatterOptions配置選項進行了相應設置(S901)。
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Console; var configuration = new ConfigurationBuilder() .AddCommandLine(args) .Build(); var singleLine = configuration.GetSection("singleLine").Get<bool>(); var colorBebavior = configuration.GetSection("color").Get<LoggerColorBehavior>(); var logger = LoggerFactory.Create(builder => builder .AddConsole() .AddSimpleConsole(options => { options.SingleLine = singleLine; options.ColorBehavior = colorBebavior; })) .CreateLogger<Program>(); var levels = (LogLevel[])Enum.GetValues(typeof(LogLevel)); levels = levels.Where(it => it != LogLevel.None).ToArray(); var eventId = 1; Array.ForEach(levels,level => logger.Log(level, eventId++, "This is a/an {0} log message.", level)); Console.Read();
我們已命名行的方式三次啟動演示程序,並利用參數(singleLine=true, color=Disabled)控制對應的格式化行為。從圖1所示的結果可以看出日志輸出的格式是與我們指定的命名行參數是匹配的。
圖1 基於SimpleConsoleFormatter的格式化
[S902]SystemdConsoleFormatter格式化器
我們使用如下這個實例來演示針對SystemdConsoleFormatter的格式化。如代碼片段所示,我們利用命令行參數“includeScopes”來決定是否支持日志范圍。在調用ILoggingBuilder接口的AddConsole擴展方法ConsoleLoggerProvider對象進行注冊之后,我們接着調用了該接口的AddSystemdConsole擴展方法對SystemdConsoleFormatter及其配置選項進行注冊。為了輸出所有等級的日志,我們將最低日志等級設置為Trace。為了體現針對異常信息的輸出,我們在調用Log方法是傳入了一個Exception對象。
using Microsoft.Extensions.Logging; var includeScopes = args.Contains("includeScopes"); var logger = LoggerFactory.Create(builder => builder .SetMinimumLevel(LogLevel.Trace) .AddConsole() .AddSystemdConsole(options => options.IncludeScopes = includeScopes)) .CreateLogger<Program>(); var levels = (LogLevel[])Enum.GetValues(typeof(LogLevel)); levels = levels.Where(it => it != LogLevel.None).ToArray(); var eventId = 1; Array.ForEach(levels, Log); Console.Read(); void Log(LogLevel logLevel) { using (logger.BeginScope("Foo")) { using (logger.BeginScope("Bar")) { using (logger.BeginScope("Baz")) { logger.Log(logLevel, eventId++, new Exception("Error..."), "This is a/an {0} log message.", logLevel); } } } }
我們采用命令行的方式兩次啟動演示程序,第一次采用默認配置,第二次利用命令行參數“includeScopes”開啟針對日志范圍的支持。從圖2所示的輸出結果可以看出六條日志均以單條文本的形式輸出到控制台上,對應的日志等級(Trace、Debug、Information、Warning、Error和Critical)均被轉換成Syslog日志等級(7、7、6、4、3和2)。
圖2 基於SystemdConsoleFormatter的格式化
[S903]JsonConsoleFormatter格式化器
我們對上面的演示程序略加改動,將針對ILoggingBuilder接口的AddSystemdConsole擴展方法的調用替換成調用AddJsonConsole擴展方法,后者幫助我們完成針對JsonConsoleFormatter及其配置選項的注冊。為了減少控制台上輸出的內容,我們移除了針對最低日志等級的設置。
using Microsoft.Extensions.Logging; var includeScopes = args.Contains("includeScopes"); var logger = LoggerFactory .Create(builder => builder .AddConsole() .AddJsonConsole(options => options.IncludeScopes = includeScopes)) .CreateLogger<Program>(); var levels = (LogLevel[])Enum.GetValues(typeof(LogLevel)); levels = levels.Where(it => it != LogLevel.None).ToArray(); var eventId = 1; Array.ForEach(levels, Log); Console.Read(); void Log(LogLevel logLevel) { using (logger.BeginScope("Foo")) { using (logger.BeginScope("Bar")) { using (logger.BeginScope("Baz")) { logger.Log(logLevel, eventId++, new Exception("Error..."), "This is a/an {0} log message.", logLevel); } } } }
我們依然采用上面的方式兩次執行改動后的程序。在默認以及開啟日志范圍的情況下,控制台分別具有圖3所示的輸出。可以看出輸出的內容不僅包含參數填充生成完整內容,還包含原始的模板。日志范圍的路徑是以數組的方式輸出的。
[S904]改變ConsoleLogger的標准輸出和錯誤輸出
ConsoleLogger具有“標准輸出”和“錯誤輸出”兩個輸出渠道,分別對應着Console類型的靜態屬性Out和Error返回的TextWriter對象。對於不高於LogToStandardErrorThreshold設定等級的日志會采用標准輸出,高於或者等於該等級的日志則采用錯誤輸出。由於LogToStandardErrorThreshold屬性的默認值為None,所以任何等級的日志都被寫入標准輸出。如下的代碼片段演示了如何通過設置這個屬性改變不同等級日志的輸出渠道。
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Console; using (var @out = new StreamWriter("out.log") { AutoFlush = true }) using (var error = new StreamWriter("error.log") { AutoFlush = true }) { Console.SetOut(@out); Console.SetError(error); var logger = LoggerFactory.Create(builder => builder .SetMinimumLevel(LogLevel.Trace) .AddConsole(options =>options.LogToStandardErrorThreshold = LogLevel.Error) .AddSimpleConsole(options =>options.ColorBehavior = LoggerColorBehavior.Disabled)) .CreateLogger<Program>(); var levels = (LogLevel[])Enum.GetValues(typeof(LogLevel)); levels = levels.Where(it => it != LogLevel.None).ToArray(); var eventId = 1; Array.ForEach(levels, Log); Console.Read(); void Log(LogLevel logLevel) => logger.Log(logLevel, eventId++, "This is a/an {0} log message.", logLevel); }
如上面的代碼片段所示,我們創建了兩個針對本地文件(out.log和error.log)的StreamWriter,並通過調用Console類型的靜態方法SetOut和SetError將其設置為控制台的標准輸出和錯誤輸出。在針對ILogingBuilder接口的AddConsole擴展方法調用中,我們將配置選項ConsoleLoggerOptions的LogToStandardErrorThreshold屬性設置為Error,並調用SetMinimumLevel方法將最低日志等級設置為Trace。我們還調用AddSimpleConsole擴展方法禁用作色方案。當程序運行之后,針對具有不同等級的六條日志,四條不高於Error的日志被輸出到如圖4所示的out.log中,另外兩條則作為錯誤日志被輸出到error.log中,控制台上將不會有任何輸出內容。
[S905]自定義控制台日志的格式化器
為了能夠更加靈活地控制日志在控制台上的輸出格式,我們自定義了如下這個格式化器類型。如代碼片段所示,這個名為TemplatedConsoleFormatter會按照指定的模板來格式化輸出的日志內容,它使用的配置選項類型為TemplatedConsoleFormatterOptions,日志格式模板就體現在它的Template屬性上。
public class TemplatedConsoleFormatter : ConsoleFormatter { private readonly bool _includeScopes; private readonly string _tempalte; public TemplatedConsoleFormatter(IOptions<TemplatedConsoleFormatterOptions> options) : base("templated") { _includeScopes = options.Value.IncludeScopes; _tempalte = options.Value?.Template?? "[{LogLevel}]{Category}/{EventId}:{Message}\n{Scopes}\n"; } public override void Write<TState>(in LogEntry<TState> logEntry, IExternalScopeProvider scopeProvider, TextWriter textWriter) { var builder = new StringBuilder(_tempalte); builder.Replace("{Category}", logEntry.Category); builder.Replace("{EventId}", logEntry.EventId.ToString()); builder.Replace("{LogLevel}", logEntry.LogLevel.ToString()); builder.Replace("{Message}", logEntry.Formatter?.Invoke(logEntry.State, logEntry.Exception)); if (_includeScopes && scopeProvider != null) { var buidler2 = new StringBuilder(); var writer = new StringWriter(buidler2); scopeProvider.ForEachScope(WriteScope, writer); void WriteScope(object? scope, StringWriter state) { writer.Write("=>" + scope); } builder.Replace("{Scopes}", buidler2.ToString()); } textWriter.Write(builder); } } public class TemplatedConsoleFormatterOptions: ConsoleFormatterOptions { public string? Template { get; set; } }
我們將TemplatedConsoleFormatter的別名指定為“templated”,格式模板利用占位符“{Category}”、“{EventId}”、“{LogLevel}”、“{Message}”和“{Scopes}”表示日志類別、事件ID、等級、消息和范圍信息。 “[{LogLevel}]{Category}/{EventId}:{Message}\n{Scopes}\n”是我們提供的默認模板。現在我們采用如下這個名為appsettings.json的JSON文件來提供所有的配置。
{ "Logging": { "Console": { "FormatterName": "templated", "LogToStandardErrorThreshold": "Error", "FormatterOptions": { "IncludeScopes": true, "UseUtcTimestamp": true, "Template": "[{LogLevel}]{Category}:{Message}\n" } } } }
如上面的代碼片段所示,我們將控制台日志輸出的相關設置定義在“Logging:Console”配置節中,並定義了格式化器名稱(“templated”)、錯誤日志最低等級(“Error”)。“FormatterOptions”配置節的內容將最終綁定到TemplatedConsoleFormatterOptions配置選項上。在如下所示的演示程序中,我們加載這個配置文件並提取代表“Logging”配置節的IConfigguration對象,我們將這個對象作為參數調用ILoggingBuilder接口的AddConfiguration擴展方法進行了注冊。
using App; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; var logger = LoggerFactory.Create(Configure).CreateLogger<Program>(); var levels = (LogLevel[])Enum.GetValues(typeof(LogLevel)); levels = levels.Where(it => it != LogLevel.None).ToArray(); var eventId = 1; Array.ForEach(levels, Log); Console.Read(); void Configure(ILoggingBuilder builder) { var configuration = new ConfigurationBuilder() .AddJsonFile("appsettings.json") .Build() .GetSection("Logging"); builder .AddConfiguration(configuration) .AddConsole() .AddConsoleFormatter<TemplatedConsoleFormatter,TemplatedConsoleFormatterOptions>(); } void Log(LogLevel logLevel) => logger.Log(logLevel, eventId++, "This is a/an {0} log message.", logLevel);
在調用ILoggingBuilder接口的AddConsole擴展方法對ConsoleLoggerProvider進行注冊之后,我們調用了它的AddConsoleFormatter<TemplatedConsoleFormatter,TemplatedConsoleFormatterOptions>方法,該方法將利用配置來綁定注冊格式化器對應的TemplatedConsoleFormatterOptions配置選項。演示程序啟動之后,每一條日志將按照配置中提供的模板進行格式化,並以圖5所示的形式輸出到控制台上。