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

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

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/healthhttp://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/


Интересные статьи

Интересные статьи

Данная статья будет полезна студентам и тем, кто хочет разобраться с тем, как происходит шумоподавление речи (Speech Denoising) с помощью глубокого обучения. На Хабре уже...
Год назад во фреймворке Next.js 9.3 появилась поддержка генерирования статических сайтов (Static Site Generation, SSG), что сделало его первым гибридным фреймворком. Я к тому моменту уже ...
В этой статье мы рассмотрим, как использовать Spring Boot 2.x и Redis для выполнения асинхронных задач, а полный код продемонстрирует шаги, описанные в этом посте. ...
Для тех, кому лень читать: карты SanDisk High Endurance используют флэш-память SanDisk/Toshiba 3D TLC. На то, чтобы установить это, у меня ушло гораздо больше времени, чем должно было...
Тема статьи навеяна результатами наблюдений за методикой создания шаблонов различными разработчиками, чьи проекты попадали мне на поддержку. Порой разобраться в, казалось бы, такой простой сущности ка...