Formatieren von Konsolenprotokollen

In .NET 5 wurde den Konsolenprotokollen im Namespace Microsoft.Extensions.Logging.Console Unterstützung für benutzerdefinierte Formatierung hinzugefügt. Es stehen drei vordefinierte Formatierungsoptionen zur Verfügung: Simple, Systemd und Json.

Wichtig

Zuvor ließ die Enumeration ConsoleLoggerFormat die Auswahl des gewünschten Protokollformats zu, entweder für Menschen lesbar (Default) oder eine einzelne Zeile, auch als Systemd bezeichnet. Diese konnten jedoch nicht angepasst werden und sind nun veraltet.

In diesem Artikel erfahren Sie mehr über die Formatierer von Konsolenprotokollen. Der Beispielquellcode veranschaulicht Folgendes:

  • Registrieren eines neuen Formatierers
  • Auswählen eines zu verwendenden registrierten Formatierers
  • Implementieren eines benutzerdefinierten Formatierers

Tipp

Der gesamte Quellcode des Protokollierungsbeispiels steht im Beispielbrowser zum Download zur Verfügung. Weitere Informationen finden Sie unter Durchsuchen von Codebeispielen: Protokollierung in .NET.

Registrieren eines Formatierers

Der Console-Protokollierungsanbieter verfügt über mehrere vordefinierte Formatierer und bietet die Möglichkeit, einen eigenen benutzerdefinierten Formatierer zu erstellen. Verwenden Sie die entsprechende Erweiterungsmethode Add{Type}Console, um verfügbare Formatierer zu registrieren:

Verfügbare Typen Methode zum Registrieren des Typs
ConsoleFormatterNames.Json ConsoleLoggerExtensions.AddJsonConsole
ConsoleFormatterNames.Simple ConsoleLoggerExtensions.AddSimpleConsole
ConsoleFormatterNames.Systemd ConsoleLoggerExtensions.AddSystemdConsole

Einfach

Um den Konsolenformatierer Simple zu verwenden, registrieren Sie ihn über 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.");
}

Im vorangehenden Beispielquellcode wurde der Formatierer ConsoleFormatterNames.Simple registriert. Er bietet für Protokolle nicht nur die Möglichkeit, Informationen wie z. B. Zeit und Protokollgrad in jeder Protokollmeldung zu umschließen, sondern ermöglicht auch die Einbettung von ANSI-Farben und Einzüge für Meldungen.

Wenn diese Beispiel-App ausgeführt wird, werden die Protokollmeldungen wie unten dargestellt formatiert:

Beispielkonsolenprotokolle, die mit dem einfachen Formatierer geschrieben wurden.

Systemd

Für die ConsoleFormatterNames.Systemd-Konsolenprotokollierung gilt Folgendes:

  • Sie verwendet das Protokolliergradformat und die Schweregrade von Syslog.
  • Sie formatiert Meldungen nicht mit Farben.
  • Sie protokolliert Meldungen immer in einer einzigen Zeile.

Dies ist im Allgemeinen nützlich für Container, die häufig die Systemd-Konsolenprotokollierung verwenden. Mit .NET 5 wird für die Simple-Konsolenprotokollierung außerdem eine kompakte Version aktiviert, die in einer einzigen Zeile protokolliert. Zudem können Farben, wie in einem früheren Beispiel gezeigt, deaktiviert werden.

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.");
}

Das Beispiel führt zu einer Ausgabe, die den folgenden Protokollmeldungen ähnelt:

Beispielkonsolenprotokolle, die mit dem Systemd-Formatierer geschrieben wurden.

Json

Zum Schreiben von Protokollen in einem JSON-Format wird der Json-Konsolenformatierer verwendet. Der Beispielquellcode zeigt, wie dieser über eine ASP.NET Core-App registriert werden kann. Erstellen Sie nun mithilfe der webapp-Vorlage eine neue .NET Core-App über den Befehl dotnet new:

dotnet new webapp -o Console.ExampleFormatters.Json

Wenn Sie die App mit dem Vorlagencode ausführen, erhalten Sie das unten gezeigte Standardprotokollformat:

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

Standardmäßig wird der Simple-Konsolenprotokollformatierer mit der Standardkonfiguration ausgewählt. Um dies zu ändern, rufen Sie AddJsonConsole in Program.cs auf:

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();

