Введение в логирование в Go: Zap и Logrus
Логирование — это критически важный компонент любого серьёзного приложения. Оно позволяет отслеживать состояние программы, фиксировать ошибки, анализировать производительность и отлаживать код в production-среде. В стандартной библиотеке Go есть пакет log, но он обладает ограниченными возможностями: нет уровней логирования (кроме примитивных), нет структурированного вывода, нет гибких форматов. Для решения этих проблем были созданы две мощные библиотеки: Zap (от Uber) и Logrus (один из первых популярных логгеров).
В этой статье мы подробно разберём обе библиотеки, их методы, примеры использования и best practices. Вы узнаете, как настроить структурированное логирование, как работать с уровнями, как добавлять контекстные поля и как избегать типичных ошибок.
Что такое Zap?
Zap — это высокопроизводительная библиотека для структурированного логирования от компании Uber. Она ориентирована на скорость и минимальное выделение памяти. Zap поддерживает два режима работы: SugaredLogger (удобный, но чуть медленнее) и Logger (максимальная производительность).
Что такое Logrus?
Logrus — это одна из первых и самых популярных библиотек для логирования в Go. Она предоставляет простой API, поддержку уровней, форматтеров (JSON, Text) и хуков (hooks) для интеграции с внешними системами (например, Sentry, Slack). Logrus проще в освоении, но уступает Zap в производительности.
Установка
Установка обеих библиотек выполняется стандартным способом через go get:
// Установка Zap
go get -u go.uber.org/zap
// Установка Logrus
go get -u github.com/sirupsen/logrus
После установки вы можете импортировать их в свой проект:
import (
"go.uber.org/zap"
"github.com/sirupsen/logrus"
)
Быстрый старт с примером кода
Пример с Zap
package main
import (
"go.uber.org/zap"
)
func main() {
// Создаём production-логгер (высокая производительность)
logger, _ := zap.NewProduction()
defer logger.Sync() // сбрасываем буфер перед выходом
// Логируем сообщение с полями
logger.Info("Приложение запущено",
zap.String("version", "1.0.0"),
zap.Int("port", 8080),
)
// Используем SugaredLogger для удобства
sugar := logger.Sugar()
sugar.Infow("Пользователь вошёл в систему",
"user", "john_doe",
"ip", "192.168.1.1",
)
}
Пример с Logrus
package main
import (
"github.com/sirupsen/logrus"
)
func main() {
// Настраиваем формат вывода (JSON)
logrus.SetFormatter(&logrus.JSONFormatter{})
// Устанавливаем уровень логирования
logrus.SetLevel(logrus.InfoLevel)
// Логируем сообщение с полями
logrus.WithFields(logrus.Fields{
"version": "1.0.0",
"port": 8080,
}).Info("Приложение запущено")
// Логируем ошибку
logrus.WithFields(logrus.Fields{
"user": "john_doe",
"ip": "192.168.1.1",
}).Warn("Попытка входа с неверным паролем")
}
Основные методы и функции
1. Zap: NewProduction() / NewDevelopment()
Что делает: Создаёт предварительно настроенный логгер для production или development среды. Production-логгер использует JSON-формат и оптимизирован для скорости. Development-логгер использует цветной текстовый вывод и включает стектрейсы.
Параметры: Нет (возвращает *zap.Logger и ошибку).
Пример:
prodLogger, _ := zap.NewProduction()
devLogger, _ := zap.NewDevelopment()
2. Zap: Logger.Info(), Logger.Warn(), Logger.Error()
Что делает: Логирует сообщение с соответствующим уровнем. Принимает строку сообщения и необязательные поля (через zap.Field).
Параметры: msg string, fields ...zap.Field.
Пример:
logger.Info("Сервер запущен", zap.String("addr", ":8080"))
logger.Error("Ошибка соединения", zap.Error(err))
3. Zap: Sugar() и методы Infow(), Errorw()
Что делает: Преобразует быстрый логгер в "сахарный" (удобный) с поддержкой ключ-значение без явного создания zap.Field. Методы Infow, Errorw принимают чередующиеся ключи и значения.
Параметры: msg string, keysAndValues ...interface{}.
Пример:
sugar := logger.Sugar()
sugar.Infow("Пользователь создан", "id", 42, "name", "Alice")
sugar.Errorw("Не удалось обработать запрос", "status", 500, "error", err)
4. Zap: With()
Что делает: Создаёт дочерний логгер с дополнительными полями, которые будут добавляться к каждому сообщению. Полезно для добавления контекста (например, ID запроса).
Параметры: fields ...zap.Field.
Пример:
requestLogger := logger.With(zap.String("request_id", "abc-123"))
requestLogger.Info("Обработка запроса")
requestLogger.Info("Запрос обработан")
// Оба сообщения будут содержать поле request_id
5. Zap: Named()
Что делает: Создаёт дочерний логгер с указанным именем. Имя добавляется в поле logger в JSON-выводе. Полезно для разделения логов по модулям.
Параметры: name string.
Пример:
dbLogger := logger.Named("database")
dbLogger.Info("Подключение к БД установлено")
// Вывод: {"level":"info","logger":"database","msg":"Подключение к БД установлено"}
6. Logrus: WithFields()
Что делает: Создаёт запись лога с дополнительными полями (структурированные данные). Возвращает *logrus.Entry, который можно использовать для логирования.
Параметры: fields logrus.Fields (map[string]interface{}).
Пример:
logrus.WithFields(logrus.Fields{
"user": "bob",
"action": "login",
}).Info("Пользователь вошёл")
7. Logrus: SetFormatter()
Что делает: Устанавливает глобальный формат вывода логов. Доступны JSONFormatter, TextFormatter (по умолчанию). Можно создавать свои форматтеры.
Параметры: formatter logrus.Formatter.
Пример:
logrus.SetFormatter(&logrus.JSONFormatter{
PrettyPrint: true, // для читаемого вывода
})
logrus.SetFormatter(&logrus.TextFormatter{
FullTimestamp: true,
})
8. Logrus: SetLevel()
Что делает: Устанавливает минимальный уровень логирования. Сообщения ниже этого уровня будут игнорироваться. Доступные уровни: PanicLevel, FatalLevel, ErrorLevel, WarnLevel, InfoLevel, DebugLevel, TraceLevel.
Параметры: level logrus.Level.
Пример:
logrus.SetLevel(logrus.DebugLevel)
logrus.Debug("Это сообщение отладки")
logrus.Info("Это информационное сообщение")
9. Logrus: AddHook()
Что делает: Добавляет хук (hook) — функцию, которая вызывается для каждой записи лога. Используется для отправки логов во внешние системы (Sentry, Slack, файлы).
Параметры: hook logrus.Hook (интерфейс с методами Levels() и Fire()).
Пример:
type MyHook struct{}
func (h *MyHook) Levels() []logrus.Level {
return []logrus.Level{logrus.ErrorLevel, logrus.FatalLevel}
}
func (h *MyHook) Fire(entry *logrus.Entry) error {
// Отправляем entry в Sentry или Slack
fmt.Println("Хук сработал для уровня:", entry.Level)
return nil
}
logrus.AddHook(&MyHook{})
logrus.Error("Ошибка, которая будет обработана хуком")
10. Logrus: Entry.WithError()
Что делает: Добавляет ошибку в поля записи лога. Удобно для логирования ошибок с контекстом.
Параметры: err error.
Пример:
err := errors.New("connection refused")
logrus.WithFields(logrus.Fields{
"service": "api",
}).WithError(err).Error("Не удалось подключиться к БД")
Реальный практический пример использования
Рассмотрим пример микросервиса на Go, который обрабатывает HTTP-запросы и логирует их с помощью Zap. Мы создадим middleware для логирования каждого запроса и отдельный логгер для бизнес-логики.
package main
import (
"net/http"
"time"
"go.uber.org/zap"
)
func main() {
// Создаём production-логгер
logger, _ := zap.NewProduction()
defer logger.Sync()
// Создаём HTTP-сервер с middleware
mux := http.NewServeMux()
mux.HandleFunc("/api/user", func(w http.ResponseWriter, r *http.Request) {
// Логируем бизнес-событие
logger.Info("Получен запрос на /api/user",
zap.String("method", r.Method),
zap.String("path", r.URL.Path),
)
// Имитация обработки
time.Sleep(100 * time.Millisecond)
w.WriteHeader(http.StatusOK)
w.Write([]byte(`{"status":"ok"}`))
})
// Оборачиваем в middleware для логирования всех запросов
handler := httpLogMiddleware(logger, mux)
logger.Info("Сервер запущен", zap.String("addr", ":8080"))
http.ListenAndServe(":8080", handler)
}
// Middleware для логирования HTTP-запросов
func httpLogMiddleware(logger *zap.Logger, next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
// Логируем входящий запрос
logger.Info("Входящий запрос",
zap.String("method", r.Method),
zap.String("path", r.URL.Path),
zap.String("remote_addr", r.RemoteAddr),
)
// Вызываем следующий обработчик
next.ServeHTTP(w, r)
// Логируем завершение запроса
logger.Info("Запрос обработан",
zap.Duration("duration", time.Since(start)),
zap.String("method", r.Method),
zap.String("path", r.URL.Path),
)
})
}
Этот пример показывает, как использовать Zap для структурированного логирования в реальном приложении. Вы можете легко ада