JavaRush /Курсы /Spring Boot /Стратегия логирования cata...

Стратегия логирования catalog-service

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

1. Профильная стратегия логирования

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

Logging-strategy — это договорённость о том, как мы используем логи в проекте: какие уровни считаем нормой, какие пакеты «шумные», где мы хотим подробности, какие поля считаем обязательным контекстом, и как делаем временную диагностику. Это не только YAML. Это ещё и стиль сообщений, и дисциплина вокруг MDC, и правило «не логируем секреты», и понимание, что DEBUG — не стиль жизни.

Почему стратегия должна быть профильной? Потому что у нас реально разные режимы существования сервиса. В local мы хотим быстро понимать, что происходит, и иногда лезть в детали своего кода. В dev обычно важна более стабильная форма (часто structured), потому что логи могут смотреть не только вы, но и коллега/QA/сосед по стенду. В prod (даже если «прод» у нас учебный) важнее предсказуемость, сдержанность и отсутствие лишнего шума: там лишний лог — это не просто строка, а ресурс и риск.

При этом очень важно удержать мысль: профильная стратегия — это не «в каждом профиле новый язык сообщений». Сообщения не должны становиться шизофреническими: сегодня «Catalog started, yay!», завтра «КАТАЛОГ ЗАПУЩЕН!!!», послезавтра «boot.sequence.completed». Меняется детализация и форма, но не смысл и не культура.

2. Профили local/dev/prod

Когда уровни, формат вывода, MDC и временная диагностика уже понятны по отдельности, профили легко начать воспринимать как три разных приложения. И дальше начинается конфигурационный сериал: в одном профиле одно, в другом другое, а в третьем забыли половину настроек. Нам нужно избежать именно этого.

Профили в нашем курсе — это три режима эксплуатации одного и того же catalog-service. Код один и тот же, доменная модель одна, endpoint’ы те же. Профиль меняет только то, что действительно зависит от среды: детализацию логов, формат вывода и жёсткость диагностических настроек.

Удобно держать в голове короткую модель:

Профиль Главная цель логов Как мы читаем логи Что чаще всего меняем
local Быстрая отладка своего кода глазами в консоли DEBUG для своих пакетов, plain text
dev Диагностика «на стенде» и стабильность глазами + «по полям» structured console (ECS/Logstash), точечный DEBUG
prod Предсказуемость и минимальный шум «по полям», осторожно structured console, сдержанные уровни

Эта таблица не означает, что structured logging запрещён в local. Он вполне может быть полезен. Но для обучения и для ежедневной «быстрой работы» plain text иногда банально удобнее: вы видите строку, вы видите смысл, вы не пытаетесь глазами читать JSON как художественную литературу.

И ещё один важный принцип: база должна быть общей, а профиль — это надстройка. То есть application.yaml задаёт «разумный дефолт», а profile-файлы делают аккуратные override’ы, а не переписывают всё заново.

3. Логгеры для уровней

Очень легко превратить настройку уровней логирования в игру «угадай антагониста»: то org.springframework шумит, то tomcat что-то бормочет, то внезапно логгер с загадочным именем o.s.b.w.e.t.TomcatWebServer заговорил. Чтобы не жить в этом хаосе, важно определить: какие логгеры для нас «свои», какие «чужие», а какие «нейтральные соседи по подъезду».

Для catalog-service мы обычно выделяем несколько зон:

Первая зона — наше приложение, то есть базовый пакет com.example.catalogservice (или тот, который вы используете в проекте). Это главный рычаг: если вы включите DEBUG на этот пакет в local, вы получите подробности по своему коду, не превращая логи Spring’а в водопад.

Вторая зона — фичевые пакеты, например com.example.catalogservice.catalog. Это удобно, когда вам нужно усилить диагностику только для каталога, но не для конфигурации, не для actuator-компонент, не для поддержки.

Третья зона — инфраструктурные пакеты внутри нашего кода, например com.example.catalogservice.config и com.example.catalogservice.actuator. Они часто ведут себя по-разному: конфиг обычно интересен только когда ломается (поэтому в prod можно поднять до WARN), actuator тоже не обязан шуметь постоянно.

