Перейти к содержимому
ГоМастер

Поиск по урокам

Найдите урок, курс, термин или раздел

Урок 6 из 25 ~16 мин +20 XP

Дефолтный логгер: SetDefault и старый пакет log

Дефолтный логгер slog и вызов slog SetDefault — то место, где сходятся два мира: новый структурный API и старый пакет log, и от этой настройки зависит, куда и в каком виде попадут строки всего сервиса и его библиотек. Разберём маршрут записи по умолчанию, побочные эффекты SetDefault в обе стороны, момент, когда часть строк успевает уйти до настройки, и что остаётся у логгеров, созданных вручную через log.New.

Содержание урока · 9 разделов

У каждого процесса на slog есть настройка, которую вы не писали: дефолтный логгер. Пока в main не появился slog.New, все вызовы slog.Info, slog.Warn и slog.Error уходят в него, и это не заглушка, а маршрут через старый пакет log. Отсюда два практических вопроса, которые обычно всплывают при первой стычке с реальным сервисом. Первый: в каком виде выглядит строка, если ничего не настраивать. Второй: как одним движением переключить на свой формат весь сервис, включая код третьих библиотек, который вы не контролируете. Оба ответа сводятся к паре slog.SetDefault и связи пакетов log и slog: новый структурный API пишет через дефолтный логгер, а после SetDefault и старый log пишет через ваш обработчик. В уроке разберём формат по умолчанию и его уровень, механику SetDefault с побочными эффектами, строки, которые успевают уйти до настройки, и мосты для логгеров из log.New.

Дефолтный логгер go: формат и уровень#

Разбор дефолтного логгер go стоит начать с того, что видит терминал при первом же запуске. Вызов slog.Info("кэш прогрет", "entries", 512) на чистом пакете печатает строку вида 2009/11/10 23:00:00 INFO кэш прогрет entries=512. Разбор по частям: дата и время — это флаги пакета log, через который дефолтный обработчик выводит запись; INFO — уровень; дальше сообщение, а за ним атрибуты в стиле text-формата из пятого урока, ключ равно значение. По умолчанию дефолтный обработчик пропускает записи уровня Info и выше: Debug на нём невидим. Это меняет функция slog.SetLogLoggerLevel, появившаяся в Go 1.22: передайте ей slog.LevelDebug, и отладочные записи начнут печататься без единой строки своего кода. Маршрут у записи простой: slog.Info собирает запись и отдаёт её обработчику по умолчанию, тот форматирует строку и отправляет в пакет log, а пакет пишет её в стандартный поток ошибок. Поэтому дежурному, который ищет строки сервиса до настройки, нужен именно stderr, а не stdout — собственный обработчик в main потом перенесёт вывод куда положено.

slog SetDefault: один вызов на весь сервис#

Функция slog SetDefault принимает логгер и делает его общим для пакета: после вызова все slog.Info, slog.Warn, slog.Error и slog.Debug без приёмника идут через обработчик этого логгера. Но у вызова есть побочный эффект, о котором в документации одна строка, а на практике — половина пользы: SetDefault переподключает и пакет log. Если обработчик нового логгера не внутренний дефолтный, функция ставит пакету log адаптер, который превращает обычные строки log.Printf в структурные записи на уровне Info, и сбрасывает флаги пакета в ноль, чтобы дата и имя файла не задваивались со служебными полями обработчика. Итог: легаси-код и библиотеки, которые пишут через log, попадают в ваш JSON или текстовый формат без правки их исходников. Это самый дешёвый способ собрать разнородные строки сервиса в один поток. Обратная сторона: уровень у таких строк один — Info, а текст сообщения становится полем msg целиком, вместе с уже раскрытыми Printf-ом глаголами. Разбирать такой JSON сложнее, чем записи с настоящими атрибутами, поэтому мост для log-строк — мера на время миграции, а не образ жизни.

SetDefault на старте main

GO
package main

import (
        "log"
        "log/slog"
        "os"
)

func main() {
        logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
        slog.SetDefault(logger)

        slog.Info("порт открыт", "port", 8080)
        log.Println("легаси-строка из старого кода")
}

Две строки настройки — и обе записи приедут в stdout одним JSON-объектом: slog.Info с атрибутом port, log.Println текстом в поле msg на уровне INFO. Даты и префиксы пакета log исчезли: SetDefault обнулил флаги, чтобы служебные поля не дублировались с time и level. Замените NewJSONHandler на NewTextHandler — формат сменится без других правок.

Куда уходит строка при каждом вызове#

