Олег Марков
Настройка уровней логирования log levels в Go
Введение
В процессе разработки приложений на Go одной из важных задач является оформление качественного логирования. Логи помогают вам оперативно диагностировать проблемы, анализировать поведение приложения и просто получать нужную отладочную информацию. Но чтобы логи действительно были полезны, важно уметь управлять их насыщенностью и гибко разделять сообщения по степени важности — для этого используются уровни логирования (log levels).
В Go стандартная библиотека предлагает минимальный функционал для log, но ничего не знает об уровнях журналирования — вы не найдете встроенных категорий "INFO", "ERROR" или "DEBUG". Тем не менее, реализовать их можно как вручную, так и через популярные сторонние решения. В этой статье я расскажу, какие подходы существуют, как реализовать уровни логирования самостоятельно, и как использовать сторонние библиотеки для более продвинутого ведения логов. Мы подробно разберем примеры и настройки, чтобы вы могли легко внедрить этот функционал в свой проект.
Что такое уровни логирования
Давайте уточним, что подразумевается под уровнями логирования. Повысить читаемость и контроль над логами позволяет разграничение сообщений по категориям:
- DEBUG — подробная техническая информация, полезна для отладки
- INFO — общая информация о работе сервиса
- WARN — предупреждения о потенциально проблемных ситуациях
- ERROR — сообщение об ошибке, которую нужно исправить
- FATAL — критические ошибки, после которых приложение не сможет продолжать работу
Такое разграничение вы, вероятно, встречали в других языках и фреймворках. Благодаря уровням вы можете фильтровать сообщения — например, в тестовой среде видеть "DEBUG", а в продакшене оставить только "ERROR" и "WARN".
Стандартная библиотека log в Go: возможности и ограничения
Начнем с самого базового — стандартного пакета log
. Его основное предназначение — просто писать сообщения в стандартный вывод, но он не поддерживает уровни логирования по умолчанию.
Посмотрите простой пример использования:
package main
import (
"log"
)
func main() {
log.Println("Приложение запущено") // Просто пишет сообщение
log.Fatal("Критическая ошибка") // Пишет сообщение и завершает приложение os.Exit(1)
}
Вы видите, что по сути разделения по уровням здесь нет, имеются только Fatal и Print, а для остального придется изобретать велосипед.
Самостоятельная реализация уровней логирования
Если вам не хочется подключать сторонние зависимости, можно быстро организовать простейшие уровни прямо поверх стандартного log. Вот шаблон, чтобы вы представляли, как добавить фильтрацию по уровням:
package main
import (
"fmt"
"log"
"os"
)
type LogLevel int
const (
Debug LogLevel = iota
Info
Warn
Error
Fatal
)
// logLevel задает текущий минимальный логируемый уровень
var logLevel = Info
func Log(level LogLevel, msg string, args ...interface{}) {
if level < logLevel {
return // Пропускаем логи ниже порога
}
prefix := ""
switch level {
case Debug:
prefix = "DEBUG"
case Info:
prefix = "INFO"
case Warn:
prefix = "WARN"
case Error:
prefix = "ERROR"
case Fatal:
prefix = "FATAL"
}
log.Printf("[%s] %s", prefix, fmt.Sprintf(msg, args...))
if level == Fatal {
os.Exit(1) // Завершаем приложение при фатальной ошибке
}
}
func main() {
Log(Debug, "Загрузка конфигурации") // Не выведется, по умолчанию Info
Log(Info, "Сервер запущен по адресу %s", "localhost:8080")
Log(Warn, "Память на сервере заканчивается")
Log(Error, "Соединение с базой данных потеряно")
}
Такой подход удобен для простых задач. Вы всегда можете поменять logLevel
на меньший (например, Debug
), чтобы видеть больше подробностей.
Если вы хотите использовать такую схему в разных пакетах, вынесите её в отдельный модуль (например, logutil
), и импортируйте как зависимость.
Наследование уровней через структуры
Для больших приложений этот подход легко масштабируется через структуры. Например, добавьте отдельный логгер для разных частей системы:
type Logger struct {
level LogLevel
}
func (l *Logger) Log(level LogLevel, msg string, args ...interface{}) {
if level < l.level {
return
}
// Аналогичное форматирование
}
// Пример использования
appLogger := Logger{level: Info}
dbLogger := Logger{level: Debug}
Так вы получите независимое логирование для отдельных компонентов.
Использование сторонних библиотек логирования
Когда стандартых возможностей не хватает, имеет смысл обратиться к сторонним решениям. Вот самые популярные среди Go-разработчиков библиотеки для логирования с поддержкой уровней:
Logrus
Библиотека Logrus — один из самых популярных вариантов. Давайте посмотрим, как она работает:
Установка
go get github.com/sirupsen/logrus
Пример использования
import (
log "github.com/sirupsen/logrus"
)
func main() {
// Установка минимального уровня логирования — только сообщения >= Info будут выведены
log.SetLevel(log.InfoLevel)
log.Debug("Это debug сообщение") // Не выведется
log.Info("Приложение запущено") // Выведется
log.Warn("Проблема с памятью")
log.Error("Ошибка доступа к базе")
log.Fatal("Критическая ошибка") // Завершает приложение
}
Logrus позволяет удобно добавлять поля (WithFields
), менять форматы вывода и логировать в разные цели (stdout, файл, удаленный сервис).
Zap
Второй по популярности пример — Zap от Uber (go.uber.org/zap). Он выделяется скоростью и возможностью структурированного логирования (JSON).
Установка
go get go.uber.org/zap
Пример использования
import "go.uber.org/zap"
func main() {
logger, _ := zap.NewProduction() // Для продакшена (выводит лог в JSON)
defer logger.Sync()
// Менять уровень можно динамически через AtomicLevel,
// но базово - через NewDevelopment или NewProduction
logger.Debug("Это debug сообщение") // Для Production по умолчанию не выводится
logger.Info("Сервис запущен")
logger.Warn("Внимание: мало места")
logger.Error("Ошибка подключения")
// Fatal завершает процесс
}
Zap удобен, если вам важно писать логи в формате JSON для последующего сбора через системы вроде ELK или Datadog.
Zerolog
Легкая и быстрая альтернатива — zerolog. Она тоже поддерживает гибкое управление уровнями, но с минимальным оверхедом.
Установка
go get github.com/rs/zerolog/log
Пример использования
import (
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"os"
)
func main() {
// Вывод в обычный текстовый формат вместо JSON
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stdout})
zerolog.SetGlobalLevel(zerolog.WarnLevel) // Выше DEBUG и INFO
log.Debug().Msg("debug лог") // Не выведется
log.Info().Msg("инфо лог") // Не выведется
log.Warn().Msg("предупреждение") // Выведется
log.Error().Msg("ошибка") // Выведется
}
В zerolog управление происходит через глобальный уровень, но вы также можете создавать логгеры разных уровней для отдельных частей приложения.
Как и где настраивать уровни логирования в приложении
Часто минимальный уровень логирования задают через переменную окружения или конфиг. Распространенный подход — считывать нужный уровень при старте приложения:
Пример с конфигом и Logrus
import (
"os"
log "github.com/sirupsen/logrus"
)
func main() {
levelStr := os.Getenv("LOG_LEVEL") // Например: "debug", "info", "warn"
level, err := log.ParseLevel(levelStr)
if err != nil {
level = log.InfoLevel
}
log.SetLevel(level)
log.Info("Уровень логирования установлен из переменной окружения")
}
Так вы сможете без переписывания кода менять насыщенность логов в разных средах — просто передавая LOG_LEVEL в окружении контейнера/сервера.
Лучшие практики использования уровней логирования
- Храните уровень логирования в конфиге или окружении. Это даст вам гибкость переключения между подробностями без перекомпиляции.
- Пишите подробные DEBUG-логи только тогда, когда это оправдано. В продакшене выводите только действительно важные сообщения.
- Используйте структурированные сообщения (например, JSON-логи), если планируете интегрировать систему с лог-агрегаторами.
- Следите, чтобы обработка ошибок сопровождалась минимум WARN- или ERROR-логами.
- Не выводите чувствительные данные в логах любых уровней. Это важное требование безопасности.
- Разделяйте логгеры между различными слоями приложения. Например, создайте отдельные логгеры для базы данных, API и фоновых задач.
Дополнительные возможности современных логгеров
Современные библиотеки логирования предлагают вам много полезных фич поверх уровней:
- Поля (Fields): передавайте дополнительные контексты вместе с логом (например, ID пользователя или имя запроса).
- Форматирование: поддержку нескольких форматов вывода — console, JSON, custom.
- Хуки и вывод в облако: вы можете отправлять логи не только в stdout, но и через network, или на сторонние сервисы.
- Динамическая смена уровня: иногда важно менять уровень “на лету”, и такие функции доступны в Zap («AtomicLevel»), Logrus и Zerolog через глобальный или контекстный уровень.
Как выбрать подход к уровням логирования для вашего проекта
- Если у вас небольшой микросервис или утилита — часто достаточно собственной простой обертки на базе стандартного log.
- Если вы строите распределенную систему или API под production-нагрузкой — используйте одну из библиотек выше, чтобы получить культуру логирования, совместимую со стандартами DevOps.
- Внимательно выбирайте между производительностью (Zap, zerolog) и гибкостью (Logrus). Для интенсивных сервисов (например, realtime API) важна скорость логгера.
Заключение
Уровни логирования — это базовый, но очень важный инструмент контроля над вашим приложением. В Go самому реализовать фильтрацию по уровням несложно, и для начальных задач этого хватает. Но если требуется что-то большее — продвинутое структурированное логирование, динамическая настройка и интеграция с внешними системами — смело выбирайте mature-библиотеки: Logrus, Zap, Zerolog.
Я показал вам, как это выглядит на практике и какие нюансы учитываются при настройке логирования под разные нужды. Используйте логирование с умом, чтобы ваши сервисы оставались надежными и управляемыми.
Частозадаваемые технические вопросы по теме статьи и ответы на них
Как изменить уровень логирования во время выполнения программы без перезапуска?
Если вы используете стандартный логгер, вам нужно сделать уровень логирования глобальной переменной и поменять её значение динамически. В сторонних библиотеках вроде Zap и Logrus есть специальные механизмы:
Zap:
import "go.uber.org/zap"
cfg := zap.NewProductionConfig()
atomicLevel := zap.NewAtomicLevelAt(zap.InfoLevel)
cfg.Level = atomicLevel
logger, _ := cfg.Build()
atomicLevel.SetLevel(zap.DebugLevel) // Меняем уровень на лету
Logrus: используйте SetLevel()
в любом месте программы.
Можно ли выводить логи одновременно в файл и консоль?
Да, почти все серьезные библиотеки логирования это умеют с помощью мультивыходов. В Logrus:
import (
"github.com/sirupsen/logrus"
"os"
)
file, _ := os.OpenFile("app.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
log := logrus.New()
log.SetOutput(io.MultiWriter(os.Stdout, file))
Как добавить пользовательские поля (например, user_id) ко всем логам?
Большинство современных логгеров поддерживают "with fields":
Logrus:
log.WithFields(logrus.Fields{
"user_id": 42,
}).Info("Пользователь авторизовался")
Zap:
logger.Info("Запрос обработан", zap.Int("user_id", 42))
Как фильтровать логи по тегам или категориям?
Для расширенного фильтра по категориям можно использовать разные логгеры или встроенные поля. Например, для Zap заведите отдельные логгеры с полем "component": "db"
. В log aggregator (Graylog, ELK) используйте фильтрацию по этим полям.
Как переслать логи из Go-приложения во внешний сервис логирования?
В большинстве популярных библиотек есть плагины или хуки для отправки логов в Sentry, Loki, ELK и аналогичные сервисы. Например, для Logrus реализован hook для отправки в Graylog, в Zap и Zerolog аналогично через внешние обработчики вывода.
Постройте личный план изучения Golang до уровня Middle — бесплатно!
Golang — часть карты развития Backend
100+ шагов развития
30 бесплатных лекций
300 бонусных рублей на счет
Бесплатные лекции
Все гайды по Golang
Лучшие курсы по теме

Основы Golang
Антон Ларичев
Nest.js с нуля
Антон Ларичев