名稱空間 Microsoft.Extensions.Logging.Console 支援控制台日誌中的自訂格式。 有三種預設格式選項:Simple、、 SystemdJson和 。
這很重要
在 .NET 5 之前,ConsoleLoggerFormat 列舉允許選擇所需的日誌格式,無論是人類可讀的格式(Default)還是單行格式(也稱為 Systemd)。 然而,這些都 無法 自訂,現已被棄用。
在本文中,你將了解主控台日誌格式化器。 範例原始碼示範如何:
- 註冊一個新的格式化器。
- 選擇已註冊的格式化器,不論是透過程式碼或是設定來使用。
- 實作自訂格式化器。 你使用 IOptionsMonitor<TOptions> 來更新設定並啟用自訂色彩格式。
小提示
所有記錄範例原始程式碼都可在範例瀏覽器中下載。 如需詳細資訊,請參閱 瀏覽程式碼範例: 在 .NET 中記錄。
Register formatter
日誌Console提供者有多種預設格式化工具,並允許自行撰寫自訂格式化工具。 要註冊任何可用的格式化器,請使用相應 Add{Type}Console 的擴充功能方法:
Simple
要使用 Simple 主控台格式化器,請註冊為 AddSimpleConsole:
using Microsoft.Extensions.Logging;
using ILoggerFactory loggerFactory =
LoggerFactory.Create(builder =>
builder.AddSimpleConsole(options =>
{
options.IncludeScopes = true;
options.SingleLine = true;
options.TimestampFormat = "HH:mm:ss ";
}));
ILogger<Program> logger = loggerFactory.CreateLogger<Program>();
using (logger.BeginScope("[scope is enabled]"))
{
logger.LogInformation("Hello World!");
logger.LogInformation("Logs contain timestamp and log level.");
logger.LogInformation("Each log message is fit in a single line.");
}
在前述範例原始碼中, ConsoleFormatterNames.Simple 格式化器被註冊。 它不僅能讓日誌在每個日誌訊息中包裹時間和日誌等級等資訊,還能支援 ANSI 色彩嵌入與縮排訊息。
當執行此範例應用程式時,日誌訊息格式如下所示:
Systemd
ConsoleFormatterNames.Systemd控制台記錄器:
- 使用「Syslog」日誌層級格式及嚴重度。
- 不會用顏色格式化訊息。
- 總是將訊息記錄在單行中。
這對於容器非常有用,因為容器通常會使用 Systemd 主控台日誌。
Simple主控台記錄器還能提供一個精簡版本,可以在單一行內記錄,並且還允許如先前範例所示選擇禁用顏色。
using Microsoft.Extensions.Logging;
using ILoggerFactory loggerFactory =
LoggerFactory.Create(builder =>
builder.AddSystemdConsole(options =>
{
options.IncludeScopes = true;
options.TimestampFormat = "HH:mm:ss ";
}));
ILogger<Program> logger = loggerFactory.CreateLogger<Program>();
using (logger.BeginScope("[scope is enabled]"))
{
logger.LogInformation("Hello World!");
logger.LogInformation("Logs contain timestamp and log level.");
logger.LogInformation("Systemd console logs never provide color options.");
logger.LogInformation("Systemd console logs always appear in a single line.");
}
範例輸出類似以下日誌訊息:
Json
若要以 JSON 格式撰寫日誌, Json 則使用主控台格式化器。 範例原始碼展示了 ASP.NET Core 應用程式如何註冊它。 使用範本 webapp ,使用 dotnet new 指令建立一個新的 ASP.NET Core應用程式:
dotnet new webapp -o Console.ExampleFormatters.Json
使用範本程式碼執行應用程式時,你會得到以下預設的日誌格式:
info: Console.ExampleFormatters.Json.Startup[0]
Hello .NET friends!
info: Microsoft.Hosting.Lifetime[14]
Now listening on: https://localhost:5001
info: Microsoft.Hosting.Lifetime[14]
Now listening on: http://localhost:5000
info: Microsoft.Hosting.Lifetime[0]
Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
Hosting environment: Development
info: Microsoft.Hosting.Lifetime[0]
Content root path: .\snippets\logging\console-formatter-json
預設情況下, Simple 主控台日誌格式化器會以預設設定被選中。 你透過呼叫AddJsonConsoleProgram.cs來改變這個狀況:
using System.Text.Json;
HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
builder.Logging.AddJsonConsole(options =>
{
options.IncludeScopes = false;
options.TimestampFormat = "HH:mm:ss ";
options.JsonWriterOptions = new JsonWriterOptions
{
Indented = true
};
});
using IHost host = builder.Build();
var logger =
host.Services
.GetRequiredService<ILoggerFactory>()
.CreateLogger<Program>();
logger.LogInformation("Hello .NET friends!");
await host.RunAsync();
或者,您也可以使用日誌設定來設定,例如 appsettings.json 檔案中的設定:
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
},
"Console": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
},
"FormatterName": "json",
"FormatterOptions": {
"SingleLine": true,
"IncludeScopes": true,
"TimestampFormat": "HH:mm:ss ",
"UseUtcTimestamp": true,
"JsonWriterOptions": {
"Indented": true
}
}
}
},
"AllowedHosts": "*"
}
再執行一次應用程式,經過上述更改後,日誌訊息格式改為 JSON:
{
"Timestamp": "02:28:19 ",
"EventId": 0,
"LogLevel": "Information",
"Category": "Console.ExampleFormatters.Json.Startup",
"Message": "Hello .NET friends!",
"State": {
"Message": "Hello .NET friends!",
"{OriginalFormat}": "Hello .NET friends!"
}
}
{
"Timestamp": "02:28:21 ",
"EventId": 14,
"LogLevel": "Information",
"Category": "Microsoft.Hosting.Lifetime",
"Message": "Now listening on: https://localhost:5001",
"State": {
"Message": "Now listening on: https://localhost:5001",
"address": "https://localhost:5001",
"{OriginalFormat}": "Now listening on: {address}"
}
}
{
"Timestamp": "02:28:21 ",
"EventId": 14,
"LogLevel": "Information",
"Category": "Microsoft.Hosting.Lifetime",
"Message": "Now listening on: http://localhost:5000",
"State": {
"Message": "Now listening on: http://localhost:5000",
"address": "http://localhost:5000",
"{OriginalFormat}": "Now listening on: {address}"
}
}
{
"Timestamp": "02:28:21 ",
"EventId": 0,
"LogLevel": "Information",
"Category": "Microsoft.Hosting.Lifetime",
"Message": "Application started. Press Ctrl\u002BC to shut down.",
"State": {
"Message": "Application started. Press Ctrl\u002BC to shut down.",
"{OriginalFormat}": "Application started. Press Ctrl\u002BC to shut down."
}
}
{
"Timestamp": "02:28:21 ",
"EventId": 0,
"LogLevel": "Information",
"Category": "Microsoft.Hosting.Lifetime",
"Message": "Hosting environment: Development",
"State": {
"Message": "Hosting environment: Development",
"envName": "Development",
"{OriginalFormat}": "Hosting environment: {envName}"
}
}
{
"Timestamp": "02:28:21 ",
"EventId": 0,
"LogLevel": "Information",
"Category": "Microsoft.Hosting.Lifetime",
"Message": "Content root path: .\\snippets\\logging\\console-formatter-json",
"State": {
"Message": "Content root path: .\\snippets\\logging\\console-formatter-json",
"contentRoot": ".\\snippets\\logging\\console-formatter-json",
"{OriginalFormat}": "Content root path: {contentRoot}"
}
}
小提示
Json主控台格式化器預設會將每則訊息記錄在一行內。 為了讓格式化器更易閱讀,請將 JsonWriterOptions.Indented 設定為 true。
謹慎
使用 JSON 主控台格式化器時,不要傳送已經序列化成 JSON 的日誌訊息。 日誌基礎設施本身負責管理日誌訊息的序列化。 所以如果你傳入一個已經序列化的日誌訊息,它會被重複序列化,導致輸出異常。
設定具備配置的格式化器
先前的範例展示了如何程式化註冊格式化器。 或者,這可以透過 設定來完成。 以之前的網頁應用程式範例原始碼為例,如果你更新 appsettings.json 檔案而不是呼叫ConfigureLoggingProgram.cs檔案,結果可能差不多。 更新後 appsettings.json 的檔案會將格式化器配置如下:
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
},
"Console": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
},
"FormatterName": "json",
"FormatterOptions": {
"SingleLine": true,
"IncludeScopes": true,
"TimestampFormat": "HH:mm:ss ",
"UseUtcTimestamp": true,
"JsonWriterOptions": {
"Indented": true
}
}
}
},
"AllowedHosts": "*"
}
需要設定的兩個關鍵值是 "FormatterName" 和 "FormatterOptions"。 如果格式化器已經註冊並設置為 "FormatterName" 值,則會選擇該格式化器,並且只要在 "FormatterOptions" 節點內以作為鍵來提供,便可以設定其屬性。 預設的格式化器名稱已在 ConsoleFormatterNames:中保留。
實作自訂格式化器
要實作自訂格式化器,你需要:
- 建立一個 ConsoleFormatter 子類別,代表你的自訂格式化器。
- 註冊你的自訂格式器:
為你建立一個擴充方法來處理這件事:
using Microsoft.Extensions.Logging;
namespace Console.ExampleFormatters.Custom;
public static class ConsoleLoggerExtensions
{
public static ILoggingBuilder AddCustomFormatter(
this ILoggingBuilder builder,
Action<CustomOptions> configure) =>
builder.AddConsole(options => options.FormatterName = "customName")
.AddConsoleFormatter<CustomFormatter, CustomOptions>(configure);
}
定義 CustomOptions 如下:
using Microsoft.Extensions.Logging.Console;
namespace Console.ExampleFormatters.Custom;
public sealed class CustomOptions : ConsoleFormatterOptions
{
public string? CustomPrefix { get; set; }
}
在前述程式碼中,選項是ConsoleFormatterOptions的子類。
AddConsoleFormatter API:
- 註冊
ConsoleFormatter的子類。 - 負責設定配置 它使用變更權杖來同步更新,依據 選項模式,並使用 IOptionsMonitor 介面。
using Console.ExampleFormatters.Custom;
using Microsoft.Extensions.Logging;
using ILoggerFactory loggerFactory =
LoggerFactory.Create(builder =>
builder.AddCustomFormatter(options =>
options.CustomPrefix = " ~~~~~ "));
ILogger<Program> logger = loggerFactory.CreateLogger<Program>();
using (logger.BeginScope("TODO: Add logic to enable scopes"))
{
logger.LogInformation("Hello World!");
logger.LogInformation("TODO: Add logic to enable timestamp and log level info.");
}
定義CustomFormatter的子類ConsoleFormatter:
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Logging.Console;
using Microsoft.Extensions.Options;
namespace Console.ExampleFormatters.Custom;
public sealed class CustomFormatter : ConsoleFormatter, IDisposable
{
private readonly IDisposable? _optionsReloadToken;
private CustomOptions _formatterOptions;
public CustomFormatter(IOptionsMonitor<CustomOptions> options)
// Case insensitive
: base("customName") =>
(_optionsReloadToken, _formatterOptions) =
(options.OnChange(ReloadLoggerOptions), options.CurrentValue);
private void ReloadLoggerOptions(CustomOptions options) =>
_formatterOptions = options;
public override void Write<TState>(
in LogEntry<TState> logEntry,
IExternalScopeProvider? scopeProvider,
TextWriter textWriter)
{
string? message =
logEntry.Formatter?.Invoke(
logEntry.State, logEntry.Exception);
if (message is null)
{
return;
}
CustomLogicGoesHere(textWriter);
textWriter.WriteLine(message);
}
private void CustomLogicGoesHere(TextWriter textWriter)
{
textWriter.Write(_formatterOptions.CustomPrefix);
}
public void Dispose() => _optionsReloadToken?.Dispose();
}
前述 CustomFormatter.Write<TState> 的 API 決定了每個日誌訊息要包裹哪些文字。 標準 ConsoleFormatter 應該能涵蓋範圍、時間戳和日誌的嚴重程度,至少是這樣。 此外,你可以在日誌訊息中編碼 ANSI 顏色,並提供所需的縮排。 但實作 CustomFormatter.Write<TState> 中缺乏這些功能。
如需進一步自訂格式的靈感,請參閱命名空間中 Microsoft.Extensions.Logging.Console 現有的實作:
自訂設定選項
為了進一步自定義日誌擴充功能,您的衍生 ConsoleFormatterOptions 類別可從任何 配置提供程序設定。 例如,你可以使用 JSON 設定提供者 來定義自訂選項。 首先定義你的 ConsoleFormatterOptions 子類別。
using Microsoft.Extensions.Logging.Console;
namespace Console.ExampleFormatters.CustomWithConfig;
public sealed class CustomWrappingConsoleFormatterOptions : ConsoleFormatterOptions
{
public string? CustomPrefix { get; set; }
public string? CustomSuffix { get; set; }
}
前述的主控台格式化選項類別定義了兩個自訂屬性,分別代表前綴與後綴。 接著,定義 appsettings.json 檔案來設定你的主控台格式化選項。
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
},
"Console": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
},
"FormatterName": "CustomTimePrefixingFormatter",
"FormatterOptions": {
"CustomPrefix": "|-<[",
"CustomSuffix": "]>-|",
"SingleLine": true,
"IncludeScopes": true,
"TimestampFormat": "HH:mm:ss.ffff ",
"UseUtcTimestamp": true,
"JsonWriterOptions": {
"Indented": true
}
}
}
},
"AllowedHosts": "*"
}
在前面的 JSON 設定檔中:
- 節點
"Logging"定義了一個"Console"。 - 節點
"Console"指定"FormatterName"作為"CustomTimePrefixingFormatter",並映射到自訂格式化器。 - 節點
"FormatterOptions"定義了一個"CustomPrefix"、 和"CustomSuffix",以及其他幾個導出選項。
小提示
$.Logging.Console.FormatterOptions JSON 路徑是保留的,當使用 ConsoleFormatterOptions Extension 方法新增時,會映射到自訂AddConsoleFormatter路徑。 這讓它能定義自訂屬性,除了現有的屬性外。
請考慮下列事項 CustomDatePrefixingFormatter:
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Logging.Console;
using Microsoft.Extensions.Options;
namespace Console.ExampleFormatters.CustomWithConfig;
public sealed class CustomTimePrefixingFormatter : ConsoleFormatter, IDisposable
{
private readonly IDisposable? _optionsReloadToken;
private CustomWrappingConsoleFormatterOptions _formatterOptions;
public CustomTimePrefixingFormatter(
IOptionsMonitor<CustomWrappingConsoleFormatterOptions> options)
// Case insensitive
: base(nameof(CustomTimePrefixingFormatter))
{
_optionsReloadToken = options.OnChange(ReloadLoggerOptions);
_formatterOptions = options.CurrentValue;
}
private void ReloadLoggerOptions(CustomWrappingConsoleFormatterOptions options) =>
_formatterOptions = options;
public override void Write<TState>(
in LogEntry<TState> logEntry,
IExternalScopeProvider? scopeProvider,
TextWriter textWriter)
{
string message =
logEntry.Formatter(
logEntry.State, logEntry.Exception);
if (message == null)
{
return;
}
WritePrefix(textWriter);
textWriter.Write(message);
WriteSuffix(textWriter);
}
private void WritePrefix(TextWriter textWriter)
{
DateTime now = _formatterOptions.UseUtcTimestamp
? DateTime.UtcNow
: DateTime.Now;
textWriter.Write($"""
{_formatterOptions.CustomPrefix} {now.ToString(_formatterOptions.TimestampFormat)}
""");
}
private void WriteSuffix(TextWriter textWriter) =>
textWriter.WriteLine($" {_formatterOptions.CustomSuffix}");
public void Dispose() => _optionsReloadToken?.Dispose();
}
在前述的格式化器實作中:
- 這些資訊
CustomWrappingConsoleFormatterOptions會被監控以追蹤變化,並相應地更新。 - 寫入的訊息會以設定的前綴和後綴包裝。
- 時間戳記會在前綴之後、訊息之前,依照配置的 ConsoleFormatterOptions.UseUtcTimestamp 和 ConsoleFormatterOptions.TimestampFormat 值進行添加。
若要使用自訂設定選項並搭配自訂格式化器實作,在呼叫ConfigureLogging(IHostBuilder, Action<HostBuilderContext,ILoggingBuilder>)時需加入它們。
using Console.ExampleFormatters.CustomWithConfig;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
builder.Logging.AddConsole()
.AddConsoleFormatter<
CustomTimePrefixingFormatter, CustomWrappingConsoleFormatterOptions>();
using IHost host = builder.Build();
ILoggerFactory loggerFactory = host.Services.GetRequiredService<ILoggerFactory>();
ILogger<Program> logger = loggerFactory.CreateLogger<Program>();
using (logger.BeginScope("Logging scope"))
{
logger.LogInformation("Hello World!");
logger.LogInformation("The .NET developer community happily welcomes you.");
}
以下的主控台輸出與你使用這個 CustomTimePrefixingFormatter時可能看到的類似。
|-<[ 15:03:15.6179 Hello World! ]>-|
|-<[ 15:03:15.6347 The .NET developer community happily welcomes you. ]>-|
實作自訂色彩格式
為了在自訂日誌格式器中正確啟用色彩功能,你可以擴充它 SimpleConsoleFormatterOptions ,因為它有一個 SimpleConsoleFormatterOptions.ColorBehavior 功能對啟用日誌中的顏色很有用。
創建一個從SimpleConsoleFormatterOptions衍生的CustomColorOptions:
using Microsoft.Extensions.Logging.Console;
namespace Console.ExampleFormatters.Custom;
public class CustomColorOptions : SimpleConsoleFormatterOptions
{
public string? CustomPrefix { get; set; }
}
接著,在類別中撰寫一些擴充方法 TextWriterExtensions ,方便地將 ANSI 編碼的顏色嵌入格式化的日誌訊息中:
namespace Console.ExampleFormatters.Custom;
public static class TextWriterExtensions
{
const string DefaultForegroundColor = "\x1B[39m\x1B[22m";
const string DefaultBackgroundColor = "\x1B[49m";
public static void WriteWithColor(
this TextWriter textWriter,
string message,
ConsoleColor? background,
ConsoleColor? foreground)
{
// Order:
// 1. background color
// 2. foreground color
// 3. message
// 4. reset foreground color
// 5. reset background color
var backgroundColor = background.HasValue ? GetBackgroundColorEscapeCode(background.Value) : null;
var foregroundColor = foreground.HasValue ? GetForegroundColorEscapeCode(foreground.Value) : null;
if (backgroundColor != null)
{
textWriter.Write(backgroundColor);
}
if (foregroundColor != null)
{
textWriter.Write(foregroundColor);
}
textWriter.WriteLine(message);
if (foregroundColor != null)
{
textWriter.Write(DefaultForegroundColor);
}
if (backgroundColor != null)
{
textWriter.Write(DefaultBackgroundColor);
}
}
static string GetForegroundColorEscapeCode(ConsoleColor color) =>
color switch
{
ConsoleColor.Black => "\x1B[30m",
ConsoleColor.DarkRed => "\x1B[31m",
ConsoleColor.DarkGreen => "\x1B[32m",
ConsoleColor.DarkYellow => "\x1B[33m",
ConsoleColor.DarkBlue => "\x1B[34m",
ConsoleColor.DarkMagenta => "\x1B[35m",
ConsoleColor.DarkCyan => "\x1B[36m",
ConsoleColor.Gray => "\x1B[37m",
ConsoleColor.Red => "\x1B[1m\x1B[31m",
ConsoleColor.Green => "\x1B[1m\x1B[32m",
ConsoleColor.Yellow => "\x1B[1m\x1B[33m",
ConsoleColor.Blue => "\x1B[1m\x1B[34m",
ConsoleColor.Magenta => "\x1B[1m\x1B[35m",
ConsoleColor.Cyan => "\x1B[1m\x1B[36m",
ConsoleColor.White => "\x1B[1m\x1B[37m",
_ => DefaultForegroundColor
};
static string GetBackgroundColorEscapeCode(ConsoleColor color) =>
color switch
{
ConsoleColor.Black => "\x1B[40m",
ConsoleColor.DarkRed => "\x1B[41m",
ConsoleColor.DarkGreen => "\x1B[42m",
ConsoleColor.DarkYellow => "\x1B[43m",
ConsoleColor.DarkBlue => "\x1B[44m",
ConsoleColor.DarkMagenta => "\x1B[45m",
ConsoleColor.DarkCyan => "\x1B[46m",
ConsoleColor.Gray => "\x1B[47m",
_ => DefaultBackgroundColor
};
}
可以定義一個自訂色彩格式化器,用於處理自訂色彩的套用,具體方法如下:
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Logging.Console;
using Microsoft.Extensions.Options;
namespace Console.ExampleFormatters.Custom;
public sealed class CustomColorFormatter : ConsoleFormatter, IDisposable
{
private readonly IDisposable? _optionsReloadToken;
private CustomColorOptions _formatterOptions;
private bool ConsoleColorFormattingEnabled =>
_formatterOptions.ColorBehavior == LoggerColorBehavior.Enabled ||
_formatterOptions.ColorBehavior == LoggerColorBehavior.Default &&
System.Console.IsOutputRedirected == false;
public CustomColorFormatter(IOptionsMonitor<CustomColorOptions> options)
// Case insensitive
: base("customName") =>
(_optionsReloadToken, _formatterOptions) =
(options.OnChange(ReloadLoggerOptions), options.CurrentValue);
private void ReloadLoggerOptions(CustomColorOptions options) =>
_formatterOptions = options;
public override void Write<TState>(
in LogEntry<TState> logEntry,
IExternalScopeProvider? scopeProvider,
TextWriter textWriter)
{
if (logEntry.Exception is null)
{
return;
}
string? message =
logEntry.Formatter?.Invoke(
logEntry.State, logEntry.Exception);
if (message is null)
{
return;
}
CustomLogicGoesHere(textWriter);
textWriter.WriteLine(message);
}
private void CustomLogicGoesHere(TextWriter textWriter)
{
if (ConsoleColorFormattingEnabled)
{
textWriter.WriteWithColor(
_formatterOptions.CustomPrefix ?? string.Empty,
ConsoleColor.Black,
ConsoleColor.Green);
}
else
{
textWriter.Write(_formatterOptions.CustomPrefix);
}
}
public void Dispose() => _optionsReloadToken?.Dispose();
}
當你執行應用程式時,日誌會顯示 CustomPrefix 訊息為綠色,當 FormatterOptions.ColorBehavior 是 Enabled 時。
備註
當 LoggerColorBehavior 為 Disabled時,日誌訊息 不會 解讀日誌訊息中嵌入的 ANSI 顏色代碼。 相反地,他們輸出的是原始訊息。 例如,請考慮下列事項:
logger.LogInformation("Random log \x1B[42mwith green background\x1B[49m message");
這樣會輸出逐字字串,且 不會 被著色。
Random log \x1B[42mwith green background\x1B[49m message