Введение
Логирование — это не просто запись событий в файл. Для Go-приложений, работающих в высоконагруженных продакшен-средах, логи являются основным источником диагностической информации. Без качественного логирования поиск ошибок превращается в гадание, а мониторинг системы становится невозможным.
Go предлагает разработчику гибкие инструменты: от встроенного пакета log до мощных сторонних библиотек. В этой статье мы разберем лучшие практики логирования в Go, научимся структурировать записи, выбирать уровни и избегать типичных ошибок. Материал будет полезен как новичкам, так и опытным разработчикам, желающим упорядочить подход к логированию.
Стандартная библиотека log: базовые возможности и ограничения
Пакет log из стандартной библиотеки Go — это самый простой способ начать логирование. Он предоставляет готовый логгер, который пишет в os.Stderr.
Простой пример использования
package main
import (
"log"
)
func main() {
log.Println("Приложение запущено")
log.Printf("Пользователь %s вошел в систему", "admin")
log.Fatal("Критическая ошибка: база данных недоступна")
}
Вывод будет выглядеть так:
2023/10/27 14:35:12 Приложение запущено
2023/10/27 14:35:12 Пользователь admin вошел в систему
2023/10/27 14:35:12 Критическая ошибка: база данных недоступна
Ограничения стандартного логгера
- Отсутствие уровней логирования — нет разделения на Info, Warn, Error. Все сообщения пишутся в один поток.
- Плохая структура — запись представляет собой простую строку, которую сложно парсить автоматически.
- Отсутствие контекста — нельзя легко добавить идентификатор запроса или пользователя.
- Проблемы с производительностью — стандартный логгер блокирует горутину при записи, что может стать узким местом.
Для небольших утилит или прототипов log вполне подходит. Но для продакшен-приложений стоит рассмотреть альтернативы.
Структурированное логирование: почему это важно?
Структурированное логирование — это подход, при котором каждое сообщение представляет собой набор пар "ключ-значение" (обычно в формате JSON). Это позволяет:
- Автоматически парсить логи в системах сбора (ELK, Splunk, Grafana Loki).
- Фильтровать записи по любым атрибутам (уровень, сервис, ID запроса).
- Строить дашборды и алерты на основе числовых полей.
Популярные библиотеки для структурированного логирования
- logrus — одна из первых и самых популярных библиотек. Предоставляет уровни, форматирование (JSON, текст), хуки для отправки логов во внешние системы. Однако имеет высокое потребление памяти.
- zerolog — библиотека с фокусом на производительность (zero allocation). Отлично подходит для микросервисов и высоконагруженных систем.
- zap — от Uber. Очень быстрый, с двумя режимами: SugaredLogger (удобный) и Logger (максимальная скорость).
- slog — стандартный пакет, добавленный в Go 1.21. Предоставляет структурированное логирование "из коробки".
Пример с использованием slog (Go 1.21+)
package main
import (
"log/slog"
"os"
)
func main() {
logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
logger.Info("Сервер запущен", "port", 8080, "env", "production")
logger.Error("Ошибка подключения к БД", "db", "postgres", "timeout", 5)
}
Вывод в JSON:
{"time":"2023-10-27T14:40:00.123Z","level":"INFO","msg":"Сервер запущен","port":8080,"env":"production"}
{"time":"2023-10-27T14:40:01.456Z","level":"ERROR","msg":"Ошибка подключения к БД","db":"postgres","timeout":5}
Лучшие практики логирования в Go
1. Используйте уровни логирования
Четко разделяйте сообщения по уровням важности:
- DEBUG — отладочная информация (только для разработки).
- INFO — общие события (запуск сервера, успешные запросы).
- WARN — потенциальные проблемы (медленный запрос, повторная попытка).
- ERROR — ошибки, требующие внимания (падение БД, ошибка валидации).
- FATAL — критические ошибки, после которых приложение завершает работу.
2. Добавляйте контекст к каждому сообщению
Всегда включайте в лог идентификатор запроса (request_id), имя сервиса, версию приложения. Это упрощает трассировку.
logger.Info("Обработка запроса",
"request_id", reqID,
"method", r.Method,
"path", r.URL.Path,
"duration_ms", duration.Milliseconds(),
)
3. Избегайте логирования чувствительных данных
Никогда не пишите в логи пароли, токены, номера кредитных карт или персональные данные пользователей. Используйте фильтры или маскировку.
4. Настройте ротацию логов
Логи не должны бесконечно расти. Используйте библиотеки типа lumberjack для ротации по размеру или времени.
import (
"gopkg.in/natefinch/lumberjack.v2"
"log/slog"
)
func main() {
writer := &lumberjack.Logger{
Filename: "/var/log/app.log",
MaxSize: 100, // мегабайт
MaxBackups: 3,
MaxAge: 28, // дней
}
logger := slog.New(slog.NewJSONHandler(writer, nil))
}
5. Не логируйте в цикле
Логирование в горячем пути