Содержание урока · 12 разделов
- Структурное логирование: запись как данные
- Почему Uber написал zap
- Четыре логгера, которые вы встретите в Go
- Slog уже в стандартной библиотеке — зачем zap?
- Признаки, что текстовым логам пора на пенсию
- Миф: структурные логи неудобно читать
- Типичная ошибка: структурный логгер, текстовое мышление
- Логи в системе обсервабельности
- Что дальше по курсу
- Песочница урока
- Коротко: что запомнить
- Вопросы и ответы
Типичный вечер сервисной команды: дашборд показывает рост 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, и с первым из них мы и знакомимся.
Первый взгляд: запись с полями
GOpackage 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 | Когда брать |
|---|---|---|---|---|
| logrus | 2014 | текст / JSON | форматтеры и хуки | старые проекты, простые утилиты |
| zap | 2017 | JSON / console | типизированные поля | высоконагруженный прод, тонкая настройка |
| zerolog | 2017 | только JSON | цепочки вызовов | максимум скорости, минимум слов |
| slog | 2023 | текст / 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 во всей красе.
Интерпретатор в браузере исполняет только чистый Go без сторонних модулей, поэтому код с go.uber.org/zap в песочнице не запускается — runner-ов в этом курсе не будет. Понадобится локальный Go и go get go.uber.org/zap: пять минут на настройку, дальше все примеры запускаются в вашем терминале, где zap честно печатает свои JSON-строки.
Проверка: устройство записи
Что именно пишет zap вместо строки?
Проверка: зачем zap
Зачем Uber построил zap, когда logrus уже был?
Что окажется в поле level?
Прочитайте код и выберите, что он выведет:
logger.Warn("disk almost full", zap.Int("percent", 93))Заполните пропуски в первой программе
Восстановите минимальную программу: имя пакета, путь импорта, production-пресет, синхронизация и строковое поле.
Соберите первую zap-программу
Расставьте строки в правильном порядке — от объявления пакета до записи с полем.
Строки
Ваша программа
Нажимайте на строки слева, чтобы собрать программу
Карточки: словарь zap
Логи в системе обсервабельности#
Логгер — одна треть обсервабельности: рядом живут метрики (числа: 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 простыми словами?
Библиотека логирования от Uber: каждый лог — запись с полями (уровень, время, сообщение, ваши данные) в JSON, который парсит машина. За счёт типизированных полей она быстрее старых логгеров в разы.
zap или slog — что выбрать?
Для нового маленького сервиса хватит slog из стандартной библиотеки. Zap берут, когда логирование на горячем пути: высокие RPS, сэмплирование, тонкая настройка через zapcore и зрелая экосистема.
Почему текстовые логи — это плохо?
Они хороши для глаз, но не для поиска: поля спрятаны в строке, и каждая выборка требует регулярки под конкретный формат. JSON-логи фильтруются по любому полю без парсеров и ломаются только при смене схемы — а её видно сразу.
Термины урока
Ключевые понятия из этого урока — короткие определения в глоссарии. Наведите на термин, чтобы вспомнить, откройте карточку для подробностей и ссылки на практику.
Сравните с альтернативами
Практический вопрос после первых уроков: а почему именно эта библиотека? Мы разобрали конкурентов по одним критериям, с кодом одной задачи рядом и персональным подбором под ваши приоритеты.
Изучайте дальше
Следующая ступень учебного пути
Colly: парсинг и веб-скрапинг на Go
Интерактивный курс Colly — фреймворка веб-скрапинга для Go: селекторы, пагинация, формы, прокси, лимиты, кэш, хранение данных. 25 уроков и итоговый проект.
- Писать скраперы на Colly от первого Visit до обхода целого сайта
- Вынимать данные через CSS-селекторы и складывать в структуры Go
- Настраивать темп: задержки, параллелизм, лимиты на домен
- Работать с капризными сайтами: заголовки, cookies, прокси