Перейти к содержимому
ГоМастер

Поиск по урокам

Найдите урок, курс, термин или раздел

Урок 3 из 25 ~18 мин +50 XP

Уровни логов zerolog: Trace, Debug, Info, Warn, Error, Fatal

Уровни логов в zerolog — это восемь числовых констант от Trace до Disabled, и одна вызов-строка zerolog.SetGlobalLevel управляет всеми записями процесса. В этом уроке разберём, что логировать на каждом уровне, почему отключённый Debug почти бесплатен, чем Fatal отличается от Error за пределами номера и где проходит спорная граница между Warn и Error.

Содержание урока · 10 разделов

Восемь констант zerolog: от Trace до Disabled#

Уровень в zerolog — это число типа int8. Снизу вверх: TraceLevel равно -1, DebugLevel — 0, InfoLevel — 1, WarnLevel — 2, ErrorLevel — 3, FatalLevel — 4, PanicLevel — 5, NoLevel — 6 и Disabled — 7; чем больше число, тем серьёзнее событие. Логгер пишет запись, только если её уровень не ниже двух порогов сразу: порога самого логгера и глобального уровня, который задаёт zerolog.SetGlobalLevel для всего процесса. Пороги складываются по максимуму: глобальный Info отсечёт Debug, даже если конкретный логгер настроен мягче. Из коробки пороги стоят так: zerolog.New создаёт логгер с уровнем Trace, но глобальный уровень по умолчанию — Debug, поэтому единственный уровень, который вы не увидите без настройки, — Trace; чтобы включить его, вызовите zerolog.SetGlobalLevel(zerolog.TraceLevel). Константа Disabled — самое жёсткое значение: SetGlobalLevel(zerolog.Disabled) глушит все записи процесса, что удобно как аварийный рубильник или заглушка в тестах. Особняком стоит NoLevel (6): это не про серьёзность, а про события без уровня — их создаёт метод log.Log(), и ключа level в JSON у таких записей нет вовсе. Встречаются они редко, но знать про них стоит, чтобы запись без level не выглядела поломкой. Примеры ниже исполняются локально — SetGlobalLevel требует библиотеки, а песочница сайта показывает тематическую программу на чистом Go. Проверка происходит дважды: когда вы вызываете метод уровня и когда событие уходит в writer. Из-за этого комбинация «мягкий логгер при жёстком глобальном» безопасна, а обратная — нет: событие, прошедшее локальный порог, всё равно упрётся в глобальный.

Семантика уровней: что писать на каждом#

УровеньЧислоЧто логироватьПример записи
Trace-1вход и выход функций, шаги цикловentering handler /users
Debug0параметры, состояния, попадания в кэшcache miss key=user:42
Info1штатные события — хроника сервисаservice started port=8080
Warn2работает, но с потерями или странноretry 2/5 attempt delayed
Error3операция провалилась, есть ошибкаdial failed err=timeout
Fatal4процесс дальше жить не можетconfig invalid missing=tls_cert
Panic5записать событие и запаниковатьinvariant broken state=empty

zerolog.SetGlobalLevel и фильтрация

GO
package main

import (
        "github.com/rs/zerolog"
        "github.com/rs/zerolog/log"
)

func main() {
        zerolog.SetGlobalLevel(zerolog.InfoLevel)

        log.Debug().Str("query", "SELECT ...").Msg("db query") // молчит
        log.Info().Msg("service started")
        log.Warn().Int("percent", 93).Msg("disk almost full")
        log.Error().Str("op", "dial").Msg("connection failed")
}

// Вывод: три строки — info, warn, error. Запись Debug отсечена.

SetGlobalLevel атомарно поднимает глобальный порог для всех логгеров процесса. Вызов Debug создаёт проверку: 0 (debug) меньше 1 (info) — событие не проходит, и цепочка превращается в no-op ещё до Msg. Остальные три вызова проходят порог и пишутся. Заметьте: проверка уровня случается в начале цепочки, поэтому поля отключённого события даже не собираются. SetGlobalLevel меняет одну атомарную переменную — переключение читается целиком из любой горутины, без мьютексов и гонок.

Nil-событие: отключённый уровень почти бесплатен#

