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 пока