---
title: "SugaredLogger: простой интерфейс"
description: "zap SugaredLogger — упрощённый интерфейс логгера: logger.Sugar(), Infow и Warnw с парами ключ-значение, ловушка нечётного числа аргументов и цена сахара."
course: "Zap: структурное логирование на Go"
url: "https://golangmaster.ru/courses/zap/lessons/05-sugaredlogger/"
published: "2026-09-19"
updated: "2026-09-21"
minutes: 16
---

# SugaredLogger: простой интерфейс

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

zap SugaredLogger — упрощённый интерфейс того же логгера: sugar := logger.Sugar(), и дальше поля передаются парами ключ-значение без единого конструктора. Разберём четыре семьи методов, ловушку нечётного числа аргументов, цену сахара в аллокациях и места, где сахар оправдан, а где мы закрываем его обратно через Desugar.

Семь конструкторов полей в одном вызове — ещё не рекорд: в HTTP-обработчике их бывает дюжина, и вызов Info обрастает хвостом из zap.String, zap.Int и zap.Duration, который переносится на три строки редактора. Код читается, но печатать утомительно, а прототип требует скорости: повесить лог с пятью значениями за десять секунд. Для таких мест у zap есть второй интерфейс — SugaredLogger, «сахарный» логгер: те же уровни, тот же вывод, но поля передаются парами ключ-значение без конструкторов. Это не отдельная библиотека и не другой формат записи — обёртка над тем же *zap.Logger, поверх того же ядра и того же приёмника. В этом уроке разберём, как её получить, что она умеет, где подставляет подножку и в каких местах я её включаю в своих сервисах, а где закрываю обратно.

### logger.Sugar(): как получить SugaredLogger

Вызов logger.Sugar() возвращает *zap.SugaredLogger — тонкую обёртку, которая хранит исходный логгер внутри. Уровни, энкодер, приёмник, поля, добавленные через With, — всё общее: запись через sugar уезжает туда же и в том же виде, что и запись через logger. Стало быть, можно держать в приложении оба интерфейса: типизированный Logger для горячих путей и SugaredLogger для всего остального — zap сам советует конвертировать на границах производительного кода, а метод Desugar делает обратное превращение. Одно правило переносится с базового логгера без изменений: буферы. SugaredLogger буферизует так же, поэтому defer sugar.Sync() ставится сразу после создания — забытый Sync съедает хвост записей так же безжалостно, как и у Logger. Песочница сайта zap не исполняет, поэтому вся проверка примеров — в локальном терминале после go get go.uber.org/zap: запустить программу с сахаром занимает минуту, а увидеть разницу своими глазами стоит этих пяти команд.

### Первая запись на сахаре

```go
package main

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

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

        sugar := logger.Sugar()

        sugar.Infow("order paid",
                "order_id", "A-1024",
                "amount", 1490,
        )

        sugar.Warnf("slow call: %d ms", 210)
}
```

logger.Sugar() создаёт обёртку — дальше вызовы идут от sugar. Infow принимает сообщение и любое число пар: ключи-строки чередуются со значениями любого типа, и на выходе получится знакомый JSON: "order_id":"A-1024", "amount":1490. Warnf — из printf-семейства: форматная строка и аргументы, как у fmt.Printf. Заметьте: defer висит на исходном logger — у обёртки те же буферы, а привычный defer sugar.Sync() тоже допустим; выберите один вариант и держитесь его во всём приложении.

### Infow, Warnf, Infoln: три семьи методов

У SugaredLogger на каждый уровень приходится по четыре метода, и запутаться в суффиксах проще, чем кажется. Семья без суффикса — Print-стиль: sugar.Info("up") печатает аргументы через пробел, как fmt.Print, без полей. Семья w — «with»: sugar.Infow(msg, ключ, значение, ключ, значение...) — структурная запись парами, читается «info with»; это главный метод урока. Семья f — printf-стиль: sugar.Infof("paid %d", amount) — форматная строка с глаголами, удобно для готовых фраз. Семья ln — Println-стиль: те же аргументы через пробел, но с переводом строки в конце. Нюанс, который экономит время на поисках в документации: Infowf не существует — у пар ключ-значение нет printf-версии, потому что смешивать форматную строку и произвольные пары в одном вызове было бы перебором. Зато в w-семью можно подмешивать готовые поля: zap.Error(err) внутри Infow — легальный приём. И голая ошибка сахару по силам: передали err без ключа — zap сам обернёт её в поле error; вторая ошибка без ключа в том же вызове уже не уместится и уедет в отдельную служебную запись.

### Четыре семьи методов на примере Info

| Семья | Вызов | Стиль | Что получится |
| --- | --- | --- | --- |
| без суффикса | sugar.Info("up", 8080) | Print | аргументы склеены через пробел, полей нет |
| w | sugar.Infow(msg, "port", 8080) | структурные пары | запись с полем port=8080 |
| f | sugar.Infof("port %d", 8080) | printf | глаголы развёрнуты прямо в msg |
| ln | sugar.Infoln("up", 8080) | Println | аргументы через пробел с переводом строки |

### Нечётное число аргументов

```go
package main

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

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

        sugar := logger.Sugar()

        sugar.Warnw("payment retry",
                "attempt", 2,
                "order_id",
        )
}
```

Ключ order_id остался без значения — поприветствуйте самую частую ошибку сахарного кода, которую компилятор не заметит: все аргументы типа any. zap не паникует и не молчит: пара attempt=2 уйдёт в запись как положено, а хвостовой ключ породит отдельную запись уровня error с сообщением «Ignored key without a value.» и полем ignored. Основная запись уедет без order_id — фильтр по нему в системе сбора вернёт пустоту. Запустите локально и посмотрите на обе строки: эффект запоминается быстрее любого текста.

### Ловушка нечётного числа аргументов

Компилятор пропускает нечётные пары молча, типы проверять некому, и на ревью такой хвост виден только внимательным глазом. Последствия двухслойные. Во-первых, в основной записи не хватает поля — все дашборды и алерты, завязанные на order_id, его не увидят, и статистика тихо врёт в меньшую сторону. Во-вторых, появляется служебная error-запись, которой не было в замысле: в проде с правилом «все Error — дежурному» она окажется ложным срабатыванием. Отсюда дисциплина: пары печатаются в вызове построчно — ключ и значение рядом, и выравнивание отступами, как в примере выше, ловит половину таких ошибок; нечётное число строк в вызове — сигнал пересчитать аргументы. Вторая ловушка рядом: ключ — это просто строка, и опечатка order_id вместо order_id не поймается ничем — в записи окажутся два разных поля с двумя разными именами, и поиск начнёт находить половину событий. Пары ключ-значение — цена сахара: скорость написания в обмен на потерю типовой проверки, а в горячем коде за это платят ещё и аллокациями, о чём ниже.

> **Ignored key — это тоже Error в общем потоке.** Нечётный хвост в Infow печатает в лог запись уровня error. На сервисе с алертом «Error — повод звонить» пара таких опечаток за неделю приучит дежурного игнорировать алерты, а это дороже пропавшего поля. Ключ без значения — баг: правьте на месте, а не фильтруйте потом.

### Цена сахара: any и аллокации

Каждый вызов Infow упаковывает аргументы в срез []interface{} — это аллокация на запись, а значения едут с типом any, и zap выясняет, что с ними делать, уже в рантайме. Типизированные поля из прошлых уроков устроены иначе: zap.String("order_id", id) — готовая структура Field, у которой тип известен при компиляции, а сериализация идёт напрямую в буфер. По бенчмаркам репозитория SugaredLogger на записи с полями стабильно проигрывает базовому Logger — и по наносекундам на вызов, и особенно по числу аллокаций; точные цифры зависят от машины, но порядок виден на любом прогоне из README. На скрипте, который запускается раз в сутки, это неважно. На обработчике с тысячей запросов в секунду каждая аллокация на логе складывается в заметную долю нагрузки на сборщик мусора — тот самый мусор, против которого zap и строился. Отсюда правило простое, как молоток: сахар — по краям приложения, типизированный Logger — на горячем пути, и граница между ними проходит через Desugar.

### Когда sugar logger оправдан

Сахар стоит своих денег в трёх местах. Первое — скрипты и CLI-утилиты: они живут секунды, пишут десятки записей, и скорость написания важнее наносекунд исполнения; если строите командные утилиты системно, посмотрите на [курс про Cobra](/courses/cobra/) — там про структуру таких программ подробный курс. Второе — обёртки и клей: функция, читающая конфиг, обработчик старта, импортёр данных — холодные пути, где Infow с парами читается быстрее, чем лестница конструкторов. Третье — прототипы: пока не ясно, какие поля понадобятся дашбордам, сахар позволяет двигаться, а переезд на типизированные поля потом — механическая работа на час. Где сахар мне не нравится: библиотечный код — заставлять пользователей тащить ваш вкус в зависимости странно, там лучше принимать *zap.Logger параметром; и горячие пути HTTP-обработчиков, где каждая запись считается на графике p99 — логирование таких запросов мы собираем в уроке про HTTP-middleware. Промежуточных случаев не существует: либо запись редкая и код дорог, либо наоборот.

