在 .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
控制台日志格式化程序自动被选择并使用默认配置。 可以通过在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.Indentedtrue
。
谨慎
使用 Json 控制台格式化程序时,不要传入已序列化为 JSON 的日志消息。 日志记录基础结构本身已经管理日志消息的序列化,因此,如果要传入已序列化的日志消息,则会进行双重序列化,从而导致格式不正确的输出。
通过配置设置格式化程序
前面的示例演示了如何以编程方式注册一个格式化程序。 或者,这可以通过 配置来完成。 如果更新 appsettings.json 文件而不是在ConfigureLogging
文件中调用,则可以获得相同的结果,请考虑以前的 Web 应用程序示例源代码。 更新后的 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扩展方法添加时,会映射到自定义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 属性,该属性在日志中启用颜色时非常有用。
创建一个CustomColorOptions
,此CustomColorOptions
派生自。
using Microsoft.Extensions.Logging.Console;
namespace Console.ExampleFormatters.Custom;
public class CustomColorOptions : SimpleConsoleFormatterOptions
{
public string? CustomPrefix { get; set; }
}
接下来,在允许在格式化日志消息中方便地嵌入 ANSI 编码颜色的类中 TextWriterExtensions
编写一些扩展方法:
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