---
title: "Уровни логов zerolog: Trace, Debug, Info, Warn, Error, Fatal"
description: "Уровни логов в zerolog: числа от -1 до 7, zerolog.SetGlobalLevel, что логировать на Trace, Debug, Info, Warn и Error, Fatal с os.Exit(1) и свой порог у логгера."
course: "Zerolog в Go: быстрое JSON-логирование"
url: "https://golangmaster.ru/courses/zerolog/lessons/03-urovni-logov/"
published: "2026-09-19"
updated: "2026-09-21"
minutes: 18
---

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

*Курс «Zerolog в Go: быстрое JSON-логирование», урок 3 из 25. ~18 минут. Обновлено 21.09.2026. Источник: https://golangmaster.ru/courses/zerolog/lessons/03-urovni-logov/*

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

### Восемь констант 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 |
| Debug | 0 | параметры, состояния, попадания в кэш | cache miss key=user:42 |
| Info | 1 | штатные события — хроника сервиса | service started port=8080 |
| Warn | 2 | работает, но с потерями или странно | retry 2/5 attempt delayed |
| Error | 3 | операция провалилась, есть ошибка | dial failed err=timeout |
| Fatal | 4 | процесс дальше жить не может | config invalid missing=tls_cert |
| Panic | 5 | записать событие и запаниковать | 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 забудут опустить, поэтому ставьте сброс по таймауту или держите возврат уровня в чек-листе инцидента — иначе через квартал кто-то удивится объёму логов. Механика чтения уровня из окружения с дефолтами и валидацией — тема [урока про конфигурацию](/courses/zerolog/lessons/16-konfiguraciya-okrug/).

### 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 в коде.

> **Fatal: запись и os.Exit(1).** log.Fatal().Msg("...") пишет запись и немедленно завершает процесс с кодом 1 — os.Exit не даёт выполниться ни одному defer, поэтому незакрытые файлы и транзакции останутся как есть. Zerolog сам закроет writer, если тот реализует io.Closer, но ваш код не запустится. Нюанс с буфером: запись до exit гарантированно уходит в writer, но если writer буферизован и без Flush — редкий, но реальный случай — хвост может не долететь: ещё одна причина не буферизовать без нужды. Место Fatal — провал на старте: битая конфигурация, недоступная база при поднятии. В библиотеках Fatal запрещён: библиотека не имеет права убивать процесс вызывающего.

> **Всё Info — как отсутствие уровней.** Сервис, где и успешный запрос, и падение оплаты пишутся Info, лишает уровни смысла: фильтр по уровню ничего не находит, алерты строятся по сообщениям — то есть по grep. Если монолит уже так живёт, начните с Error и Warn: перенесите сначала провалы, потом деградации, и лента обретёт слои. Быстрый диагностический вопрос команде: какие события у нас будят дежурного? Если ответ «ничто» — Error-лента пуста или алертов нет, и уровни пока дают только слова в JSON.

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

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

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

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

- Trace: пишется вообще всё, включая Trace-записи
- ✅ Debug: пишется всё, кроме Trace
- Info: Debug и Trace отсечены
- Disabled: zerolog молчит без явной настройки

Глобальный уровень по умолчанию равен DebugLevel (0), поэтому Trace (-1) не проходит порог, а Debug и выше пишутся. Чтобы увидеть Trace-записи, вызовите zerolog.SetGlobalLevel(zerolog.TraceLevel).

### Проверка: Fatal

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

- Паникует: стек можно поймать через recover
- Ничего: Fatal — это просто уровень с номером 4
- ✅ Завершает процесс через os.Exit(1): defer не выполняется
- Закрывает все открытые файлы процесса

Fatal пишет запись и вызывает os.Exit(1) — отложенные функции не запускаются, очистка на вашей совести. Для writer-а с io.Closer zerolog сделает Close сам, но остальной defer-код выполнен не будет.

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

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

- ✅ zerolog.SetGlobalLevel(zerolog.Disabled)
- zerolog.SetGlobalLevel(zerolog.TraceLevel)
- logger.Level(zerolog.Disabled) у главного логгера
- Такого способа нет — только не вызывать логгер

Disabled (7) — максимальная константа: с ней ни одно событие не пройдёт глобальную проверку. Локальный уровень у одного логгера заглушил бы только его копию, а глобальный уровень нужен именно процессный рубильник.

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

```go
zerolog.SetGlobalLevel(zerolog.InfoLevel)

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

**Что выведет программа?**

- Ни одной
- Одну: b
- ✅ Три: b, c, d
- Четыре: a, b, c, d

Порог Info (1) отсекает только Trace (-1). Info, Warn и Error проходят, и программа печатает три JSON-строки. Проверка происходит при вызове уровня — поля события Trace даже не собираются.

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

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

```go
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")                     // напечатается
}
```

Подсказка: Функция называется SetGlobalLevel; порог Info записывается константой InfoLevel; отсечённый вызов — Debug.

Правильные ответы в пропусках: `Set`, `Info`, `Debug`

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

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

Правильный порядок строк:

1. `package main`
2. `import (`
3. `  "github.com/rs/zerolog"`
4. `  "github.com/rs/zerolog/log"`
5. `)`
6. `func main() {`
7. `  zerolog.SetGlobalLevel(zerolog.InfoLevel)`
8. `  log.Warn().Int("percent", 93).Msg("disk almost full")`
9. `  log.Error().Msg("connection failed")`
10. `}`

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

- **TraceLevel (-1)** — Самый подробный уровень: входы, выходы, шаги. По умолчанию отсечён глобальным Debug — включается SetGlobalLevel(zerolog.TraceLevel).
- **SetGlobalLevel** — Процессный порог для всех логгеров: событие пишется, если его уровень не ниже. Значение Disabled глушит всё.
- **logger.Level(lvl)** — Копия логгера со своим порогом — исходный логгер не меняется. Глобальный и локальный пороги работают по максимуму.
- **Warn** — Операция выжила с потерями: успешный ретрай, fallback, деградация. Серверная граница с Error — предмет командной конвенции.
- **Fatal** — Запись события и os.Exit(1): defer не выполняется. Только для провалов на старте процесса; в библиотеках запрещён.
- **Panic** — Занесение события в лог и паника с текстом сообщения — сценарии с recover выше по стеку.

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

Следующий урок — [поля события](/courses/zerolog/lessons/04-polya-sobytiya/): Str, Int, Err и Dur, ошибка как данные, а не строка в сообщении. Потом — ConsoleWriter: как уровни превращаются в цветные INF и ERR на терминале. Если главный вопрос «как уровни помогают продам» — пригодятся уроки про [sampling](/courses/zerolog/lessons/14-sampling/), где вместо глушения поток прореживают, и про [логи на проде](/courses/zerolog/lessons/24-logi-na-prode/), где фильтр по уровню становится первым инструментом дежурного.

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

- Уровни 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?

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: он паникует после записи, а не выходит из процесса.
