Observability в Java: трейсинг, метрики, логи для профи

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Observability в Java: от «почему упало?» к «я знаю, что будет через минуту»

Представьте: ваше Java-приложение работает в проде. Всё отлично. А потом — бац! — пользователи жалуются, что страница грузится 10 секунд. Вы лезете в сервер, смотрите логи, видите кучу INFO и ни одного ERROR. Что делать? Куда смотреть?

В классическом мониторинге вы проверяете: жив ли сервер, сколько памяти занято, не упал ли процесс. Но это как проверять, горит ли лампочка на приборной панели — полезно, но недостаточно. Observability (наблюдаемость) — это следующий уровень. Это способность понимать внутреннее состояние системы по её внешним выходным данным: логам, метрикам и трейсам.

В этой статье я покажу, как внедрить observability в Java на реальных примерах. Мы будем использовать OpenTelemetry, Micrometer, Spring Boot Actuator и классический SLF4J. Ты узнаешь, как связать логи, метрики и трейсы в единую картину, а не просто «собирать всё в кучу».

Что такое Observability и почему это не просто «сбор логов»?

Observability пришла из теории управления. Система наблюдаема, если по её выходным данным можно восстановить внутреннее состояние. В мире Java это означает три столпа:

  • Логи — структурированные записи событий (что произошло?);
  • Метрики — числовые показатели (сколько? как долго?);
  • Трейсы — цепочки вызовов (какой путь прошёл запрос?).

Без observability вы слепы. Вы знаете, что CPU загружен на 95%, но не знаете, какой именно эндпоинт его грузит. Вы видите ошибку в логе, но не понимаете, какой пользователь её вызвал и какие сервисы участвовали. Observability в Java — это не про «поставить либу», а про культуру разработки.

OpenTelemetry: универсальный инструмент для observability

Раньше был зоопарк: Zipkin для трейсинга, Prometheus для метрик, ELK для логов. Каждый со своим SDK. OpenTelemetry (OTel) пришёл, чтобы объединить их. Это open-source стандарт для сбора телеметрии. Он поддерживает Java нативно и легко встраивается в Spring Boot, Micronaut, Quarkus.

Как это работает? Вы добавляете агент или SDK в ваше Java-приложение, и OTel автоматически перехватывает HTTP-запросы, вызовы баз данных, очереди сообщений. Вам не нужно писать код для каждого инструмента отдельно.

Пример: настройка OpenTelemetry в Spring Boot

Добавьте зависимость в pom.xml:

<dependency>
    <groupId>io.opentelemetry</groupId>
    <artifactId>opentelemetry-api</artifactId>
    <version>1.40.0</version>
</dependency>
<dependency>
    <groupId>io.opentelemetry</groupId>
    <artifactId>opentelemetry-sdk</artifactId>
    <version>1.40.0</version>
</dependency>
<dependency>
    <groupId>io.opentelemetry.instrumentation</groupId>
    <artifactId>opentelemetry-spring-boot-starter</artifactId>
    <version>2.6.0</version>
</dependency>

Теперь в конфигурации (application.yml) укажите экспортёр, например, в Jaeger или OTLP-коллектор:

otel:
  exporter:
    otlp:
      endpoint: http://localhost:4317
  traces:
    exporter: otlp
  metrics:
    exporter: otlp
  logs:
    exporter: otlp

Вуаля! Теперь каждый HTTP-запрос к вашему контроллеру автоматически создаёт трейс. Вы можете увидеть, сколько времени занял каждый вызов базы данных, внешнего API или просто задержка в потоке.

Трейсинг в Java: как найти «бутылочное горлышко»

Трейсинг — это, пожалуй, самая мощная часть observability. Когда ваше приложение состоит из десятков микросервисов, трейсы — единственный способ понять, почему запрос идёт 2 секунды, хотя каждый сервис отвечает за 100 мс.

OpenTelemetry добавляет контекст (traceId, spanId) в каждый запрос. Этот контекст автоматически передаётся через HTTP-заголовки, очереди, gRPC. В результате вы видите полную картину.

Создаём свой первый трейс вручную

Иногда нужно добавить кастомную логику. Например, измерить время выполнения сложного алгоритма внутри метода:

import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.Tracer;
import io.opentelemetry.context.Scope;

public class BusinessService {
    private final Tracer tracer;

    public BusinessService(OpenTelemetry openTelemetry) {
        this.tracer = openTelemetry.getTracer("business-service");
    }

    public void processOrder(String orderId) {
        Span span = tracer.spanBuilder("processOrder")
                .setAttribute("order.id", orderId)
                .startSpan();

        try (Scope scope = span.makeCurrent()) {
            // Здесь ваш бизнес-код
            Thread.sleep(200); // симуляция работы
            span.addEvent("Order processed", 
                io.opentelemetry.api.common.Attributes.of("orderId", orderId));
        } catch (Exception e) {
            span.recordException(e);
            span.setStatus(io.opentelemetry.api.trace.StatusCode.ERROR);
        } finally {
            span.end();
        }
    }
}

Теперь в Jaeger или Grafana Tempo вы увидите span «processOrder» с атрибутами и событиями. Это бесценно для отладки.

Метрики в Java: не просто «счётчики»

Метрики — это агрегированные числовые данные. Они отвечают на вопросы: «Сколько запросов в секунду?», «Какое время ответа 95-го перцентиля?», «Сколько активных сессий?».

Для Java стандартом де-факто является Micrometer. Он встроен в Spring Boot Actuator и поддерживает Prometheus, Graphite, Datadog и десятки других систем.

Пишем кастомную метрику

Допустим, вы хотите считать количество успешных и неуспешных платежей:

import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.MeterRegistry;
import org.springframework.stereotype.Component;

@Component
public class PaymentMetrics {
    private final Counter successCounter;
    private final Counter failureCounter;

    public PaymentMetrics(MeterRegistry registry) {
        this.successCounter = Counter.builder("payments.success")
                .description("Number of successful payments")
                .register(registry);
        this.failureCounter = Counter.builder("payments.failure")
                .description("Number of failed payments")
                .register(registry);
    }

    public void recordSuccess(String paymentMethod) {
        successCounter.increment();
    }

    public void recordFailure(String reason) {
        failureCounter.increment();
    }
}

Используйте эти метрики в контроллере. Prometheus соберёт их, а Grafana пока

Блоги

Рекомендаций по книгам