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

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

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

Урок 2 из 25 ~18 мин +20 XP

Первые строки: slog.Info и вывод по умолчанию

Первый slog пример обычно вызывают до чтения документации — и это правильно: slog.Info мгновенно даёт строку с уровнем, сообщением и полями. Но строка по умолчанию собрана из трёх источников, и её стоит разобрать на части: что даёт пакет log, откуда берётся уровень INFO и почему пары ключ-значение печатаются именно в этом порядке. Разберём вывод до символа, найдём stderr и соберём проверку из терминала.

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

Типичный день: вы пишете первый HTTP-обработчик и хотите видеть в консоли, что происходит внутри. fmt.Println справляется, но уже на второй день строк становится десятки, все одного веса, и отличить обычный ход работы от проблемы можно только прочитав каждую. Здесь появляется slog.Info — вызов, которому передают сообщение и поля отдельно, а он сам собирает запись с уровнем и временем. В этом уроке разберём анатомию вызова и формат вывода по умолчанию: что печатается, куда печатается и почему выглядит именно так. Затем проверим себя из терминала — перенаправлениями и флагами пакета log — и честно поговорим о том, что этот формат рабочий инструмент разработки, а не контракт для продакшена.

Slog Info: анатомия вызова#

Сигнатура выглядит скромно: func Info(msg string, args ...any) — сообщение и любое число аргументов. Аргументы читаются парами: строка-ключ, затем значение, — и так до конца списка. Вызов slog.Info("заказ оформлен", "id", 42, "amount", 990) означает: записать событие уровня Info с сообщением «заказ оформлен» и двумя полями. По той же форме работают slog.Debug, slog.Warn и slog.Error — разница только в уровне, который попадёт в запись. Сам вызов ничего не форматирует: он собирает Record — структуру с временем, уровнем, сообщением и атрибутами — и передаёт её обработчику, а тот решает, во что превратить запись. Эта развязка «вызов — обработчик» и есть главная идея slog: код логирования не меняется, когда вы меняете формат вывода. У обработчика по умолчанию формат неожиданно простой, и его стоит увидеть глазами — этим займёмся прямо сейчас.

Минимальная программа целиком

GO
package main

import (
        "log/slog"
)

func main() {
        slog.Info("сервис запущен", "port", 8080)
}

Четыре значимые строки — и запись уже в консоли. На экране появится строка вида «2025/03/14 09:30:00 INFO сервис запущен port=8080». Дата и время в начале — не заслуга slog: обработчик по умолчанию пишет через пакет log, а тот добавляет свой префикс. INFO — уровень записи, дальше сообщение и пары ключ=значение в порядке вызова. Если запустить программу и не увидеть строку — смотрите stderr: в терминале оба потока смешаны, но конвейеры их различают. Разбор старого пакета log и моста в slog — тема десятого урока.

Теперь разложим строку по частям, потому что в ней смешаны три источника. Префикс с датой и временем даёт пакет log с его стандартными флагами — slog к нему отношения не имеет. Слово INFO ставит сам slog: это уровень записи, напечатанный заглавными буквами. Всё остальное — сообщение и атрибуты — рендерит обработчик: сообщение печатается как есть, каждая пара выглядит как ключ=значение и отделяется пробелом. Значения форматируются так, как fmt печатает любое значение по умолчанию: число остаётся числом без кавычек, строка — строкой, слайс разворачивается в квадратные скобки. Важно, что порядок пар — это порядок аргументов в вызове: обработчик ничего не сортирует и не переименует. Отсюда практическое правило: самые важные для поиска поля ставьте первыми, чтобы в длинной строке их было видно без прокрутки. Полная карта частей — в таблице ниже.

Части строки по умолчанию#

ЧастьКто её поставилПример
Дата и времяпакет log со стандартными флагами2025/03/14 09:30:00
Уровеньslog по вызову Info, Warn, ErrorINFO
Сообщениепервый аргумент вызовасервис запущен
Атрибутыпары ключ-значение из argsport=8080
Потокstderr у дефолтного обработчикавиден в docker logs и journalctl

