Изучение и анализ Spring Boot приложения с помощью Actuator и jq

Kate

Administrator
Команда форума
Spring Boot Actuator помогает нам отслеживать и управлять нашими приложениями в производственной среде. Он предоставляет конечные точки, которые публикуются показатели работоспособности и другая информация о запущенном приложении. Мы также можем использовать его для изменения уровня ведения журнала приложения, создания дампа потока и т. д. - короче говоря, возможностей, которые упрощают работу в производственной среде.
Хотя Actuator в основном используется в производственной среде, он также может помочь нам во время разработки и сопровождения. Мы можем использовать его для изучения и анализа нового приложения Spring Boot.
В этой статье мы увидим, как использовать некоторые из его конечных точек для изучения нового приложения, с которым мы не знакомы. Мы будем работать в командной строке и использовать curlи jq, с изящным и мощным JSON процессором командной строки.

Пример кода​

Эта статья сопровождается примером рабочего кода на GitHub.

Зачем использовать Actuator для анализа и изучения приложения?​

Представим, что мы впервые работаем над новой кодовой базой на основе Spring Boot. Мы, вероятно, изучим структуру папок, посмотрим на имена папок, проверим имена пакетов и имена классов, чтобы попытаться построить модель приложения в нашем уме. Мы могли бы сгенерировать некоторые UML диаграммы, чтобы помочь определить зависимости между модулями, пакетами, классами и т. д.
Хотя это важные шаги, они дают нам только статичное представление о приложении. Мы не можем получить полную картину, не понимая, что происходит во время выполнения. Например, что представляют собой все создаваемые Spring Beans? Какие конечные точки API доступны? Каковы все фильтры, через которые проходит запрос?
Построение этой мысленной модели формы выполнения приложения очень полезно. Она позволит глубже погрузиться в чтение и более эффективно понять важные части кода.

Общий обзор Spring Boot Actuator​

Начнем с краткого обзора по Spring Boot Actuator.
На верхнем уровне, когда мы работаем с Actuator, мы делаем следующие шаги:
  1. Добавляем Actuator как зависимость к нашему проекту
  2. Включаем и открываем конечные точки
  3. Защищаем и настраиваем конечные точки
Давайте кратко рассмотрим каждый из этих шагов.

Шаг 1. Добавьте Actuator зависимость​

Добавление Actuator в наш проект похоже на добавление любой другой зависимости. Вот фрагмент для Maven pom.xml:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
Если бы мы использовали Gradle, мы бы добавили в файл build.gradleследующий фрагмент :
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-actuator'
}
Простое добавление указанной выше зависимости в приложение Spring Boot предоставляет некоторые готовые конечные точки, такие как /actuator/health, которые могут использоваться, например, для поверхностной проверки работоспособности с помощью балансировщика нагрузки.
$ curl http://localhost:8080/actuator/health
{"status":"UP"}
Мы можем перейти на конечную точку /actuator, чтобы просмотреть другие конечные точки, доступные по умолчанию. Конечная точка /actuatorоткрывает «обзорную страницу» со всеми доступными конечными точками:
$ curl http://localhost:8080/actuator
{"_links":{"self":{"href":"http://localhost:8080/actuator","templated":false},"health":{"href":"http://localhost:8080/actuator/health","templated":false},"health-path":{"href":"http://localhost:8080/actuator/health/{*path}","templated":true},"info":{"href":"http://localhost:8080/actuator/info","templated":false}}}

Шаг 2. Включите и откройте конечные точки​

Конечные точки идентифицируются идентификаторами, такими как health, info, metricsи так далее. Включение и открытие конечной точки делает ее доступной для использования по пути /actuator URL-адреса приложения, например http://your-service.com/actuator/health, http://your-service.com/actuator/metricsи т. д.
Большинство конечных точек, за исключением shutdown, включены по умолчанию. Мы можем отключить конечную точку, установив для свойства management.endpoint.<id>.enabledзначение falseв application.propertiesфайле. Например, вот как мы отключим metricsконечную точку:
management.endpoint.metrics.enabled=false
Доступ к отключенной конечной точке возвращает ошибку HTTP 404:
$ curl http://localhost:8080/actuator/metrics
{"timestamp":"2021-04-24T12:55:40.688+00:00","status":404,"error":"Not Found","message":"","path":"/actuator/metrics"}

