← назад к разделу

В 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)
три библиотеки — три причины брать slog стандарт Go 1.21 JSONHandler из коробки общий язык экосистемы мост в OTel: otelslog zerolog цепочка Info().Str().Msg() переиспользуемый буфер ноль аллокаций на событие выбор для очень частых логов zap самый настраиваемый sampling, Config, уровни богатые интеграции legacy в старых сервисах новый проект — slog · самые частые логи — zerolog · работающий сервис — не трогаем

Одно и то же событие «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 — не мигрируем без причины.

Что почитать дальше