Slog пример с разными типами значений#

Пары принимают any, поэтому в атрибуты попадают числа, булевы, слайсы и ошибки. Смотри slog пример ниже: значения печатаются так, как fmt рисует их по умолчанию, — и это удобно для человека, но у подхода есть граница, о которой стоит знать заранее. А ещё пары умеют ошибаться: нечётное число аргументов или нестроковый ключ не роняют программу — вместо этого появляется поле с служебным именем !BADKEY. Программа продолжает работать, запись печатается, но поле получает имя-заглушку, и поиск по настоящему ключу в хранилище его больше не найдёт. Это самая тихая из ошибок логирования: компилятор молчит, тесты зелёные, а поля в логе нет. Типизированные конструкторы атрибутов — slog.Int, slog.String и остальные — закрывают часть таких ситуаций на этапе компиляции, их подробно разбирает четвёртый урок.

Типы значений и ловушка !BADKEY

GO
slog.Info("заказ оформлен", "id", 42, "ok", true, "items", []string{"coffee", "tea"})

// нечётное число аргументов: последнему не хватило ключа
slog.Info("платёж завис", "id", 42, "urgent")

Первая запись напечатается так: INFO заказ оформлен id=42 ok=true items=[coffee tea] — слайс развернулся в скобках, булево стало true, всё без кавычек. Вторая запись покажет сюрприз: INFO платёж завис id=42 !BADKEY=urgent. Значение urgent есть, ключа нет — slog честно сообщил об этом служебным именем, а не паникой. На проде такая строка ищется по слову urgent в теле сообщения, а не по полю, и именно поэтому нечётные пары разыскивают код-ревью, а не оставляют на удачу.

Лог из песочницы и из терминала#

Честно про инструменты этого курса: в песочнице сайта пакета log/slog нет — там работает подмножество стандартной библиотеки, о котором скажем ниже в совете. Поэтому slog info вывод проверяется из терминала, и проверка занимает полминуты. Создайте папку, выполните go mod init example/logdemo, положите файл main.go с программой из урока и запустите go run main.go — строка появится сразу. Теперь два эксперимента, которые закрепляют понимание потоков: go run main.go 2> app.log покажет пустой экран и непустой файл, потому что записи ушли в stderr; конвейер go run main.go 2>&1 | grep INFO вернёт строку обратно в смешанный поток и отфильтрует по уровню. На живом сервисе те же проверки делает окружение: docker logs показывает оба потока вместе, а двадцать четвёртый урок разбирает сбор полностью. Механику же форматирования полезно прогнать руками в песочнице — fmt печатает значения так же, как дефолтный обработчик slog.

Шаги проверки из терминала#

  1. go mod init example/logdemo — инициализация модуля, чтобы go run работал без сюрпризов.
  2. Сохраните main.go с программой из урока: импорт log/slog и один вызов slog.Info.
  3. go run main.go — строка с датой, INFO, сообщением и полями появляется на экране.
  4. ./app 2> app.log (после go build) — stdout пуст, все записи в файле ошибок.
  5. go run main.go 2>&1 | grep INFO — проверка фильтра по уровню в конвейере.
  6. log.SetFlags(0) в коде — строка без даты: видно, что префикс давал пакет log.

Спорное мнение: дефолтного формата хватает до продакшена#

Мнение для обсуждения: менять формат вывода в первые недели проекта — преждевременная оптимизация. Дефолтный вид читается глазами, требует нуля строк конфигурации и честно показывает уровни и поля; TextHandler добавит разве что поле time в другом формате. Против этого два честных аргумента: если логи с первого дня уезжают в хранилище, парсить префикс пакета log коллектору неприятно; если команда привыкла к JSON в консоли, возврат к тексту раздражает. Граница, по моему опыту, такая: пока проект читает логи сам в терминале — сидите на дефолте; как только появился первый внешний потребитель записей — ставьте JSONHandler одной строкой и не носите с собой совместимость. Ошибка противоположного рода — настраивать структуру поля заранее, до первого запроса дежурного: реальный список нужных полей появляется после первого же разбора, а не из фантазии на архитектурной доске.

