В Go три имени встречаются в каждом обсуждении логирования: slog, zerolog и zap. Вопрос «чем они отличаются и что выбрать» — стандартный на собеседовании, и отвечать на него надо не перечислением фич, а пониманием, откуда каждая библиотека выросла и какую цену платит.
Откуда взялись три библиотеки
До Go 1.21 в стандартной библиотеке не было структурированного логирования — только примитивный log со склейкой строк. Эту дыру закрыли две библиотеки: zap от Uber (2016) и zerolog от создателя fasthttp (2017). Обе делали одно и то же — JSON-логи без аллокаций на горячем пути, — и обе стали фактическим стандартом в высоконагруженных сервисах.
В Go 1.21 (август 2023) в стандартную библиотеку вошёл log/slog. Он вобрал идеи обеих: уровни, поля-ключи, JSONHandler, проброс через context.Context. Сторонние библиотеки не исчезли — они остались там, где их главное преимущество (скорость) реально востребовано, а всё новое пишут на slog, потому что он в стандартной библиотеке и его API стал общим языком экосистемы.
Одна строка — в трёх библиотеках
Различия видны на одном и том же действии. Сначала slog — он собирает поля через slog.Any/slog.String и отдаёт их обработчику:
// slog: стандартная библиотека, работает из коробки
logger.Info("order created", slog.String("order_id", id), slog.Int("total", 1250))
zerolog строит событие цепочкой методов и кодирует его в переиспользуемый буфер — его API повторяет стиль fasthttp:
// zerolog: цепочка вызовов, ноль аллокаций на событие
log.Info().Str("order_id", id).Int("total", 1250).Msg("order created")
zap конфигурируется явно и традиционно делится на быстрый путь с sugared-API и медленный с типизированным:
// zap: sugared API для удобства, обычно в паре с конфигом NewProduction
sugar.Infow("order created", "order_id", id, "total", 1250)
Одно и то же событие «order created» пишется по-разному, но выбирают не по API — а по тому, чья цена вам подходит.
Чем они отличаются на практике
Скорость. В классических бенчмарках zerolog и zap быстрее slog: они кодируют событие в переиспользуемый байтовый буфер почти без аллокаций, а slog на каждое поле делает лишнюю работу через интерфейсы. Разница заметна, только когда сервис пишет тысячи строк лога на запрос. Для типового сервиса с десятком строк на запрос она в пределах шума — не гонитесь за наносекундами логера, пока pprof не показал, что логирование — узкое место.
Зависимости и интеграции. slog — ноль зависимостей, его Handler понимают все новые библиотеки, а мост otelslog связывает его с OpenTelemetry. zap и zerolog тянут свой API в каждый пакет, который логирует: переход с одной библиотеки на другую — это рефакторинг всех вызовов, а не замена импорта.
Настройка. zap традиционно самый гибкий: sampling (писать каждую сотую строку на уровне debug), разные уровни для разных пакетов, развитый zap.Config. zerolog проще, но умеет главное: уровни, поля, вывод в нужный writer.
Типовая ошибка — наказывать fmt там, где нужны поля:
// так не надо: строка собирается в куче, а поля не разобрать поиском по логам
log.Printf("order %s created, total=%d, user=%s", id, total, userID)
// так надо: событие с полями сразу кодируется в JSON
logger.Info("order created", slog.String("order_id", id), slog.Int("total", total), slog.String("user_id", userID))
Второй вариант — и он выполняется сам по себе, можно поменять уровень и посмотреть на JSON:
живой пример
package main
import (
"log/slog"
"os"
)
func main() {
logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelInfo}))
logger.Info("order created", slog.String("order_id", "a-1024"), slog.Int("total", 1250))
}
Запустить
Запуск примеров доступен в платном доступе. Там этот же код выполняется прямо в статье: редактор, запуск и проверка рядом с абзацем. Три дня бесплатно →
Что выбрать
Новый сервис — slog. Это стандарт, его достаточно, экосистема движется в его сторону, а лишняя зависимость не нужна. Очень частые логи на горячем пути — zerolog: если pprof показывает, что логирование съедает заметную долю CPU, цепочечный API с нулевыми аллокациями — самый дешёвый выход. Старый сервис на zap или zerolog — не трогать без причины: работающее логирование не ломают ради моды, миграция оправдана только когда реально мешает (например, унифицируют observability-платформу).
Коротко
- До Go 1.21 структурированного логирования в стандарте не было — его делали zap и zerolog.
- slog — стандарт с Go 1.21: ноль зависимостей, JSONHandler, мост
otelslogв OpenTelemetry. - zerolog — цепочка
Info().Str().Msg()и переиспользуемый буфер: минимум аллокаций на событие. - zap — самый настраиваемый: sampling и конфиги уровней; встречается в старых сервисах.
- Выбор: новый проект — slog; горячий путь с тысячами строк в лог — zerolog; legacy — не мигрируем без причины.
Что почитать дальше
- Логирование в Go: slog, JSON и context.Context — как устроен slog изнутри: поля, уровни, requestId через контекст.
- Трассировка в Go — как связать логи со span-ами через OpenTelemetry.
- Метрики в Go — второй столб наблюдаемости: что считать и как не взорвать хранилище cardinality.