Всем привет, меня зовут Сергей Прощаев. Я Tech Lead и руководитель направления Java/Kotlin разработки в FinTech & E-commerce, а ещё преподаю на курсах разработки и архитектуры. В этой статье я покажу, как за полдня настроить observability в Spring Boot 3, чтобы наконец перестать гадать, почему ваш микросервис вдруг начинает тормозить или падать.

Рис. 1. Observability — «глаз» над микросервисами
Рис. 1. Observability — «глаз» над микросервисами

1. Почему вы читаете эту статью (и я её пишу)

Мне как-то попалась статистика: больше половины инцидентов в микросервисных системах приходится на слепую отладку — разработчики и SRE тыкают наугад, смотрят логи одного сервиса, потом другого, строят догадки.

Помню реальный случай из своей практики. Один наш сервис в e-commerce начал периодически отвечать с задержкой 5 секунд. Логи были чисты, метрики CPU — в норме. Команда две недели перебирала гипотезы: то сеть винили, то базу данных. А оказалось — забыли про propagation контекста трейса, и один микросервис делал повторный запрос к внешнему API без таймаута. Если бы у нас тогда был нормальный трейсинг с Zipkin, мы бы нашли причину за час, а не за две недели.

С тех пор я твёрдо уверен: observability — это не фича, а базовая необходимость. Особенно на Spring Boot 3, где инструменты для этого уже встроены нативно.

2. Исходные условия (чтобы вы могли повторить)

Прежде чем переходить к шагам, давайте зафиксируем, с чем мы работаем:

  • Версия Spring Boot: 3.2.x или новее (3.3, 3.4 — подойдёт любая).

  • Java: 17 или 21.

  • Проект: обычное веб-приложение с spring-boot-starter-web и spring-boot-starter-actuator (если нет — добавьте).

  • Инфраструктура: Docker и Docker Compose для поднятия Zipkin, Prometheus, Loki и Grafana. Можно и без Docker, но с ним проще.

  • Цель: получить единый дашборд, где метрики, трейсы и логи связаны одним идентификатором.

Ограничение: гайд не про продовое масштабирование на 10 000+ RPS. Для больших нагрузок понадобится сэмплирование и кластеризация. Но для команд до 20 микросервисов решение отличное.

3. Пошаговый маршрут: от нуля до дашборда

Я разбил процесс на 7 шагов. Каждый шаг заканчивается проверкой — чтобы вы сразу видели, что работает.

Шаг 1. Добавляем зависимости для метрик и трейсинга

В build.gradle (или pom.xml) добавьте:

implementation 'org.springframework.boot:spring-boot-starter-actuator'
implementation 'io.micrometer:micrometer-registry-prometheus'
implementation 'io.micrometer:micrometer-tracing-bridge-brave'
implementation 'io.zipkin.reporter2:zipkin-reporter-brave'

Зачем: actuator даёт эндпоинты, micrometer-registry-prometheus — экспорт метрик в формате Prometheus, tracing-bridge-brave и zipkin-reporter — отправку трейсов в Zipkin.

Проверка: после запуска приложения перейдите на http://localhost:8080/actuator. Если видите список эндпоинтов (health, info, metrics) — шаг выполнен.

Шаг 2. Настраиваем метрики и открываем /prometheus

В application.yml (или application.properties) настройте метрики:

management:
  endpoints:
    web:
      exposure:
        include: health,info,prometheus
  metrics:
    export:
      prometheus:
        enabled: true
    tags:
      application: my-awesome-service

Теперь метрики доступны по адресу /actuator/prometheus. Prometheus будет забирать их оттуда.

Проверка: откройте http://localhost:8080/actuator/prometheus — вы должны увидеть длинный список метрик (jvm_memory_used_bytes, http_server_requests_seconds и т.д.). Если страница не пустая — всё ок.

Шаг 3. Настраиваем трейсинг с Micrometer Tracing (НЕ Sleuth!)

Важное уточнение: в Spring Boot 3.x больше нет Spring Cloud Sleuth! Вместо него используется Micrometer Tracing. Поэтому настройки изменились.

В application.yml добавьте:

management:
  tracing:
    sampling:
      probability: 1.0   # 100% запросов в трейсы (для теста. В проде 0.1)
  zipkin:
    tracing:
      endpoint: http://localhost:9411/api/v2/spans   # Новый endpoint!

Эти настройки говорят Spring Boot 3 отправлять трейсы (спаны) на Zipkin сервер по новому адресу /api/v2/spans.

Заметка про выбор бекенда: для простоты демо мы используем Zipkin. В реальных системах в 2026 году чаще применяют Grafana Tempo или OTel Collector — они лучше интегрируются с современным стеком и масштабируются. Но для локального теста Zipkin остаётся самым лёгким вариантом.

Проверка: Сделайте любой HTTP-запрос к вашему приложению. Затем откройте http://localhost:9411 и нажмите «Run query». Если увидите хотя бы один трейс — трейсинг работает.

Шаг 4. Настраиваем логи с traceId (гарантированно)

Чтобы связать логи с трейсами, добавим traceId в вывод логов.

Важное предупреждение: в ряде конфигураций Micrometer Tracing может не прокидывать traceId в MDC автоматически (это зависит от используемого bridge и логгера). Это один из частых подводных камней при переходе на Spring Boot 3. Чтобы гарантировать появление traceId в логах, добавьте в любой @Configuration класс два бина:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import io.micrometer.tracing.logging.Slf4JEventListener;
import io.micrometer.tracing.brave.bridge.BraveBaggageTagCorrelation;
import java.util.List;

@Configuration
public class TracingConfig {
    
    @Bean
    public Slf4JEventListener slf4JEventListener() {
        return new Slf4JEventListener();
    }
    
    @Bean
    public BraveBaggageTagCorrelation baggageTagCorrelation() {
        return new BraveBaggageTagCorrelation(List.of("traceId", "spanId"));
    }
}

Важно: этот способ MDC-корреляции работает только для bridge Brave (micrometer-tracing-bridge-brave). Если вы переходите на OpenTelemetry (micrometer-tracing-bridge-otel), используйте вместо него OtelBaggageTagCorrelation из пакета io.micrometer.tracing.otel.bridge:

import io.micrometer.tracing.otel.bridge.OtelBaggageTagCorrelation;

@Bean
public OtelBaggageTagCorrelation baggageTagCorrelation() {
    return new OtelBaggageTagCorrelation(List.of("traceId", "spanId"));
}

Без этой замены traceId в логах снова пропадёт.

После этого в logback-spring.xml укажите паттерн:

<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] traceId=%X{traceId} spanId=%X{spanId} %-5level %logger{36} - %msg%n</pattern>

Проверка: запустите приложение, сделайте запрос. Найдите в консоли строку лога — там должно быть traceId=.... Если есть — логи обогащены.

Шаг 5. Поднимаем Prometheus и настраиваем сбор метрик

Есть два способа: упрощённый (через host.docker.internal) и более правильный (через docker-compose). Покажу оба.

Способ 1 (для быстрой проверки на Windows/Mac):

Создайте prometheus.yml:

scrape_configs:
  - job_name: 'spring-boot-app'
    metrics_path: '/actuator/prometheus'
    static_configs:
      - targets: ['host.docker.internal:8080']

На Linux host.docker.internal не работает из коробки. Используйте способ 2.

Способ 2 (рекомендуемый, универсальный):

Запустите и приложение, и Prometheus в одной Docker-сети. Например, docker-compose.yml:

version: '3'
services:
  app:
    build: .
    ports:
      - "8080:8080"
    networks:
      - observability
  
  prometheus:
    image: prom/prometheus
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml
    ports:
      - "9090:9090"
    networks:
      - observability

networks:
  observability:

В prometheus.yml тогда пишем:

scrape_configs:
  - job_name: 'spring-boot-app'
    metrics_path: '/actuator/prometheus'
    static_configs:
      - targets: ['app:8080']   # обращение по имени сервиса

Этот подход ближе к реальному продакшену (Kubernetes, docker swarm).

Проверка: откройте http://localhost:9090, введите в строке запроса up. Если ваш сервис появляется со значением 1 — метрики собираются.

Шаг 6. Настраиваем Loki для логов

Loki — легковесная замена ELK. Запустите её вместе с Promtail (агент для tail логов из файлов) через Docker Compose.

Минимальный конфиг promtail.yml для чтения логов приложения из файла:

scrape_configs:
  - job_name: spring-logs
    static_configs:
      - targets: [localhost]
        labels:
          job: spring
          __path__: /var/log/application/*.log   # путь к вашим логам

Запустите Loki и Promtail (например, через docker-compose), и логи появятся в Grafana. Полный docker-compose.yml с Loki + Grafana можно найти в официальной документации — не привожу его, чтобы не раздувать статью.

Проверка: если настроили Loki, то в Grafana вы сможете выбирать источник данных «Loki» и видеть логи, отфильтрованные по traceId.

Шаг 7. Собираем всё в Grafana

Запустите Grafana:

docker run -d -p 3000:3000 grafana/grafana

Зайдите на http://localhost:3000 (логин admin/admin). Добавьте источники данных:

  • Prometheus — URL http://localhost:9090

  • Zipkin — выберите в списке Zipkin (если его нет, установите плагин zipkin из каталога Grafana Plugins). Не путайте с Jaeger — это разные источники.

  • Loki — если настроили

Затем импортируйте любой готовый дашборд для Spring Boot из каталога Grafana (например, популярный дашборд с ID 4701) или создайте свой. Я обычно добавляю три панели:

  1. График RPS и ошибок (из Prometheus)

  2. Список последних трейсов (из Zipkin)

  3. Логи по выбранному traceId (из Loki)

Финальная проверка: выполните запрос к приложению, который генерирует ошибку или долго выполняется. В Grafana найдите этот трейс, кликните на него — должны подтянуться соответствующие логи. Если да — вы построили полноценную observability.

Ниже — схема того, как всё это взаимодействует (рис. 2).

Рис. 2. Архитектура observability: три источника данных стекаются в Grafana
Рис. 2. Архитектура observability: три источника данных стекаются в Grafana

Эта схема показывает, как три типа данных — метрики, трейсы и логи — покидают микросервис Spring Boot 3 и стекаются в единую точку визуализации — Grafana. Метрики собираются Prometheus (через /actuator/prometheus), трейсы отправляются в Zipkin, а логи через Promtail передаются в Loki. Grafana подключается ко всем трём хранилищам и позволяет анализировать их совместно.

Observability строится не из одного инструмента, а из трёх независимых каналов, каждый из которых решает свою задачу, но в Grafana они объединяются в единую картину!

А вот как выглядит сквозная трассировка одного запроса через сервисы (рис. 3):

Рис. 3. Сквозной трейс: одинаковый traceId позволяет увидеть весь путь запроса
Рис. 3. Сквозной трейс: одинаковый traceId позволяет увидеть весь путь запроса

Диаграмма последовательности показывает один HTTP-запрос GET /order, который проходит через три звена: Client → ServiceA → ServiceB → DB. Ключевой момент: во всех сообщениях передаётся один и тот же traceId = abc, а также добавляются spanId для каждого шага. Это позволяет отследить весь путь запроса от клиента до базы данных и обратно.

Сквозная трассировка связывает разрозненные вызовы микросервисов в единую историю благодаря общему traceId. Даже если запрос прошёл через несколько сервисов, вы можете увидеть его целиком!

4. Где автоматическая propagation НЕ работает (и что делать)

Автоматическая передача контекста трейса между сервисами — это магия, но только для определённых технологий.

Работает из коробки:

  • RestTemplate

  • WebClient

  • FeignClient

Не работает автоматически (нужно донастраивать):

  • Kafka / RabbitMQ (потоковая обработка)

  • gRPC

  • кастомные HTTP-клиенты (Apache HttpClient, OkHttp без интерцепторов)

  • асинхронные вызовы (@Async, пулы потоков)

Если в вашей системе есть Kafka, вы не увидите сквозного трейса, пока не добавите в конфигурацию продюсера и консьюмера специальные интерцепторы для Micrometer Tracing.
Для Kafka это решается подключением модуля micrometer-tracing-integration-kafka, который регистрирует необходимые интерцепторы в продюсере и консьюмере и прокидывает trace-контекст через заголовки сообщений.

5. Добавляем бизнес-метки в трейсы (современный способ)

Просто иметь traceId — это уже много. Но добавить в трейс orderId или userId — следующий уровень.

В новых версиях Micrometer (1.12+) предпочтительнее использовать Observation API вместо прямого обращения к Tracer. Вот как это выглядит:

import io.micrometer.observation.Observation;
import org.springframework.stereotype.Service;

@Service
public class OrderService {
    
    public Order createOrder(OrderRequest request) {
        Observation current = Observation.current();
        if (current != null) {
            current.highCardinalityKeyValue("userId", request.getUserId());
            current.highCardinalityKeyValue("orderId", request.getOrderId());
        }
        // ваша бизнес-логика
    }
}

Откуда вообще берётся Observation?Spring Boot автоматически создаёт Observation для каждого входящего HTTP-запроса, а также для вызовов RestTemplateWebClient, JDBC и многих других интеграций. Это работает, потому что Spring Boot автоматически регистрирует ObservationThreadLocalAccessor, который хранит текущий контекст в ThreadLocal. Внутри такого контекста Observation.current() не null. Однако в кастомных потоках (например, @Async, собственных пулах потоков, обработчиках Kafka) контекста может не быть. В этих случаях нужно создавать наблюдение вручную:

Observation observation = Observation.createNotStarted("myCustomOperation", registry);
try (Observation.Scope scope = observation.openScope()) {
    // ваш код
} finally {
    observation.stop();
}

registry — это бин ObservationRegistry, который уже есть в контексте Spring Boot и может быть внедрён через @Autowired.

Почему это лучше: Observation — это более высокоуровневый API, который абстрагирует и трейсинг, и метрики. Он появился в Micrometer 1.10 и стал стандартом в Spring Boot 3.2+. Использовать Tracer.currentSpan() напрямую — устаревшая практика.

Проверка: Откройте Zipkin, найдите трейс запроса, в котором были эти метки, — они должны отображаться в деталях спана.

6. Коротко про OpenTelemetry — почему это важно в 2026

Вы могли слышать про OpenTelemetry (OTel). Это индустриальный стандарт для сбора метрик, трейсов и логов, который поддерживается всеми крупными игроками.

Spring Boot 3 с Micrometer Tracing прекрасно с ним работает. Чтобы переключиться с Brave на OTel, достаточно заменить две зависимости:

// Было:
// implementation 'io.micrometer:micrometer-tracing-bridge-brave'
// implementation 'io.zipkin.reporter2:zipkin-reporter-brave'

// Стало:
implementation 'io.micrometer:micrometer-tracing-bridge-otel'
implementation 'io.opentelemetry:opentelemetry-exporter-zipkin'

И всё! Остальные настройки не меняются. OTel даёт:

  • Единый стандарт — не привязаны к вендору.

  • Лучшую поддержку в современных системах (например, в Grafana Tempo).

  • Гибкость: можно слать данные в Zipkin, Jaeger, Tempo и другие системы без изменения кода.

В 2026 году начинать новые проекты на OTel — это best practice.

7. Реальная история, которая перевернула мой подход

Помню, как работал над проектом для крупного финтех-клиента. У нас было около 40 микросервисов, и производительность начала резко падать каждую пятницу в 18:00. Пользователи бесились, бизнес терял деньги. SRE полгода боролись с «призраками»: меняли сеть, увеличивали ресурсы, переписывали код.

А потом мы наконец внедрили полноценный трейсинг с OpenTelemetry и подключили его к Grafana Tempo. И сразу увидели: каждую пятницу один из сервисов начинал делать повторные запросы к устаревшему SOAP-сервису с таймаутом в 30 секунд. Виновник — баг в расписании кэширования, которое не сбрасывало токены. Без трейсинга мы бы не связали падение производительности с конкретным вызовом.

Что я вынес из этой истории:

  • Observability должна быть спроектирована до того, как система пойдёт в прод. Не «потом починим», а сразу.

  • Всегда добавляйте в трейсы бизнес-метки (например, userIdorderId), чтобы можно было найти проблему конкретного клиента.

8. Где этот подход может не сработать (честные ограничения)

Observability — не серебряная пуля. Вот когда я бы не стал её разворачивать в полном объёме:

  • Очень маленький проект (2-3 сервиса, 500 запросов в сутки) — проще смотреть логи через kubectl logs. Накладные расходы на инфраструктуру могут превысить пользу.

  • Жёсткие требования к задержкам (real-time, high-frequency trading). Добавление трейсинга может внести микросекундные задержки. Решение — использовать низкоуровневые трейсеры вроде async profiler, но это уже не про Spring.

  • Когда нет культуры SRE — если команда не умеет интерпретировать метрики и трейсы, то даже самая красивая Grafana будет просто картинкой.

Но в 90% случаев — ставьте, не пожалеете!

9. Итог и следующий шаг

Мы прошли 7 шагов: от добавления зависимостей до дашборда в Grafana. Главное, что вы теперь умеете:

  • Настраивать метрики и выставлять их в Prometheus.

  • Добавлять трейсинг через Micrometer Tracing (не путать со Sleuth!).

  • Гарантированно добавлять traceId в логи через MDC-бины (и знаете разницу между Brave и OTel).

  • Понимать, где propagation работает, а где нет (и как починить Kafka).

  • Добавлять бизнес-метки через Observation API.

  • Выбирать между Zipkin (демо) и Tempo/OTel (прод).

  • Сводить всё в единый дашборд.

Мой вам совет: начните с малого — метрики + трейсинг 10% запросов. Это уже даст 80% пользы. Логи централизуйте позже, но с обязательным добавлением traceId.

Если этот гайд был полезен и вы хотите не просто прочитать, а научиться настраивать observability в продакшене, приходите на открытые уроки в OTUS. Разберём реальные дашборды, SLO, алерты и кейсы из практики.

  • 10 июня, 20:00 — «Мониторинг распределенных систем». Записаться
    Поговорим о том, как наблюдать за микросервисами, видеть сбои раньше пользователей и связывать метрики, логи и трейсы в единую картину.

  • 16 июня, 20:00 — «Инцидент-менеджмент в SRE. Как быстро находить, устранять и предотвращать сбои в системе». Записаться
    Разберём, как действовать при инцидентах: быстро локализовать проблему, не теряться в догадках и снижать риск повторных сбоев.

А пока — ставьте трейсинг, включайте метрики и пусть ваши логи всегда содержат traceId. До встречи в эфире.

Не забывайте подписываться на канал OTUS в MAX — там публикуем новые открытые уроки, разборы архитектуры, DevOps, Java, Spring и другие материалы для IT-специалистов.

Комментарии (0)