Мы можем предоставить доступ к конечным точкам через HTTP и / или JMX. Хотя обычно используется HTTP, для некоторых приложений может быть предпочтительнее JMX.
Мы можем раскрыть конечные точки, установив management.endpoints.[web|jmx].exposure.includeдля списка идентификаторов конечных точек, которые мы хотим раскрыть. Вот как мы можем открыть metricsконечную точку, например:
management.endpoints.web.exposure.include=metrics
Чтобы конечная точка была доступна, она должна быть включена и доступна.

Шаг 3. Защитите и настройте конечные точки​

Поскольку многие из конечных точек содержат конфиденциальную информацию, важно защитить их. Конечные точки должны быть доступны только авторизованным пользователям, которые управляют нашим приложением и работают с ним в производственной среде, а не обычным пользователям нашего приложения. Представьте себе катастрофические последствия обычного пользователя приложения, имеющий доступ к конечным точкам heapdumpили shutdown!
В этой статье мы не будем подробно рассматривать вопросы защиты конечных точек, поскольку мы в основном заинтересованы в использовании Spring Actuator для изучения приложения в нашей локальной среде разработки. Вы можете найти подробности в документации здесь.

Краткое введение в jq​

jqпредставляет собой JSON-процессор командной строки. Он работает как фильтр, принимая входные данные и производя выходные данные. Доступно множество встроенных фильтров, операторов и функций. Мы можем комбинировать фильтры, направлять выходной сигнал одного фильтра в другой и т. д.
Предположим, у нас в файле есть следующий JSON sample.json:
{
"students": [
{
"name": "John",
"age": 10,
"grade": 3,
"subjects": ["math", "english"]
},
{
"name": "Jack",
"age": 10,
"grade": 3,
"subjects": ["math", "social science", "painting"]
},
{
"name": "James",
"age": 11,
"grade": 5,
"subjects": ["math", "environmental science", "english"]
},
.... other student objects omitted ...
]
}
Это объект, содержащий массив объектов «student» с некоторыми деталями для каждого ученика.
Давайте рассмотрим несколько примеров обработки и преобразования этого JSON с помощью jq.
$ cat sample.json | jq '.students[] | .name'
"John"
"Jack"
"James"
Рассмотрим jqкоманду, чтобы понять, что происходит:
ВыражениеЭффект
.students[]перебирать массив students
|вывод каждого studentдля следующего фильтра
.nameвыборка атрибута nameиз объекта student
Теперь давайте составим список студентов, изучающих такие предметы, как «экология», «обществоведение» и т. д.:
$ cat sample.json | jq '.students[] | select(.subjects[] | contains("science"))'
{
"name": "Jack",
"age": 10,
"grade": 3,
"subjects": [
"math",
"social science",
"painting"
]
}
{
"name": "James",
"age": 11,
"grade": 5,
"subjects": [
"math",
"environmental science",
"english"
]
}

Рассмотрим команду еще раз:
ВыражениеЭффект
.students[]перебирать массив students
|вывод каждого studentдля следующего фильтра
select(.subjects[] | contains("science"))выберите студента, если его массив subjects содержит элемент со строкой «наука»
С одним небольшим изменением мы можем снова собрать эти элементы в массив:
$ cat sample.json | jq '[.students[] | select(.subjects[] | contains("science"))]'
[
{
"name": "Jack",
"age": 10,
"grade": 3,
"subjects": [
"math",
"social science",
"painting"
]
},
{
"name": "James",
"age": 11,
"grade": 5,
"subjects": [
"math",
"environmental science",
"english"
]
}
]

Все, что нам нужно было сделать, это заключить все выражение в квадратные скобки.
Мы можем использовать jqкак для фильтрации, так и для изменения формы JSON:
$ cat sample.json | jq '[.students[] | {"studentName": .name, "favoriteSubject": .subjects[0]}]'
[
{
"studentName": "John",
"favoriteSubject": "math"
},
{
"studentName": "Jack",
"favoriteSubject": "math"
},
{
"studentName": "James",
"favoriteSubject": "math"
}
]

Мы, выполнив итерацию по массиву students, создали новый объект, содержащий свойство studentNameи favoriteSubjectсо значениями устанавленными из атрибута name и первого subjectиз исходного объекта student. В результате мы собрали все новые объекты в массив.
Мы можем многое сделать с помощью нескольких команд jq. Поскольку большинство API-интерфейсов, с которыми мы обычно работаем, используют JSON, это отличный инструмент в нашем арсенале инструментов.
Ознакомьтесь с учебником и руководством из официальной документации. jqplay - отличный ресурс для экспериментов и построения наших jqвыражений.

Изучение приложения Spring Boot​

В оставшейся части этой статьи мы будем использовать Actuator для изучения работающего приложения Spring Boot. Само приложение представляет собой очень упрощенный пример приложения для обработки заказов электронной коммерции. В нем есть только скелетный код, необходимый для иллюстрации идей.
Хотя доступно множество конечных точек Actuator, мы сосредоточимся только на тех, которые помогают нам понять структуру приложения во время выполнения.
се конечные точки, которые мы увидим, включены по умолчанию. Давйте откроем их:
management.endpoints.web.exposure.include=mappings,beans,startup,env,scheduledtasks,caches,metrics

Использование конечной точки отображения​

Проверка доступных API-интерфейсов обычно является хорошим местом для начала знакомства с сервисом. Конечная точка отображения предоставляет все маршруты и обработчики, а также дополнительные сведения.
Давайте перейдем на конечную точку с помощью команды curl и направим ответ в jq, чтобы красиво его распечатать:
$ curl http://localhost:8080/actuator/mappings | jq
Вот ответ:
{
"contexts": {
"application": {
"mappings": {
"dispatcherServlets": {
"dispatcherServlet": [
{
"handler": "Actuator web endpoint 'metrics'",
"predicate": "{GET [/actuator/metrics], produces [application/vnd.spring-boot.actuator.v3+json || application/vnd.spring-boot.actuator.v2+json || application/json]}",
"details": {
"handlerMethod": {
"className": "org.springframework.boot.actuate.endpoint.web.servlet.AbstractWebMvcEndpointHandlerMapping.OperationHandler",
"name": "handle",
"descriptor": "(Ljavax/servlet/http/HttpServletRequest;Ljava/util/Map;)Ljava/lang/Object;"
},
"requestMappingConditions": {
... properties omitted ...
],
"params": [],
"patterns": [
"/actuator/metrics"
],
"produces": [
... properties omitted ...
]
}
}
},
... 20+ more handlers omitted ...
]
},
"servletFilters": [
{
"servletNameMappings": [],
"urlPatternMappings": [
"/*"
],
"name": "webMvcMetricsFilter",
"className": "org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter"
},
... other filters omitted ...
],
"servlets": [
{
"mappings": [
"/"
],
"name": "dispatcherServlet",
"className": "org.springframework.web.servlet.DispatcherServlet"
}
]
},
"parentId": null
}
}
}
По-прежнему может быть немного утомительным изучение этого JSON ответа - в нем много деталей обо всех обработчиках запросов, сервлетах и фильтрах сервлетов.
jq для дальнейшей фильтрации этой информации. Поскольку мы знаем имена пакетов из нашей службы, jq будет выбирать только те обработчики, которые содержат имя нашего пакета io.reflectoring.springboot.actuator:
Давайте воспользуемся jq для дальнейшей фильтрации этой информации. Поскольку мы знаем имена пакетов из нашей службы, jq select будет выбирать только те обработчики, которые содержат имя нашего пакета io.reflectoring.springboot.actuator:
$ curl http://localhost:8080/actuator/mappings | jq '.contexts.application.mappings.dispatcherServlets.dispatcherServlet[] | select(.handler | contains("io.reflectoring.springboot.actuator"))'
{
"handler": "io.reflectoring.springboot.actuator.controllers.PaymentController#processPayments(String, PaymentRequest)",
"predicate": "{POST [/{orderId}/payment]}",
"details": {
"handlerMethod": {
"className": "io.reflectoring.springboot.actuator.controllers.PaymentController",
"name": "processPayments",
"descriptor": "(Ljava/lang/String;Lio/reflectoring/springboot/actuator/model/PaymentRequest;)Lio/reflectoring/springboot/actuator/model/PaymentResponse;"
},
"requestMappingConditions": {
"consumes": [],
"headers": [],
"methods": [
"POST"
],
"params": [],
"patterns": [
"/{orderId}/payment"
],
"produces": []
}
}
}
{
"handler": "io.reflectoring.springboot.actuator.controllers.OrderController#getOrders(String)",
"predicate": "{GET [/{customerId}/orders]}",
"details": {
"handlerMethod": {
"className": "io.reflectoring.springboot.actuator.controllers.OrderController",
"name": "getOrders",
"descriptor": "(Ljava/lang/String;)Ljava/util/List;"
},
"requestMappingConditions": {
"consumes": [],
"headers": [],
"methods": [
"GET"
],
"params": [],
"patterns": [
"/{customerId}/orders"
],
"produces": []
}
}
}
{
"handler": "io.reflectoring.springboot.actuator.controllers.OrderController#placeOrder(String, Order)",
"predicate": "{POST [/{customerId}/orders]}",
"details": {
"handlerMethod": {
"className": "io.reflectoring.springboot.actuator.controllers.OrderController",
"name": "placeOrder",
"descriptor": "(Ljava/lang/String;Lio/reflectoring/springboot/actuator/model/Order;)Lio/reflectoring/springboot/actuator/model/OrderCreatedResponse;"
},
"requestMappingConditions": {
"consumes": [],
"headers": [],
"methods": [
"POST"
],
"params": [],
"patterns": [
"/{customerId}/orders"
],
"produces": []
}
}
}