Что именно происходит с отключённой записью — лучшая деталь zerolog. Когда уровень события ниже порога, Info-подобный метод возвращает nil вместо *Event, а каждый метод цепочки — Str, Int, Err, Msg — начинается с проверки receiver на nil и просто возвращает событие дальше. Ни паники, ни записи, ни аллокаций: в собственных бенчмарках README отключённое событие стоит около четырёх наносекунд. Отсюда и спокойная практика для библиотек: библиотечный код логирует Debug сколько угодно — включать его детали решает приложение, а не автор библиотеки. Из этого следует практика, которую в других логгерах приходится строить руками: отладочные вызовы не надо оборачивать в if и не надо удалять из горячего пути — Debug-цепочка в HTTP-обработчике стоит ноль, когда уровень Info. Это же делает честной связку «в dev включаем Debug, в prod выключаем»: код одинаковый, меняется одна константа при старте. Практический вывод для ревью: обёртки if вокруг Debug-вызовов — мусор, принесённый из других логгеров. Здесь условие не нужно никогда: дешевле вызвать и отсечься порогом, чем плодить ветвления, которые кто-то потом будет поддерживать. Подробный разбор цены записей с бенчмарками — в уроке про производительность. Именно поэтому в zerolog не нужен отдельный заглушечный логгер для библиотек: роль заглушки играет сам порог, а для случаев, когда логировать нельзя вовсе, есть zerolog.Nop() — логгер, который глотает всё.

Глобальный уровень — не единственная ручка. Метод Level возвращает копию логгера с собственным порогом, не трогая исходник: quiet := logger.Level(zerolog.WarnLevel). Иммутабельность здесь та же, что и у всего zerolog: дочерние логгеры создаются, а не перенастраиваются. Пороги глобальный и локальный работают как максимум из двух, поэтому заглушить шумный модуль через локальный уровень не выйдет, если глобальный стоит выше, — сначала опустите глобальный. Типичное применение: сторонний клиент в вашем процессе пишет слишком много Info — оберните его логгер копией с Warn. Систему сублоггеров на модули мы соберём в уроке про сублоггеры, здесь важно само свойство: уровень — часть конфигурации логгера, а не свойство вызова. Спорное место — локальный Disabled: логгер с ним молчит на всё, включая Fatal. Для временной заглушки шума это удобно, но если под заглушкой случится настоящий фатал — вы узнаете о нём по падению процесса без единой строки в журнале. Я не ставлю Disabled локально никогда: глобальный рубильник видно снаружи и его можно поднять, локальный — невидимка.

Свой порог у отдельного логгера

GO
package main

import (
        "os"

        "github.com/rs/zerolog"
)

func main() {
        logger := zerolog.New(os.Stdout).With().Timestamp().Logger()

        quiet := logger.Level(zerolog.WarnLevel)

        quiet.Info().Msg("не напечатается: порог Warn")
        quiet.Error().Msg("connection lost")
        logger.Info().Msg("исходный логгер не изменился")
}

// Вывод: две записи — error от quiet и info от logger.

logger.Level(zerolog.WarnLevel) возвращает копию с новым порогом: quiet молчит на Info, но пишет Error. Исходный logger продолжает писать Info — копирование не задевает оригинал. Именно поэтому 3 (Error) не ниже 2 (Warn) — запись проходит. Если бы где-то выше стоял SetGlobalLevel(zerolog.DebugLevel), оба логгера подчинились бы и ему: глобальный порог всегда проверяется дополнительно.

SetGlobalLevel — не только настройка при старте: порог хранится в атомарной переменной, и менять его безопасно из любой горутины на живом процессе. Прочитать текущее значение можно зеркальной функцией zerolog.GlobalLevel() — она избавляет от копий порога в собственных структурах, которые всегда врут. Классический сценарий: в проде случилась странность, деплоить нельзя — дежурный дёргает админ-эндпоинт или сигнал, обработчик парсит строку через zerolog.ParseLevel, поднимает порог до Debug, разбирается и опускает обратно. Отсюда два правила гигиены. Первое: строку конфига парсит ParseLevel, а не таблица if-ов из вашего кода — функция знает все имена уровней и возвращает ошибку на мусор. Второе: поднятый Debug забудут опустить, поэтому ставьте сброс по таймауту или держите возврат уровня в чек-листе инцидента — иначе через квартал кто-то удивится объёму логов. Механика чтения уровня из окружения с дефолтами и валидацией — тема урока про конфигурацию.

Trace, Debug и Info: три скорости детализации#

