Spring Boot 3 Custom Client Request Observation Convention

Spring Boot 3: How To Override Tags in Rest Template Metrics

Understand how we can override tags in rest template metrics using the client request observation convention interface available in Spring Boot 3. This will override the default tags that are provided by the framework.

Spring Boot 3 Custom Client Request Observation Convention
Spring Boot 3 Custom Client Request Observation Convention

Context

When we add Spring Boot Starter Actuator dependency in our Spring Boot application, we can access the metrics for our application endpoints via the actuator metrics endpoint. Similarly, if we want to access the metrics for external calls that are performed by our application then we need to send the GET request specifying the name of metrics we are interested in i.e. in our case client requests metrics.

curl --request GET 'http://localhost:8080/actuator/metrics/http.client.requests'
ShellScript

By default, metrics are available with the name ‘http.client.requests’. This name can be customized by setting one property in the application.properties file.

When we send the request to the above endpoint, by default, we get the following tags in the output:

  • method: HTTP method.
  • uri: endpoints for which we received the requests
  • outcome: whether the request was processed successfully or not
  • status: HTTP status code
  • clientName: hostname of the URL that we are trying to call

Note:

The client metrics will available only if we have created the RestTemplate bean using the RestTemplateBuilder. If we have created the RestTemplate object using the new RestTemplate(), in that case, client metrics will not be available.


Advantages

Once the custom tags are added, we can utilize this information to create charts and dashboards displaying information at a more granular level.

Example: Consider we have an external service that provides information about the user whether the user is a freemium user or user is a premium user. Based on the output received from this service, we can add our custom tags highlighting the user type. We can utilize this information to create graphs and charts and see the ratio of requests that we are receiving from different types of users.

Adding Custom Tags

Below mentioned steps works only in case of Spring Boot 3.x. For Spring Boot 2.x, kindly refer to article Spring Boot 2: Override Tags in Rest Template Metrics.

If we want to override the tags that are provided by Spring Framework then we can do it with the help of the Custom Client Request Observation Convention.

Steps:

  1. Create a class that implements the ClientRequestObservationConvention interface.
  2. Provide the implementation for the getLowCardinalityKeyValues method wherein we can add our custom tags.
  3. Register this class as Spring bean using @Component annotation.
import io.micrometer.common.KeyValue;
import io.micrometer.common.KeyValues;
import org.springframework.http.client.ClientHttpRequest;
import org.springframework.http.client.observation.ClientHttpObservationDocumentation;
import org.springframework.http.client.observation.ClientRequestObservationContext;
import org.springframework.http.client.observation.ClientRequestObservationConvention;
import org.springframework.stereotype.Component;
import org.springframework.util.MultiValueMap;
import org.springframework.web.util.UriComponentsBuilder;

/**
 * The type Bootcamp to prod custom client request observation convention.
 * Useful for adding our own tags in rest template metrics. This will override the default tags that are provided by the framework.
 */
@Component
public class BootcampToProdCustomClientRequestObservationConvention implements ClientRequestObservationConvention {

    @Override
    public String getName() {
        // Will be used for the metric name
        // We can customize the metric name as per our own requirement
        return "http.client.requests";
    }

    @Override
    public KeyValues getLowCardinalityKeyValues(ClientRequestObservationContext context) {
        // Here we are adding the tags related to HTTP method, HTTP status, exception
        return KeyValues.of(method(context), status(context), exception(context)).and(additionalTags(context));
    }

    protected KeyValues additionalTags(ClientRequestObservationContext context) {
        KeyValues keyValues = KeyValues.empty();

        ClientHttpRequest request = context.getCarrier();
        String uri = request.getURI().toString();

        // Optional tag which will be present in metrics only when the condition is evaluated to true
        MultiValueMap<String, String> parameters = UriComponentsBuilder.fromUriString(uri).build().getQueryParams();

        if (parameters.containsKey("id")) {
            keyValues = keyValues.and(KeyValue.of("userId", parameters.get("id").get(0)));
        }

        // Custom tag which will be present in all the controller metrics
        keyValues = keyValues.and(KeyValue.of("tag", "value"));

        return keyValues;
    }

    // Adding info related to HTTP Method
    protected KeyValue method(ClientRequestObservationContext context) {
        // You should reuse as much as possible the corresponding ObservationDocumentation for key names
        return KeyValue.of(ClientHttpObservationDocumentation.LowCardinalityKeyNames.METHOD, context.getCarrier().getMethod().toString());
    }

    // Adding info related to HTTP Status
    protected KeyValue status(ClientRequestObservationContext context) {
        String statusCode = "";
        try {
            statusCode = Integer.toString(context.getResponse().getStatusCode().value());
        } catch (Exception e) {

        }
        // You should reuse as much as possible the corresponding ObservationDocumentation for key names
        return KeyValue.of(ClientHttpObservationDocumentation.LowCardinalityKeyNames.STATUS, statusCode);
    }

    // Adding info related to exception
    protected KeyValue exception(ClientRequestObservationContext context) {
        if (context.getError() != null) {
            // You should reuse as much as possible the corresponding ObservationDocumentation for key names
            return KeyValue.of(ClientHttpObservationDocumentation.LowCardinalityKeyNames.EXCEPTION, context.getError().getClass().getName());
        } else {
            return KeyValue.of(ClientHttpObservationDocumentation.LowCardinalityKeyNames.EXCEPTION, "none");
        }
    }
}
Java

We have control over a request object using context.getCarrier(), response object using context.getResponse(), and exception object using context.getError(). Based on this, we can add our own tags.

In the above example, we have added two tags “userId” and “tag” wherein the “userId” tag is optional. It will be added to metrics only if the URL contains the query parameter “id” whereas the “tag” tag will be added to metrics for all the requests that we have sent using the rest template.



Output:

Custom Tags Using Custom Client Request Observation Convention
Custom Tags Using Custom Client Request Observation Convention

Source Code

The source code for this example can be found on GitHub. Link: click here



Learn More

#

Curious to Learn Something New?

Check out how we can extend default tags in Spring Boot 3.

Add a Comment

Your email address will not be published.