Маршрутизація логів до різних пунктів призначення

Дізнайтеся, як маршрутизувати логи з одного ILogger до різних OTLP пунктів призначення за допомогою власного процесора.

Цей посібник показує, як маршрутизувати логи з одного ILogger до різних OTLP пунктів призначення за допомогою власного процесора. Він слідує шаблону Routing, описаному в додаткових рекомендаціях специфікації OpenTelemetry.

Чому варто маршрутизувати логи?

У деяких сценаріях ви хочете, щоб весь код застосунку використовував одну конвеєрну лінію ILogger, але певні логи надсилалися до одного бекенду, а решта — до іншого. Наприклад:

  • Логи з компонентів payment повинні надсилатися до спеціальної точки доступу колектора (OTLP2).
  • Всі інші логи повинні надсилатися до стандартної точки доступу (OTLP1).

Якщо ваш застосунок може створювати кілька екземплярів ILoggerFactory і дозволяти викликам обирати відповідний, розгляньте можливість використання спеціальної конвеєрної лінії логування замість цього.

Як це працює

Рішення про маршрутизацію приймається на рівні процесора шляхом перевірки CategoryName кожного LogRecord. Власний процесор перевіряє, чи починається імʼя категорії з налаштованого префікса, і пересилає запис до відповідного експортного конвеєра:

ILogger (один конвеєр)
   |
   v
LoggerProvider
   |
   v
RoutingProcessor (власний процесор)
   +-- CategoryName запускається з префіксом --> ExportProcessor -> OtlpLogExporter (OTLP2)
   +-- інакше ---------------------------------> ExportProcessor -> OtlpLogExporter (OTLP1)
  1. Створюються два екземпляри OtlpLogExporter, кожен з яких вказує на різну точку доступу.
  2. Кожен екземпляр експортера обгортається в BatchLogRecordExportProcessor.
  3. Власний RoutingProcessor розширює BaseProcessor<LogRecord> і перевизначає метод OnEnd. Він перевіряє, чи починається CategoryName запису журналу з налаштованого префікса, щоб визначити, який внутрішній процесор отримає запис.
  4. Процесор маршрутизації реєструється в LoggerProvider через AddProcessor.

Реалізація

Власний процесор маршрутизації

Власний RoutingProcessor перевіряє CategoryName кожного запису журналу і пересилає його до одного з двох внутрішніх процесорів. Він також делегує виклики ForceFlush, Shutdown та Dispose, щоб обидва експортні конвеєри були належним чином очищені та завершені:

using OpenTelemetry;
using OpenTelemetry.Logs;

internal sealed class RoutingProcessor : BaseProcessor<LogRecord>
{
    private readonly string categoryPrefix;
    private readonly BaseProcessor<LogRecord> defaultProcessor;
    private readonly BaseProcessor<LogRecord> paymentProcessor;

    public RoutingProcessor(
        string categoryPrefix,
        BaseProcessor<LogRecord> defaultProcessor,
        BaseProcessor<LogRecord> paymentProcessor)
    {
        this.categoryPrefix = categoryPrefix ?? throw new ArgumentNullException(nameof(categoryPrefix));
        this.defaultProcessor = defaultProcessor ?? throw new ArgumentNullException(nameof(defaultProcessor));
        this.paymentProcessor = paymentProcessor ?? throw new ArgumentNullException(nameof(paymentProcessor));
    }

    public override void OnEnd(LogRecord data)
    {
        if (data.CategoryName?.StartsWith(this.categoryPrefix, StringComparison.Ordinal) == true)
        {
            this.paymentProcessor.OnEnd(data);
        }
        else
        {
            this.defaultProcessor.OnEnd(data);
        }
    }

    protected override bool OnForceFlush(int timeoutMilliseconds)
    {
        var result1 = this.defaultProcessor.ForceFlush(timeoutMilliseconds);
        var result2 = this.paymentProcessor.ForceFlush(timeoutMilliseconds);
        return result1 && result2;
    }

    protected override bool OnShutdown(int timeoutMilliseconds)
    {
        var result1 = this.defaultProcessor.Shutdown(timeoutMilliseconds);
        var result2 = this.paymentProcessor.Shutdown(timeoutMilliseconds);
        return result1 && result2;
    }

    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            this.defaultProcessor.Dispose();
            this.paymentProcessor.Dispose();
        }

        base.Dispose(disposing);
    }
}

Реєстрування процесора маршрутизації в LoggerProvider

Реєстрування процесора маршрутизації в LoggerProvider. Обидва логери нижче використовують той самий ILoggerFactory та конвеєр LoggerProvider, але їхні записи журналу спрямовуються до різних OTLP-цілей залежно від назви категорії:

using Microsoft.Extensions.Logging;
using OpenTelemetry;
using OpenTelemetry.Exporter;
using OpenTelemetry.Logs;

// Створимо два OTLP-експортери, спрямовані на різні цілі.
var otlpExporter1 = new OtlpLogExporter(new OtlpExporterOptions
{
    Endpoint = new Uri("http://localhost:4317"), // OTLP ціль 1
});

var otlpExporter2 = new OtlpLogExporter(new OtlpExporterOptions
{
    Endpoint = new Uri("http://localhost:4318"), // OTLP ціль 2
});

// Огорнемо кожен експортер у BatchLogRecordExportProcessor.
var defaultExportProcessor = new BatchLogRecordExportProcessor(otlpExporter1);
var paymentExportProcessor = new BatchLogRecordExportProcessor(otlpExporter2);

// Створимо процесор маршрутизації. Логи, чиї назви категорій починаються з
// "Payment.", надсилаються до OTLP2; все інше йде до OTLP1.
var routingProcessor = new RoutingProcessor(
    categoryPrefix: "Payment.",
    defaultProcessor: defaultExportProcessor,
    paymentProcessor: paymentExportProcessor);

var loggerFactory = LoggerFactory.Create(builder =>
{
    builder.AddOpenTelemetry(logging =>
    {
        logging.AddProcessor(routingProcessor);

        // Опціонально: також додайте консольний експортер, щоб бачити всі логи локально.
        logging.AddConsoleExporter();
    });
});

// Обидва логери використовують той самий ILoggerFactory / конвеєр LoggerProvider.
var orderLogger = loggerFactory.CreateLogger("Order.Processing");
var paymentLogger = loggerFactory.CreateLogger("Payment.Processing");

orderLogger.LogInformation("Processing order {OrderId}.", "ORD-001");     // --> OTLP1
paymentLogger.LogInformation("Processing payment {PaymentId}.", "PAY-001"); // --> OTLP2
orderLogger.LogInformation("Order {OrderId} completed.", "ORD-001");      // --> OTLP1

// Звільніть ресурси фабрики логерів перед завершенням роботи програми.
// Це призведе до скидання залишкових логів і завершення конвеєра логування.
loggerFactory.Dispose();

Основні моменти

  • Умова маршрутизації оцінюється для кожного запису логу. Тримайте логіку швидкою — вона виконується синхронно при кожному виклику логу.
  • Управління життєвим циклом. Переконайтеся, що процесор маршрутизації делегує ForceFlush, Shutdown та Dispose всім внутрішнім процесорам, щоб кожен експортний конвеєр був належним чином очищений і завершений.
  • Один конвеєр. Оскільки існує лише один ILoggerFactory та LoggerProvider, цей підхід добре працює з впровадженням залежностей, де компоненти не знають про наявність кількох логерів.

Додаткова література


Востаннє змінено December 26, 2024: [uk] Ukrainian documentation for OpenTelemetry (c6a7b896)