命名空间 Microsoft.Extensions.Logging.Console 支持控制台日志中的自定义格式设置。 有三个可用的预定义格式设置选项:Simple和SystemdJson。
重要
在 .NET 5 之前, ConsoleLoggerFormat 枚举允许选择所需的日志格式,可以选择人类可读的 Default 格式,或称为单行的 Systemd 格式。 但是,这些不可自定义,现在已弃用。
在本文中,你将了解控制台日志格式化程序。 示例源代码演示如何:
- 注册新的格式化程序。
- 选择一个已注册格式化程序来通过代码或配置使用。
- 实现自定义格式化器。 通过 IOptionsMonitor<TOptions> 更新配置并启用自定义颜色格式。
小窍门
所有日志记录示例源代码都可以在示例浏览器中下载。 有关详细信息,请参阅浏览代码示例:.NET 中的日志记录。
注册格式化程序
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 使用默认配置选择控制台日志格式化程序。 可以通过在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.");
}
定义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"指定一个"FormatterName"与"CustomTimePrefixingFormatter"的关系,映射到自定义格式化程序。 - 该
"FormatterOptions"节点定义一个"CustomPrefix"、"CustomSuffix",以及一些其他派生选项。
小窍门
JSON $.Logging.Console.FormatterOptions 路径是保留的,将使用扩展方法添加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 属性,这对在日志中启用颜色很有用。
创建一个派生自 SimpleConsoleFormatterOptions 的 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();
}
运行应用程序时,当FormatterOptions.ColorBehaviorEnabled时, 日志将显示CustomPrefix消息为绿色。
注释
如果 LoggerColorBehavior 是 Disabled,日志消息将 不会 解释其中嵌入的 ANSI 颜色代码。 而是输出原始消息。 例如,请考虑以下事项:
logger.LogInformation("Random log \x1B[42mwith green background\x1B[49m message");
这会输出逐字字符串,并且 不会 着色。
Random log \x1B[42mwith green background\x1B[49m message