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

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

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

Урок 1 из 25 ~16 мин +50 XP

Знакомство с zap: зачем структурные логи

Два часа ночи, прод сыпется, а в логах двести тысяч одинаковых строк, из которых надо вытащить пять заказов и одну причину. Знакомо? Тогда знакомьтесь: zap для golang — логгер Uber, который пишет записи не строками, а JSON-объектами с полями. Разберём, чем плох grep-мир текстовых логов, что такое структурное логирование и как zap живёт рядом с logrus, slog и zerolog.

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

Типичный вечер сервисной команды: дашборд показывает рост 5xx, вы открываете kibana или просто tail -f по логам и начинаете охоту. Логи текстовые: одна строка — одна запись, все поля склеены в предложение. «user 42 order 100500 not found» — что здесь ключ, а что значение, понимает только человек. Следующий сервис пишет иначе: order_id=100500 failed for uid=42. Третий вообще пишет одним абзацем. Под каждую строку — своя регулярка, каждый новый сервис — новый парсер в системе сбора логов. А теперь умножьте на объём: на пике сервис выдаёт десятки тысяч строк в минуту, и grep по ним — не поиск, а лотерея. Найдёте только то, точный вид чего угадали: остальное — числа, идентификаторы, причины — остаётся запертым внутри строки, недоступное ни фильтрам, ни графикам, ни алертам.

Структурное логирование: запись как данные#

Идея структурного логирования проста до обидного: лог — это не текст, а данные. Каждая запись — событие с фиксированным набором полей: уровень серьёзности, время, сообщение и сколько угодно своих пар ключ-значение: user_id=42, order_id=100500, elapsed_ms=317. Формат записи машиночитаем — чаще всего это JSON, реже key=value в потоке. JSON-логи парсит любая система сбора: Loki, Elasticsearch, ClickHouse — им не нужна регулярка под каждый сервис, схема полей и так известна. Взамен вы получаете фильтры, которых grep не даёт никогда: все ошибки за последний час, где user_id=42 и elapsed_ms больше 500; частоты кодов ошибок; графики по любому полю. Человек это тоже читает — JSON не поэзия, но при аккуратных полях дежурный разберётся быстрее, чем в трёх форматах трёх сервисов. Сегодня структурное логирование — дефолт индустрии, а не экзотика: в Go его долго олицетворял logrus, теперь — zap, slog и zerolog, и с первым из них мы и знакомимся.

Первый взгляд: запись с полями

GO
package main

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

func main() {
        logger, _ := zap.NewProduction()

        logger.Info("order created",
                zap.String("order_id", "A-1024"),
                zap.Int("amount", 1490),
        )

        _ = logger.Sync()
}

logger, _ := zap.NewProduction() — готовый логгер с production-настройками; zap.String и zap.Int — типизированные поля. После go mod init demo и go get go.uber.org/zap запуск напечатает одну строку JSON: {"level":"info","ts":1712345678.12,"caller":"main/main.go:10","msg":"order created","order_id":"A-1024","amount":1490}. Уровень, время, место в коде и поля — всё отдельными ключами. Подробный разбор пресетов — в следующем уроке, здесь важно само устройство записи: не фраза для человека, а документ для машины.

Почему Uber написал zap#

В 2017 году логгером по умолчанию в Go был logrus: удобный, с хуками и форматтерами — и медленный. Запись лога в logrus — это рефлексия по полям, сборка строки и приличная пачка аллокаций на каждое событие. Для обычного CRUD это невидимо, но Uber гонял тысячи сервисов с высокими RPS, где логи пишутся на горячем пути каждого запроса, и профилирование показывало: на логирование уходят проценты CPU и горы мусора для сборщика. Ответом стал zap: типизированные конструкторы полей вместо рефлексии, пул записей и буферов, ноль аллокаций на отключённых уровнях. По данным репозитория, на их бенчмарке с сообщением и десятью полями zap пишет лог более чем в семь раз быстрее logrus и примерно вдвое быстрее slog из стандартной библиотеки — цифры синтетические, но порядок верный: разницу видно и без бенчмарков, на графике p99. С тех пор API стабилен: версия 1.x, и код из туториалов 2017-го компилируется сегодняшним компилятором без правок.

