WSO2 Micro Integrator - добавление кастомных метрик. Fear and loathing...

Для одного из заказчиков недавно возникла необходимость получать кастомные метрики по сервисам WSO2 Micro Integrator - API, Proxy, Inbound Endpoint. Изначально это выглядело вполне реализуемым штатными средствами, но не тут-то было!

Для выведения метрик и оповещений мы используем связку Prometheus и Grafana. У WSO2 для этого есть подготовленные dashboard-ы, что очень удобно.

Пример для прокси сервиса:

Для сбора логов используется Grafana Loki.

Под сбор Prometheus-ом подготавливаются несколько метрик для сервисов.

На примере Proxy стандартно есть 2 счётчика, для остальных сущностей метрики идентичны:

  • wso2_integration_proxy_request_count_total — общее количество запросов
  • wso2_integration_proxy_request_count_error_total — количество ошибок

гистограмма по задержкам ответа:

  • wso2_integration_proxy_latency_seconds

 

и таймер «Up time»

  • wso2_integration_service_up

 

Сбор метрик на Micro Integrator 4.0.0 (MI) запускается предельно просто: в deployment.toml дописываем:

[[synapse_handlers]]

name="CustomObservabilityHandler" class="org.wso2.micro.integrator.observability.metric.handler.MetricHandler"

и запускаем MI командой

micro-integrator.bat -DenablePrometheusApi=true

Более подробная статья по запуску и настройке мониторинга по ссылке.

Заказчику нужен был сбор метрик по параметрам из тела сообщения...

Сразу возникла мысль:

Вариант 1:

  1. На входе в сервис разбираем тело сообщения и выводим в лог строки в определённом формате, таким образом любой параметр в кармане.
  2. Собираем логи упомянутым в статье по мониторингу Grafana Loki.
  3. Выводим в Grafana, успех!

Но нет.

При формировании dashboard в Grafana с расчётными графиками и гистограммами за большой период получаем большую нагрузку на Loki и, как следствие, большую задержку.

В теории, для этих целей можно использовать более мощный стек ELK(Elasticsearch, Logstash and Kibana), но это дополнительные сервера и последующая их поддержка. Из-за этого вынуждены были всё же отказаться от этой идеи.

Вариант 2:

Так же вполне удобным и простым выглядело расширение метрик для связки Prometheus и Grafana.

Реализация:

Переопределяем упомянутый выше обработчик  org.wso2.micro.integrator.observability.metric.handler.MetricHandler и прописываем его в deployment.toml:

[[synapse_handlers]]

name="CustomObservabilityHandler" class="ru.emdev.test.common.CustomMetricHandler"

Ура, но нет...

 

Это только начало тернистого пути...

Наследуемся от MetricHandler, живущего в бандле org.wso2.micro.integrator.observability_4.0.0, переопределяем метод handleRequestInFlow в котором происходит обработка запросов.

У нас есть MessageContext и доступ к функционалу клиента Prometheus, что ещё для счастья надо?)

Но весь функционал работы с Prometheus находится в org.wso2.micro.integrator.observability.metric.handler.prometheus.reporter.PrometheusReporter, который хранится в приватном поле родителя MetricHandler.

Для получения доступа к полю metricReporterInstance используем Reflection.

К сожалению, в методе для создания метрик PrometheusReporter.createMetrics все метрики заданы жёстко и создаются вне зависимости от переданного названия….

Значит одним  MetricHandler тут не отделаться, переопределяем PrometheusReporter!

К счастью, в инициализации MetricHandler была замечены такая строка

Object metricReporterClass = configs.get(MetricConstants.METRIC_HANDLER + "." + METRIC_REPORTER);

 т.е. можно легально переопределить данный класс.

Наследуемся от  PrometheusReporter, подключаем библиотеки для работы с Prometheus.

Фиксим момент с жёстко заданными метриками типа:

TOTAL_REQUESTS_RECEIVED_PROXY_SERVICE = Counter.build(MetricConstants.PROXY_REQUEST_COUNT_TOTAL, metricHelp).

                        labelNames(labels).register();

metricMap.put(metricName, TOTAL_REQUESTS_RECEIVED_PROXY_SERVICE);

где при создании счётчика мы используем константу PROXY_REQUEST_COUNT_TOTAL, как наименование, а далее уже складываем в Map используя  metricName.

Собираем бандл, подкладываем в папку lib, перезагружаем и получаем не удивительную вещь...

java.lang.ClassCastException: class io.prometheus.client.Counter cannot be cast to class io.prometheus.client.Counter (io.prometheus.client.Counter is in unnamed module of loader org.eclipse.osgi.internal.loader.EquinoxClassLoader @63590aae; io.prometheus.client.Counter is in unnamed module of loader org.eclipse.osgi.internal.loader.EquinoxClassLoader @56854758)

Полностью вытаскивая весь класс PrometheusReporter мы избавляемся от ClassCastException, но упираемся в проблему с разными classloader ещё раз, из-за того, что в реализации клиента Prometheus повсеместно используется static-а. Что наглядно видно при входе в сервис метрик <host>:9201/metric-service/metrics - там просто-напросто пусто, т.к. все метрики в classloader нашего бандла.

Останавливаться нельзя, - только вперёд!!!

Принято решение вытащить бандл org.wso2.micro.integrator.observability полностью.

По сути осталось всего 3 класса:

  1. MetricAPI
  2. MetricFormatter
  3. MetricResource

Упомянутые классы переносим без изменений.

Выставляемое API <host>:9201/metric-service/metrics, которое реализуется в классе MetricAPI, относится к служебным и прописано в конфигурационном файле internal-apis.xml

Прописываем свой класс для PrometheusApi:

<api name="PrometheusApi" protocol="http" class="org.wso2.micro.integrator.observability.metric.publisher.MetricAPI">

После перезагрузки MI начинает отбиваться от инородных захватчиков и восстанавливает файл internal-apis.xml до заводского состояния, а файл с изменениями складывает в папку backup/conf.

Прописываем свой класс ещё раз и MI тут же сдаётся под натиском и перестаёт восстанавливать файл.

В итоге получаем почти полный бандл мониторинга за исключением утильного класса MetricConstants.

  • MetricHandler отнаследовались
  • PrometheusReporter полностью
  • MetricAPI полностью
  • MetricFormatter полностью
  • MetricResource полностью

Изменили конфигурационные файлы:

  • deployment.toml
  • internal-apis.xml

Используя это решение мы можем без проблем добавлять метрики для Prometheus, обрабатывая запрос в методе MetricHandler.handleRequestInFlow.

 

Удачных вам интеграций!

11.02.2022