Когда мы хотим знать, в каком состоянии находятся системы, с которыми наше приложение взаимодействует, мы используем механизм метрик. Самым распространенным механизмом работы с метриками в приложениях на 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 ответов в метриках.