JavaRush /Курсы /Spring Boot /Structured logging в...

Structured logging в Spring Boot 4

Spring Boot
21 уровень , 0 лекция
Открыта

1. Минусы plain-text логов

На старте проекта обычные текстовые логи кажутся идеальными: открыл консоль — увидел «приложение запустилось», «курсы загрузились», «порт такой-то» — красота. Проблема появляется не потому, что текст плох, а потому что текст слишком «человеческий»: он отлично читается глазами, но плохо поддается стабильному разбору по полям, когда вы хотите искать, фильтровать и сравнивать записи не по догадке, а по фактам.

Представьте типичный жизненный сценарий catalog-service. Вы получили жалобу: «иногда не находится курс по slug». Вы включили больше логов, у вас пошли сообщения из разных пакетов, и вы хотите ответить на простой вопрос: где именно происходила ошибка и с какими параметрами. В plain-text формате вы часто упираетесь в то, что нужные куски данных спрятаны внутри строки сообщения, а формат сообщений может отличаться от класса к классу. В итоге вы либо «на глаз» листаете консоль, либо начинаете играть в регулярные выражения, что особенно весело, когда лог-сообщение слегка поменялось, и ваш «поиск по шаблону» внезапно перестал работать.

Есть и более приземленная проблема: вы очень быстро хотите фильтровать по уровню, по классу-логгеру, по имени сервиса, по потоку, по конкретному «корреляционному» идентификатору операции. В plain-text это возможно, но хрупко. Любая мелкая смена формата приводит к тому, что машина перестает понимать логи так же хорошо, как человек. А мы ведь пишем backend не только для людей. Иногда его читают и машины — просто потому, что машин в продакшене обычно больше.

Чтобы не звучало слишком «enterprise», давайте сформулируем приземлённо: plain-text лог — это когда все важные поля склеены в одну строку. Structured лог — это когда эти поля остаются полями, и вы не должны «выковыривать» уровень или логгер из текста, как изюм из булочки.

Небольшая табличка, чтобы зафиксировать смысл на пальцах:

Задача Plain-text лог Structured лог
Быстро глазами понять, что происходит Удобно Часто неудобно, потому что JSON получается многословным
Найти все записи, где level=ERROR Обычно через grep/шаблон, иногда хрупко По полю level — стабильно
Фильтровать по логгеру/пакету По совпадению строки По отдельному полю
Автоматически анализировать логи программой Сложнее, нужен парсинг текста Проще, потому что это уже JSON или набор полей

2. Structured logging и machine-readable line

Structured logging легко случайно понять неправильно, поэтому начнем с честной и простой модели. У каждого log event есть набор данных: время, уровень, имя логгера, сообщение, иногда исключение, иногда имя потока и другие служебные штуки. В plain-text формате эти данные превращаются в одну строку «для человека». В structured формате они остаются структурой — чаще всего в виде JSON-объекта — и печатаются как одна строка JSON, пригодная для парсинга машиной.

Термин machine-readable log line означает: каждая строка лога — это самостоятельный объект фиксированного формата, который можно разобрать без «угадывания». Если это JSON-лог, то одна строка — один JSON-объект. Да, выглядит как «в консоль вывалили словарь», но именно это и ценность: поля не потерялись и не растворились внутри текста.

Полезно представить себе путь лога так, очень упрощённо, без деталей Logback и без погружения в конфиги:

flowchart LR
  %% Упрощенная схема: важно понять, что structured — это про "как печатаем", а не про "как пишем log.info"
  A["Ваш код: log.info(...)"] --> B["Log event (поля: time, level, logger, message, ...)"]
  B --> C["Encoder/Formatter (plain или structured)"]
  C --> D["Вывод: консоль/файл"]

Обратите внимание на важную мысль: структурированность — это про формат вывода, а не про то, что мы «по-другому пишем log.info(...)». Вы создаете событие логирования точно так же, а вот «принтер» печатает его либо как человеко-читаемую строку, либо как машино-читаемый JSON.

Пример сверх-минимального structured лога, чтобы увидеть идею:

{
  "timestamp": "2026-03-19T10:15:00Z",
  "level": "INFO",
  "logger": "com.example.catalogservice.catalog.service.CourseCatalogService",
  "message": "Featured courses loaded: 4"
}

В реальности Spring Boot поддерживает несколько стандартных структурированных форматов, например ECS, GELF и Logstash, и они отличаются названиями полей и их группировкой. В этой лекции нам важна не таблица всех полей, а понимание ключевого эффекта: уровень, логгер, сообщение и время становятся отдельными полями, а не частью «склеенной» строки.

3. Формат вывода вместо log.info(...)

Самая приятная новость: чтобы перейти на structured logging, вам обычно не нужно переписывать прикладной код. Если вы уже логируете правильно — через SLF4J-логгер, с плейсхолдерами {} — то структурирование меняет в основном то, как лог будет выглядеть снаружи. То есть мы не переписываем сервисы и контроллеры, а аккуратно подкручиваем конфигурацию — как и положено в Spring Boot, где большая часть поведения управляется конфигом.

Давайте возьмем маленький, понятный фрагмент из контекста catalog-service. Например, наш сервис каталога логирует факт загрузки featured-курсов. Код при этом максимально обычный, без всякой structured-магии:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class CourseCatalogService {

    // SLF4J-логгер: код не "знает" про JSON, он просто создает log-события
    private static final Logger log =
            LoggerFactory.getLogger(CourseCatalogService.class);

    public void logFeaturedCount(int count) {
        // Плейсхолдер {} + отдельный аргумент: так параметр передается системе логирования, а не склеивается строкой
        log.info("Featured courses loaded: {}", count); // сообщение останется тем же
    }
}

Здесь важно не пропустить два момента. Во‑первых, мы используем плейсхолдер {} и передаем параметр отдельно, а не склеиваем строку через +. Это и быстрее, и чище, и лучше дружит с логированием как системой. Во‑вторых, никакого JSON в коде нет и не должно быть. Это принципиально: structured logging не должен превращать ваш доменный код в код под лог-формат.

Если вы сейчас включите structured logging, эта же запись log.info(...) поедет наружу уже в structured виде. То есть изменится «фотография», а не «модель». И это очень по-boot’овски: поведение меняется через конфигурацию, а не через переписывание десяти классов.

4. Включаем structured logging в Spring Boot 4

Теперь переходим к самому практическому: как включить structured logging так, чтобы он действительно включился, а не остался «идеей в голове». В Spring Boot 4 structured logging включается конфигурацией. Нас интересуют два свойства: одно отвечает за консоль, другое — за файл. Даже если вы пока пишете только в консоль, важно понимать, что это две независимые цели: консоль обычно читает человек, а файлы и потоки логов часто читает инструмент.

Вот компактная табличка по настройкам, чтобы не путать их:

Куда пишем Свойство Что управляет
В консоль (stdout) logging.structured.format.console Формат вывода в консоль
В файл logging.structured.format.file Формат вывода в файл

Для старта нам достаточно включить structured формат для консоли. Например, в src/main/resources/application.yaml можно добавить:

spring:
  application:
    name: catalog-service # имя сервиса попадет в structured-поля (например, service.name)

logging:
  structured:
    format:
      console: ecs # включаем structured формат для консоли (stdout)

Это минимальное включение structured logging в консоль. Для catalog-service в курсе считаем ecs рабочим baseline structured console: на нём фиксируем дальнейшие примеры и финальную policy проекта. Значение ecs — это название одного из поддерживаемых форматов. GELF и Logstash нам ещё пригодятся, но уже как форматы, которые нужно уметь узнавать и читать, а не как плавающий выбор внутри проекта.

Обратите внимание: мы не пишем logback.xml, не подключаем странные энкодеры вручную и не влезаем в дебри конфигурации. Мы делаем ровно то, что Boot любит больше всего: описываем желаемое поведение декларативно.

Если вам нужно включить structured формат для файла, логика будет аналогичной — меняется только точка назначения:

logging:
  structured:
    format:
      file: ecs # включаем structured формат для файла (обычно его забирает сборщик логов)

Мы сознательно не обсуждаем здесь настройку имени файла, ротацию и архивирование — это отдельная вселенная. Сейчас фиксируем главное: в Boot structured logging включается свойствами, и ваш Java-код при этом не обязан «знать», что логи стали JSON.

Наконец, важная практическая мысль: structured logging — это не «вечно включенная опция». Он должен включаться там, где он дает ценность. Если вы включили JSON-логи в консоль и внезапно почувствовали, что дебажить стало неудобнее, это не «вы неправы», это нормальный эффект. Просто вы включили инструмент не под ту задачу. Консоль обычно для глаз, structured формат — для стабильной обработки.

5. Сравнение: plain vs structured

Чтобы не было ощущения «я что-то включил, но не понимаю, что изменилось», давайте сравним два вывода для одной и той же записи log.info("Featured courses loaded: {}", count) из CourseCatalogService. С точки зрения кода ничего не поменялось. Поменялся только способ печати события логирования.

Пример того, как подобная запись может выглядеть в plain-text формате:

2026-03-19T10:15:00.123 INFO  18432 --- [catalog-service]  c.e.c.c.service.CourseCatalogService : Featured courses loaded: 4

А теперь условный structured вариант:

{"@timestamp":"2026-03-19T10:15:00.123Z","log.level":"INFO","log.logger":"com.example.catalogservice.catalog.service.CourseCatalogService","service.name":"catalog-service","message":"Featured courses loaded: 4"}

Если вы сейчас думаете: «Выглядит хуже, чем раньше», то поздравляю — вы правильно читаете глазами. Structured лог зачастую реально хуже читается глазами, потому что он оптимизирован не под человека, а под стабильные поля. Зато у него появляется суперсила: уровень — это поле log.level, логгер — отдельное поле, имя сервиса — отдельное поле. Машина не должна угадывать, где именно в строке спрятано имя логгера, и не должна надеяться, что вы не поменяете порядок частей.

Иногда полезно показать JSON красиво, хотя в логах он обычно в одну строку. Это не новый формат, а просто визуализация того, что внутри:

{
  "@timestamp": "2026-03-19T10:15:00.123Z",
  "log.level": "INFO",
  "service.name": "catalog-service",
  "message": "Featured courses loaded: 4"
}

И вот здесь хорошо видно, ради чего всё это затевается: если вам нужно быстро отфильтровать все INFO от нашего сервиса, вы не парсите строку. Вы смотрите на поля.

6. Где полезен structured logging

С structured logging легко попасть в ловушку «раз это современно — включу везде». Но логирование — это не показ мод. Оно либо помогает диагностировать систему, либо мешает. Structured формат особенно полезен там, где логи должны быть стабильными входными данными для обработки: для поиска, агрегации, построения статистики, разборов по полям. Это может быть и локально, если вы хотите быстро отбирать записи по полям, и в средах, где логи куда-то собираются и анализируются.

Но есть и обратная сторона. Когда вы запускаете catalog-service в IDE и просто читаете консоль, JSON может превращать простой дебаг в чтение бухгалтерского отчета. Вам хочется увидеть короткую строку, а вы видите объект с полями. Это нормально. Structured logging не отменяет ценность plain-text вывода, он просто решает другую задачу.

Очень важно зафиксировать ещё одну мысль, чтобы не было магических ожиданий: structured logging не делает лог «умным» автоматически. Если вы написали плохое сообщение, оно останется плохим — просто в JSON. Вот пример плохого лога, который формально structured, но практически бесполезен:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class StartupSummaryRunner {
    private static final Logger log = LoggerFactory.getLogger(StartupSummaryRunner.class);

    public void run() {
        // Проблема не в формате, а в смысле: по такому логу непонятно, что именно "Started"
        log.info("Started"); // слишком мало смысла
    }
}

А вот более полезный вариант — тот же уровень, тот же логгер, но сообщение уже несет конкретный сигнал:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class StartupSummaryRunner {
    private static final Logger log = LoggerFactory.getLogger(StartupSummaryRunner.class);

    public void run(int coursesCount) {
        // Тут есть конкретный факт + параметр передается отдельно через {}, что корректно для логирования
        log.info("Catalog started. Courses loaded: {}", coursesCount); // понятный сигнал
    }
}

Structured формат не спасает от дисциплины: не логировать огромные объекты целиком только потому, что JSON якобы «для этого». Если вы начнете печатать весь список курсов в лог, вы получите гигантский шум, и никакая структурированность не вернет из него полезный сигнал.

И да — structured формат тоже не отменяет базовое правило безопасности. Если вы залогировали токен, то вы залогировали токен. Он будет не менее опасен, просто красиво упакован в JSON.

7. Типичные ошибки при включении structured logging

Structured logging — штука полезная, но новичкам он особенно часто «стреляет в ногу» не потому, что технология плохая, а потому что ожидания от неё не совпадают с реальностью. Если заранее знать типовые ошибки, можно сэкономить себе кучу времени, а заодно не превратить логи в странный артефакт, который никто не хочет читать.

Ошибка №1: включить structured logging и ожидать «красивую консоль».
Structured логи часто выглядят в консоли хуже, чем plain-text, и это нормально. Они не обязаны быть удобными для глаз. Перед включением стоит честно ответить: вы сейчас лог читаете глазами или вам важны поля для стабильного разбора. Если цель — быстро дебажить руками, plain-text иногда выигрывает.

Ошибка №2: думать, что JSON-форма заменяет хороший текст сообщения.
Если сообщение «не о чём» или слишком расплывчатое, structured формат лишь упакует эту расплывчатость в JSON. Смысл всё равно остается в сообщении. Loading... и Started почти всегда хуже, чем Catalog started. Courses loaded: 12 — независимо от формата.

Ошибка №3: логировать большие объекты целиком, потому что «раз уже JSON».
Печатать целый список курсов, целую конфигурацию или целый response «для удобства» — очень соблазнительно. Но это рождает шум, увеличивает объем логов и делает реальные проблемы менее заметными. Лучше логировать агрегаты и факты: количество, slug, флаги, важные решения, а не все данные мира.

Ошибка №4: смешать разные стили логов без ясного правила.
Например, часть окружений пишет plain-text, часть — structured, а часть — и то и другое вперемешку, потому что конфигурация разъехалась. В итоге вы не уверены, что именно увидите при запуске. Логи должны быть предсказуемыми: либо здесь plain, либо здесь structured, и это закреплено конфигом, а не памятью разработчика.

Ошибка №5: перепутать structured logging с настройкой уровней.
Structured logging — это не «DEBUG вместо INFO» и не «больше логов». Это форма вывода. Можно писать structured логи на INFO, можно на DEBUG, можно на ERROR. Уровни решают, сколько событий вы пишете; structured формат решает, как эти события выглядят и насколько легко их разбирать по полям.

1
Задача
Spring Boot, 21 уровень, 0 лекция
Недоступна
Structured console logging в ECS
Structured console logging в ECS
1
Задача
Spring Boot, 21 уровень, 0 лекция
Недоступна
Один и тот же `INFO`-лог в plain и structured режиме
Один и тот же `INFO`-лог в plain и structured режиме
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