Четыре логгера, которые вы встретите в Go#

ЛоггерГодФорматAPIКогда брать
logrus2014текст / JSONформаттеры и хукистарые проекты, простые утилиты
zap2017JSON / consoleтипизированные полявысоконагруженный прод, тонкая настройка
zerolog2017только JSONцепочки вызововмаксимум скорости, минимум слов
slog2023текст / JSONстандартный интерфейсновые проекты без особых требований к скорости

Slog уже в стандартной библиотеке — зачем zap?#

С августа 2023 в Go есть slog — логгер в стандартной поставке, и первый честный вопрос: зачем тогда zap? Для многих проектов — незачем: slog с JSONHandler закрывает структурное логирование без единой зависимости, и на новом маленьком сервисе я бы начинал именно с него. Zap выигрывает там, где логи — часть горячей нагрузки: быстрее (по данным всё того же репозитория), с сэмплированием из коробки, с динамической сменой уровня на живом процессе, с богатым ядром zapcore, на котором собирают кастомные энкодеры и приёмники. Плюс экосистема: zap умеет быть бэкендом для интерфейса logr — так логирует Kubernetes через мост zapr; у него зрелый инструментарий тестов и observer для проверки записей. Моё правило: микросервис на сотню RPS — slog; сервис с десятками тысяч запросов в секунду, где каждая микросекунда overhead-а читается на графике, — zap. Всё, что разбирается дальше, про второй случай, но API slog и zap так похожи, что знания переносятся почти без потерь.

Признаки, что текстовым логам пора на пенсию#

  • под каждый сервис в системе сбора живёт своя регулярка-парсер
  • «сходи на машину и посмотри глазами» — рабочий способ отладки
  • чтобы посчитать, сколько раз упал платёж, пишут скрипт по grep
  • в записи нет поля, по которому её можно отфильтровать: ни user_id, ни trace_id
  • уровни проставлены наугад — всё Info, включая ошибки
  • новый сервис копирует формат старого не потому, что так правильно, а потому что так работал шаблон

Миф: структурные логи неудобно читать#

Главное возражение против JSON-логов я слышу на каждом ревью: в них невозможно читать. Возражение устарело лет на десять. Во-первых, zap умеет console-энкодер: тот же логгер, те же поля, но вывод колонками для человека — его включают в development-окружении и на локальной машине. Во-вторых, JSON парсится одной командой: jq '.msg' по потоку — и вы видите только сообщения; jq 'select(.level=="error")' — и перед вами одни ошибки. В-третьих, системы сбора показывают записи таблицей: поля разворачиваются в колонки, и читать их удобнее, чем сплошной текст, где нужное значение прячется между словами. Боль реальна только у тех, кто открывает сырой поток логов в notepad — так открывать и текстовые логи тоже больно. Итог: человек читает логи через инструмент, машина — через поля, и JSON обслуживает обоих дешевле, чем любой самодельный текстовый формат, который придётся ещё и парсить.

Типичная ошибка: структурный логгер, текстовое мышление#

zap ставят быстро, а мышление меняется медленно. Классика переходного периода: logger.Info(fmt.Sprintf("order %s not found", id)) — JSON снаружи, grep-мир внутри. Запись выглядит структурной, но фильтру в системе сбора ловить нечего: все данные спрятаны в msg, поле одно на всю запись, дашборд по ней не построишь. Я ловил такое в ревью у людей, которые месяцем раньше клялись структурными логами: привычка склеивать строки живёт дольше, чем кажется. Приём для самопроверки простой: посмотрите на запись и спросите, что в ней можно посчитать — частоту событий, долю промахов, медиану времени. Если ответ «только количество строк», данные надо выносить в поля: zap.String("order_id", id) — и сообщение сокращается до факта «order not found», а идентификатор становится фильтром. Что будет, если ошибку не заметить: через квартал команда обнаруживает, что платит за систему сбора логов, а ищет всё равно grep-ом по JSON — только теперь регулярки ломаются ещё и о кавычки. Отдельный случай — строка, собранная для человека: читаемость в консоли обеспечивает console-энкодер, а не Sprintf внутри msg; форматом управляет энкодер, и это тема урока, где мы сравним JSON и console во всей красе.

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

Что именно пишет zap вместо строки?

Проверка: зачем zap

Зачем Uber построил zap, когда logrus уже был?

Что окажется в поле level?

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

logger.Warn("disk almost full", zap.Int("percent", 93))

Заполните пропуски в первой программе

Восстановите минимальную программу: имя пакета, путь импорта, production-пресет, синхронизация и строковое поле.

package import ( "go.uber.org/" ) func main() { logger, _ := zap.New() defer logger.() logger.Info("order created", zap.("order_id", "A-1024"), ) }

Соберите первую zap-программу

Расставьте строки в правильном порядке — от объявления пакета до записи с полем.

Строки

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

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

Карточки: словарь zap

Термин

zap

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

Структурный логгер Uber для Go (2017). Пишет записи из полей — JSON или console. По бенчмаркам репозитория — самый быстрый из популярных логгеров.

1 / 6

Логи в системе обсервабельности#

Логгер — одна треть обсервабельности: рядом живут метрики (числа: RPS, латентности, коды ответов) и трейсинг (путь запроса через сервисы). Логи отвечают на вопрос «что именно случилось», метрики — «насколько плохо», трейсы — «где по пути». Связывает их поле: если каждая запись несёт request_id или trace_id, то по алерту на графике вы за секунду находите конкретные записи в Loki или Kibana. JSON-логи zap для этого и созданы: trace_id станет обычным ключом, по которому системы сбора связывают записи тысяч запросов. В курсе мы доведём это до практики: в уроке про middleware подключим zap к HTTP-роутеру так, чтобы каждый запрос логировался со своим идентификатором — тот же приём, на котором построены middleware в Gin, если вы уже писали на фреймворках.

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

Во втором уроке — первый логгер: zap.NewProduction и zap.NewDevelopment, Sync и первые записи с полями, разбор JSON по ключам. Третий — уровни логов, четвёртый — поля во всей красе. Карта всех двадцати пяти тем — на странице курса. А пока установите zap локально и запустите пример из этого урока: первая JSON-строка в терминале стоит всех карточек этого урока.

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

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

Вывод

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

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

  • zap — структурный логгер Uber (2017): запись — JSON с полями, а не строка для человека.
  • Текстовые логи ищутся grep-ом и регулярками; структурные фильтруются по любому полю.
  • Uber построил zap против медленного logrus: типизированные поля без рефлексии, пулы, ноль аллокаций на отключённых уровнях.
  • По данным репозитория, на бенчмарках zap опережает logrus более чем в семь раз, а slog — примерно вдвое.
  • slog закрывает простые случаи; zap берут на горячем пути и ради тонкой настройки через zapcore.
  • Песочница zap не исполняет: примеры запускаются локально после go get go.uber.org/zap.

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

Установите zap локально: go mod init demo, затем go get go.uber.org/zap. Запустите пример из урока и рассмотрите JSON по ключам. Потом замените NewProduction на NewDevelopment и сравните вывод. Найдите в своей старой программе fmt.Println с диагностикой и перепишите вызовом logger.Info с тремя полями — сравните, какой вариант легче искать по user_id.

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

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

Что такое zap простыми словами?

Библиотека логирования от Uber: каждый лог — запись с полями (уровень, время, сообщение, ваши данные) в JSON, который парсит машина. За счёт типизированных полей она быстрее старых логгеров в разы.

zap или slog — что выбрать?

Для нового маленького сервиса хватит slog из стандартной библиотеки. Zap берут, когда логирование на горячем пути: высокие RPS, сэмплирование, тонкая настройка через zapcore и зрелая экосистема.

Почему текстовые логи — это плохо?

Они хороши для глаз, но не для поиска: поля спрятаны в строке, и каждая выборка требует регулярки под конкретный формат. JSON-логи фильтруются по любому полю без парсеров и ломаются только при смене схемы — а её видно сразу.

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

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

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

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

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

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

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

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

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