Adding micrometer metrics for SOAP interactions

When we want to know the state of the systems with which our application interacts, we use a metrics mechanism. The most common mechanism for working with metrics in Spring Boot applications is micrometer.

For HTTP integrations using REST it is very convenient to use spring-boot-starter-actuator. The actuator already provides a set of http_client_requests And http_server_requests metrics broken down by uri, method and result.

But there are still projects that interact using the SOAP protocol. Although the Spring ecosystem (as well as vanilla Java) provide the ability to collect web services and clients for the SOAP protocol, there is no packaged solution for taking metrics from such clients.

What is SOAP

SOAP is quite old and rarely used protocol for exchanging arbitrary messages in XML format. I hope you are lucky and never run into him)

Let’s imagine that a requirement has arisen – to implement a mechanism for taking metrics from clients that interact via SOAP. We will count the number of requests sent and the result of the request, broken down by uri.

For Java and Spring projects, there are two ways to build a web client for the SOAP protocol – Spring WebService And JAX-WS. Let’s consider solutions for both of these options, since it will not be possible to create a single mechanism.

Capturing metrics for a client using Spring WebService

The Spring WebService module uses a WebServiceTemplate class to create a client, which can be extended with a set of different ClientInterceptors. ClientInterceptor interface provides 4 methods.

We are interested

void afterCompletion(MessageContext messageContext, Exception ex),

since it is called in all cases – sending a request, receiving a response (including with Fault), throwing any exception during the exchange process.

Let’s implement our own implementation of the interface. In it we override only the method afterCompletion, and react only to the response (or exception). The fact of sending a request in metrics is not interesting to us.

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();
        }
    }
} 

As a result, we obtain a set of metrics of the form

soap_ws_client_requests_total{outcome="SUCCESS",status="200",uri="/path/to/service",}

Capturing metrics for a client using JAX-WS

For clients implemented using JAX-WS, unfortunately, there is no way to build a beautiful option for taking metrics that fits the Spring paradigm.

Since the JAX-WS mechanism only offers a call to an interface method generated by the plugin and not explicitly implemented in code, all that remains is to use the annotation @Timed. It is necessary to implement in the client a set of methods that repeat the methods of the JAX-WS interface and annotate them.

Let’s say there is a wsdl schema from which the interface will be generated:

@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
    ;
}

We need to implement a wrapper that extends the SOAP interface, the methods of which are annotated with @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);
    }
}

As a result, we get a set of standard metrics, which will indicate the name of the class, the name of the called method, and the presence or absence of an exception when called. Unfortunately, with this approach there is no way to get the url and status of responses in metrics.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *