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

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

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

Урок 7 из 25 ~20 мин +50 XP

zap.Config: полная настройка логгера

zap.Config — это полная настройка zap в одной структуре: уровень, формат, приёмники и стартовые поля собираются в одном месте и превращаются в логгер вызовом Build. Два пресета из вторых уроков — просто готовые конфиги; как только staging просит файл вместо stdout, а в каждую запись нужно поле app — приходит время настроить zap руками.

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

До сих пор мы выбирали из двух готовых состояний: NewDevelopment для терминала и NewProduction для продакшена. Рано или поздно этого перестаёт хватать — по вполне рабочим причинам: staging пишет одновременно в stdout и файл, уровень включается на короткое окно расследования, в каждую запись с первого дня нужно поле app и version, а конфигурация живёт рядом с остальными настройками сервиса, а не в коде. Для всего этого в zap есть zap.Config — структура, в которой собрана вся настройка логгера: от уровня и формата до списка приёмников. Пресеты — это два предзаполненных экземпляра этой структуры, не более; поняв её поля, вы поймёте и то, что именно настраивали за вас. Разберём поля по одному, соберём логгер методом Build, посмотрим на YAML-файл, из которого конфиг читается, и разберём ловушки правки пресетов — их у этой структуры прилично.

zap.Config: все поля одной структуры#

Поля zap.Config разбиты на четыре группы. Поведение: Level — минимальный включённый уровень, хранится как AtomicLevel, а значит, может меняться на живом процессе; Development — флаг режима разработки, который меняет поведение DPanic и щедрость стек-трейсов; DisableCaller и DisableStacktrace выключают соответствующие части записи; Sampling — политика сэмплирования, nil отключает. Формат: Encoding — строка «json» или «console» (плюс сторонние зарегистрированные), EncoderConfig — тот самый набор ключей и правил сериализации из урока про энкодеры. Вывод: OutputPaths — список путей или URL-схем, куда уходят записи; ErrorOutputPaths — куда zap пишет собственные ошибки. Контекст: InitialFields — карта полей, которая попадает в каждую запись от корневого логгера. Обратите внимание на типы: Level не число, а AtomicLevel, OutputPaths не строка, а срез — обёртка настроена на несколько приёмников с первого дня, и это не ошибка дизайна, а его суть.

Поля zap.Config и их роль#

ПолеТипЧто делает
LevelAtomicLevelминимальный включённый уровень; SetLevel меняет его на живом процессе
Developmentboolрежим разработки: DPanic паникует, стеки с Warn
DisableCaller / DisableStacktraceboolубирают caller или автостеки из записей
Sampling*SamplingConfigполитика сэмплирования; nil — сэмплирования нет
Encodingstring"json" или "console"
EncoderConfigzapcore.EncoderConfigключи и правила сериализации записи
OutputPaths[]stringкуда пишутся записи: stderr, stdout, файлы
ErrorOutputPaths[]stringкуда zap пишет свои внутренние ошибки
InitialFieldsmap[string]interface{}поля, попадающие в каждую запись

Полный конфиг и Build()

GO
package main

import (
        "go.uber.org/zap"
)

func main() {
        cfg := zap.Config{
                Level:            zap.NewAtomicLevelAt(zap.DebugLevel),
                Development:      false,
                Encoding:         "json",
                EncoderConfig:    zap.NewProductionEncoderConfig(),
                OutputPaths:      []string{"stderr"},
                ErrorOutputPaths: []string{"stderr"},
                InitialFields: map[string]interface{}{
                        "app":     "shop",
                        "version": "1.4.2",
                },
        }

        logger, err := cfg.Build()
        if err != nil {
                panic(err)
        }
        defer logger.Sync()

        logger.Info("built from config", zap.Int("port", 8080))
}

Конфиг собран с нуля: уровень Debug, JSON-энкодер с production-набором настроек, вывод в stderr и два стартовых поля. cfg.Build() проверяет конфиг, открывает приёмники и возвращает (*zap.Logger, error) — ошибку проверяем: кривой путь в OutputPaths обнаружится именно здесь. В каждой записи появятся app и version: {"level":"info","app":"shop","version":"1.4.2",...}. Запустите локально — песочница zap не исполняет.

Build(): от конфига к логгеру#

Build — это фабрика: он разбирает Encoding, строит энкодер из EncoderConfig, открывает приёмники из OutputPaths через ту же механику, что и zap.Open, собирает ядро и оборачивает его в логгер с опциями — Development и сэмплирование прикладываются здесь же. Ошибка возвращается для всего, что нельзя распарсить: неизвестная схема пути, недопустимое Encoding, недоступный файл. Отсюда важное следствие: NewProduction() — это NewProductionConfig().Build(), и ничего больше. Пресеты не содержат магии, скрытой от вас: прод — это уровень Info, JSON, сэмплирование и стеки с Error; development — уровень Debug, консольный энкодер, стеки с Warn. Когда понадобится что-то между — конфиг пресета модифицируется по полям, как обычная структура. И раз пресеты — конфиги, их можно сравнивать: откройте NewProductionConfig и NewDevelopmentConfig в документации и увидите все различия списком — хороший способ убедиться, что никакой магии нет. Заметьте и цену: Build открывает файлы при старте и держит их открытыми — логгер, собранный в двух местах программы, откроет файл дважды; стройте один раз, как и всякий другой ресурс с состоянием.

OutputPaths и ErrorOutputPaths: два разных потока#

Пути в конфиге — это URL или файловые пути: «stderr», «stdout», «logs/app.log» — схема по умолчанию файловая, зарегистрировать можно и свои через RegisterSink. OutputPaths — приёмники записей; ErrorOutputPaths — то, куда zap пишет собственные проблемы: не открылся основной файл, упала запись, кончился диск. Потоки принципиально разные, и смешивать их не стоит: если основной приёмник и error-приёмник — один и тот же файл, сбой этого файла съест и записи, и сообщения о том, почему их нет. Дефолт для внутренних ошибок — stderr, и это разумный минимум; в серьёзной конфигурации error-поток направляют туда, за которым присматривает мониторинг. Полная механика приёмников — файлы, мультиплекс, буферизация — следующий урок; здесь держите в голове разделение ролей: записи — это данные, ошибки логгера — служебный канал, и пути у них свои. Проверка путей при этом ранняя: Build вернёт ошибку на неизвестной схеме и недоступном файле ещё при старте, а не в момент первой записи — ловите её и падайте с понятным сообщением, а не обнаруживайте пустые логи в три ночи.

Пресеты как конфиг: NewProductionConfig#

Самый частый сценарий настройки — не конфиг с нуля, а модификация пресета: cfg := zap.NewProductionConfig(), дальше правятся нужные поля, и вызывается Build. Такой подход безопаснее ручного заполнения: EncoderConfig уже на месте, сэмплирование и стеки — как в проде, и вы меняете ровно то, что хотите. Типовые правки: cfg.Level = zap.NewAtomicLevelAt(zap.DebugLevel) — включить отладку; cfg.OutputPaths = append(cfg.OutputPaths, "logs/app.log") — добавить файл к stdout; cfg.InitialFields = map[string]interface{}{...} — стартовые поля. Отдельная тонкость — флаг Development: он не про вывод, а про поведение — в true DPanic паникует и стеки идут с Warn. Меняя его, вы меняете семантику уровней, о которых договорились в третьем уроке.

Модифицированный пресет

GO
package main

import (
        "go.uber.org/zap"
)

func main() {
        cfg := zap.NewProductionConfig()

        cfg.Level = zap.NewAtomicLevelAt(zap.DebugLevel)
        cfg.OutputPaths = []string{"stderr", "logs/app.log"}
        cfg.InitialFields = map[string]interface{}{
                "app":     "shop",
                "version": "1.4.2",
        }

        logger, err := cfg.Build()
        if err != nil {
                panic(err)
        }
        defer logger.Sync()

        logger.Debug("debug enabled", zap.String("env", "staging"))
        logger.Info("config applied")
}

База — production-пресет: JSON-энкодер и его EncoderConfig остались нетронутыми. Уровень поднят до Debug — обе записи дойдут и до stderr, и до logs/app.log (папка должна существовать: zap не создаёт каталоги, и ошибка открытия уйдёт в ErrorOutputPaths, не в панику). Стартовые поля появятся в каждой записи. Такой конфиг — типичный staging: подробный, с локальной копией логов и меткой приложения.

Ловушки правки пресетов вслепую#

  • сменили Encoding у development-конфига на "json" — ключи станут T, L, M: короткие имена рассчитаны на колонки, а не на парсеры; проверяйте EncoderConfig после смены формата
  • cfg.Level = zap.DebugLevel не скомпилируется — Level это AtomicLevel, нужен NewAtomicLevelAt или SetLevel
  • Development = false у копии development-пресета молча отключает панику DPanic — тестовая ловушка перестаёт ловить
  • OutputPaths = []string{"logs/app.log"} затёрло stdout: записи уехали только в файл, сборщик с stdout остался голодным
  • папка для файла не создана — zap не mkdir-ит; ошибка открытия уйдёт в ErrorOutputPaths, а записи — в никуда
  • скопировали пресет копированием структуры и забыли про Sampling: в одном логгере сэмплирование есть, в другом нет — и объёмы разошлись

YAML-конфиг: настройка как часть приложения#

У всех полей zap.Config есть теги json и yaml, поэтому конфиг логгера — обычная структура для маршалинга: её можно хранить в YAML-файле рядом с остальной настройкой сервиса и загружать при старте. Даже сложные правила сериализации описываются строками: encodeTime: "iso8601" или "epoch", encodeLevel: "lowercase" или "capital" — у этих типов есть UnmarshalText, и yaml-библиотека их разворачивает. Логгер при этом становится конфигурируемым без пересборки: поменяли файл — перезапустили сервис, формат и уровень новые. Это же дверь к строгой проверке: Decoder с KnownFields поймает опечатку в ключе yaml вместо молчаливого нулевого значения. А в реальных приложениях файлы конфигов читает Viper — он умеет YAML, TOML и env-переменные поверх; в курсе про Cobra этому посвящён целый урок, здесь достаточно идеи: конфиг логов — часть общей конфигурации, и живёт она не в исходниках.

Конфиг из YAML-файла

GO
// zap.yaml
level: debug
development: false
encoding: json
encoderConfig:
  messageKey: msg
  levelKey: level
  timeKey: ts
  encodeTime: iso8601
  encodeLevel: lowercase
outputPaths:
  - stderr
  - logs/app.log
errorOutputPaths:
  - stderr
initialFields:
  app: shop

// main.go
func loadLogger(path string) (*zap.Logger, error) {
        data, err := os.ReadFile(path)
        if err != nil {
                return nil, err
        }

        var cfg zap.Config
        if err := yaml.Unmarshal(data, &cfg); err != nil {
                return nil, err
        }

        return cfg.Build()
}

YAML повторяет структуру конфига по тегам: encoderConfig вложен, outputPaths — список. encodeTime: iso8601 и encodeLevel: lowercase разворачиваются в энкодеры через UnmarshalText, поэтому строки тут не метафора, а рабочий механизм. loadLogger читает файл, разворачивает в zap.Config и строит логгер; ошибка на любом этапе возвращается наверх — в main её обрабатывают с fallback-логгером. Для строгости используйте yaml.Decoder с KnownFields(true): опечатка в ключе станет ошибкой чтения, а не молчаливым дефолтом.

InitialFields: стартовые поля каждой записи#

InitialFields — карта any: эти поля попадают в каждую запись от построенного логгера и от всех его потомков через With. Типичное содержимое — app, version, env: метки, по которым в централизованном хранилище логи сортируются по сервисам и релизам ещё до фильтра по содержимому. Цена та же, что у zap.Any из урока про поля: значения едут как interface{}, и сериализация идёт через рефлексию — для полей, которые пишутся один раз на запись и известны при старте, это приемлемо, но горячие поля лучше добавлять типизированным With на построенном логгере. Отдельная история — альтернатива: logger.With("app", "shop") после Build даёт тот же эффект без карты any и прозрачнее в коде; InitialFields выигрывает там, где конфиг приходит из YAML — карту удобнее описывать рядом с остальной настройкой, а не дописывать в main.

Проверка: пустой EncoderConfig

Что напечатает логгер, собранный из zap.Config{Encoding: "json"} без EncoderConfig?

Проверка: ErrorOutputPaths

Что именно пишет zap в ErrorOutputPaths?

Сколько записей дойдёт до stderr?

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

cfg := zap.NewProductionConfig()
cfg.Level = zap.NewAtomicLevelAt(zap.DebugLevel)
cfg.OutputPaths = []string{"stderr", "logs/app.log"}
logger, _ := cfg.Build()
logger.Info("one")
logger.Debug("two")

Соберите конфиг staging

Восстановите модификацию пресета: уровень, список приёмников и сборка логгера.

cfg := zap.NewProductionConfig() cfg.Level = zap.NewAtomicLevelAt(zap.Level) cfg. = []string{"stderr", "logs/app.log"} cfg.InitialFields = map[string]interface{}{"app": "shop"} logger, err := cfg.() if err != nil { panic(err) }

Соберите модификацию пресета

Расставьте строки: от базового пресета до первой записи.

Строки

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

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

Карточки: конфигурация zap

Термин

zap.Config

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

Структура полной настройки логгера: Level, Development, Encoding, EncoderConfig, OutputPaths, ErrorOutputPaths, InitialFields. Пресеты — два готовых экземпляра.

1 / 6

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

Конфиг управляет приёмниками списком путей — но что физически происходит при открытии файла, как дублировать записи в stdout и файл и зачем нужна буферизация, разбирается в уроке про приёмники вывода. Смена уровня на живом процессе — тема урока про AtomicLevel: поле Level из этого урока там станет рулем без перезапуска. Карта курса — на странице курса.

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

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

Вывод

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

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

  • zap.Config — вся настройка в одной структуре; пресеты NewProduction/NewDevelopment — два готовых конфига.
  • cfg.Build() строит логгер: энкодер из EncoderConfig, приёмники из OutputPaths, ошибку возвращает явно.
  • Encoding без EncoderConfig даёт записи без ключей — берите NewProductionEncoderConfig как базу.
  • OutputPaths — поток записей, ErrorOutputPaths — служебный канал внутренних ошибок zap; их не смешивают.
  • Конфиг читается из YAML: у полей есть yaml-теги, encodeTime/encodeLevel разворачиваются из строк.
  • InitialFields — стартовые метки каждой записи; альтернатива — logger.With после Build.
  • Модификация пресета по полям безопаснее конфига с нуля: EncoderConfig и сэмплирование уже на месте.

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

Соберите staging-конфиг модификацией пресета: уровень Debug, приёмники stderr и logs/staging.log, InitialFields с app и env. Запустите и проверьте файл глазами. Затем уберите EncoderConfig из полноручного конфига и посмотрите на урезанные записи — впишите вывод в заметки как маркер ошибки. В конце вынесите конфиг в zap.yaml и загрузите его через yaml.Unmarshal, добавив KnownFields(true) и поймав опечатку в ключе.

Личные заметкиПусто
Хранятся только в вашем браузере (Zap · zap.Config: полная настройка логгера)

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

zap.Config или два пресета — когда что?

Пока приложение пишет в stdout и разница между dev и прод исчерпывается уровнем и форматом — хватит пресетов и выбора по APP_ENV. Файлы, стартовые поля, YAML-конфигурация и нестандартные уровни — сигнал переходить на zap.Config.

Почему Build возвращает ошибку, а пресеты — нет?

Возвращают, просто в примерах её отбрасывают через _: оба пресета — это Config.Build под капотом. Ошибка означает проблему конфигурации — кривой путь или Encoding; в боевом коде её обрабатывают с запасным логгером.

Можно ли менять zap.Config после Build?

Логгер уже построен — правка структуры конфига на него не влияет. Level — исключение: это AtomicLevel, и SetLevel меняет порог на живом процессе. Для остального стройте новый конфиг и новый логгер.

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

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

Сравните с альтернативами

Практический вопрос после первых уроков: а почему именно эта библиотека? Мы разобрали конкурентов по одним критериям, с кодом одной задачи рядом и персональным подбором под ваши приоритеты.

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

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

Colly: парсинг и веб-скрапинг на Go

Интерактивный курс Colly — фреймворка веб-скрапинга для Go: селекторы, пагинация, формы, прокси, лимиты, кэш, хранение данных. 25 уроков и итоговый проект.

  • Писать скраперы на Colly от первого Visit до обхода целого сайта
  • Вынимать данные через CSS-селекторы и складывать в структуры Go
  • Настраивать темп: задержки, параллелизм, лимиты на домен
  • Работать с капризными сайтами: заголовки, cookies, прокси