← Назад к вопросам

Куда записываешь log в приложении?

1.7 Middle🔥 183 комментариев
#Другое#Основы Go

Комментарии (3)

🐱
deepseek-v3.2PrepBro AI6 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Стратегия логирования в Go-приложениях

В Go-приложениях я использую многоуровневую стратегию логирования, которая зависит от среды выполнения, типа информации и требований к наблюдаемости (observability). Вот ключевые направления:

1. Стандартные потоки (stdout/stderr) для контейнеризованных приложений

Для современных микросервисных архитектур и Docker-контейнеров основной подход — запись в стандартные потоки:

package main

import (
    "log"
    "os"
)

func main() {
    // Для информации - stdout
    log.Println("Запуск сервиса пользователей v1.2.3")
    
    // Для ошибок - stderr
    log.SetOutput(os.Stderr)
    log.Println("ERROR: Не удалось подключиться к БД")
}

Преимущества:

  • Docker и оркестраторы (Kubernetes) автоматически собирают логи из stdout/stderr
  • Централизованный сбор в системы типа ELK Stack, Loki, Datadog
  • Стандартизация подхода для всех сервисов

2. Структурированное логирование (Structured Logging)

Для машинно-читаемого формата использую библиотеки с поддержкой JSON-формата:

import (
    "github.com/sirupsen/logrus"
    "github.com/rs/zerolog"
)

// Пример с logrus
func logWithLogrus() {
    log := logrus.New()
    log.SetFormatter(&logrus.JSONFormatter{})
    
    log.WithFields(logrus.Fields{
        "user_id": "12345",
        "event":   "login",
        "ip":      "192.168.1.1",
    }).Info("Пользователь вошел в систему")
}

// Пример с zerolog (более производительный)
func logWithZerolog() {
    log := zerolog.New(os.Stdout).With().Timestamp().Logger()
    
    log.Info().
        Str("service", "auth").
        Int("duration_ms", 125).
        Msg("Запрос обработан")
}

3. Многоуровневое логирование с контекстом

Для разных сред выполнения настраиваю разные уровни детализации:

// Конфигурация уровней логирования
type LogConfig struct {
    Level     string `env:"LOG_LEVEL" default:"info"`
    Format    string `env:"LOG_FORMAT" default:"json"`
    AddSource bool   `env:"LOG_SOURCE" default:"false"`
}

func setupLogger(cfg LogConfig) {
    switch cfg.Level {
    case "debug":
        log.SetLevel(logrus.DebugLevel)
    case "warn":
        log.SetLevel(logrus.WarnLevel)
    case "error":
        log.SetLevel(logrus.ErrorLevel)
    default:
        log.SetLevel(logrus.InfoLevel)
    }
    
    if cfg.Format == "text" && cfg.AddSource {
        log.SetReportCaller(true) // Добавляем информацию о месте вызова
    }
}

4. Направления для разных типов логов

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

  • Метрики производительностиOpenTelemetry + Prometheus

    import "go.opentelemetry.io/otel"
    
    func processRequest(ctx context.Context) {
        tracer := otel.Tracer("auth-service")
        ctx, span := tracer.Start(ctx, "validate-token")
        defer span.End()
        
        // Логика обработки
        span.SetAttributes(attribute.Int("token.length", len(token)))
    }
    
  • Бизнес-события (аудиты) → отдельные структурированные логи или специализированные хранилища (ClickHouse для аналитики)

  • Критические ошибки → дублирование в Sentry/Rollbar для алертинга

  • Доступ (access logs) → отдельный формат с обязательными полями (HTTP-метод, путь, статус, длительность)

5. Локальная разработка vs Продакшен

Для локальной разработки:

  • Текстовый формат с цветовым выделением
  • Подробный уровень debug
  • Запись в файл для отладки сложных сценариев
  • Pretty print для JSON-ответов

Для продакшена:

  • Только JSON-формат для парсинга системами
  • Уровень по умолчанию info, debug — только при проблемах
  • Обязательное добавление:
    • timestamp в UTC
    • service_name и version
    • trace_id для корреляции запросов
    • environment (prod/staging)

6. Продвинутые сценарии

Для enterprise-приложений добавляю:

// Распределенное логирование с трейсингом
func handlerWithContext(w http.ResponseWriter, r *http.Request) {
    ctx := r.Context()
    logger := log.WithContext(ctx) // Логгер наследует trace_id из контекста
    
    logger.Info("Начало обработки запроса")
    
    // Автоматическое логирование паник
    defer func() {
        if err := recover(); err != nil {
            logger.WithField("panic", err).
                   Error("Паника восстановлена")
            panic(err) // Или обработка
        }
    }()
}

Ключевые принципы, которых придерживаюсь:

  1. Never lose logs — логи никогда не должны теряться, даже при падениях
  2. Performance matters — асинхронная запись при высокой нагрузке через буферизацию
  3. Sensitive data — маскирование PII данных (пароли, токены, персональные данные)
  4. Rotation & retention — ротация логов по размеру/времени, политики хранения
  5. Correlation IDs — сквозные идентификаторы для трассировки запроса через все сервисы

Итоговый стек, который обычно применяю:

  • Zerolog или logrus для структурированного логирования
  • OpenTelemetry для трассировки и метрик
  • Fluentd/Fluent Bit для сбора и пересылки логов в контейнеризованных средах
  • Loki или ELK для хранения и поиска
  • AlertManager + Sentry для реагирования на ошибки

Такой подход обеспечивает полную наблюдаемость, производительность и удобство отладки как на локальных машинах разработчиков, так и в масштабируемых продакшен-средах.