---
title: "Знакомство с zap: зачем структурные логи"
description: "Что такое zap: обзор логгера Uber для Go — боль текстовых логов, идея структурных полей, сравнение с logrus, slog и zerolog, бенчмарки и первый код."
course: "Zap: структурное логирование на Go"
url: "https://golangmaster.ru/courses/zap/lessons/01-znakomstvo-zap/"
published: "2026-09-19"
updated: "2026-09-21"
minutes: 16
---

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

*Курс «Zap: структурное логирование на Go», урок 1 из 25. ~16 минут. Обновлено 21.09.2026. Источник: https://golangmaster.ru/courses/zap/lessons/01-znakomstvo-zap/*

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

Типичный вечер сервисной команды: дашборд показывает рост 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 | Когда брать |
| --- | --- | --- | --- | --- |
| 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 во всей красе.

> **Песочница zap не запустит.** Интерпретатор в браузере исполняет только чистый Go без сторонних модулей, поэтому код с go.uber.org/zap в песочнице не запускается — runner-ов в этом курсе не будет. Понадобится локальный Go и go get go.uber.org/zap: пять минут на настройку, дальше все примеры запускаются в вашем терминале, где zap честно печатает свои JSON-строки.

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

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

- Форматированную строку, как fmt.Printf
- ✅ JSON-объект: уровень, время, сообщение и поля ключ-значение
- XML-документ с атрибутами
- Строку в таблице базы данных

Запись zap — структурированные данные: json-энкодер собирает объект с ключами level, ts, msg и вашими полями. Текстовый формат существует — console-энкодер, — но запись всё равно собирается из полей, а не из конкатенации строк.

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

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

- Чтобы у логов был другой цвет в консоли
- ✅ Чтобы убрать рефлексию и аллокации с горячего пути — логирование стало в разы быстрее
- Чтобы логи писались только в файлы
- Чтобы заменить метрики Prometheus

logrus тратил время на рефлексию и аллокации на каждой записи; zap заменил их типизированными полями и пулами. По данным репозитория, их бенчмарки дают более чем семикратный отрыв от logrus на записи с десятью полями.

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

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

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

- ✅ "level":"warn"
- "level":"WARNING"
- "level":"Warn"
- Поле level не пишется вовсе

zap пишет уровни строчными буквами: debug, info, warn, error. Заглавные вроде WARNING — привычка logrus; в zap такой формы нет. Запустите пример локально и убедитесь сами.

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

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

```go
package ___

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

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

        defer logger.___()

        logger.Info("order created",
                zap.___("order_id", "A-1024"),
        )
}
```

Подсказка: Имя пакета и последний сегмент пути импорта совпадают; пресет называется NewProduction; буфер сбрасывает Sync; строку оборачивает zap.String.

Правильные ответы в пропусках: `main`, `zap`, `Production`, `Sync`, `String`

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

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

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

1. `package main`
2. `import (`
3. `  "go.uber.org/zap"`
4. `)`
5. `func main() {`
6. `  logger, _ := zap.NewProduction()`
7. `  defer logger.Sync()`
8. `  logger.Info("service started", zap.Int("port", 8080))`
9. `}`

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

- **zap** — Структурный логгер Uber для Go (2017). Пишет записи из полей — JSON или console. По бенчмаркам репозитория — самый быстрый из популярных логгеров.
- **Структурное логирование** — Подход, при котором запись лога — данные: уровень, время, сообщение и пары ключ-значение, а не строка для человека.
- **JSON-логи** — Записи в формате JSON: их парсит любая система сбора — Loki, Elasticsearch — без регулярок под каждый сервис.
- **Поле (field)** — Пара ключ-значение в записи: zap.String("order_id", "A-1024"). Типизированная — сериализуется без рефлексии.
- **Уровень логирования** — Серьёзность записи: debug, info, warn, error, dpanic, panic, fatal. zap пишет его строчными в поле level.
- **logrus** — Популярный логгер Go до эры slog. Удобен, но медленнее zap в разы: рефлексия и аллокации на каждой записи.

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

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

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

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

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

- 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-логи фильтруются по любому полю без парсеров и ломаются только при смене схемы — а её видно сразу.