Мы можем видеть доступные API-интерфейсы и подробную информацию об HTTP методе, пути запроса и т. д. В сложном реальном приложении это дало бы консолидированное представление обо всех API-интерфейсах и их деталях, независимо от того, как пакеты были организованы в несколько -модуль кодовая база. Это полезный метод для начала изучения приложения, особенно при работе с многомодульной устаревшей кодовой базой, где даже документация Swagger может быть недоступна.
Точно так же мы можем проверить, через какие фильтры проходят наши запросы, прежде чем они достигнут контроллеров:
$ curl http://localhost:8080/actuator/mappings | jq '.contexts.application.mappings.servletFilters'
[
{
"servletNameMappings": [],
"urlPatternMappings": [
"/*"
],
"name": "webMvcMetricsFilter",
"className": "org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter"
},
... other filters omitted ...
]

Использование конечной точки beans​

Теперь давайте посмотрим на список созданных bean-компонентов:
$ curl http://localhost:8080/actuator/beans | jq
{
"contexts": {
"application": {
"beans": {
"endpointCachingOperationInvokerAdvisor": {
"aliases": [],
"scope": "singleton",
"type": "org.springframework.boot.actuate.endpoint.invoker.cache.CachingOperationInvokerAdvisor",
"resource": "class path resource [org/springframework/boot/actuate/autoconfigure/endpoint/EndpointAutoConfiguration.class]",
"dependencies": [
"org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration",
"environment"
]
},
.... other beans omitted ...
}
}
}


Это дает общее представление обо всех компонентах в ApplicationContext. Промотр его дает нам некоторое представление о структуре приложения во время выполнения - какие внутренние bean-компоненты Spring, каковы bean-компоненты приложения, каковы их области действия, каковы зависимости каждого bean-компонента и т. д.
Опять же, мы можем использовать jqдля фильтрации ответов и сосредоточиться на тех частях ответа, которые нам интересны:
$ curl http://localhost:8080/actuator/beans | jq '.contexts.application.beans | with_entries(select(.value.type | contains("io.reflectoring.springboot.actuator")))'
{
"orderController": {
"aliases": [],
"scope": "singleton",
"type": "io.reflectoring.springboot.actuator.controllers.OrderController",
"resource": "file [/code-examples/spring-boot/spring-boot-actuator/target/classes/io/reflectoring/springboot/actuator/controllers/OrderController.class]",
"dependencies": [
"orderService",
"simpleMeterRegistry"
]
},
"orderService": {
"aliases": [],
"scope": "singleton",
"type": "io.reflectoring.springboot.actuator.services.OrderService",
"resource": "file [/code-examples/spring-boot/spring-boot-actuator/target/classes/io/reflectoring/springboot/actuator/services/OrderService.class]",
"dependencies": [
"orderRepository"
]
},
... other beans omitted ...
"cleanUpAbandonedBaskets": {
"aliases": [],
"scope": "singleton",
"type": "io.reflectoring.springboot.actuator.services.tasks.CleanUpAbandonedBaskets",
"resource": "file [/code-examples/spring-boot/spring-boot-actuator/target/classes/io/reflectoring/springboot/actuator/services/tasks/CleanUpAbandonedBaskets.class]",
"dependencies": []
}
}

Это дает представление обо всех компонентах приложения и их зависимостях с высоты птичьего полета.
Чем это полезно? Мы можем получить дополнительную информацию из этого типа представления: например, если мы видим некоторую зависимость, повторяющуюся в нескольких bean-компонентах, вероятно, в нем инкапсулированы важные функции, которые влияют на несколько потоков. Мы могли бы отметить этот класс как важный, который мы хотели бы понять, когда углубимся в код. Или, возможно, этот bean-компонент является God object, который требует некоторого рефакторинга, когда мы поймем кодовую базу.

Использование конечной точки startup​

В отличие от других конечных точек, которые мы видели, для настройки конечной точки startup требуются некоторые дополнительные действия. Мы должны обеспечить реализацию ApplicationStartupдля нашего приложения:
SpringApplication app = new SpringApplication(DemoApplication.class);
app.setApplicationStartup(new BufferingApplicationStartup(2048));
app.run(args);
Здесь мы установили для нашего приложения ApplicationStartupзначение a, BufferingApplicationStartupкоторое является структурой в памяти, которая фиксирует события в сложном процессе запуска Spring. Внутренний буфер будет иметь указанную нами емкость - 2048.
Теперь перейдем конечной точке startup. В отличие от других конечных точек startupподдерживает POSTметод:
$ curl -XPOST 'http://localhost:8080/actuator/startup' | jq
{
"springBootVersion": "2.4.4",
"timeline": {
"startTime": "2021-04-24T12:58:06.947320Z",
"events": [
{
"startupStep": {
"name": "spring.boot.application.starting",
"id": 1,
"parentId": 0,
"tags": [
{
"key": "mainApplicationClass",
"value": "io.reflectoring.springboot.actuator.DemoApplication"
}
]
},
"startTime": "2021-04-24T12:58:06.956665337Z",
"endTime": "2021-04-24T12:58:06.998894390Z",
"duration": "PT0.042229053S"
},
{
"startupStep": {
"name": "spring.boot.application.environment-prepared",
"id": 2,
"parentId": 0,
"tags": []
},
"startTime": "2021-04-24T12:58:07.114646769Z",
"endTime": "2021-04-24T12:58:07.324207009Z",
"duration": "PT0.20956024S"
},
.... other steps omitted ....
{
"startupStep": {
"name": "spring.boot.application.started",
"id": 277,
"parentId": 0,
"tags": []
},
"startTime": "2021-04-24T12:58:11.169267550Z",
"endTime": "2021-04-24T12:58:11.212604248Z",
"duration": "PT0.043336698S"
},
{
"startupStep": {
"name": "spring.boot.application.running",
"id": 278,
"parentId": 0,
"tags": []
},
"startTime": "2021-04-24T12:58:11.213585420Z",
"endTime": "2021-04-24T12:58:11.214002336Z",
"duration": "PT0.000416916S"
}
]
}
}

Ответ представляет собой массив с подробной информацией о событиях: name, startTime, endTimeи duration.
Как эта информация может помочь нам в исследовании приложения? Если мы знаем, какие шаги требуют больше времени при запуске, мы можем проверить эту область кодовой базы, чтобы понять, почему. Например, может случиться так, что, прогрев кэша выполняет предварительную выборку данных из базы данных или предварительное вычисление некоторых данных.
Поскольку приведенный выше ответ содержит много деталей, давайте сузим его, отфильтровав по spring.beans.instantiateшагу, а также отсортируем события по продолжительности в порядке убывания:
$ curl -XPOST 'http://localhost:8080/actuator/startup' | jq '.timeline.events | sort_by(.duration) | reverse[] | select(.startupStep.name | contains("instantiate"))'
$
Что здесь произошло? Почему мы не получили ответа? Вызов конечной точки startupтакже очищает внутренний буфер. Повторим попытку после перезапуска приложения:
$ curl -XPOST 'http://localhost:8080/actuator/startup' | jq '[.timeline.events | sort_by(.duration) | reverse[] | select(.startupStep.name | contains("instantiate")) | {beanName: .startupStep.tags[0].value, duration: .duration}]'
[
{
"beanName": "orderController",
"duration": "PT1.010878035S"
},
{
"beanName": "orderService",
"duration": "PT1.005529559S"
},
{
"beanName": "requestMappingHandlerAdapter",
"duration": "PT0.11549366S"
},
{
"beanName": "tomcatServletWebServerFactory",
"duration": "PT0.108340094S"
},
... other beans omitted ...
]

Таким образом, на создание bean-компонентов orderControllerи уходит больше секунды orderService! Это интересно - теперь у нас есть конкретная область приложения, на которой мы можем сосредоточиться, чтобы понять больше.
Команда jqздесь была немного сложнее по сравнению с предыдущими. Давайте разберемся, чтобы понять, что происходит:
jq '[.timeline.events \
| sort_by(.duration) \
| reverse[] \
| select(.startupStep.name \
| contains("instantiate")) \
| {beanName: .startupStep.tags[0].value, duration: .duration}]'

ВыражениеЭффект
.timeline.events | sort_by(.duration) | reverseотсортируйте массив timeline.eventsпо свойству durationи реверсируйте результат, чтобы он был отсортирован в порядке убывания
[]перебирать результирующий массив
select(.startupStep.name | contains("instantiate"))выберите элемент объекта только в том случае, если свойство startupStepэлемента nameсодержит текст «instantiate»
{beanName: .startupStep.tags[0].value, duration: .duration}создать новый объект JSON со свойствами beanNameи duration
Скобки над всем выражением указывают на то, что мы хотим собрать все созданные объекты JSON в массив.

Использование конечной точки env​

Конечная точка envдает обобщенное представление всех свойств конфигурации приложения. Сюда входят конфигурации из application.propertiesфайла, системные свойства JVM, переменные среды и т. д.
Мы можем использовать конечную точку env, чтобы увидеть, есть ли в приложении конфигурации, установленные с помощью переменных окружения, какие файлы jar находятся в его пути к классам и т. д.:
$ curl http://localhost:8080/actuator/env | jq
{
"activeProfiles": [],
"propertySources": [
{
"name": "server.ports",
"properties": {
"local.server.port": {
"value": 8080
}
}
},
{
"name": "servletContextInitParams",
"properties": {}
},
{
"name": "systemProperties",
"properties": {
"gopherProxySet": {
"value": "false"
},
"java.class.path": {
"value": "/target/test-classes:/target/classes:/Users/reflectoring/.m2/repository/org/springframework/boot/spring-boot-starter-actuator/2.4.4/spring-boot-starter-actuator-2.4.4.jar:/Users/reflectoring/.m2/repository/org/springframework/boot/spring-boot-starter/2.4.4/spring-boot-starter-2.4.4.jar: ... other jars omitted ... "
},
... other properties omitted ...
}
},
{
"name": "systemEnvironment",
"properties": {
"USER": {
"value": "reflectoring",
"origin": "System Environment Property \"USER\""
},
"HOME": {
"value": "/Users/reflectoring",
"origin": "System Environment Property \"HOME\""
}
... other environment variables omitted ...
}
},
{
"name": "Config resource 'class path resource [application.properties]' via location 'optional:classpath:/'",
"properties": {
"management.endpoint.logfile.enabled": {
"value": "true",
"origin": "class path resource [application.properties] - 2:37"
},
"management.endpoints.web.exposure.include": {
"value": "metrics,beans,mappings,startup,env, info,loggers",
"origin": "class path resource [application.properties] - 5:43"
}
}
}
]
}

Использование конечной точки scheduledtasks​

Эта конечная точка позволяет нам проверять, выполняет ли приложение какую-либо задачу периодически, используя @Scheduledаннотацию Spring :
$ curl http://localhost:8080/actuator/scheduledtasks | jq
{
"cron": [
{
"runnable": {
"target": "io.reflectoring.springboot.actuator.services.tasks.ReportGenerator.generateReports"
},
"expression": "0 0 12 * * *"
}
],
"fixedDelay": [
{
"runnable": {
"target": "io.reflectoring.springboot.actuator.services.tasks.CleanUpAbandonedBaskets.process"
},
"initialDelay": 0,
"interval": 900000
}
],
"fixedRate": [],
"custom": []
}

Из ответа мы видим, что приложение генерирует некоторые отчеты каждый день в 12 часов дня и что существует фоновый процесс, который выполняет некоторую очистку каждые 15 минут. Затем мы могли бы прочитать код этих конкретных классов, если бы мы хотели знать, что это за отчеты, какие шаги необходимо предпринять для очистки брошенной корзины и т. д.

Использование конечной точки caches​

Эта конечная точка перечисляет все кэши приложений:
$ curl http://localhost:8080/actuator/caches | jq
{
"cacheManagers": {
"cacheManager": {
"caches": {
"states": {
"target": "java.util.concurrent.ConcurrentHashMap"
},
"shippingPrice": {
"target": "java.util.concurrent.ConcurrentHashMap"
}
}
}
}
}

Мы можем сказать, что приложение кэширует некоторые данные: statesи shippingPrice. Это дает нам еще одну область приложения, которую нужно изучить и узнать больше: как создаются кеши, когда удаляются записи кеша и т. д.

Использование конечной точки health​

Конечная точка healthпоказывает информацию о здоровье приложения:
$ curl http://localhost:8080/actuator/health
{"status":"UP"}
Обычно это неглубокая проверка здоровья. Хотя это полезно в производственной среде для частой проверки балансировщиком нагрузки, это не помогает нам в понимании приложения.
Многие приложения также реализуют глубокие проверки работоспособности, которые могут помочь нам быстро узнать, каковы внешние зависимости приложения, к каким базам данных и брокерам сообщений оно подключается и т. д.
Прочтите эту статью на Reflectoring, чтобы узнать больше о реализации проверок работоспособности с помощью Actuator.

Использование конечной точки metrics​

Эта конечная точка перечисляет все метрики, сгенерированные приложением:
$ curl http://localhost:8080/actuator/metrics | jq
{
"names": [
"http.server.requests",
"jvm.buffer.count",
"jvm.buffer.memory.used",
"jvm.buffer.total.capacity",
"jvm.threads.states",
"logback.events",
"orders.placed.counter",
"process.cpu.usage",
... other metrics omitted ...
]
}

Затем мы можем получить данные отдельных показателей:
$ curl http://localhost:8080/actuator/metrics/jvm.memory.used | jq
{
"name": "jvm.memory.used",
"description": "The amount of used memory",
"baseUnit": "bytes",
"measurements": [
{
"statistic": "VALUE",
"value": 148044128
}
],
"availableTags": [
{
"tag": "area",
"values": [
"heap",
"nonheap"
]
},
{
"tag": "id",
"values": [
"CodeHeap 'profiled nmethods'",
"G1 Old Gen",
... other tags omitted ...
]
}
]
}

Особенно полезна проверка доступных пользовательских метрик API. Это может дать нам некоторое представление о том, что важно в этом приложении с точки зрения бизнеса. Например, из списка показателей мы можем видеть, что есть индикатор, orders.placed.counterкоторый, вероятно, сообщает нам, сколько заказов было размещено за определенный период времени.

Заключение​

В этой статье мы узнали, как использовать Spring Actuator в нашей локальной среде разработки для изучения нового приложения. Мы рассмотрели несколько конечных точек исполнительных механизмов, которые могут помочь нам определить важные области кодовой базы, которые могут потребовать более глубокого изучения. Попутно мы также узнали, как обрабатывать JSON в командной строке, используя легкий и чрезвычайно мощный инструмент jq.
Вы можете поиграть с полным приложением, иллюстрирующим эти идеи, используя код на GitHub.

Источник статьи: https://habr.com/ru/post/561994/
 
Сверху