логотип PurpleSchool
логотип PurpleSchool

Настройка уровней логирования 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 и фоновых задач.

Дополнительные возможности современных логгеров

Современные библиотеки логирования предлагают вам много полезных фич поверх уровней:

  1. Поля (Fields): передавайте дополнительные контексты вместе с логом (например, ID пользователя или имя запроса).
  2. Форматирование: поддержку нескольких форматов вывода — console, JSON, custom.
  3. Хуки и вывод в облако: вы можете отправлять логи не только в stdout, но и через network, или на сторонние сервисы.
  4. Динамическая смена уровня: иногда важно менять уровень “на лету”, и такие функции доступны в 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Оркестрация контейнеров Go с Kubernetes + DockerGjСтрелочка вправо

Постройте личный план изучения Golang до уровня Middle — бесплатно!

Golang — часть карты развития Backend

  • step100+ шагов развития
  • lessons30 бесплатных лекций
  • lessons300 бонусных рублей на счет

Бесплатные лекции

Все гайды по Golang

Работа с YAML в GolangПреобразование типов в GolangКонвертация структур в JSON в GolangStrconv в GolangИспользование пакета SQLx для работы с базами данных в GolangРазбираемся с SQL в GolangРазделение строк с помощью функции split в GolangSort в GoПоиск и замена строк в Go - GolangИспользование пакета reflect в GolangРабота с PostgreSQL в GoPointers в GolangПарсинг в GoРабота со списками (list) в GolangПреобразование int в string в GolangРабота с числами с плавающей точкой в GolangРабота с полями в GolangИспользование enum в GolangОбработка JSON в GoЧтение и запись CSV-файлов в GolangРабота с cookie в GolangРегистры в GoКэширование данных в GolangПреобразование byte в string в GolangByte в GoИспользование bufio для работы с потоками данных в GolangДобавление данных и элементов (add) в Go
Логирование в Golang. Zap, Logrus, Loki, GrafanaРабота с Docker-контейнерами в GoИспользование pprof в GolangМеханизмы синхронизации в GolangРабота с пакетом S3 в GolangМониторинг Golang приложений с помощью PrometheusОптимизация проектов на GoПаттерны проектирования в GolangТрейсинг запросов с OpenTelemetry в GoНастройка шины событий NATS NSQ в GoМиграции базы данных в GolangНастройка уровней логирования log levels в GoОркестрация контейнеров Go с Kubernetes + DockerGjGo Playground и компилятор GolangИспользование go mod init для создания модулей GolangРабота с переменными окружения (env) в GolangКоманда go build в GolangАвтоматизация Golang проектов — CI/CD с GitLab CI и JenkinsРуководство по embed в GoОтладка кода в GolangЧтение и использование конфигурации в приложениях на GolangКомпиляция в GolangКак развернуть Go-приложение на облаке AWSАутентификация в Golang
Сетевые протоколы в GoПеременные в GolangЗначения в GolangДженерик %T и его применение в GolangТипы данных в GolangИспользование tls в GolangИспользование tag в структурах GolangSwitch в GoСтроки в GolangРабота с потоками (stream) в GolangSelect в GoРуны в GoРабота с пакетом params в GolangКонвертация строк в числа в GolangNull, Nil, None, 0 в GoНаименования переменных, функций и структур в GoInt в GolangУстановка GolangЧтение и установка HTTP заголовков в GolangMethods в GolangGoLand — IDE для разработки на Golang от JetBrainsОбработка «not found» в GolangFloat в GolangФлаги командной строки в Go (Golang)Запуск внешних команд в GolangОбработка ошибок в GoИспользование defer в GolangЗначения default в GolangГенерация кода в GoФорматирование кода в GolangЧистая архитектура в GolangКаналы (channels) в GolangПолучение body из HTTP запроса в Golang
Открыть базу знаний

Лучшие курсы по теме

изображение курса

Основы Golang

Антон Ларичев
AI-тренажеры
Практика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.7
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

Nest.js с нуля

Антон Ларичев
AI-тренажеры
Практика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.6
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

Docker и Ansible

Антон Ларичев
AI-тренажеры
Гарантия
Бонусы
иконка звёздочки рейтинга4.8
3 999 ₽ 6 990 ₽
Подробнее