Уровни — это скорость ленты. Trace — самая быстрая: входы и выходы функций, итерации, шаги конвейера; включается на минуты точечной отладки и выключается, иначе лента станет бесконечной. Debug — подробности штатной работы: параметры запросов, состояния кэша, решения маршрутизатора; в dev живёт всегда, в prod — по требованию, потому что именно Debug-записи вы будете читать, разбирая, почему запрос пошёл не туда. Info — хроника сервиса: старт и стоп, принятая конфигурация, приём и завершение значимых операций. Правило качества: Info-лента должна читаться как дневник — глаз дежурного пробегает её и видит события, а не шум. Если Info-записи можно удалять пачками без потери смысла, они лежат на неправильном уровне: это Debug. И наоборот, если без Debug-вызовов не собрать картину инцидента, что-то важное спряталось не на своём уровне. Ориентир по объёму: на один HTTP-запрос у взрослого сервиса выходит одна-две Info-записи, десяток Debug и сотня Trace. Если Info выходит по десять на запрос — хроника утонула в шуме ещё до системы сбора, и фильтр по уровню перестаёт быть инструментом дежурного.

Warn против Error: спорная граница#

Границу Warn/Error каждая команда проводит сама, и это честно спорное место. Базовое правило: Warn — операция выжила с потерями (ретрай удался, включился fallback, данные деградировали), Error — операция провалилась, и у записи есть ошибка. Спор начинается на HTTP-статусах. Моё правило: 4xx от клиента — Warn, а не Error, потому что сервер свою работу сделал, а некорректный запрос — не дефект сервера. Условие, при котором я готов уступить: если у вас нет алертов на Error-ленту, то Error безопаснее — вы ничего не пропустите; если алерты есть, Error на каждый 404 разбудит дежурного из-за бота-сканера. Что сломается при путанице уровней в обратную сторону: Warn на всё — и горящий узел чувствует себя нормально, потому что алерты настроены на Error, а дежурный видит лишь спокойную жёлтую ленту. Уровни — это язык алертов: договоритесь в команде один раз и запишите конвенцию в репозиторий. И ошибка — не единственный признак Error: операция без объекта ошибки, которая всё-таки провалилась (нет свободного места, исчерпан лимит соединений), тоже Error — с полем-причиной, даже если err формально нет. Уровень описывает судьбу операции, а не наличие объекта error в коде.

Конвенция уровней одним списком#

  • Trace — отладка шагов: вход/выход функций, итерации; в prod выключен, включается точечно
  • Debug — подробности штатной работы: параметры, состояния, кэш; включается по требованию
  • Info — хроника сервиса: старт, конфигурация, значимые операции; читается как дневник
  • Warn — операция выжила с потерями: ретраи, fallback, деградация, некорректные запросы клиента
  • Error — операция провалилась, запись всегда с ошибкой в поле error; здесь живут алерты
  • Fatal — только на старте процесса, когда продолжать нельзя; в библиотечном коде запрещён
  • Panic — редкий гость: записать инвариант и запаниковать, если выше есть recover
  • SetGlobalLevel — при старте из конфига окружения, а не константой в коде

Проверка: дефолты

Какой уровень zerolog действует по умолчанию, если SetGlobalLevel не вызывали?

Проверка: Fatal

Что делает log.Fatal().Msg("config invalid") после записи события?

Проверка: рубильник

Как полностью заглушить все записи zerolog в процессе?

Сколько строк напечатает программа?

Прочитайте код и выберите, что он выведет:

zerolog.SetGlobalLevel(zerolog.InfoLevel)

log.Trace().Msg("a")
log.Info().Msg("b")
log.Warn().Msg("c")
log.Error().Msg("d")

Управление уровнями

Восстановите вызовы: глобальный порог Info, тихий отладочный вызов и запись об ошибке.