Alternativ können Sie dies auch mithilfe der Protokollierungskonfiguration in der Datei appsettings.json konfigurieren:

{
    "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": "*"
}

Führen Sie die App mit der obigen Änderung erneut aus. Die Protokollmeldung ist jetzt als JSON formatiert:

{
  "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}"
  }
}

Tipp

Standardmäßig protokolliert der Json-Konsolenformatierer jede Meldung in einer einzigen Zeile. Um die Lesbarkeit zu erhöhen, legen Sie beim Konfigurieren des Formatierers JsonWriterOptions.Indented auf true fest.

Achtung

Wenn Sie den Formatierer der JSON-Konsole verwenden, übergeben Sie keine Protokollmeldungen, die bereits als JSON serialisiert wurden. Die Protokollierungsinfrastruktur selbst verwaltet bereits die Serialisierung von Protokollnachrichten. Wenn Sie also eine Protokollnachricht übergeben möchten, die bereits serialisiert ist, wird sie doppelt serialisiert, was zu einer fehlerhaften Ausgabe führt.

Einstellen des Formatierers durch Konfiguration

In den vorherigen Beispielen wurde gezeigt, wie ein Formatierer programmgesteuert registriert wird. Alternativ dazu können Sie dies auch über die Konfiguration durchführen. Ziehen Sie den Quellcode des vorherigen Webanwendungsbeispiels in Betracht, wenn Sie die Datei appsettings.js aktualisieren, statt ConfigureLogging in der Datei Program.cs aufzurufen. Das Ergebnis könnte dasselbe sein. Durch die aktualisierte Datei appsettings.json würde der Formatierer wie folgt konfiguriert:

{
    "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": "*"
}

Die beiden Schlüsselwerte, die festgelegt werden müssen, sind "FormatterName" und "FormatterOptions". Wenn ein Formatierer bereits mit dem für "FormatterName" festgelegten Wert registriert ist, wird dieser Formatierer ausgewählt. Seine Eigenschaften sind konfigurierbar, müssen aber als Schlüssel innerhalb des Knotens "FormatterOptions" bereitgestellt werden. Die vordefinierten Formatierernamen sind unter ConsoleFormatterNames reserviert:

Implementieren eines benutzerdefinierten Formatierers

Zum Implementieren eines benutzerdefinierten Formatierers müssen Sie folgende Schritte durchführen:

Erstellen Sie eine Erweiterungsmethode, die diese Aufgabe für Sie übernimmt:

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 werden wie folgt definiert:

using Microsoft.Extensions.Logging.Console;

namespace Console.ExampleFormatters.Custom;

public sealed class CustomOptions : ConsoleFormatterOptions
{
    public string? CustomPrefix { get; set; }
}

Im vorangehenden Code handelt es sich bei den Optionen um eine Unterklasse von ConsoleFormatterOptions.

Die AddConsoleFormatter-API führt Folgendes durch:

  • Sie registriert eine Unterklasse von ConsoleFormatter.
  • Sie übernimmt die Konfiguration:
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.");
}

Definieren Sie eine CustomFormatter-Unterklasse von 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();
}

Die vorangehende CustomFormatter.Write<TState>-API gibt vor, von welchem Text die einzelnen Protokollmeldungen umschlossen werden. Ein Standard-ConsoleFormatter sollte zumindest Bereiche, Zeitstempel und den Schweregrad von Protokollen umschließen können. Darüber hinaus können Sie ANSI-Farben in den Protokollmeldungen codieren und auch die gewünschten Einzüge bereitstellen. Bei der Implementierung von CustomFormatter.Write<TState> fehlen diese Funktionen.

Weitere Informationen zur weiteren Anpassung der Formatierung finden Sie in den vorhandenen Implementierungen im Namespace Microsoft.Extensions.Logging.Console:

Benutzerdefinierte Konfigurationsoptionen

Ihre abgeleitete ConsoleFormatterOptions-Klasse kann von jedem Konfigurationsanbieter konfiguriert werden, um die Erweiterbarkeit der Protokollierung weiter anzupassen. Beispielsweise können Sie den JSON-Konfigurationsanbieter verwenden, um Ihre benutzerdefinierten Optionen anzupassen. Definieren Sie zunächst Ihre ConsoleFormatterOptions-Unterklasse.

