天天看點

ASP.NET Core 6架構揭秘執行個體示範「15」:針對控制台的日志輸出

作者:日行四善

針對控制台的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所示的結果可以看出日志輸出的格式是與我們指定的命名行參數比對的。

ASP.NET Core 6架構揭秘執行個體示範「15」:針對控制台的日志輸出

圖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)。

ASP.NET Core 6架構揭秘執行個體示範「15」:針對控制台的日志輸出

圖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所示的輸出。可以看出輸出的内容不僅包含參數填充生成的完整内容,還包含原始的模闆。日志範圍的路徑是以數組的方式輸出的。

圖3 基于JsonConsoleFormatter的格式化

[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中,控制台上将不會有任何輸出内容。

ASP.NET Core 6架構揭秘執行個體示範「15」:針對控制台的日志輸出

圖4 标準輸入和錯誤輸出

[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所示的形式輸出到控制台上。

ASP.NET Core 6架構揭秘執行個體示範「15」:針對控制台的日志輸出

圖5 基于TemplatedConsoleFormatter的格式化

原文位址:https://www.cnblogs.com/artech/p/inside-asp-net-core-6-15.html