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

Как внедрить logger в приложение?

2.0 Middle🔥 251 комментариев
#Observability

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

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

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

Внедрение логгера в Go-приложение

Внедрение логгера — критически важная практика для создания поддерживаемых, отлаживаемых и наблюдаемых приложений. Вот комплексный подход к решению этой задачи.

Основные подходы к внедрению

1. Интерфейсный подход (предпочтительный)

Создайте абстракцию логгера через интерфейс, что позволяет подменять реализации:

// logger/logger.go
package logger

// Logger интерфейс для абстракции
type Logger interface {
    Debug(msg string, fields ...Field)
    Info(msg string, fields ...Field)
    Warn(msg string, fields ...Field)
    Error(msg string, fields ...Field)
    Fatal(msg string, fields ...Field)
    
    With(fields ...Field) Logger
}

// Field представляет структурированное поле лога
type Field struct {
    Key   string
    Value interface{}
}

// Реализация на основе zap или другой библиотеки
type zapLogger struct {
    logger *zap.Logger
}

func NewZapLogger(level string) (Logger, error) {
    // Инициализация конкретной реализации
}

2. Dependency Injection через конструктор

Внедряйте логгер как зависимость в структуры:

// service/user_service.go
package service

type UserService struct {
    logger logger.Logger
    repo   UserRepository
}

func NewUserService(logger logger.Logger, repo UserRepository) *UserService {
    return &UserService{
        logger: logger.With(logger.Field{Key: "component", Value: "user_service"}),
        repo:   repo,
    }
}

func (s *UserService) CreateUser(ctx context.Context, user *User) error {
    s.logger.Info("creating user", 
        logger.Field{Key: "user_id", Value: user.ID},
        logger.Field{Key: "email", Value: user.Email},
    )
    
    // Бизнес-логика
    if err := s.repo.Save(ctx, user); err != nil {
        s.logger.Error("failed to create user",
            logger.Field{Key: "error", Value: err.Error()},
        )
        return err
    }
    
    return nil
}

Полная архитектурная реализация

Шаг 1: Фабрика и конфигурация

// config/logger_config.go
package config

type LoggerConfig struct {
    Level       string `yaml:"level" env:"LOG_LEVEL" default:"info"`
    Encoding    string `yaml:"encoding" env:"LOG_ENCODING" default:"json"`
    OutputPaths []string `yaml:"output_paths" env:"LOG_OUTPUT_PATHS" default:"stdout"`
}

// factory/logger_factory.go
package factory

func CreateLogger(cfg *config.LoggerConfig) (logger.Logger, error) {
    switch cfg.Encoding {
    case "json":
        return logger.NewZapLogger(cfg.Level)
    case "console":
        return logger.NewConsoleLogger(cfg.Level)
    default:
        return logger.NewDefaultLogger()
    }
}

Шаг 2: Внедрение через контекст (дополнительно)

// middleware/logging_middleware.go
package middleware

func LoggingMiddleware(logger logger.Logger) func(http.Handler) http.Handler {
    return func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            start := time.Now()
            requestID := r.Header.Get("X-Request-ID")
            
            // Создаем логгер с контекстом запроса
            reqLogger := logger.With(
                logger.Field{Key: "request_id", Value: requestID},
                logger.Field{Key: "method", Value: r.Method},
                logger.Field{Key: "path", Value: r.URL.Path},
            )
            
            // Помещаем логгер в контекст
            ctx := context.WithValue(r.Context(), "logger", reqLogger)
            r = r.WithContext(ctx)
            
            reqLogger.Info("request started")
            
            // Продолжаем обработку
            next.ServeHTTP(w, r)
            
            duration := time.Since(start)
            reqLogger.Info("request completed",
                logger.Field{Key: "duration_ms", Value: duration.Milliseconds()},
            )
        })
    }
}

Шаг 3: Получение логгера из контекста

// utils/context_logger.go
package utils

func FromContext(ctx context.Context) logger.Logger {
    if l, ok := ctx.Value("logger").(logger.Logger); ok {
        return l
    }
    return logger.NewNoopLogger() // Возвращаем заглушку
}

Продвинутые практики

  1. Структурированное логирование:

    • Всегда используйте структурированные логи вместо форматированных строк
    • Добавляйте корреляционные ID (request_id, trace_id)
    • Включайте контекстные поля (user_id, session_id)
  2. Уровни логирования:

    // Используйте разные уровни осмысленно:
    logger.Debug("периодическая задача запущена", fields...)
    logger.Info("пользователь создан", fields...)
    logger.Warn("необычное, но не критичное событие", fields...)
    logger.Error("ошибка бизнес-логики", fields...)
    logger.Fatal("критическая ошибка, приложение остановлено", fields...)
    
  3. Производительность:

    • Используйте zero-allocation логгеры в hot-path
    • Выполняйте дорогие вычисления только при нужном уровне:
    if logger.IsDebugEnabled() {
        logger.Debug("complex data", 
            logger.Field{Key: "stats", Value: calculateExpensiveStats()},
        )
    }
    

Контейнеризация и окружения

# docker-compose.yml
services:
  app:
    environment:
      - LOG_LEVEL=${LOG_LEVEL:-info}
      - LOG_ENCODING=${LOG_ENCODING:-json}
      - LOG_OUTPUT_PATHS=stdout

Тестирование с использованием логгера

// service/user_service_test.go
func TestUserService_CreateUser(t *testing.T) {
    // Мок-логгер для тестов
    mockLogger := &testutils.MockLogger{
        OnInfo: func(msg string, fields ...logger.Field) {
            assert.Equal(t, "creating user", msg)
        },
    }
    
    service := NewUserService(mockLogger, mockRepo)
    // ... тест логики
}

Рекомендуемые библиотеки

  • Zap от Uber — максимальная производительность
  • Logrus — удобный API, популярен в сообществе
  • ZeroLog — zero-allocation логгер
  • Slog (стандартная с Go 1.21) — будущее стандартизации

Ключевые принципы успешного внедрения

  1. Абстрагируйтесь от конкретной реализации через интерфейсы
  2. Внедряйте зависимости явно через конструкторы
  3. Используйте контекст для передачи request-scoped логгеров
  4. Настройте ротацию логов в production
  5. Интегрируйте с системами мониторинга (ELK, Loki, CloudWatch)
  6. Логируйте в machine-readable формате (JSON) для production

Итог: Правильное внедрение логгера создает фундамент для наблюдаемости приложения. Начните с интерфейса, внедряйте через зависимости, используйте структурированный формат и помните о производительности. Это окупится при первой же production-проблеме, требующей отладки.