Форматирование журнала консоли

В .NET 5 была добавлена поддержка пользовательского форматирования в журналах консоли в пространстве имен Microsoft.Extensions.Logging.Console. Доступны три предопределенных варианта форматирования: Simple, Systemd и Json.

Внимание

Ранее перечисление ConsoleLoggerFormat позволяло выбрать требуемый формат журнала: либо удобный для чтения (Default), либо запись в одну строку (Systemd). Но настроить их было нельзя, и теперь эти варианты считаются устаревшими.

В этой статье собраны сведения о форматировщиках журнала консоли. Этот пример исходного кода демонстрирует следующие действия:

  • регистрация нового форматировщика;
  • Выбор зарегистрированного средства форматирования для использования
  • Реализация пользовательского модуля форматирования
    • обновление конфигурации через IOptionsMonitor<TOptions>;
    • включение пользовательского цветового форматирования.

Совет

Весь пример исходного кода для ведения журнала доступен для загрузки в Обозревателе примеров. Дополнительные сведения см. в разделе Обзор примеров кода: ведение журнала в .NET.

Регистрация форматировщика

Поставщик ведения журнала Console имеет несколько предопределенных форматировщиков и предоставляет возможность создавать собственные пользовательские форматировщики. Чтобы зарегистрировать любой из уже доступных форматировщиков, используйте соответствующий метод расширения Add{Type}Console.

Доступные типы Метод для регистрации этого типа
ConsoleFormatterNames.Json ConsoleLoggerExtensions.AddJsonConsole
ConsoleFormatterNames.Simple ConsoleLoggerExtensions.AddSimpleConsole
ConsoleFormatterNames.Systemd ConsoleLoggerExtensions.AddSystemdConsole

Простая

Чтобы использовать форматировщик консоли 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.");
}

В примере создаются выходные данные, аналогичные следующим сообщениям журнала:

Примеры журналов консоли, написанные с помощью systemd formatter.

Json

Для записи журналов в формате JSON используется форматировщик консоли Json. В этом примере кода показано, как зарегистрировать его из приложения ASP.NET Core. Используя шаблон webapp, создайте новое приложение ASP.NET Core с помощью команды dotnet new.

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 из program.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. Сама инфраструктура ведения журнала уже управляет сериализацией сообщений журнала, поэтому если вы хотите передать сообщение журнала, которое уже сериализовано, оно будет двойным сериализованным, таким образом вызывая неправильные выходные данные.

Настройка форматировщика с помощью конфигурации

В предыдущих примерах показано, как зарегистрировать форматировщик программными средствами. Эту же операцию можно выполнить путем настройки. Давайте рассмотрим приведенный выше пример исходного кода веб-приложения. Вместо вызова ConfigureLogging в файле program.cs, можно получить такой же результат изменением файла appsettings.json. Обновленный 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.

реализация пользовательского форматировщика;

Для реализации пользовательского форматировщика сделайте следующее:

Создайте метод расширения для решения этой задачи.

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.

API-интерфейс AddConsoleFormatter выполняет следующие действия:

  • Регистрирует подкласс ConsoleFormatter.
  • Обрабатывает конфигурацию:
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();
}

Приведенный выше API CustomFormatter.Write<TState> определяет, какой текст добавляется вокруг каждого сообщения журнала. В стандартной реализации 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. Оно будет полезным для включения цвета в журналы.

Создайте CustomColorOptions, который наследует от SimpleConsoleFormatterOptions.

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

См. также