Search code examples
prometheusopenapiapache-httpclient-4.xopenapi-generatoropenapi-generator-maven-plugin

How to gather webflux client metrics when client is generated by OpenAPI


in our Spring Boot application we are using actuator and integrated prometheus. Now we would like to gather HTTP client metrics.

In our scenario we are using OpenAPI specification for defining the rest interfaces. With OpenApi generator the client is generated. For that we are using maven plugin org.openapitools:openapi-generator-maven-plugin:6.0.1

Typically, gathering the metrics mentioned above are configured via WebClientAutoConfiguration (see https://docs.spring.io/spring-boot/docs/2.1.9.RELEASE/reference/html/production-ready-metrics.html#production-ready-metrics-http-clients). However, since the web client builder generated by OpenAPI generator is not a Spring Bean, Spring's autoconfiguration does not work:

WebClientAutoConfiguration#webClientBuilder matched:
  - @ConditionalOnMissingBean (types: org.springframework.web.reactive.function.client.WebClient$Builder; SearchStrategy: all) did not find any beans (OnBeanCondition)

The reason for this is the OpenAPI generator. It generates a static method and no Spring Bean:

public static WebClient.Builder buildWebClientBuilder(ObjectMapper mapper) {
    ExchangeStrategies strategies = ExchangeStrategies
        .builder()
        .codecs(clientDefaultCodecsConfigurer -> {
            clientDefaultCodecsConfigurer.defaultCodecs().jackson2JsonEncoder(new Jackson2JsonEncoder(mapper, MediaType.APPLICATION_JSON));
            clientDefaultCodecsConfigurer.defaultCodecs().jackson2JsonDecoder(new Jackson2JsonDecoder(mapper, MediaType.APPLICATION_JSON));
        }).build();
    WebClient.Builder webClientBuilder = WebClient.builder().exchangeStrategies(strategies);
    return webClientBuilder;
}

See generator/ mustache template code in https://github.com/OpenAPITools/openapi-generator/blob/ad7ce7cba69031ffe6406d0cc0a84d4b2a3feadf/modules/openapi-generator/src/main/resources/Java/libraries/webclient/ApiClient.mustache#L171

In my opinion there is no chance to extend the WebClient in order to gather the webflux client metrics. Do you habe any idea on how to gather the HTTP client metrics when using OpenAPI client generator? Is there any configuration of OpenApi generator that I missed und would help me here?

Or is the only way modifying the open api generator mustache templates? (https://openapi-generator.tech/docs/templating#modifying-templates)


Solution

  • As changing the mustache templates were not an option for me, I found a quite nice and simple solution for that problem.

    I defined and added an ExchangeFilterFunction.

    These snippets should help to unterstand the solution:

    Adding maven dependencies

    <dependency>
        <!-- E.g. class MetricsWebClientFilterFunction + autoconfig for MetricsProperties -->
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-actuator-autoconfigure</artifactId>
    </dependency>
    <dependency>
        <!-- class MeterRegistry -->
        <groupId>io.micrometer</groupId>
        <artifactId>micrometer-core</artifactId>
    </dependency>
    

    Definition of additional required beans

    @ConditionalOnMissingBean
    @Bean
    MetricsWebClientFilterFunction metricsWebClientFilterFunction(MeterRegistry meterRegistry,
            MetricsProperties properties, WebClientExchangeTagsProvider webClientExchangeTagsProvider) {
        MetricsProperties.Web.Client.ClientRequest request = properties.getWeb().getClient().getRequest();
        return new MetricsWebClientFilterFunction(meterRegistry, webClientExchangeTagsProvider,
                request.getMetricName(), request.getAutotime());
    }
    
    @ConditionalOnMissingBean
    @Bean
    WebClientExchangeTagsProvider defaultWebClientExchangeTagsProvider() {
        return new DefaultWebClientExchangeTagsProvider();
    }
    

    Create ApiClient bean and add the filter

    @Bean
    public ApiClient apiClientRefJPers(@Autowired MetricsWebClientFilterFunction metricsWebClientFilterFunction) {
        final WebClient.Builder webClientBuilder = ApiClient.buildWebClientBuilder().filter(metricsWebClientFilterFunction);
        ApiClient client = new ApiClient(webClientBuilder.build());
        // ... 
        // client.setBasePath(...);
        // ... 
        return client;
    }