Как правильно организовать логирование в Go-приложении?

Ответ

Правильная организация логирования критически важна для отладки, мониторинга и аудита. Вот ключевые подходы и лучшие практики:

1. Выбор инструмента

  • log (стандартная библиотека): Подходит для очень простых приложений или примеров. Не имеет уровней логирования и структурирования.
  • log/slog (стандартная библиотека, Go 1.21+): Рекомендуемый стандарт. Встроенный, производительный логгер для структурированного логирования (JSON или key-value).

    import "log/slog"
    import "os"
    
    func main() {
        logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
        logger.Info("User logged in", "user_id", 123, "status", "success")
    }
  • Сторонние библиотеки:
    • zap: Самый быстрый логгер, идеален для высокопроизводительных систем.
    • logrus: Очень популярный, с удобным API и системой хуков (hooks), но медленнее zap и slog.

2. Лучшие практики

  1. Структурированное логирование: Всегда используйте структурированные форматы (JSON, logfmt). Это позволяет легко парсить, фильтровать и анализировать логи в системах вроде Loki, ELK, Splunk.

  2. Контекстное логирование: Добавляйте в логи контекст запроса (например, request_id, user_id). Это позволяет отследить весь путь обработки одного запроса по логам разных сервисов.

    // Пример с slog
    requestLogger := logger.With("request_id", "abc-123")
    requestLogger.Info("Processing request")
    // ...
    requestLogger.Warn("Operation took too long")
  3. Уровни логирования (Log Levels): Используйте разные уровни для сообщений: DEBUG (для отладки), INFO (штатные события), WARN (потенциальные проблемы), ERROR (ошибки), FATAL (критические ошибки, после которых приложение падает).

  4. Внедрение через зависимости (Dependency Injection): Не используйте глобальный логгер. Вместо этого передавайте инстанс логгера в компоненты через их конструкторы, желательно через интерфейс. Это упрощает тестирование и конфигурирование.

    type Logger interface {
        Info(msg string, args ...any)
        Error(msg string, args ...any)
    }
    
    type MyService struct {
        log Logger
    }
    
    func NewMyService(log Logger) *MyService {
        return &MyService{log: log}
    }