Add Micrometer [bff] 11/135911/4 montreal
authorFiete Ostkamp <Fiete.Ostkamp@telekom.de>
Wed, 6 Sep 2023 06:32:33 +0000 (08:32 +0200)
committerFiete Ostkamp <Fiete.Ostkamp@telekom.de>
Mon, 11 Sep 2023 08:29:53 +0000 (10:29 +0200)
- add Micrometer dependencies
- uses Zipkin as trace protocol, typically for port 9411
- requires changing dependency injection of WebClient.Builder [1]

[1] Micrometer is injecting the trace context into the WebClient.Builder bean.
To add the ExchangeFilterFunctions for authentication, errorhandling and logging, the existing bean needs to be modified instead of statically creating a new one

Issue-ID: PORTALNG-57
Change-Id: I13dd18d297cd56fa2dfbb525723c79f4abb41f87
Signed-off-by: Fiete Ostkamp <Fiete.Ostkamp@telekom.de>
app/src/main/resources/application.yml
build.gradle
development/.env
development/docker-compose.yml
lib/build.gradle
lib/src/main/java/org/onap/portalng/bff/config/BeansConfig.java
lib/src/main/java/org/onap/portalng/bff/config/WebClientConfig.java [new file with mode: 0644]
version

index 0bbd9e2..87493a0 100644 (file)
@@ -13,6 +13,13 @@ management:
     web:
       exposure:
         include: "*"
+  tracing:
+    enabled: true
+    sampling:
+      probability: 1.0 # sample every request
+  zipkin:
+    tracing:
+      endpoint: http://${COLLECTOR_HOST}:${COLLECTOR_PORT}/api/v2/spans
 
 spring:
   application:
index 4ca21a3..8b484e2 100644 (file)
@@ -12,6 +12,7 @@ ext {
     mapStructExtensionsVersion = '1.0.2'
     logbackVersion = '7.4'
     lombokVersion = '1.18.28'
+    micrometerVersion = '1.0.0'
 
     // app
     wiremockVersion = '4.0.4'
index 2a2a73d..6cf5579 100644 (file)
@@ -20,6 +20,10 @@ POSTGRES_VERSION=15rc1
 MONGO_IMAGE=mongo
 MONGO_VERSION=latest
 
+# tracing
+COLLECTOR_HOST=jaeger
+COLLECTOR_PORT=9411
+
 # preferences
 PREFERENCES_IMAGE_NAME=preferences
 PREFERENCES_IMAGE_TAG=latest
index 1e7d282..10946d4 100644 (file)
@@ -52,6 +52,8 @@ services:
       MONGO_PORT: ${HISTORY_MONGO_PORT}
       KEYCLOAK_URL: ${KEYCLOAK_URL}
       KEYCLOAK_REALM: ${KEYCLOAK_REALM}
+      COLLECTOR_HOST: ${COLLECTOR_HOST}
+      COLLECTOR_PORT: ${COLLECTOR_PORT}
     depends_on:
       - mongo-history
   mongo-preferences:
@@ -73,5 +75,20 @@ services:
       MONGO_PORT: ${PREFERENCES_MONGO_PORT}
       KEYCLOAK_URL: ${KEYCLOAK_URL}
       KEYCLOAK_REALM: ${KEYCLOAK_REALM}
+      COLLECTOR_HOST: ${COLLECTOR_HOST}
+      COLLECTOR_PORT: ${COLLECTOR_PORT}
     depends_on:
-      - mongo-preferences
\ No newline at end of file
+      - mongo-preferences
+  jaeger: 
+    image: jaegertracing/all-in-one:latest
+    container_name: jaeger
+    ports:
+      - 5775:5775/udp
+      - 6831:6831/udp
+      - 6832:6832/udp
+      - 5778:5778
+      - 16686:16686
+      - 14268:14268
+      - 9411:9411
+    environment:
+      COLLECTOR_ZIPKIN_HOST_PORT: 9411
\ No newline at end of file
index e72062d..93064db 100644 (file)
@@ -30,6 +30,11 @@ dependencies {
     implementation "org.mapstruct.extensions.spring:mapstruct-spring-annotations:$mapStructExtensionsVersion"
     implementation "org.mapstruct.extensions.spring:mapstruct-spring-extensions:$mapStructExtensionsVersion"
 
+    implementation(platform("io.micrometer:micrometer-tracing-bom:$micrometerVersion"))
+    implementation("io.micrometer:micrometer-tracing")
+    implementation("io.micrometer:micrometer-tracing-bridge-otel")
+    implementation("io.opentelemetry:opentelemetry-exporter-zipkin")
+
     annotationProcessor "org.mapstruct:mapstruct-processor:$mapStructVersion"
     annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'
 
index 926108a..3eb5f87 100644 (file)
@@ -33,11 +33,8 @@ import java.util.List;
 import lombok.extern.slf4j.Slf4j;
 import org.onap.portalng.bff.exceptions.DownstreamApiProblemException;
 import org.onap.portalng.bff.utils.Logger;
-import org.springframework.beans.factory.annotation.Qualifier;
-import org.springframework.beans.factory.config.ConfigurableBeanFactory;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
-import org.springframework.context.annotation.Scope;
 import org.springframework.http.codec.ClientCodecConfigurer;
 import org.springframework.http.codec.json.Jackson2JsonDecoder;
 import org.springframework.http.codec.json.Jackson2JsonEncoder;
@@ -46,7 +43,6 @@ import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClient
 import org.springframework.security.oauth2.client.web.reactive.function.client.ServerOAuth2AuthorizedClientExchangeFilterFunction;
 import org.springframework.web.reactive.function.client.ExchangeFilterFunction;
 import org.springframework.web.reactive.function.client.ExchangeStrategies;
-import org.springframework.web.reactive.function.client.WebClient;
 import org.zalando.problem.jackson.ProblemModule;
 import reactor.core.publisher.Mono;
 
@@ -55,12 +51,12 @@ import reactor.core.publisher.Mono;
 public class BeansConfig {
 
   public static final String OAUTH2_EXCHANGE_FILTER_FUNCTION = "oauth2ExchangeFilterFunction";
-  private static final String ID_TOKEN_EXCHANGE_FILTER_FUNCTION = "idTokenExchangeFilterFunction";
-  private static final String ERROR_HANDLING_EXCHANGE_FILTER_FUNCTION =
+  public static final String ID_TOKEN_EXCHANGE_FILTER_FUNCTION = "idTokenExchangeFilterFunction";
+  public static final String ERROR_HANDLING_EXCHANGE_FILTER_FUNCTION =
       "errorHandlingExchangeFilterFunction";
-  private static final String LOG_REQUEST_EXCHANGE_FILTER_FUNCTION =
+  public static final String LOG_REQUEST_EXCHANGE_FILTER_FUNCTION =
       "logRequestExchangeFilterFunction";
-  private static final String LOG_RESPONSE_EXCHANGE_FILTER_FUNCTION =
+  public static final String LOG_RESPONSE_EXCHANGE_FILTER_FUNCTION =
       "logResponseExchangeFilterFunction";
   private static final String CLIENT_REGISTRATION_ID = "keycloak";
   public static final String X_REQUEST_ID = "X-Request-Id";
@@ -148,25 +144,6 @@ public class BeansConfig {
         .build();
   }
 
-  // we need to use prototype scope to always create new instance of the bean
-  // because internally WebClient.Builder is mutable
-  @Bean
-  @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
-  WebClient.Builder webClientBuilder(
-      ExchangeStrategies exchangeStrategies,
-      @Qualifier(ID_TOKEN_EXCHANGE_FILTER_FUNCTION)
-          ExchangeFilterFunction idTokenExchangeFilterFunction,
-      @Qualifier(ERROR_HANDLING_EXCHANGE_FILTER_FUNCTION)
-          ExchangeFilterFunction errorHandlingExchangeFilterFunction,
-      @Qualifier(LOG_RESPONSE_EXCHANGE_FILTER_FUNCTION)
-          ExchangeFilterFunction logResponseExchangeFilterFunction) {
-    return WebClient.builder()
-        .exchangeStrategies(exchangeStrategies)
-        .filter(idTokenExchangeFilterFunction)
-        .filter(errorHandlingExchangeFilterFunction)
-        .filter(logResponseExchangeFilterFunction);
-  }
-
   @Bean
   Clock clock() {
     return Clock.systemUTC();
diff --git a/lib/src/main/java/org/onap/portalng/bff/config/WebClientConfig.java b/lib/src/main/java/org/onap/portalng/bff/config/WebClientConfig.java
new file mode 100644 (file)
index 0000000..7aa31bf
--- /dev/null
@@ -0,0 +1,56 @@
+package org.onap.portalng.bff.config;
+
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.beans.factory.config.BeanPostProcessor;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.stereotype.Component;
+import org.springframework.web.reactive.function.client.ExchangeFilterFunction;
+import org.springframework.web.reactive.function.client.ExchangeStrategies;
+import org.springframework.web.reactive.function.client.WebClient;
+
+@Configuration
+public class WebClientConfig {
+
+  @Component
+  public static class WebClientBeanPostProcessor implements BeanPostProcessor {
+
+    private final ExchangeStrategies exchangeStrategies;
+    private final ExchangeFilterFunction idTokenExchangeFilterFunction;
+    private final ExchangeFilterFunction errorHandlingExchangeFilterFunction;
+    private final ExchangeFilterFunction logResponseExchangeFilterFunction;
+
+    public WebClientBeanPostProcessor(
+        ExchangeStrategies exchangeStrategies,
+        @Qualifier(BeansConfig.ID_TOKEN_EXCHANGE_FILTER_FUNCTION)
+            ExchangeFilterFunction idTokenExchangeFilterFunction,
+        @Qualifier(BeansConfig.ERROR_HANDLING_EXCHANGE_FILTER_FUNCTION)
+            ExchangeFilterFunction errorHandlingExchangeFilterFunction,
+        @Qualifier(BeansConfig.LOG_RESPONSE_EXCHANGE_FILTER_FUNCTION)
+            ExchangeFilterFunction logResponseExchangeFilterFunction) {
+      this.exchangeStrategies = exchangeStrategies;
+      this.idTokenExchangeFilterFunction = idTokenExchangeFilterFunction;
+      this.errorHandlingExchangeFilterFunction = errorHandlingExchangeFilterFunction;
+      this.logResponseExchangeFilterFunction = logResponseExchangeFilterFunction;
+    }
+
+    @Override
+    public Object postProcessBeforeInitialization(Object bean, String beanName)
+        throws BeansException {
+      return bean;
+    }
+
+    @Override
+    public Object postProcessAfterInitialization(Object bean, String beanName)
+        throws BeansException {
+      if (bean instanceof WebClient.Builder builder) {
+        return builder
+            .exchangeStrategies(exchangeStrategies)
+            .filter(idTokenExchangeFilterFunction)
+            .filter(errorHandlingExchangeFilterFunction)
+            .filter(logResponseExchangeFilterFunction);
+      }
+      return bean;
+    }
+  }
+}
diff --git a/version b/version
index 17e51c3..d917d3e 100644 (file)
--- a/version
+++ b/version
@@ -1 +1 @@
-0.1.1
+0.1.2