ВызовДо SetDefaultПосле SetDefault с JSONHandler
slog.Infostderr, текстовый формат дефолтного обработчикаваш обработчик: JSON в stdout
slog.Debugскрыт: минимум дефолтного обработчика — Infoрешает уровень вашего обработчика
log.Printlnstderr, формат пакета log с датойваш обработчик, уровень Info, текст в msg
свой логгер из log.Newего собственный writerтот же writer: SetDefault его не трогает

Что видят библиотеки и когда они это видят#

Главный вопрос интеграции: что видят библиотеки, чей код вы не правили. Ответ зависит от того, как они логируют. Библиотека, использующая slog.Info напрямую, при каждом вызове берёт текущий дефолтный логгер — значит, после SetDefault её строки окажутся в вашем формате с полной структурой. Библиотека на log.Printf после SetDefault тоже попадает в ваш обработчик, но текстом на уровне Info. А вот библиотека, которая при старте сохранила собственный логгер, созданный через log.New с отдельным writer, продолжает писать в свой поток: SetDefault меняет логгер пакета, а не чужие экземпляры. Второй важный нюанс — момент. SetDefault работает на будущее: всё, что успело напечататься до вызова, уже ушло в старый вывод и не перепрочитается. Импортируемые пакеты выполняют свои init-функции до main, и если библиотека логирует на инициализации, эти строки пройдут мимо настройки. Отсюда правило, которое стоит сделать рефлексом: SetDefault — первая содержательная строка main, до чтения конфига, до запуска фоновых горутин и до конструирования клиентов, которые могут захотеть залогировать подключение.

Уровень дефолтного обработчика

GO
// Go 1.22 и новее: поднять минимум у дефолтного обработчика
slog.SetLogLoggerLevel(slog.LevelDebug)
slog.Debug("соединение переподключено", "attempt", 3)

До этой строки Debug на дефолтном логгере отбрасывался: минимум стоял на Info. SetLogLoggerLevel меняет и порог дефолтного обработчика, и уровень, на котором пакет log пишет после моста. Полезно на отладке локального стенда, когда свой логгер ещё не собран, а Debug-вывод чужой библиотеки нужен целиком.

log и slog после SetDefault: что теряется#

Мост из log в slog удобен, но у него есть цена, и лучше знать её заранее. Теряются: префиксы, которые задавал log.SetPrefix, флаги с датой, файлом и строкой вызова и уровень — его у текстовой строки нет, поэтому всё пишется как Info. Сохраняются: текст сообщения с раскрытыми глаголами форматирования и порядок строк — записи не перемешаются, потому что пакет log сериализует вывод мьютексом, как и до моста. Отдельно про обёртки: если в проекте есть хелпер, который зовёт log.Output(2, msg) ради правильного имени файла в выводе, при переходе на slog вычислять глубину вызова не нужно — Handler сам достанет место вызова через AddSource из двенадцатого урока. А если сервис держит отдельные log.Logger для разных назначений, их мостит slog.NewLogLogger — тема десятого урока; здесь достаточно помнить, что SetDefault перенаправляет только логгер пакета, то есть log.Default().

Правила дефолтного логгера#

  • SetDefault — первая содержательная строка main: всё, что напечатано до неё, уходит в старый вывод.
  • После SetDefault строки пакета log идут через ваш обработчик на уровне Info с обнулёнными флагами.
  • Свои логгеры из log.New SetDefault не трогает — для них есть slog.NewLogLogger из десятого урока.
  • Минимальный уровень дефолтного обработчика меняет slog.SetLogLoggerLevel (Go 1.22).
  • До настройки вывод идёт в стандартный поток ошибок: ищите строки сервиса в stderr, а не в stdout.
  • Мост для log-строк — мера на время миграции: текст в msg проигрывает настоящим атрибутам.

Три вопроса, которые чаще всего всплывают на ревью такой настройки. Первый: «не потеряем ли строки, если SetDefault сработает неудачно?» — вызов не возвращает ошибку и не паникует: он назначает логгер, а сам логгер уже собран и проверен New-фабрикой. Второй: «что будет, если writer закрыт?» — обработчик пишет в переданный writer, и ошибка записи возвращается туда, куда её отдаёт Writer; это повод не строить молчаливые обёртки, а оставить обычный os.Stdout и собрать вывод на стороне окружения. Третий: «зачем мост, если легаси перепишем через месяц?» — потому что месяц переезда на живом сервисе выглядит как недели смешанного вывода, и без моста половина инцидентов разбиралась бы по двум файлам с разными форматами. На такие вопросы отвечает и таблица выше: маршрут каждой строки зависит от того, кем она написана и успел ли сработать SetDefault.

Связи с другими уроками курса: первый вывод slog.Info показывал формат по умолчанию вживую, пятый урок разбирает форматы, которые SetDefault подставляет всему сервису. Куда писать после настройки — вопрос шестнадцатого урока про stdout и файлы, а мосты для старого пакета log разворачивает десятый урок про миграцию. Если структурные логи понадобились, не дожидаясь конца миграции, посмотрите курс по zap — там та же схема полей и уровней с другим обработчиком. Карта курса — на странице курса.

Проверка: мост для пакета log

После slog.SetDefault(slog.New(slog.NewJSONHandler(os.Stdout, nil))) легаси-код вызывает log.Println("готово"). Что окажется в выводе?

Проверка: Debug на дефолтном логгере

Сервис ещё без своего обработчика, а Debug-записи нужны на локальном стенде. Что сделать?

Что напечатает программа?

Прочитайте код и выберите, что он выведет:

func main() {
        slog.Info("кэш прогрет", "entries", 512)
}

Соберите настройку на старте

Допишите: назначить логгер дефолтным, сделать структурную запись и печать из старого пакета.

func main() { logger := slog.New(slog.NewJSONHandler(os.Stdout, nil)) slog.(logger) slog.("порт открыт", "port", 8080) log.("легаси-строка из старого кода") }

Порядок настройки в main

Расставьте строки main по порядку выполнения: открыть функцию, создать логгер, назначить его дефолтным, структурная запись, легаси-запись, закрыть функцию.

Строки

Ваша программа

Нажимайте на строки слева, чтобы собрать программу

Карточки: дефолтный логгер

Термин

slog.SetDefault

нажмите, чтобы перевернуть
Определение

Назначает логгер общим для пакета: slog.Info без приёмника и строки пакета log после вызова идут через его обработчик.

1 / 5

Песочница урока

Редактируйте код и запускайте его прямо здесь — интерпретатор работает в браузере, ничего устанавливать не нужно. За каждый запуск — +2 XP.

Вывод

Запустите код, чтобы увидеть вывод.

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

  • Без настройки slog пишет через дефолтный обработчик: текстовый формат с датой, минимум Info, вывод в stderr.
  • slog.SetDefault делает логгер общим и перенаправляет пакет log в его обработчик, сбрасывая флаги.
  • log-строки после моста пишутся на уровне Info текстом в msg — это мера на время миграции.
  • Свои логгеры из log.New SetDefault не трогает — для них нужен slog.NewLogLogger.
  • Минимальный уровень дефолтного обработчика поднимает slog.SetLogLoggerLevel (Go 1.22).
  • SetDefault ставят первой содержательной строкой main: init-код и ранние горутины успевают написать до неё.

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

Запустите программу урока локально, затем поменяйте NewJSONHandler на NewTextHandler и убедитесь, что и структурная строка, и легаси-строка сменили формат без других правок. После этого вызовите SetLogLoggerLevel с LevelDebug, добавьте в main пару slog.Debug и проверьте, какие из записей видны, а какие остались скрытыми.

Личные заметкиПусто
Хранятся только в вашем браузере (Slog · Дефолтный логгер: SetDefault и старый пакет log)

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

Нужно ли вызывать SetDefault, если slog используется только в своём коде?

Тогда логгер можно держать в main и передавать по зависимостям — это явнее глобального состояния. SetDefault нужен, когда хочется повлиять на библиотеки: и на те, что зовут slog.Info напрямую, и на те, что пишут через пакет log.

Что произойдёт, если вызвать SetDefault дважды?

Действовать будет последний вызов: и slog.Info без приёмника, и пакет log пойдут через обработчик нового логгера. В тестах этим пользуются — подменяют дефолтный логгер и восстанавливают его через defer, в проде настройку делают один раз в main.

Куда попадают записи после SetDefault с JSONHandler(os.Stdout)?

В стандартный поток вывода процесса, а дальше — куда он ведёт: в Docker-журнал, коллектор или терминал. Сам пакет log до моста писал в stderr, поэтому на переходном этапе строки могут оказаться в двух потоках — ещё один аргумент настроить всё сразу.

Термины урока

Ключевые понятия из этого урока — короткие определения в глоссарии. Наведите на термин, чтобы вспомнить, откройте карточку для подробностей и ссылки на практику.

Следующая ступень учебного пути

pgx25 уроков~12 чпрактика25 уроков уже доступны

PostgreSQL в Go: драйвер pgx с нуля до продакшена

Курс pgx для Go: подключение к PostgreSQL, pgxpool, сканирование структур, транзакции, Batch, COPY, JSONB, LISTEN/NOTIFY и тесты. 25 уроков.

  • Подключаться к PostgreSQL через pgx и pgxpool
  • Сканировать строки в структуры и справляться с NULL
  • Работать с типами Postgres: UUID, JSONB, массивы, timestamptz
  • Проводить транзакции и массовые вставки через Batch и COPY