Логирование в Go приложениях: лучшие практики для начинающих
Логирование — одна из ключевых составляющих любого серьёзного приложения. Оно помогает отслеживать ошибки, анализировать поведение системы и понимать, что происходит "под капотом". В этой статье мы разберём лучшие практики логирования в Go (Golang) для начинающих разработчиков. Вы узнаете, какие библиотеки использовать, как организовать логи и избежать типичных ошибок.
Почему логирование важно в Go?
Go — язык, ориентированный на создание высоконагруженных серверных приложений и микросервисов. В таких системах без качественного логирования невозможно:
- Быстро найти причину сбоя
- Понять последовательность событий
- Собрать метрики для мониторинга
- Отлаживать код в production-среде
Плохо настроенное логирование может привести к потере критических данных или, наоборот, к переполнению диска миллионами бесполезных сообщений. Поэтому важно с самого начала заложить правильную архитектуру.
Стандартная библиотека log
Go предоставляет встроенный пакет log, который подходит для простых проектов. Он умеет выводить сообщения в stdout/stderr, добавлять временные метки и завершать программу при фатальных ошибках.
package main
import (
"log"
)
func main() {
log.Println("Приложение запущено")
log.Printf("Пользователь %s вошёл в систему", "admin")
// log.Fatal("Критическая ошибка") // завершает программу
}
Недостатки стандартного логгера:
- Нет уровней логирования (debug, info, warn, error)
- Нет структурированного вывода (только текст)
- Сложно менять формат и направление вывода
Для учебных проектов и простых утилит этого достаточно, но для production-приложений лучше использовать более мощные библиотеки.
Популярные библиотеки для логирования
Рассмотрим три наиболее распространённых варианта.
1. Logrus
Logrus — одна из первых сторонних библиотек для Go. Она поддерживает уровни логирования, структурированные поля и множество форматов вывода (JSON, текст).
package main
import (
"github.com/sirupsen/logrus"
)
func main() {
log := logrus.New()
log.SetFormatter(&logrus.JSONFormatter{})
log.SetLevel(logrus.InfoLevel)
log.WithFields(logrus.Fields{
"user": "ivan",
"action": "login",
}).Info("Пользователь вошёл в систему")
}
Logrus прост в использовании, но имеет высокое потребление памяти и немного медленнее современных альтернатив.
2. Zerolog
Zerolog — современная библиотека с фокусом на производительность и минимальное выделение памяти. Она использует zero-allocation подход и идеально подходит для высоконагруженных систем.
package main
import (
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
)
func main() {
zerolog.TimeFieldFormat = zerolog.TimeFormatUnix
log.Info().
Str("user", "ivan").
Str("action", "login").
Msg("Пользователь вошёл в систему")
log.Error().
Err(fmt.Errorf("connection refused")).
Msg("Ошибка подключения к БД")
}
Zerolog выводит логи в формате JSON по умолчанию, что упрощает их парсинг системами мониторинга (ELK, Grafana Loki).
3. Zap
Zap от Uber — ещё один высокопроизводительный логгер. Он работает в двух режимах: SugaredLogger (удобный, но чуть медленнее) и Logger (максимальная скорость).
package main
import (
"go.uber.org/zap"
)
func main() {
logger, _ := zap.NewProduction()
defer logger.Sync()
logger.Info("Сервер запущен",
zap.String("port", "8080"),
zap.Int("workers", 10),
)
}
Для начинающих я рекомендую Zerolog или Logrus — они имеют понятный API и хорошую документацию. Zap выбирают, когда производительность критична.
Лучшие практики логирования
Чтобы логи приносили пользу, а не превращались в "шум", следуйте этим правилам.
1. Используйте уровни логирования
Определите для себя четыре базовых уровня:
- Debug — отладочная информация, только для разработки
- Info — стандартные события (запуск, остановка, действия пользователя)
- Warn — предупреждения (нештатные, но не критичные ситуации)
- Error — ошибки, требующие внимания
Не используйте log.Fatal без крайней необходимости — он завершает программу, что может быть опасно в production.
2. Структурируйте логи
Вместо строк вида "User 123 logged in" используйте структурированные поля:
log.Info().
Str("user_id", "123").
Str("ip", "192.168.1.1").
Msg("user logged in")
Такой подход позволяет легко фильтровать и агрегировать логи в системах анализа.
3. Логируйте контекст запроса
В веб-приложениях важно связывать логи с конкретным HTTP-запросом. Для этого используйте request ID или trace ID:
requestID := "req-abc-123"
log.Info().
Str("request_id", requestID).
Str("path", "/api/users").
Msg("обработка запроса")
4. Избегайте логирования чувствительных данных
Никогда не пишите в логи пароли, токены, номера кредитных карт и другую конфиденциальную информацию. Если нужно залогировать запрос — маскируйте чувствительные поля.
5. Настройте ротацию логов
В production логи пишутся в файлы. Чтобы они не заняли весь диск, настройте ротацию по размеру или времени. Для этого можно использовать пакет lumberjack или настроить внешнюю утилиту (logrotate).
import "gopkg.in/natefinch/lumberjack.v2"
log.SetOutput(&lumberjack.Logger{
Filename: "/var/log/myapp/app.log",
MaxSize: 100, // мегабайт
MaxBackups: 3,
MaxAge: 28, // дней