package main import ( "github.com/rs/zerolog" "github.com/rs/zerolog/log" ) func main() { zerolog.GlobalLevel(zerolog.Level) log.().Str("key", "user:42").Msg("cache miss") // не напечатается log.Error().Msg("api failed") // напечатается }

Программа с уровнями

Расставьте строки в правильном порядке: импорты, глобальный порог и две записи.

Строки

Ваша программа

Нажимайте на строки слева, чтобы собрать программу

Карточки: уровни zerolog

Термин

TraceLevel (-1)

нажмите, чтобы перевернуть
Определение

Самый подробный уровень: входы, выходы, шаги. По умолчанию отсечён глобальным Debug — включается SetGlobalLevel(zerolog.TraceLevel).

1 / 6

Что дальше по курсу#

Следующий урок — поля события: Str, Int, Err и Dur, ошибка как данные, а не строка в сообщении. Потом — ConsoleWriter: как уровни превращаются в цветные INF и ERR на терминале. Если главный вопрос «как уровни помогают продам» — пригодятся уроки про sampling, где вместо глушения поток прореживают, и про логи на проде, где фильтр по уровню становится первым инструментом дежурного.

Песочница урока

Редактируйте код и запускайте его прямо здесь — интерпретатор работает в браузере, ничего устанавливать не нужно. За каждый запуск — +2 XP.

Вывод

Запустите код, чтобы увидеть вывод.

Коротко: что запомнить

  • Уровни zerolog — числа: Trace -1, Debug 0, Info 1, Warn 2, Error 3, Fatal 4, Panic 5, Disabled 7.
  • Событие пишется, если проходит и порог логгера, и глобальный SetGlobalLevel — работает максимум из двух.
  • По умолчанию глобальный уровень Debug: из коробки пишется всё, кроме Trace.
  • Отключённый уровень возвращает nil-событие: цепочка no-op за единицы наносекунд, Debug можно не вычищать из кода.
  • Warn — выжили с потерями, Error — операция провалилась; конвенцию уровней записывают один раз на команду.
  • Fatal пишет запись и вызывает os.Exit(1): defer не выполняется, в библиотеках Fatal запрещён.
  • Порог хранится атомарно: SetGlobalLevel безопасен на живом процессе, zerolog.GlobalLevel() читает значение, ParseLevel парсит строку конфига.

Домашнее задание

Соберите программу урока и прогоните её при четырёх глобальных уровнях: Trace, Debug, Info, Warn — посчитайте строки вывода для каждого. Потом замените SetGlobalLevel на zerolog.Disabled и убедитесь, что вывод пуст. Наконец, напишите функцию, которая принимает уровень строкой ("info", "warn") из os.Args, конвертирует через zerolog.ParseLevel и выставляет глобальный порог — это заготовка урока про конфигурацию по окружению. Проверьте под go run -race, что смена уровня из отдельной горутины не даёт гонки.

Личные заметкиПусто
Хранятся только в вашем браузере (zerolog · Уровни логов zerolog: Trace, Debug, Info, Warn, Error, Fatal)

Вопросы и ответы по уроку

Какие уровни логов есть в zerolog?

Trace (-1), Debug (0), Info (1), Warn (2), Error (3), Fatal (4), Panic (5), NoLevel (6) и Disabled (7). Для каждой константы есть метод события: Trace(), Debug(), Info() и так далее, а глобальный порог задаёт zerolog.SetGlobalLevel. Порядок стоит помнить именно числами: сравнение с порогом идёт по ним.

Какой уровень выбрать для Debug-записей в проде?

Держите Debug в коде и выключайте уровнем: отключённый Debug в zerolog почти бесплатен — nil-событие и no-op цепочка. Включайте порог Debug точечно: по окружению, по модулю или при разборе инцидента — для последнего уровень можно поднять на живом процессе через админ-эндпоинт, деплой не нужен.

Чем Fatal отличается от Error?

Error фиксирует провал операции, процесс продолжает работать. Fatal пишет запись и завершает процесс через os.Exit(1) — defer не выполняется. Fatal уместен только при старте, когда сервис не может работать без упавшей зависимости. Для сценария «записать и упасть с восстановлением через recover» есть Panic: он паникует после записи, а не выходит из процесса.

Термины урока

Ключевые понятия из этого урока — короткие определения в глоссарии. Наведите на термин, чтобы вспомнить, откройте карточку для подробностей и ссылки на практику.

Следующая ступень учебного пути

Validator25 уроков~11 чпрактика25 уроков уже доступны

Валидация в Go: go-playground/validator

Курс validator для Go: теги required/min/max, email, dive, вложенные структуры, свои валидаторы, Gin и Echo, русские сообщения, тесты. 25 уроков.

  • Валидировать структуры тегами binding и validate
  • Понимать ValidationErrors и писать понятные сообщения
  • Работать с вложенностью, срезами и картами через dive
  • Писать свои функции-валидаторы и StructLevel-правила