Проверка: поток по умолчанию

Куда обработчик по умолчанию пишет записи slog.Info?

Проверка: нечётное число аргументов

Что напечатает slog.Info("платёж завис", "id", 42, "urgent")?

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

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

func main() {
        log.SetFlags(0)
        slog.Info("готово", "id", 7)
}

Соберите проверку формата

Допишите отключение префикса, вызов записи и значение атрибута.

func main() { log.(0) slog.("готово", "id", ) }

Соберите программу с двумя записями

Расставьте строки в порядке выполнения: пакет, импорт, точка входа, обычная запись, предупреждение, закрывающая скобка.

Строки

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

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

Карточки: slog.Info и вывод по умолчанию

Термин

slog.Info

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

Запись уровня Info: сообщение и пары ключ-значение. По той же форме работают Debug, Warn и Error — разница в уровне.

1 / 6

Первые строки написаны и разобраны до символа: вызов собирает запись, обработчик по умолчанию рисует её через пакет log в stderr, пары ключ-значение сохраняют порядок вызова, а потерянный ключ честно зовётся !BADKEY. Дальше по спирали: в третьем уроке разберём уровни и решим, что писать на Debug, а что на Error; в четвёртом — типизированные конструкторы атрибутов, которые выключают целый класс ошибок с парами; в пятом — настоящую смену формата через TextHandler и JSONHandler. Карта курса — на его странице, домашнее задание ниже закрепляет поток stderr руками.

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

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

Вывод

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

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

  • slog.Info(msg, args...) читает аргументы парами ключ-значение и передаёт запись обработчику, не форматируя ничего сам.
  • Строка по умолчанию: дата и время из пакета log, уровень INFO, сообщение, пары ключ=значение в порядке вызова.
  • Дефолтный обработчик пишет в stderr — ./app > out.log оставляет логи мимо файла, нужен 2>.
  • Значения печатаются как fmt: числа без кавычек, слайсы в скобках; поля не сортируются.
  • Нечётное число аргументов даёт !BADKEY вместо паники: ловить в ревью, а не на проде.
  • log.SetFlags и log.SetOutput настраивают префикс и поток; смену формата делает замена обработчика.

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

Соберите программу из урока и прогоните три проверки: go run main.go с выводом на экран, запуск после go build с перенаправлением 2> app.log и конвейер 2>&1 | grep INFO. Затем добавьте в код log.SetFlags(0) и второй вызов с нечётным числом аргументов — найдите !BADKEY в своей же записи и перепишите вызов правильными парами.

Личные заметкиПусто
Хранятся только в вашем браузере (Slog · Первые строки: slog.Info и вывод по умолчанию)

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

Почему в строке slog дата и время в формате пакета log?

Обработчик по умолчанию пишет через пакет log, а тот добавляет префикс по своим флагам. Смените его через log.SetFlags(0) — префикс исчезнет; поставьте TextHandler или JSONHandler — дата станет полем time в собственном формате slog.

Можно ли смешивать строковые пары и slog.Attr в одном вызове?

Да: аргументы вида ключ, значение и готовые slog.Int("id", 42) соседствуют в одном вызове, порядок сохраняется. Смешение удобно при переходе — но в чистом коде команды обычно договариваются об одном стиле, и Attr выигрывает проверкой типов.

Как напечатать структуру целиком?

Передайте её значением: slog.Info("заказ", "order", order) — дефолтный обработчик отрисует поля через fmt. Для машинного вывода это скверно: структура развалится в текст, а не в поля JSON. Разложите нужные поля атрибутами или примените slog.Any с LogValuer — об этом девятый урок.

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

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

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

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