Когда мы хотим знать, в каком состоянии находятся системы, с которыми наше приложение взаимодействует, мы используем механизм метрик. Самым распространенным механизмом работы с метриками в приложениях на Spring Boot является micrometer.
Для интеграций по HTTP с использованием REST очень удобно использовать spring-boot-starter-actuator. Актуатор уже из коробки предоставляет набор из http_client_requests и http_server_requests метрик с разбивкой по uri, method и результату.
Но остались еще проекты, которые взаимодействуют по протоколу SOAP. Хотя экосистема Spring (как и ванильная Java) предоставляют возможность собирать веб-сервисы и клиенты для протокола SOAP, но коробочного решения для снятия метрик с таких клиентов не существует.
Что такое SOAP
SOAP - это довольно старый и редко используемый протокол для обмена произвольными сообщениями в формате XML. Надеюсь вам повезет и вы никогда с ним не столкнетесь)
Представим, что появилось требование - реализовать механизм снятия метрик с клиентов, которые взаимодействуют по SOAP. Считать будем количество отправленных запросов и результат выполнения запроса с разбивкой по uri.
Для проектов на Java и Spring есть два способа построить веб-клиента для протокола SOAP - Spring WebService и JAX-WS. Рассмотрим решения под оба этих варианта, поскольку единый механизм создать не получится.
Снятие метрик для клиента с использованием Spring WebService
В модуле Spring WebService для создания клиента применяется класс WebServiceTemplate, который может быть расширен с помощью набора различных ClientInterceptor. Интерфейс ClientInterceptor предоставляет 4 метода.
Нам интересен
void afterCompletion(MessageContext messageContext, Exception ex),
поскольку он вызывается во всех случаях - отправки запроса, получения ответа (в том числе с Fault), выброса какого-либо исключения в процессе обмена.
Реализуем свою имплементацию интерфейса. В ней переопределяем только метод afterCompletion, и реагируем только на ответ (или исключение). Факт отправки запроса в метриках нам не интересен.
public class SpringWsMetricsInterceptor implements ClientInterceptor { private static final Pattern pattern = Pattern.compile("^[^#]*?://.*?(?<pathGroup>/.*)$"); private final MeterRegistry meterRegistry; public SpringWsMetricsInterceptor(MeterRegistry meterRegistry) { this.meterRegistry = meterRegistry; } @Override public boolean handleRequest(MessageContext messageContext) throws WebServiceClientException { return true; } @Override public boolean handleResponse(MessageContext messageContext) throws WebServiceClientException { return true; } @Override public boolean handleFault(MessageContext messageContext) throws WebServiceClientException { return true; } @Override public void afterCompletion(MessageContext messageContext, Exception e) throws WebServiceClientException { if (messageContext.hasResponse() || e != null) { //в метрики пишем либо успешный ответ, либо ошибку TransportContext context = TransportContextHolder.getTransportContext(); String url = context.getConnection().getUri().getPath(); String outcome = e != null ? "SERVER_ERROR" : "SUCCESS"; String status = e != null ? e.getMessage() : "200"; Matcher matcher = pattern.matcher(url); String uri = matcher.find() ? matcher.group("pathGroup") : url; Counter.builder("soap.ws.client.requests") .tag("outcome", outcome) .tag("status", status) .tag("uri", uri) .register(meterRegistry) .increment(); } } }
В результате получаем набор метрик вида
soap_ws_client_requests_total{outcome="SUCCESS",status="200",uri="/path/to/service",}
Снятие метрик для клиента с использованием JAX-WS
Для клиентов, реализованных с использованием JAX-WS, к сожалению, нет возможности построить красивый и укладывающийся в парадигму Spring вариант снятия метрик.
Поскольку механизм JAX-WS предлагает только вызов метода интерфейса, сгенерированного плагином, и явно не имплементированного в коде, то остается только использовать аннотацию @Timed. Необходимо в клиенте реализовать набор методов, повторяющих методы JAX-WS интерфейса и проаннотировать их.
Допустим, есть wsdl-схема, из который будет сгенерирован интерфейс:
@WebService(name = "MyJaxWsInterface", targetNamespace = "urn:myJaxWs:interface") public interface MyJaxWsInterface { @WebMethod(operationName = "Create") @WebResult(name = "createResponse", targetNamespace = "urn:myJaxWs:types", partName = "result") public CreateResponse create( @WebParam(name = "createRequest", targetNamespace = "urn:myJaxWs:types", partName = "request") CreateRequest request) throws IllegalUsageException_Exception, InternalSystemErrorException_Exception ; }
Нужно реализовать враппер, расширяющий SOAP интерфейс, методы которого проаннотированы @Timed.
public class JaxWsClientWrapper implements MyJaxWsInterface { private MyJaxWsInterface myJaxWsInterface; public JaxWsClientWrapper(MyJaxWsInterface myJaxWsInterface) { this.myJaxWsInterface = myJaxWsInterface; } @Override @Timed public CreateResponse create(CreateRequest request){ return myJaxWsInterface.createRequest(request); } }
В результате получаем набор стандартных метрик, где будет указано имя класса, имя вызываемого метода и наличие или отсутствие exception при вызове. К сожалению, при таком подходе нет возможности получить url и status ответов в метриках.
