Условная буферизация логов в Go

от автора

В .NET 9 появилась интересная функциональность — Log Buffering, которая позволяет буферизовать логи в памяти и выводить их только при определенных условиях. Меня заинтересовала эта идея, что я решил реализовать аналогичный механизм для Go. Так появился EmitLog — пакет для условной буферизации логов.

Проблема традиционного логирования

Представьте типичный веб-сервис с детальным логированием:

func ProcessPayment(ctx context.Context, paymentID string) error {     log.Debug("Starting payment processing")     log.Debug("Validating payment data")     log.Debug("Checking user balance")     log.Debug("Connecting to payment gateway")     log.Info("Payment processed successfully")     return nil }

При высокой нагрузке такой сервис генерирует огромное количество логов:

  • 99% успешных транзакций = миллионы ненужных debug-логов

  • Высокие расходы на хранение в системах типа ELK, Datadog

  • Замедление поиска важной информации в море рутинных записей

Но если убрать debug-логи совсем, то при ошибке мы потеряем контекст происходящего.

Решение: условная буферизация

EmitLog решает эту дилемму:

  1. Буферизует все логи запроса в памяти

  2. Анализирует результат выполнения

  3. Решает — сохранить логи или отбросить

// При ошибке — видим полный контекст [ERROR] Payment failed: insufficient funds [DEBUG] Starting payment processing [DEBUG] Validating payment data   [DEBUG] Checking user balance [INFO] User balance: $50, required: $100 // При успехе — логи отбрасываются (или сохраняются с вероятностью 5%)

Цепочка контекста логирования

Вот как выглядит полный flow:

HTTP Request → Middleware создает BufferingWriter                     ↓               Создает logger с контекстом запроса                     ↓               Logger помещается в context.Context                     ↓               Handler извлекает logger: GetLoggerFromContext(ctx)                     ↓               Все логи пишутся в буфер в памяти                     ↓               [Возникла ошибка?]                  ↙        ↘               Да           Нет                ↓            ↓          Flush всех    [Random < SaveRate?]            логов          ↙        ↘                         Да          Нет                          ↓           ↓                    Сохранить    Отбросить

Практическое использование

Базовая настройка

func main() {     // Настраиваем zerolog     log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr})          // Конфигурация EmitLog     config := emitlog.Config{         SaveRate:         10.0,      // 10% успешных запросов         BufferingEnabled: true,               FlushOnError:     true,      // Всегда при ошибках         FlushOnWarn:      false,     // Игнорируем warnings         BufferSize:       64 * 1024, // 64KB на запрос     }          // Применяем middleware     handler := emitlog.Middleware(config, os.Stderr)(mux)     http.ListenAndServe(":8080", handler) }

Использование в хендлерах

func ProcessOrderHandler(w http.ResponseWriter, r *http.Request) {     // Извлекаем настроенный logger из контекста     logger := emitlog.GetLoggerFromContext(r.Context())          logger.Debug().Msg("Parsing order request")          var order Order     if err := json.NewDecoder(r.Body).Decode(&order); err != nil {         logger.Error().Err(err).Msg("Failed to parse order")         http.Error(w, "Bad request", 400)         return // Все debug-логи будут выведены!     }          logger.Debug().         Str("order_id", order.ID).         Float64("amount", order.Amount).         Msg("Processing order")          if err := processPayment(order); err != nil {         logger.Error().Err(err).Msg("Payment failed")         http.Error(w, "Payment failed", 500)         return // Видим полный контекст ошибки     }          logger.Info().Msg("Order processed successfully")     w.WriteHeader(200)     // При успехе логи сохранятся только с вероятностью SaveRate }

Преимущества подхода

1. Снижение объема логов

Традиционное логирование:

  • 1M запросов/день × 10 логов/запрос = 10M записей

  • При SaveRate = 5%: 50K записей + логи всех ошибок

  • Экономия: 95%+ на хранении

2. Полный контекст при ошибках

Когда что-то идет не так, вы видите всю историю запроса:

  • Все debug-сообщения

  • Промежуточные состояния

  • Точную последовательность операций

3. Производительность

  • Снижение I/O: меньше записей на диск/сеть

  • Батчинг: при сбросе логи пишутся одним блоком

4. Гибкая конфигурация

// Development config.BufferingEnabled = false  // Видим все сразу config.SaveRate = 100.0          // Сохраняем все  // Staging   config.SaveRate = 50.0           // Половину для анализа config.FlushOnWarn = true        // Warnings тоже важны  // Production config.SaveRate = 5.0            // Только 5% успешных config.BufferSize = 128 * 1024   // Больше буфер

Когда использовать EmitLog

Идеально подходит для:

  • Высоконагруженных API с детальным логированием

  • Микросервисов с дорогой инфраструктурой логирования

  • Систем, где ошибки требуют полного контекста для отладки

Не подходит для:

  • Критичных аудит-логов (compliance)

  • Реального времени мониторинга

  • Маленьких приложений с небольшим объемом логов

Заключение

Код проекта доступен на GitHub: https://github.com/AndreyyTs/emitlog

Буду рад фидбеку и предложениям по развитию проекта!

мой телерамм для связи https://t.me/atsaregorodtsev


ссылка на оригинал статьи https://habr.com/ru/articles/922256/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *