«1. Обзор

Spring Boot предоставляет несколько различных способов проверки состояния и работоспособности запущенного приложения и его компонентов. Среди этих подходов два наиболее заметных API HealthContributor и HealthIndicator.

В этом уроке мы познакомимся с этими API, узнаем, как они работают, и увидим, как мы можем добавлять в них пользовательскую информацию.

2. Зависимости

Источники информации о работоспособности являются частью исполнительного модуля Spring Boot, поэтому нам нужна соответствующая зависимость Maven:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

3. Встроенные индикаторы работоспособности

Из коробки, Spring Boot регистрирует множество HealthIndicators, чтобы сообщать о работоспособности определенного аспекта приложения.

Некоторые из этих индикаторов почти всегда регистрируются, например DiskSpaceHealthIndicator или PingHealthIndicator. Первый сообщает о текущем состоянии диска, а второй служит конечной точкой проверки связи для приложения.

С другой стороны, Spring Boot некоторые индикаторы регистрирует условно. То есть, если некоторые зависимости находятся в пути к классам или выполняются некоторые другие условия, Spring Boot также может зарегистрировать несколько других HealthIndicators. Например, если мы используем реляционные базы данных, Spring Boot регистрирует DataSourceHealthIndicator. Точно так же он зарегистрирует CassandraHealthIndicator, если мы будем использовать Cassandra в качестве хранилища данных.

Чтобы проверить состояние работоспособности приложения Spring Boot, мы можем вызвать конечную точку /actuator/health. Эта конечная точка будет сообщать об агрегированном результате всех зарегистрированных HealthIndicators.

Кроме того, чтобы просмотреть отчет о работоспособности по одному конкретному индикатору, мы можем вызвать конечную точку /actuator/health/{name}. Например, вызов конечной точки /actuator/health/diskSpace вернет отчет о состоянии из DiskSpaceHealthIndicator: состояние компонента или подсистемы. Для этого все, что нам нужно сделать, это зарегистрировать реализацию интерфейса HealthIndicator как компонент Spring.

{
  "status": "UP",
  "details": {
    "total": 499963170816,
    "free": 134414831616,
    "threshold": 10485760,
    "exists": true
  }
}

Например, следующая реализация сообщает об ошибке случайным образом:

Согласно отчету о работоспособности этого индикатора, приложение должно работать только 90% времени. Здесь мы используем Health Builders, чтобы сообщать информацию о здоровье.

Однако в реактивных приложениях мы должны зарегистрировать bean-компонент типа ReactiveHealthIndicator. Метод реактивного здоровья() возвращает Mono\u003cHealth\u003e вместо простого Health. Кроме этого, другие детали одинаковы для обоих типов веб-приложений.

@Component
public class RandomHealthIndicator implements HealthIndicator {

    @Override
    public Health health() {
        double chance = ThreadLocalRandom.current().nextDouble();
        Health.Builder status = Health.up();
        if (chance > 0.9) {
            status = Health.down();
        }
        return status.build();
    }
}

4.1. Название индикатора

Чтобы увидеть отчет для этого конкретного индикатора, мы можем вызвать конечную точку /actuator/health/random. Например, вот как может выглядеть ответ API:

random в URL-адресе /actuator/health/random является идентификатором этого индикатора. Идентификатор конкретной реализации HealthIndicator равен имени компонента без суффикса HealthIndicator. Поскольку имя компонента — randomHealthIdenticator, префикс random будет идентификатором.

С помощью этого алгоритма, если мы изменим имя компонента, скажем, на rand:

{"status": "UP"}

Тогда идентификатор индикатора будет рандом, а не случайным.

4.2. Отключение индикатора

@Component("rand")
public class RandomHealthIndicator implements HealthIndicator {
    // omitted
}

Чтобы отключить определенный индикатор, мы можем установить для свойства конфигурации «management.health.\u003cindicator_identifier\u003e.enabled» значение false. Например, если мы добавим следующее в наш application.properties:

Тогда Spring Boot отключит RandomHealthIndicator. Чтобы активировать это свойство конфигурации, мы также должны добавить аннотацию @ConditionalOnEnabledHealthIndicator к индикатору:

Теперь, если мы вызовем /actuator/health/random, Spring Boot вернет HTTP-ответ 404 Not Found:

management.health.random.enabled=false

Обратите внимание, что отключение встроенных или пользовательских индикаторов аналогично друг другу. Следовательно, мы можем применить ту же конфигурацию и к встроенным индикаторам.

@Component
@ConditionalOnEnabledHealthIndicator("random")
public class RandomHealthIndicator implements HealthIndicator { 
    // omitted
}

4.3. Дополнительные сведения

@SpringBootTest
@AutoConfigureMockMvc
@TestPropertySource(properties = "management.health.random.enabled=false")
class DisabledRandomHealthIndicatorIntegrationTest {

    @Autowired
    private MockMvc mockMvc;

    @Test
    void givenADisabledIndicator_whenSendingRequest_thenReturns404() throws Exception {
        mockMvc.perform(get("/actuator/health/random"))
          .andExpect(status().isNotFound());
    }
}

«В дополнение к отчету о статусе мы можем прикрепить дополнительную информацию о ключе-значении, используя withDetail(key, value):

Здесь мы добавляем две части информации в отчет о статусе. Кроме того, мы можем добиться того же, передав Map\u003cString, Object\u003e методу withDetails(map):

Теперь, если мы вызовем /actuator/health/random, мы можем увидеть что-то вроде: ~ ~~

public Health health() {
    double chance = ThreadLocalRandom.current().nextDouble();
    Health.Builder status = Health.up();
    if (chance > 0.9) {
        status = Health.down();
    }

    return status
      .withDetail("chance", chance)
      .withDetail("strategy", "thread-local")
      .build();
}

Мы также можем проверить это поведение с помощью автоматизированного теста:

Map<String, Object> details = new HashMap<>();
details.put("chance", chance);
details.put("strategy", "thread-local");
        
return status.withDetails(details).build();

Иногда при обмене данными с системным компонентом, таким как база данных или диск, возникает исключение. Мы можем сообщить о таких исключениях, используя метод withException(ex):

{
  "status": "DOWN",
  "details": {
    "chance": 0.9883560157173152,
    "strategy": "thread-local"
  }
}

Мы также можем передать исключение методу down(ex), который мы видели ранее:

mockMvc.perform(get("/actuator/health/random"))
  .andExpect(jsonPath("$.status").exists())
  .andExpect(jsonPath("$.details.strategy").value("thread-local"))
  .andExpect(jsonPath("$.details.chance").exists());

Теперь отчет о работоспособности будет содержать стек трассировка:

if (chance > 0.9) {
    status.withException(new RuntimeException("Bad luck"));
}

4.4. Предоставление сведений

if (chance > 0.9) {
    status = Health.down(new RuntimeException("Bad Luck"));
}

Свойство конфигурации management.endpoint.health.show-details управляет уровнем сведений, которые может предоставлять каждая конечная точка работоспособности.

{
  "status": "DOWN",
  "details": {
    "error": "java.lang.RuntimeException: Bad Luck",
    "chance": 0.9603739107139401,
    "strategy": "thread-local"
  }
}

Например, если мы установим для этого свойства значение always, то Spring Boot всегда будет возвращать поле сведений в отчете о работоспособности, как в приведенном выше примере.

С другой стороны, если мы установим для этого свойства значение never, то Spring Boot всегда будет опускать детали из вывода. Существует также значение when_authorized, которое предоставляет дополнительные сведения только для авторизованных пользователей. Пользователь авторизован тогда и только тогда, когда:

Он аутентифицирован И он обладает ролями, указанными в свойстве конфигурации management.endpoint.health.roles

4.5. Состояние работоспособности

    По умолчанию Spring Boot определяет четыре различных значения состояния работоспособности:

UP — компонент или подсистема работает должным образом. DOWN — компонент не работает OUT_OF_SERVICE — компонент временно не работает UNKNOWN — состояние компонента неизвестно

Эти состояния объявляются как общедоступные статические конечные экземпляры вместо перечислений Java. Таким образом, можно определить наши собственные состояния работоспособности. Для этого мы можем использовать метод состояния (имя):

    Состояние работоспособности влияет на код состояния HTTP конечной точки работоспособности. По умолчанию Spring Boot отображает состояния DOWN и OUT_OF_SERVICE, чтобы выдать код состояния 503. С другой стороны, UP и любые другие несопоставленные статусы будут преобразованы в код состояния 200 OK.

Чтобы настроить это сопоставление, мы можем установить свойство конфигурации management.endpoint.health.status.http-mapping.\u003cstatus\u003e на желаемый номер кода состояния HTTP:

Health.Builder warning = Health.status("WARNING");

Теперь Spring Boot сопоставит DOWN status на 500, OUT_OF_SERVICE на 503 и WARNING на 500. Коды состояния HTTP: ) принимает состояние работоспособности в качестве входных данных и возвращает код состояния HTTP в качестве выходных данных. Кроме того, можно сопоставить пользовательские экземпляры состояния:

По умолчанию Spring Boot регистрирует простую реализацию этого интерфейса с сопоставлениями по умолчанию. SimpleHttpCodeStatusMapper также может считывать сопоставления из файлов конфигурации, как мы видели ранее.

management.endpoint.health.status.http-mapping.down=500
management.endpoint.health.status.http-mapping.out_of_service=503
management.endpoint.health.status.http-mapping.warning=500

5. Медицинская информация и метрики

mockMvc.perform(get("/actuator/health/warning"))
  .andExpect(jsonPath("$.status").value("WARNING"))
  .andExpect(status().isInternalServerError());

Нетривиальные приложения обычно содержат несколько разных компонентов. Например, рассмотрим приложения Spring Boot, использующие Cassandra в качестве своей базы данных, Apache Kafka в качестве платформы публикации и подписки и Hazelcast в качестве сетки данных в памяти.

@Component
public class CustomStatusCodeMapper implements HttpCodeStatusMapper {

    @Override
    public int getStatusCode(Status status) {
        if (status == Status.DOWN) {
            return 500;
        }
        
        if (status == Status.OUT_OF_SERVICE) {
            return 503;
        }
        
        if (status == Status.UNKNOWN) {
            return 500;
        }

        return 200;
    }
}

Мы должны использовать HealthIndicators, чтобы увидеть, может ли приложение взаимодействовать с этими компонентами или нет. Если канал связи выходит из строя или сам компонент не работает или работает медленно, то у нас есть неработоспособный компонент, о котором мы должны знать. Другими словами, эти индикаторы следует использовать для сообщения о работоспособности различных компонентов или подсистем.

if (status.getCode().equals("WARNING")) {
    return 500;
}

Наоборот, нам следует избегать использования HealthIndicators для измерения значений, подсчета событий или измерения длительности. Вот почему у нас есть метрики. Проще говоря, метрики — это лучший инструмент для создания отчетов об использовании ЦП, средней нагрузке, размере кучи, распределении ответов HTTP и т. д.

6. Заключение

«В этом руководстве мы увидели, как добавить больше информации о работоспособности в конечные точки работоспособности привода. Кроме того, мы подробно рассмотрели различные компоненты в API-интерфейсах работоспособности, такие как Health, Status и статус отображения состояния HTTP.

В завершение мы быстро обсудили разницу между информацией о здоровье и показателями, а также узнали, когда использовать каждый из них.

Как обычно, все примеры доступны на GitHub.

«

In this tutorial, we saw how to contribute more health information to actuator health endpoints. Moreover, we had in-depth coverage of different components in the health APIs such as HealthStatus, and the status of HTTP status mapping.

To wrap things up, we had a quick discussion on the difference between health information and metrics and also, learned when to use each of them.

As usual, all the examples are available over on GitHub.