Четвёртая зона — framework и контейнер: org.springframework, org.apache.catalina и прочие. Мы почти никогда не хотим дебажить Spring «по умолчанию» (особенно новичком), потому что это быстро превращается в «я открыл лог — и потерялся». Это делается точечно и временно, обычно через endpoint loggers, и только когда есть конкретная гипотеза.

Если зафиксировать это как простую схему, получится примерно так:

flowchart TD
  A["root"] --> B["com.example.catalogservice"]
  B --> C["...catalog"]
  B --> D["...config"]
  B --> E["...actuator"]
  A --> F["org.springframework"]
  A --> G["org.apache.* / tomcat"]

Смысл: root задаёт «погоду в доме», пакеты приложения могут быть детальнее, а Spring/Tomcat — обычно сдержаннее, пока мы не пошли в диагностику осознанно.

4. Уровни логирования в YAML и наследование

Настраивать уровни логирования через logging.level.* — это один из самых простых и полезных навыков в Spring Boot. При этом он регулярно ломает мозг новичку, потому что кажется: «я поставил DEBUG, а оно всё равно не печатает» или «я ничего не ставил, а оно внезапно печатает слишком много». Обычно это вопрос наследования уровней и того, где именно вы переопределили настройку.

В Spring Boot (и в большинстве нормальных logging-систем) действует логика: более конкретный логгер может переопределить более общий. Если root=INFO, а com.example.catalogservice=DEBUG, то для классов в этом пакете будет действовать DEBUG. Если вы дополнительно поставите com.example.catalogservice.config=WARN, то конфиг-пакет снова станет тише, даже если весь ваш код в DEBUG. Это очень удобная модель, потому что позволяет держать один общий фон и несколько «окошек» для деталей.

Начнём с базового application.yaml, где мы зададим общий baseline, одинаковый во всех профилях:

# src/main/resources/application.yaml
logging:
  level:
    root: INFO # Общий уровень «по умолчанию» для всего приложения
    com.example.catalogservice: INFO # Базовый уровень для нашего кода (дальше профили делают override)
    org.springframework: WARN # Приглушаем шум Spring, чтобы видеть сигнал от приложения

В этом кусочке есть важная мысль: мы не начинаем жизнь с DEBUG на root, потому что это почти всегда «тревожная кнопка». Мы также слегка приглушаем Spring до WARN, потому что в учебном проекте обычно важнее видеть наши события, а не подробный внутренний разговор фреймворка.

Теперь в local мы хотим добавить «подробности про наш код», не трогая всё остальное:

# src/main/resources/application-local.yaml
logging:
  level:
    com.example.catalogservice: DEBUG

И вот тут новичков часто спасает мысль: нам не нужно копировать root и Spring уровни ещё раз. Мы делаем только override. root: INFO остаётся из базового файла.

Для dev можно сделать более точечно: например, дебажить только каталог, но не всё приложение:

# src/main/resources/application-dev.yaml
logging:
  level:
    com.example.catalogservice: INFO
    com.example.catalogservice.catalog: DEBUG

Да, здесь я специально вернул общий пакет на INFO. Это помогает избежать ситуации «в dev шумит всё подряд», а вам нужно только понять логику фильтрации каталога или загрузки данных.

А в prod обычно делают ещё строже и тише: например, конфиг-пакет держим на WARN, чтобы он не печатал лишнего, но если там что-то реально пошло не так — мы это увидим:

# src/main/resources/application-prod.yaml
logging:
  level:
    com.example.catalogservice: INFO
    com.example.catalogservice.config: WARN

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

5. Формат вывода: plain text и structured

Формат вывода мы уже отделили от самого log.info(...). Здесь осталось зафиксировать policy: в local приоритет у быстрого чтения глазами, а в dev и prod — у стабильных полей и machine-readable формы. Поэтому для catalog-service structured baseline уже выбран — ecs; GELF и Logstash нам полезны как форматы, которые нужно уметь узнавать, но проект между ними не прыгает без причины.

В local plain text часто удобнее просто потому, что вы читаете консоль в IDE и хотите быстро увидеть смысл строки. В dev и prod structured формат даёт более предсказуемую форму логов и делает поведение между средами похожим.

В Boot 4 structured console включается одной настройкой. В dev просто держим уже выбранный baseline:

# src/main/resources/application-dev.yaml
logging:
  structured:
    format:
      console: ecs # Structured-логирование в консоль: поля удобнее парсить и сравнивать между средами