using Microsoft.Extensions.Logging.Console;

namespace Console.ExampleFormatters.CustomWithConfig;

public sealed class CustomWrappingConsoleFormatterOptions : ConsoleFormatterOptions
{
    public string? CustomPrefix { get; set; }

    public string? CustomSuffix { get; set; }
}

Die vorangehende Klasse für Konsolenformatiereroptionen definiert zwei benutzerdefinierte Eigenschaften, die ein Präfix und ein Suffix darstellen. Definieren Sie als Nächstes die Datei appsettings.json, mit der die Konsolenformatiereroptionen konfiguriert werden.

{
    "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": "*"
}

In der oben stehenden JSON-Konfigurationsdatei passiert Folgendes:

  • Der "Logging"-Knoten definiert ein "Console"-Objekt.
  • Der "Console"-Knoten gibt ein "FormatterName"-Objekt von "CustomTimePrefixingFormatter" an, das einem benutzerdefinierten Formatierer zugeordnet wird.
  • Der "FormatterOptions"-Knoten definiert ein "CustomPrefix"-Objekt und "CustomSuffix" sowie einige andere abgeleitete Optionen.

Tipp

Der JSON-Pfad $.Logging.Console.FormatterOptions ist reserviert und wird einem benutzerdefinierten ConsoleFormatterOptions-Objekt zugeordnet, wenn er mit der Erweiterungsmethode AddConsoleFormatter hinzugefügt wird. Dies bietet die Möglichkeit, zusätzlich zu den verfügbaren auch benutzerdefinierte Eigenschaften zu definieren.

Angenommen, Sie verfügen über die Datei 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();
}

In der oben stehenden Formatierungsimplementierung ist Folgendes passiert:

Fügen Sie beim Aufrufen von ConfigureLogging(IHostBuilder, Action<HostBuilderContext,ILoggingBuilder>) hinzu, um benutzerdefinierte Konfigurationsoptionen mit benutzerdefinierten Formatiererimplementierungen zu verwenden.

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.");
}

Die folgende Konsolenausgabe ist in etwa das, was Sie bei der Verwendung von CustomTimePrefixingFormatter erwarten können.

|-<[ 15:03:15.6179 Hello World! ]>-|
|-<[ 15:03:15.6347 The .NET developer community happily welcomes you. ]>-|

Implementieren der benutzerdefinierten Farbformatierung

Um Farbfunktionen in Ihrem benutzerdefinierten Protokollformatierer ordnungsgemäß zu aktivieren, können Sie SimpleConsoleFormatterOptions erweitern. Dieses Element verfügt über eine Eigenschaft SimpleConsoleFormatterOptions.ColorBehavior, die zum Aktivieren von Farben in Protokollen nützlich sein kann.

Erstellen Sie ein CustomColorOptions-Element, das von SimpleConsoleFormatterOptions abgeleitet wird:

using Microsoft.Extensions.Logging.Console;

namespace Console.ExampleFormatters.Custom;

public class CustomColorOptions : SimpleConsoleFormatterOptions
{
    public string? CustomPrefix { get; set; }
}

Schreiben Sie als Nächstes einige Erweiterungsmethoden in einer TextWriterExtensions-Klasse, die eine bequeme Einbettung von ANSI-codierten Farben in formatierte Protokollmeldungen ermöglichen:

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
        };
}

Ein benutzerdefinierter Farbformatierer zum Anwenden benutzerdefinierter Farben kann wie folgt definiert werden:

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();
}

Bei Ausführung der Anwendung wird die Meldung CustomPrefix in der Farbe Grün angezeigt, wenn FormatterOptions.ColorBehavior den Wert Enabled aufweist.

Hinweis

Wenn LoggerColorBehavior auf Disabled festgelegt ist, werden keine eingebetteten ANSI-Farbcodes in Protokollmeldungen interpretiert. Stattdessen wird die unformatierte Meldung ausgegeben. Berücksichtigen Sie beispielsweise Folgendes:

logger.LogInformation("Random log \x1B[42mwith green background\x1B[49m message");

Hierdurch wird die ausführliche Zeichenfolge ohne Farbformatierung ausgegeben.

Random log \x1B[42mwith green background\x1B[49m message

Siehe auch