### Скрипт: логируем чтение конфига

```go
package main

import (
        "os"

        "go.uber.org/zap"
)

func main() {
        logger, _ := zap.NewProduction()
        sugar := logger.Sugar()
        defer sugar.Sync()

        data, err := os.ReadFile("config.yaml")
        if err != nil {
                sugar.Errorw("config read failed",
                        "path", "config.yaml",
                        zap.Error(err),
                )
                os.Exit(1)
        }

        sugar.Infow("config loaded",
                "path", "config.yaml",
                "bytes", len(data),
        )
}
```

Классический скриптовый сценарий: короткая программа с несколькими точками выхода. Errorw с парами описывает провал, Infow — успех; в Errorw подмешан zap.Error(err) — готовые поля и пары живут рядом без конвертаций. defer sugar.Sync() один на всю программу: сахар не отменяет требование сбросить буферы перед выходом. Для программы, которая выполняется один раз, цена any-аргументов незаметна — зато код читается с листа.

### Desugar(): вернуть типизированный Logger

Обратное превращение — sugar.Desugar() — возвращает *zap.Logger, спрятанный внутри обёртки. Стоимость мала: по документации десугарить разумно хоть на каждой границе модуля. Отсюда гибридная схема, которую я считаю рабочим дефолтом среднего сервиса: приложение в целом живёт на сахаре — писать и читать удобно, — а внутри модулей с высокой нагрузкой первый же вызов Desugar возвращает типизированный Logger, и дальше работают zap.String с компанией. У обёртки, впрочем, есть собственные With и Named: sugar.With("service", "billing") вернёт SugaredLogger с прицепленными полями, Named добавит подскоп в имя — часть контекстной работы доступна и без десугаринга. Про прокидывание контекста по дереву вызовов — отдельный урок про [With и Named](/courses/zap/lessons/12-with-named/), здесь достаточно факта: sugar и logger конвертируются в обе стороны дёшево, а значит, выбор интерфейса — не приговор на весь проект, а решение для конкретного модуля.

### Проверка: нечётные аргументы

**Вопрос:** Что сделает zap с вызовом sugar.Infow("paid", "order_id", "A-1024", "amount")?

- Упадёт с паникой — так нельзя
- amount попадёт в основную запись под ключом extraKey
- ✅ Напечатает запись с order_id и отдельную error «Ignored key without a value.»
- Молча проигнорирует весь вызов

Хвостовой ключ без значения порождает отдельную запись уровня error с полем ignored. Основная запись уйдёт без amount: фильтр по нему её не найдёт. Компилятор здесь бессилен — все аргументы типа any.

### Проверка: выбор интерфейса

**Вопрос:** Где SugaredLogger уместен с чистой совестью?

- В HTTP-обработчике с тысячей запросов в секунду
- В библиотеке, которую подключат чужие проекты
- ✅ В скрипте миграции, который выполняется раз в сутки
- В горячем цикле, который логирует на каждой итерации

Скрипт живёт секунды и пишет десятки записей — цена any незаметна. Библиотекам сахар навязывать не стоит, а на горячих путях аллокации среза интерфейсов складываются в заметную долю нагрузки.

### Что напечатает printf-вызов?

```go
sugar := logger.Sugar()
sugar.Infof("paid %s for %d", "A-1024", 1490)
```

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

- ✅ Запись с msg "paid A-1024 for 1490" — глаголы заменены в сообщении
- Запись с msg "paid %s for %d" — глаголы не заменяются
- Две записи: одна с полями order_id и amount
- Запись без msg — только поля

Семья f работает как fmt.Printf: глаголы заменяются значениями ещё на уровне сообщения, поэтому в msg приезжает готовая фраза. Поля при этом не создаются — для полей нужна w-семья.

### Дойдет ли Debug через сахар?

```go
logger, _ := zap.NewProduction()
defer logger.Sync()
sugar := logger.Sugar()
sugar.Debugw("cache miss", "key", "user:42")
```

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

- JSON-запись с полем key — сахар не смотрит на уровни
- ✅ Ничего не напечатается: production стартует на уровне Info
- Паника — Debug запрещён на сахаре
- Текстовая строка без JSON

SugaredLogger живёт на том же ядре с тем же уровнем: Debug ниже порога Info и отсекается до упаковки аргументов. Обёртка меняет интерфейс, а не настройки — об этом был урок про уровни логов.

### Получите сахар и запишите пару

Соберите скелет: обёртка поверх логгера, сброс буферов и запись с парами ключ-значение.

```go
package main

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

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

        sugar := logger.___()

        defer sugar.___()

        sugar.___("order paid", "order_id", "A-1024", "amount", 1490)
}
```

Подсказка: Обёртка создаётся методом Sugar; буферы сбрасывает Sync; пары ключ-значение пишет семья w — Infow.

Правильные ответы в пропусках: `Sugar`, `Sync`, `Infow`

### Соберите хвост main на сахаре

Расставьте строки: от готового логгера до записей двух семей.

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

1. `logger, _ := zap.NewProduction()`
2. `sugar := logger.Sugar()`
3. `defer sugar.Sync()`
4. `sugar.Infow("order paid", "order_id", "A-1024")`
5. `sugar.Warnw("slow call", "elapsed_ms", 210)`
6. `sugar.Infof("done: %d orders", 2)`

### Карточки: сахарный интерфейс

- **SugaredLogger** — Обёртка над *zap.Logger с упрощённым API: пары ключ-значение вместо типизированных полей. Создаётся logger.Sugar(), возвращается обратно sugar.Desugar().
- **Infow** — Метод w-семьи: сообщение плюс пары ключ-значение. Ключи — строки, значения — any. Аргументы упаковываются в срез интерфейсов — отсюда аллокации.
- **Infof** — printf-семья: форматная строка и аргументы, как у fmt.Printf. Глаголы заменяются в самом сообщении, поля не создаются.
- **Ignored key without a value** — Служебная error-запись при нечётном числе аргументов в w-методе; хвостовой ключ без значения не попадает в основную запись.
- **Desugar** — sugar.Desugar() возвращает исходный *zap.Logger. Дёшево: конвертация в обе стороны уместна на границах горячего кода.
- **sugar.Sync()** — Сброс буферов обёртки — тех же, что у исходного логгера. Ставится defer сразу после создания.

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

Сахар решает вопрос «как удобно писать», следующий — «в каком виде писать»: в уроке про [JSON и console энкодеры](/courses/zap/lessons/06-json-console/) разбираем два формата вывода и то, чем прод отличается от ноутбука разработчика. Если вы дошли до гибридной схемы с Desugar, карта остальных тем — на [странице курса](/courses/zap/): дальше начинается настройка — конфиг, приёмники, ротация.

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

- logger.Sugar() возвращает SugaredLogger — обёртку над тем же ядром: уровни, энкодер и приёмник общие.
- Четыре семьи методов: без суффикса (Print), w (пары ключ-значение), f (printf), ln (Println); Infowf не существует.
- Нечётное число аргументов порождает error-запись «Ignored key without a value.» — поле в основной записи не появляется.
- Цена сахара: упаковка аргументов в []interface{} — аллокации на каждой записи; горячий путь остаётся за типизированным Logger.
- Сахар оправдан в скриптах, обёртках и прототипах; sugar.Desugar() дёшево возвращает типизированный Logger.
- defer sugar.Sync() обязателен: буферы у обёртки те же, что у исходного логгера.

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

Возьмите программу из урока про поля и перепишите её на SugaredLogger: logger.Sugar(), три вызова — Infow с двумя парами, Infof с форматной строкой и Warnw с подмешанным zap.Error. Затем добавьте четвёртый вызов с нечётным числом аргументов и посмотрите на служебную error-запись в терминале. В конце закройте сахар вызовом Desugar и убедитесь, что типизированные методы снова работают — сравните обе формы одной записи глазами.

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

### SugaredLogger медленнее Logger — насколько?

На записи с полями — заметно: обёртка пакует аргументы в срез интерфейсов и выясняет типы в рантайме. По бенчмаркам репозитория отрыв виден и в наносекундах, и в аллокациях. Для скриптов и холодных путей это неважно, для горячего пути берут Logger.

### Можно ли смешивать logger и sugar в одном приложении?

Да, это штатный сценарий: обёртка построена поверх того же ядра, записи идут в один приёмник в одном формате. zap советует конвертировать на границах производительного кода: logger.Sugar() и sugar.Desugar() стоят дёшево.

### Почему компилятор не ловит нечётное число аргументов в Infow?

Метод объявлен как keysAndValues ...interface{}: типы и чётность проверяются только в рантайме. Лечится дисциплиной оформления — пары построчно, ключ и значение рядом — и ревью, где нечётный хвост ищут специально.