А в prod оставляем тот же ecs, чтобы не менять орфографию логов между средами:

# src/main/resources/application-prod.yaml
logging:
  structured:
    format:
      console: ecs

Почему именно ECS остаётся baseline? Потому что он даёт достаточно «собранную» структуру, и в примерах он хорошо читался. Но если вам ближе Logstash JSON — это тоже нормальный формат для распознавания и чтения чужих логов. Важно не то, что аббревиатура красивая, а то, что вы держите один формат и не прыгаете между ними случайно.

Ключевой принцип стратегии здесь такой: профиль меняет форму вывода, но не меняет прикладной код и смысл сообщений. То есть мы не пишем в коде if (profile == prod) log.info("...") else log.debug("..."). Это почти всегда путь к хаосу. Мы хотим управлять логами через конфигурацию.

6. Минимальная политика MDC для catalog-service

MDC мы уже довели до маленького и достаточного baseline: общий requestId, локальные доменные поля вроде courseSlug или track, и обязательный cleanup через встроенный MDC.putCloseable(...). Для catalog-service этого достаточно: отдельная обёртка вокруг MDC имеет смысл только если команда реально убирает ею повторяющуюся боль, а не просто переименовывает тот же паттерн.

На уровне policy здесь важны три правила:

  • базовые ключи остаются стабильными: requestId, courseSlug, track;
  • в MDC кладём только маленькие и безопасные значения, а не целые объекты и не секреты;
  • cleanup должен быть автоматическим, чтобы контекст не протекал между запросами.

Когда в коде нужно временное поле, нам хватает уже знакомого try-with-resources с MDC.putCloseable(...). Профили могут менять уровни и формат вывода, но не сам набор базовых корреляционных ключей: иначе логи между local, dev и prod перестанут складываться в одну картину.

7. Runtime-диагностика

Когда базовые уровни уже зафиксированы в YAML, временная диагностика должна быть узкой и обратимой. Поэтому /actuator/loggers мы воспринимаем не как вторую систему конфигурации, а как короткий runtime-override: посмотрели effectiveLevel, усилили нужный пакет, собрали сигнал, вернули обратно.

В стратегии мы фиксируем простой ритуал. Сначала читаем состояние логгера, чтобы понять, что реально происходит. Именно тут полезны configuredLevel и effectiveLevel: первый — что явно задано, второй — что реально действует. Потом делаем override только для узкой области. Например, не root, а com.example.catalogservice.catalog. После того как нашли причину — возвращаем configuredLevel в null, чтобы вернуться к наследованию уровней из YAML.

И самое важное: runtime override не должен стать «новым способом конфигурировать прод». Если вы понимаете, что DEBUG нужен постоянно, значит, проблема в базовой стратегии или в том, что вы логируете не те сигналы на INFO/WARN. Постоянные решения должны возвращаться в YAML, иначе вы получаете «конфигурацию, живущую в чьей-то памяти».

В учебном catalog-service мы обычно разрешаем такую диагностику в local/dev, а в prod (даже если он условный) мы держим идею, что всё должно быть максимально предсказуемо и безопасно. При этом сама стратегия не обязана запрещать диагностику; она должна делать её осознанной и не превращать в религию.

8. Профильная стратегия catalog-service

Теперь соберём всё в одну картину так, чтобы вы могли открыть репозиторий catalog-service через неделю и быстро понять: «ага, вот почему в local так, в dev иначе, а в prod всё строго». Важно: мы не добавляем никаких новых технологий и не уходим в logback XML. Всё делаем через YAML и аккуратную дисциплину сообщений/контекста.

Structured baseline у проекта уже фиксирован на ecs, поэтому здесь мы не выбираем формат заново, а просто раскладываем эту policy по профилям.

Начнём с идеи, что application.yaml задаёт общий baseline (уровни + чуть меньше шума Spring), а profile-файлы добавляют только то, что действительно зависит от окружения (verbosity и structured).

Вот хороший «скелет» для базового файла:

# src/main/resources/application.yaml
spring:
  application:
    name: catalog-service # Имя сервиса: будет попадать в логи/метрики там, где это поддерживается

logging:
  level:
    root: INFO
    com.example.catalogservice: INFO
    org.springframework: WARN

Теперь local. Мы хотим быстро дебажить свой код и не страдать от JSON в консоли, если он мешает. Поэтому просто включаем DEBUG на наш пакет:

# src/main/resources/application-local.yaml
logging:
  level:
    com.example.catalogservice: DEBUG

dev. Мы хотим тот же structured console baseline — ecs — и точечный DEBUG только для каталога, а не для всего приложения:

# src/main/resources/application-dev.yaml
logging:
  structured:
    format:
      console: ecs
  level:
    com.example.catalogservice: INFO
    com.example.catalogservice.catalog: DEBUG

prod. Structured console оставляем тем же, но держим уровни сдержанными. Дополнительно можно приглушить config-пакет до WARN, чтобы он не разговаривал слишком много «в обычной жизни»:

# src/main/resources/application-prod.yaml
logging:
  structured:
    format:
      console: ecs
  level:
    com.example.catalogservice: INFO
    com.example.catalogservice.config: WARN

Если смотреть на это как на стратегию, а не как на три несвязанных YAML-файла, то она отвечает на важные вопросы.

Во-первых, «что меняется по профилям?». Меняется structured/plain и точечный уровень детальности (DEBUG в local, DEBUG только для каталога в dev, строгие уровни в prod). Во-вторых, «что не меняется по профилям?». Не меняются имена логгеров, смысл сообщений, и наша дисциплина вокруг MDC (маленький стабильный набор ключей + гарантированная очистка).

И третье: везде читается один и тот же общий принцип — мы не кричим в логах, мы даём сигнал. Для этого даже полезно чуть привести в порядок стиль сообщений, особенно в StartupSummaryRunner. Если ваш startup-лог сейчас выглядит как «Привет мир! Я запустился!», то в local это может быть мило, но в dev/prod это просто неинформативно. Лучше писать коротко и по делу: что стартовало, какие профили, сколько курсов загружено, включён ли maintenance-mode, какой лимит featured. (И да, всё это можно логировать на INFO в одном-двух сообщениях, без спама.)

9. Типичные ошибки при логировании

Ошибка №1: превращать профили в три разных набора правил и стилей сообщений.
Очень соблазнительно начать «подстраивать текст логов под профиль»: в local писать расслабленно, в dev добавлять «технические формулировки», а в prod делать сухие сообщения. На практике это ломает сопоставимость логов и делает дебаг сложнее. Профиль должен менять детальность и форму, а не смысл и язык.

Ошибка №2: включать DEBUG на root как первую реакцию на проблему.
root=DEBUG почти гарантированно превращает логи в водопад. Вы утонете в сообщениях Spring и контейнера быстрее, чем найдёте свою проблему. Гораздо полезнее усиливать только пакет приложения (com.example.catalogservice) или ещё уже — конкретную фичу (...catalog), а иногда и конкретный класс.

Ошибка №3: держать runtime override как «постоянную конфигурацию».
Endpoint loggers — отличный инструмент диагностики, но плохой способ «настроить проект навсегда». Если вы неделю живёте с runtime override, то это значит, что конфигурация перестала быть воспроизводимой: новый человек запустит сервис и получит другое поведение. Постоянные решения возвращаются в YAML.

Ошибка №4: использовать MDC как свалку данных и забывать очищать контекст.
MDC должен быть маленьким и безопасным. Если вы кладёте туда крупные объекты или чувствительные значения, вы либо засоряете логи, либо создаёте риск утечки. А если не очищаете MDC, то контекст начинает «протекать» между разными операциями, и логи становятся обманчивыми: вроде поле courseSlug есть, но к сообщению оно не относится.

Ошибка №5: смешивать plain text и structured logging в одном профиле без правила.
Иногда это происходит случайно: часть логов идёт structured, часть — обычными строками (например, из-за разного запуска или из-за хаотичных настроек). В итоге читать сложно и глазами, и «по полям». Лучше выбрать один основной стиль на профиль. В нашем дне это обычно: local — plain, dev/prod — structured.

1
Задача
Spring Boot, 21 уровень, 4 лекция
Недоступна
Одинаковое сообщение и одинаковый `requestId` в двух профилях
Одинаковое сообщение и одинаковый `requestId` в двух профилях
1
Опрос
Структурные логи, 21 уровень, 4 лекция
Недоступен
Структурные логи
JSON, MDC и уровни
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