控制台日志格式设置
在 .NET 5 中,已向 Microsoft.Extensions.Logging.Console
命名空间中的控制台日志添加了对自定义格式设置的支持。 有三种预定义的格式设置选项可供选择:Simple
、Systemd
和 Json
。
重要
以前,ConsoleLoggerFormat 枚举允许选择所需的日志格式,可以是人工可读取的 (Default
),也可以是单行(也称为 Systemd
)。 不过,这些都是不能进行自定义的,现已弃用。
本文介绍了控制台日志格式化程序。 示例源代码演示了如何执行以下操作:
- 注册新的格式化程序
- 选择要使用的已注册格式化程序
- 通过代码或配置
- 实现自定义格式化程序
- 通过 IOptionsMonitor<TOptions> 更新配置
- 启用自定义颜色格式设置
提示
所有日志记录示例源代码都可以在示例浏览器中下载。 有关详细信息,请参阅浏览代码示例:.NET 中的日志记录。
注册格式化程序
Console
日志记录提供程序有几个预定义的格式化程序,并公开了可供你编写自己的自定义格式化程序的功能。 要注册任何可用的格式化程序,请使用相应的 Add{Type}Console
扩展方法:
简单
要使用 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
控制台日志记录。 在 .NET 5 中,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
控制台日志格式化程序。 可以通过在 Program.cs 中调用 AddJsonConsole
来更改此设置:
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 的日志消息。 日志记录基础结构本身已管理日志消息的序列化,因此,如果你传入已经序列化的日志消息,它将双重序列化,从而导致输出格式不正确。
通过配置设置格式化程序
前面的示例展示了如何以编程方式注册格式化程序。 另外,也可以通过配置来完成。 考虑到之前的 Web 应用示例源代码,如果更新 appsettings.json 文件,而不是调用 Program.cs 文件中的 ConfigureLogging
,可以得到相同的结果。 更新后的 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.");
}
定义 ConsoleFormatter
的 CustomFormatter
子类:
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"
节点指定"CustomTimePrefixingFormatter"
的"FormatterName"
,它映射到自定义格式化程序。"FormatterOptions"
节点定义"CustomPrefix"
和"CustomSuffix"
,以及其他一些派生的选项。
提示
$.Logging.Console.FormatterOptions
JSON 路径已保留,使用 AddConsoleFormatter 扩展方法添加时,它将映射到自定义 ConsoleFormatterOptions。 除了可用属性之外,这还能定义自定义属性。
假设为以下 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();
}
当你运行应用程序时,日志会在 FormatterOptions.ColorBehavior
为 Enabled
时用绿色显示 CustomPrefix
消息。
注意
当 LoggerColorBehavior 为 Disabled
时,日志消息不会解释日志消息中嵌入的 ANSI 颜色代码。 相反,它们会输出原始消息。 例如,请考虑如下事项:
logger.LogInformation("Random log \x1B[42mwith green background\x1B[49m message");
这会输出逐字字符串,且不会着色。
Random log \x1B[42mwith green background\x1B[49m message