From 382e9255fcad542a9f350324740cbe0e69ff3d7b Mon Sep 17 00:00:00 2001 From: Saikat Chakraborty <40471715+saikatcse03@users.noreply.github.com> Date: Tue, 7 Feb 2023 00:31:12 +0530 Subject: [PATCH] Bael 5961: OpenTelemetry integration with Spring Boot application (#13252) * Open telemetry in spring boot * Removed unused field * Test add and seperate packages * Refactored code * Version moved to property * Removed unused logback files * update version in docker file * corrected spacing * COde review refactoring * COde review refactoring * COde review refactoring * corrected property * PostContruct add on repo setup * corrected var names * change to junit 5 and other improvements * Port reverted back * Code review implement * Logger update * Logger var update --------- Co-authored-by: Saikat --- spring-cloud-modules/pom.xml | 1 + .../docker-compose.yml | 30 +++++ .../otel-config.yml | 23 ++++ .../spring-cloud-open-telemetry/pom.xml | 22 ++++ .../spring-cloud-open-telemetry1/Dockerfile | 7 ++ .../spring-cloud-open-telemetry1/pom.xml | 116 ++++++++++++++++++ .../opentelemetry/ProductApplication.java | 12 ++ .../opentelemetry/api/client/PriceClient.java | 34 +++++ .../configuration/RestConfiguration.java | 15 +++ .../controller/ProductController.java | 36 ++++++ .../exception/ProductControllerAdvice.java | 22 ++++ .../exception/ProductNotFoundException.java | 7 ++ .../baeldung/opentelemetry/model/Price.java | 39 ++++++ .../baeldung/opentelemetry/model/Product.java | 27 ++++ .../repository/ProductRepository.java | 55 +++++++++ .../src/main/resources/application.properties | 5 + .../opentelemetry/SpringContextTest.java | 16 +++ .../controller/ProductControllerUnitTest.java | 92 ++++++++++++++ .../spring-cloud-open-telemetry2/Dockerfile | 7 ++ .../spring-cloud-open-telemetry2/pom.xml | 115 +++++++++++++++++ .../opentelemetry/PriceApplication.java | 12 ++ .../controller/PriceController.java | 29 +++++ .../exception/PriceNotFoundException.java | 7 ++ .../exception/ProductControllerAdvice.java | 16 +++ .../baeldung/opentelemetry/model/Price.java | 27 ++++ .../repository/PriceRepository.java | 53 ++++++++ .../src/main/resources/application.properties | 4 + .../opentelemetry/SpringContextTest.java | 16 +++ .../controller/PriceControllerUnitTest.java | 57 +++++++++ 29 files changed, 902 insertions(+) create mode 100644 spring-cloud-modules/spring-cloud-open-telemetry/docker-compose.yml create mode 100644 spring-cloud-modules/spring-cloud-open-telemetry/otel-config.yml create mode 100644 spring-cloud-modules/spring-cloud-open-telemetry/pom.xml create mode 100644 spring-cloud-modules/spring-cloud-open-telemetry/spring-cloud-open-telemetry1/Dockerfile create mode 100644 spring-cloud-modules/spring-cloud-open-telemetry/spring-cloud-open-telemetry1/pom.xml create mode 100644 spring-cloud-modules/spring-cloud-open-telemetry/spring-cloud-open-telemetry1/src/main/java/com/baeldung/opentelemetry/ProductApplication.java create mode 100644 spring-cloud-modules/spring-cloud-open-telemetry/spring-cloud-open-telemetry1/src/main/java/com/baeldung/opentelemetry/api/client/PriceClient.java create mode 100644 spring-cloud-modules/spring-cloud-open-telemetry/spring-cloud-open-telemetry1/src/main/java/com/baeldung/opentelemetry/configuration/RestConfiguration.java create mode 100644 spring-cloud-modules/spring-cloud-open-telemetry/spring-cloud-open-telemetry1/src/main/java/com/baeldung/opentelemetry/controller/ProductController.java create mode 100644 spring-cloud-modules/spring-cloud-open-telemetry/spring-cloud-open-telemetry1/src/main/java/com/baeldung/opentelemetry/exception/ProductControllerAdvice.java create mode 100644 spring-cloud-modules/spring-cloud-open-telemetry/spring-cloud-open-telemetry1/src/main/java/com/baeldung/opentelemetry/exception/ProductNotFoundException.java create mode 100644 spring-cloud-modules/spring-cloud-open-telemetry/spring-cloud-open-telemetry1/src/main/java/com/baeldung/opentelemetry/model/Price.java create mode 100644 spring-cloud-modules/spring-cloud-open-telemetry/spring-cloud-open-telemetry1/src/main/java/com/baeldung/opentelemetry/model/Product.java create mode 100644 spring-cloud-modules/spring-cloud-open-telemetry/spring-cloud-open-telemetry1/src/main/java/com/baeldung/opentelemetry/repository/ProductRepository.java create mode 100644 spring-cloud-modules/spring-cloud-open-telemetry/spring-cloud-open-telemetry1/src/main/resources/application.properties create mode 100644 spring-cloud-modules/spring-cloud-open-telemetry/spring-cloud-open-telemetry1/src/test/java/com/baeldung/opentelemetry/SpringContextTest.java create mode 100644 spring-cloud-modules/spring-cloud-open-telemetry/spring-cloud-open-telemetry1/src/test/java/com/baeldung/opentelemetry/controller/ProductControllerUnitTest.java create mode 100644 spring-cloud-modules/spring-cloud-open-telemetry/spring-cloud-open-telemetry2/Dockerfile create mode 100644 spring-cloud-modules/spring-cloud-open-telemetry/spring-cloud-open-telemetry2/pom.xml create mode 100644 spring-cloud-modules/spring-cloud-open-telemetry/spring-cloud-open-telemetry2/src/main/java/com/baeldung/opentelemetry/PriceApplication.java create mode 100644 spring-cloud-modules/spring-cloud-open-telemetry/spring-cloud-open-telemetry2/src/main/java/com/baeldung/opentelemetry/controller/PriceController.java create mode 100644 spring-cloud-modules/spring-cloud-open-telemetry/spring-cloud-open-telemetry2/src/main/java/com/baeldung/opentelemetry/exception/PriceNotFoundException.java create mode 100644 spring-cloud-modules/spring-cloud-open-telemetry/spring-cloud-open-telemetry2/src/main/java/com/baeldung/opentelemetry/exception/ProductControllerAdvice.java create mode 100644 spring-cloud-modules/spring-cloud-open-telemetry/spring-cloud-open-telemetry2/src/main/java/com/baeldung/opentelemetry/model/Price.java create mode 100644 spring-cloud-modules/spring-cloud-open-telemetry/spring-cloud-open-telemetry2/src/main/java/com/baeldung/opentelemetry/repository/PriceRepository.java create mode 100644 spring-cloud-modules/spring-cloud-open-telemetry/spring-cloud-open-telemetry2/src/main/resources/application.properties create mode 100644 spring-cloud-modules/spring-cloud-open-telemetry/spring-cloud-open-telemetry2/src/test/java/com/baeldung/opentelemetry/SpringContextTest.java create mode 100644 spring-cloud-modules/spring-cloud-open-telemetry/spring-cloud-open-telemetry2/src/test/java/com/baeldung/opentelemetry/controller/PriceControllerUnitTest.java diff --git a/spring-cloud-modules/pom.xml b/spring-cloud-modules/pom.xml index 68aa4cd2e5..43e2687d74 100644 --- a/spring-cloud-modules/pom.xml +++ b/spring-cloud-modules/pom.xml @@ -55,6 +55,7 @@ spring-cloud-data-flow spring-cloud-sleuth spring-cloud-openfeign-2 + spring-cloud-open-telemetry diff --git a/spring-cloud-modules/spring-cloud-open-telemetry/docker-compose.yml b/spring-cloud-modules/spring-cloud-open-telemetry/docker-compose.yml new file mode 100644 index 0000000000..8a6833095c --- /dev/null +++ b/spring-cloud-modules/spring-cloud-open-telemetry/docker-compose.yml @@ -0,0 +1,30 @@ +version: "4.0" + +services: + product-service: + platform: linux/x86_64 + build: spring-cloud-open-telemetry1/ + ports: + - "8080:8080" + + price-service: + platform: linux/x86_64 + build: spring-cloud-open-telemetry2/ + ports: + - "8081" + + jaeger-service: + image: jaegertracing/all-in-one:latest + ports: + - "16686:16686" + - "14250" + + collector: + image: otel/opentelemetry-collector:0.47.0 + command: [ "--config=/etc/otel-collector-config.yml" ] + volumes: + - ./otel-config.yml:/etc/otel-collector-config.yml + ports: + - "4317:4317" + depends_on: + - jaeger-service \ No newline at end of file diff --git a/spring-cloud-modules/spring-cloud-open-telemetry/otel-config.yml b/spring-cloud-modules/spring-cloud-open-telemetry/otel-config.yml new file mode 100644 index 0000000000..886c10a1c3 --- /dev/null +++ b/spring-cloud-modules/spring-cloud-open-telemetry/otel-config.yml @@ -0,0 +1,23 @@ +receivers: + otlp: + protocols: + grpc: + http: + +processors: + batch: + +exporters: + logging: + logLevel: debug + jaeger: + endpoint: jaeger-service:14250 + tls: + insecure: true + +service: + pipelines: + traces: + receivers: [ otlp ] + processors: [ batch ] + exporters: [ logging, jaeger ] diff --git a/spring-cloud-modules/spring-cloud-open-telemetry/pom.xml b/spring-cloud-modules/spring-cloud-open-telemetry/pom.xml new file mode 100644 index 0000000000..69b3a1a478 --- /dev/null +++ b/spring-cloud-modules/spring-cloud-open-telemetry/pom.xml @@ -0,0 +1,22 @@ + + 4.0.0 + com.baeldung.spring.cloud + spring-cloud-open-telemetry + 1.0.0-SNAPSHOT + spring-cloud-open-telemetry + pom + + + com.baeldung.spring.cloud + spring-cloud-modules + 1.0.0-SNAPSHOT + + + + spring-cloud-open-telemetry1 + spring-cloud-open-telemetry2 + + + \ No newline at end of file diff --git a/spring-cloud-modules/spring-cloud-open-telemetry/spring-cloud-open-telemetry1/Dockerfile b/spring-cloud-modules/spring-cloud-open-telemetry/spring-cloud-open-telemetry1/Dockerfile new file mode 100644 index 0000000000..8bae52cec4 --- /dev/null +++ b/spring-cloud-modules/spring-cloud-open-telemetry/spring-cloud-open-telemetry1/Dockerfile @@ -0,0 +1,7 @@ +FROM adoptopenjdk/openjdk11:alpine + +COPY target/spring-cloud-open-telemetry1-1.0.0-SNAPSHOT.jar spring-cloud-open-telemetry.jar + +EXPOSE 8081 + +ENTRYPOINT ["java","-jar","/spring-cloud-open-telemetry.jar"] \ No newline at end of file diff --git a/spring-cloud-modules/spring-cloud-open-telemetry/spring-cloud-open-telemetry1/pom.xml b/spring-cloud-modules/spring-cloud-open-telemetry/spring-cloud-open-telemetry1/pom.xml new file mode 100644 index 0000000000..5011590e73 --- /dev/null +++ b/spring-cloud-modules/spring-cloud-open-telemetry/spring-cloud-open-telemetry1/pom.xml @@ -0,0 +1,116 @@ + + + 4.0.0 + spring-cloud-open-telemetry1 + com.baeldung.spring.cloud + 1.0.0-SNAPSHOT + spring-cloud-open-telemetry1 + jar + + + com.baeldung.spring.cloud + spring-cloud-open-telemetry + 1.0.0-SNAPSHOT + + + + + + org.springframework.boot + spring-boot-dependencies + ${spring-boot-dependencies.version} + pom + import + + + org.springframework.cloud + spring-cloud-dependencies + ${release.train.version} + pom + import + + + org.springframework.cloud + spring-cloud-sleuth-otel-dependencies + ${spring-cloud-sleuth-otel.version} + import + pom + + + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.cloud + spring-cloud-starter-sleuth + + + org.springframework.cloud + spring-cloud-sleuth-brave + + + + + org.springframework.cloud + spring-cloud-sleuth-otel-autoconfigure + + + io.opentelemetry + opentelemetry-exporter-otlp-trace + + + io.grpc + grpc-okhttp + ${grpc-okhttp.version} + + + org.junit.jupiter + junit-jupiter-engine + ${junit-jupiter.version} + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + repackage + + + + + + + + + + spring-milestones + https://repo.spring.io/milestone + + + + + spring-milestones + https://repo.spring.io/milestone + + + + + 2.5.7 + 2020.0.4 + 1.0.0-M12 + 1.42.1 + + + \ No newline at end of file diff --git a/spring-cloud-modules/spring-cloud-open-telemetry/spring-cloud-open-telemetry1/src/main/java/com/baeldung/opentelemetry/ProductApplication.java b/spring-cloud-modules/spring-cloud-open-telemetry/spring-cloud-open-telemetry1/src/main/java/com/baeldung/opentelemetry/ProductApplication.java new file mode 100644 index 0000000000..8427603340 --- /dev/null +++ b/spring-cloud-modules/spring-cloud-open-telemetry/spring-cloud-open-telemetry1/src/main/java/com/baeldung/opentelemetry/ProductApplication.java @@ -0,0 +1,12 @@ +package com.baeldung.opentelemetry; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class ProductApplication { + + public static void main(String[] args) { + SpringApplication.run(ProductApplication.class, args); + } +} diff --git a/spring-cloud-modules/spring-cloud-open-telemetry/spring-cloud-open-telemetry1/src/main/java/com/baeldung/opentelemetry/api/client/PriceClient.java b/spring-cloud-modules/spring-cloud-open-telemetry/spring-cloud-open-telemetry1/src/main/java/com/baeldung/opentelemetry/api/client/PriceClient.java new file mode 100644 index 0000000000..d8b43b61a5 --- /dev/null +++ b/spring-cloud-modules/spring-cloud-open-telemetry/spring-cloud-open-telemetry1/src/main/java/com/baeldung/opentelemetry/api/client/PriceClient.java @@ -0,0 +1,34 @@ +package com.baeldung.opentelemetry.api.client; + +import com.baeldung.opentelemetry.model.Price; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Component; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.client.RestTemplate; + +@Component +public class PriceClient { + + private static final Logger LOGGER = LoggerFactory.getLogger(PriceClient.class); + + private final RestTemplate restTemplate; + + @Autowired + public PriceClient(RestTemplate restTemplate) { + this.restTemplate = restTemplate; + } + + @Value("${priceClient.baseUrl}") + private String baseUrl; + + public Price getPrice(@PathVariable("id") long productId){ + LOGGER.info("Fetching Price Details With Product Id {}", productId); + String url = String.format("%s/price/%d", baseUrl, productId); + ResponseEntity price = restTemplate.getForEntity(url, Price.class); + return price.getBody(); + } +} diff --git a/spring-cloud-modules/spring-cloud-open-telemetry/spring-cloud-open-telemetry1/src/main/java/com/baeldung/opentelemetry/configuration/RestConfiguration.java b/spring-cloud-modules/spring-cloud-open-telemetry/spring-cloud-open-telemetry1/src/main/java/com/baeldung/opentelemetry/configuration/RestConfiguration.java new file mode 100644 index 0000000000..5d8ba4beac --- /dev/null +++ b/spring-cloud-modules/spring-cloud-open-telemetry/spring-cloud-open-telemetry1/src/main/java/com/baeldung/opentelemetry/configuration/RestConfiguration.java @@ -0,0 +1,15 @@ +package com.baeldung.opentelemetry.configuration; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.client.RestTemplate; + +@Configuration +public class RestConfiguration { + + @Bean + public RestTemplate restTemplate() { + return new RestTemplate(); + } + +} diff --git a/spring-cloud-modules/spring-cloud-open-telemetry/spring-cloud-open-telemetry1/src/main/java/com/baeldung/opentelemetry/controller/ProductController.java b/spring-cloud-modules/spring-cloud-open-telemetry/spring-cloud-open-telemetry1/src/main/java/com/baeldung/opentelemetry/controller/ProductController.java new file mode 100644 index 0000000000..5586f5e9d0 --- /dev/null +++ b/spring-cloud-modules/spring-cloud-open-telemetry/spring-cloud-open-telemetry1/src/main/java/com/baeldung/opentelemetry/controller/ProductController.java @@ -0,0 +1,36 @@ +package com.baeldung.opentelemetry.controller; + +import com.baeldung.opentelemetry.api.client.PriceClient; +import com.baeldung.opentelemetry.model.Product; +import com.baeldung.opentelemetry.repository.ProductRepository; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class ProductController { + + private static final Logger LOGGER = LoggerFactory.getLogger(ProductController.class); + + private final PriceClient priceClient; + + private final ProductRepository productRepository; + + @Autowired + public ProductController(PriceClient priceClient, ProductRepository productRepository) { + this.priceClient = priceClient; + this.productRepository = productRepository; + } + + @GetMapping(path = "/product/{id}") + public Product getProductDetails(@PathVariable("id") long productId){ + LOGGER.info("Getting Product and Price Details With Product Id {}", productId); + Product product = productRepository.getProduct(productId); + product.setPrice(priceClient.getPrice(productId)); + + return product; + } +} diff --git a/spring-cloud-modules/spring-cloud-open-telemetry/spring-cloud-open-telemetry1/src/main/java/com/baeldung/opentelemetry/exception/ProductControllerAdvice.java b/spring-cloud-modules/spring-cloud-open-telemetry/spring-cloud-open-telemetry1/src/main/java/com/baeldung/opentelemetry/exception/ProductControllerAdvice.java new file mode 100644 index 0000000000..0ab031f517 --- /dev/null +++ b/spring-cloud-modules/spring-cloud-open-telemetry/spring-cloud-open-telemetry1/src/main/java/com/baeldung/opentelemetry/exception/ProductControllerAdvice.java @@ -0,0 +1,22 @@ +package com.baeldung.opentelemetry.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.client.HttpServerErrorException; + + +@RestControllerAdvice +public class ProductControllerAdvice { + + @ExceptionHandler(ProductNotFoundException.class) + public ResponseEntity handleProductNotFoundException(ProductNotFoundException exception) { + return new ResponseEntity<>(exception.getMessage(), HttpStatus.NOT_FOUND); + } + + @ExceptionHandler(HttpServerErrorException.ServiceUnavailable.class) + public ResponseEntity handleException(HttpServerErrorException.ServiceUnavailable exception) { + return new ResponseEntity<>(exception.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR); + } +} diff --git a/spring-cloud-modules/spring-cloud-open-telemetry/spring-cloud-open-telemetry1/src/main/java/com/baeldung/opentelemetry/exception/ProductNotFoundException.java b/spring-cloud-modules/spring-cloud-open-telemetry/spring-cloud-open-telemetry1/src/main/java/com/baeldung/opentelemetry/exception/ProductNotFoundException.java new file mode 100644 index 0000000000..9b698d5416 --- /dev/null +++ b/spring-cloud-modules/spring-cloud-open-telemetry/spring-cloud-open-telemetry1/src/main/java/com/baeldung/opentelemetry/exception/ProductNotFoundException.java @@ -0,0 +1,7 @@ +package com.baeldung.opentelemetry.exception; + +public class ProductNotFoundException extends RuntimeException { + public ProductNotFoundException(String productNotFound) { + super(productNotFound); + } +} diff --git a/spring-cloud-modules/spring-cloud-open-telemetry/spring-cloud-open-telemetry1/src/main/java/com/baeldung/opentelemetry/model/Price.java b/spring-cloud-modules/spring-cloud-open-telemetry/spring-cloud-open-telemetry1/src/main/java/com/baeldung/opentelemetry/model/Price.java new file mode 100644 index 0000000000..dccaf978fd --- /dev/null +++ b/spring-cloud-modules/spring-cloud-open-telemetry/spring-cloud-open-telemetry1/src/main/java/com/baeldung/opentelemetry/model/Price.java @@ -0,0 +1,39 @@ +package com.baeldung.opentelemetry.model; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class Price { + + @JsonProperty("productId") + private long productId; + + @JsonProperty("price_amount") + private double priceAmount; + + @JsonProperty("discount") + private double discount; + + public long getProductId() { + return productId; + } + + public void setProductId(long productId) { + this.productId = productId; + } + + public double getPriceAmount() { + return priceAmount; + } + + public void setPriceAmount(double priceAmount) { + this.priceAmount = priceAmount; + } + + public double getDiscount() { + return discount; + } + + public void setDiscount(double discount) { + this.discount = discount; + } +} diff --git a/spring-cloud-modules/spring-cloud-open-telemetry/spring-cloud-open-telemetry1/src/main/java/com/baeldung/opentelemetry/model/Product.java b/spring-cloud-modules/spring-cloud-open-telemetry/spring-cloud-open-telemetry1/src/main/java/com/baeldung/opentelemetry/model/Product.java new file mode 100644 index 0000000000..2a3a279dd8 --- /dev/null +++ b/spring-cloud-modules/spring-cloud-open-telemetry/spring-cloud-open-telemetry1/src/main/java/com/baeldung/opentelemetry/model/Product.java @@ -0,0 +1,27 @@ +package com.baeldung.opentelemetry.model; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class Product { + + @JsonProperty("id") + private long id; + + @JsonProperty("name") + private String name; + + @JsonProperty("price") + private Price price; + + public void setId(long id) { + this.id = id; + } + + public void setName(String name) { + this.name = name; + } + + public void setPrice(Price price) { + this.price = price; + } +} diff --git a/spring-cloud-modules/spring-cloud-open-telemetry/spring-cloud-open-telemetry1/src/main/java/com/baeldung/opentelemetry/repository/ProductRepository.java b/spring-cloud-modules/spring-cloud-open-telemetry/spring-cloud-open-telemetry1/src/main/java/com/baeldung/opentelemetry/repository/ProductRepository.java new file mode 100644 index 0000000000..07f94f626e --- /dev/null +++ b/spring-cloud-modules/spring-cloud-open-telemetry/spring-cloud-open-telemetry1/src/main/java/com/baeldung/opentelemetry/repository/ProductRepository.java @@ -0,0 +1,55 @@ +package com.baeldung.opentelemetry.repository; + +import com.baeldung.opentelemetry.exception.ProductNotFoundException; +import com.baeldung.opentelemetry.model.Product; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; + +import javax.annotation.PostConstruct; +import java.util.HashMap; +import java.util.Map; + +@Component +public class ProductRepository { + + private static final Logger LOGGER = LoggerFactory.getLogger(ProductRepository.class); + + private final Map productMap = new HashMap<>(); + + public Product getProduct(Long productId){ + LOGGER.info("Getting Product from Product Repo With Product Id {}", productId); + + if(!productMap.containsKey(productId)){ + LOGGER.error("Product Not Found for Product Id {}", productId); + throw new ProductNotFoundException("Product Not Found"); + } + + return productMap.get(productId); + } + + @PostConstruct + private void setupRepo() { + Product product1 = getProduct(100001, "apple"); + productMap.put(100001L, product1); + + Product product2 = getProduct(100002, "pears"); + productMap.put(100002L, product2); + + Product product3 = getProduct(100003, "banana"); + productMap.put(100003L, product3); + + Product product4 = getProduct(100004, "mango"); + productMap.put(100004L, product4); + + Product product5 = getProduct(100005, "test"); + productMap.put(100005L, product5); + } + + private static Product getProduct(int id, String name) { + Product product = new Product(); + product.setId(id); + product.setName(name); + return product; + } +} diff --git a/spring-cloud-modules/spring-cloud-open-telemetry/spring-cloud-open-telemetry1/src/main/resources/application.properties b/spring-cloud-modules/spring-cloud-open-telemetry/spring-cloud-open-telemetry1/src/main/resources/application.properties new file mode 100644 index 0000000000..1645b6144d --- /dev/null +++ b/spring-cloud-modules/spring-cloud-open-telemetry/spring-cloud-open-telemetry1/src/main/resources/application.properties @@ -0,0 +1,5 @@ +server.port= 8080 +spring.application.name=product-service +priceClient.baseUrl=http://price-service:8081 +spring.sleuth.otel.config.trace-id-ratio-based=1.0 +spring.sleuth.otel.exporter.otlp.endpoint=http://collector:4317 \ No newline at end of file diff --git a/spring-cloud-modules/spring-cloud-open-telemetry/spring-cloud-open-telemetry1/src/test/java/com/baeldung/opentelemetry/SpringContextTest.java b/spring-cloud-modules/spring-cloud-open-telemetry/spring-cloud-open-telemetry1/src/test/java/com/baeldung/opentelemetry/SpringContextTest.java new file mode 100644 index 0000000000..4f4a918cb4 --- /dev/null +++ b/spring-cloud-modules/spring-cloud-open-telemetry/spring-cloud-open-telemetry1/src/test/java/com/baeldung/opentelemetry/SpringContextTest.java @@ -0,0 +1,16 @@ +package com.baeldung.opentelemetry; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit.jupiter.SpringExtension; + + +@ExtendWith(SpringExtension.class) +@SpringBootTest(classes = ProductApplication.class) +class SpringContextTest { + + @Test + void whenSpringContextIsBootstrapped_thenNoExceptions() { + } +} diff --git a/spring-cloud-modules/spring-cloud-open-telemetry/spring-cloud-open-telemetry1/src/test/java/com/baeldung/opentelemetry/controller/ProductControllerUnitTest.java b/spring-cloud-modules/spring-cloud-open-telemetry/spring-cloud-open-telemetry1/src/test/java/com/baeldung/opentelemetry/controller/ProductControllerUnitTest.java new file mode 100644 index 0000000000..fcb3e23752 --- /dev/null +++ b/spring-cloud-modules/spring-cloud-open-telemetry/spring-cloud-open-telemetry1/src/test/java/com/baeldung/opentelemetry/controller/ProductControllerUnitTest.java @@ -0,0 +1,92 @@ +package com.baeldung.opentelemetry.controller; + +import com.baeldung.opentelemetry.api.client.PriceClient; +import com.baeldung.opentelemetry.exception.ProductNotFoundException; +import com.baeldung.opentelemetry.model.Price; +import com.baeldung.opentelemetry.model.Product; +import com.baeldung.opentelemetry.repository.ProductRepository; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; + +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.HttpStatus; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.web.client.HttpServerErrorException; + + +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + + +@ExtendWith(SpringExtension.class) +@WebMvcTest(ProductController.class) +class ProductControllerUnitTest { + + @MockBean + private PriceClient priceCLient; + + @MockBean + private ProductRepository productRepository; + + @Autowired + private MockMvc mockMvc; + + + @Test + void givenProductandPriceDataAvailable_whenGetProductCalled_thenReturnProductDetails() throws Exception { + long productId = 100000L; + + Price price = createPrice(productId); + Product product = createProduct(productId); + product.setPrice(price); + + when(productRepository.getProduct(productId)).thenReturn(product); + when(priceCLient.getPrice(productId)).thenReturn(price); + + mockMvc.perform(get("/product/" + productId)) + .andExpect(status().is(HttpStatus.OK.value())); + } + + @Test + void givenProductNotFound_whenGetProductCalled_thenReturnInternalServerError() throws Exception { + long productId = 100000L; + Price price = createPrice(productId); + + when(productRepository.getProduct(productId)).thenThrow(ProductNotFoundException.class); + when(priceCLient.getPrice(productId)).thenReturn(price); + + mockMvc.perform(get("/product/" + productId)) + .andExpect(status().is(HttpStatus.NOT_FOUND.value())); + } + + @Test + void givenPriceServiceNotAvailable_whenGetProductCalled_thenReturnInternalServerError() throws Exception { + long productId = 100000L; + Product product = createProduct(productId); + + when(productRepository.getProduct(productId)).thenReturn(product); + when(priceCLient.getPrice(productId)).thenThrow(HttpServerErrorException.ServiceUnavailable.class); + + mockMvc.perform(get("/product/" + productId)) + .andExpect(status().is(HttpStatus.INTERNAL_SERVER_ERROR.value())); + } + + private static Product createProduct(long productId) { + Product product = new Product(); + product.setId(productId); + product.setName("test"); + return product; + } + + private static Price createPrice(long productId) { + Price price = new Price(); + price.setProductId(productId); + price.setPriceAmount(12.00); + price.setDiscount(2.5); + return price; + } +} diff --git a/spring-cloud-modules/spring-cloud-open-telemetry/spring-cloud-open-telemetry2/Dockerfile b/spring-cloud-modules/spring-cloud-open-telemetry/spring-cloud-open-telemetry2/Dockerfile new file mode 100644 index 0000000000..fb0e3b3263 --- /dev/null +++ b/spring-cloud-modules/spring-cloud-open-telemetry/spring-cloud-open-telemetry2/Dockerfile @@ -0,0 +1,7 @@ +FROM adoptopenjdk/openjdk11:alpine + +COPY target/spring-cloud-open-telemetry2-1.0.0-SNAPSHOT.jar spring-cloud-open-telemetry.jar + +EXPOSE 8081 + +ENTRYPOINT ["java","-jar","/spring-cloud-open-telemetry.jar"] \ No newline at end of file diff --git a/spring-cloud-modules/spring-cloud-open-telemetry/spring-cloud-open-telemetry2/pom.xml b/spring-cloud-modules/spring-cloud-open-telemetry/spring-cloud-open-telemetry2/pom.xml new file mode 100644 index 0000000000..2d7ac8204e --- /dev/null +++ b/spring-cloud-modules/spring-cloud-open-telemetry/spring-cloud-open-telemetry2/pom.xml @@ -0,0 +1,115 @@ + + + 4.0.0 + spring-cloud-open-telemetry2 + com.baeldung.spring.cloud + 1.0.0-SNAPSHOT + spring-cloud-open-telemetry2 + jar + + + com.baeldung.spring.cloud + spring-cloud-open-telemetry + 1.0.0-SNAPSHOT + + + + + + org.springframework.boot + spring-boot-dependencies + ${spring-boot-dependencies.version} + pom + import + + + org.springframework.cloud + spring-cloud-dependencies + ${release.train.version} + pom + import + + + org.springframework.cloud + spring-cloud-sleuth-otel-dependencies + ${spring-cloud-sleuth-otel.version} + import + pom + + + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.cloud + spring-cloud-starter-sleuth + + + org.springframework.cloud + spring-cloud-sleuth-brave + + + + + org.springframework.cloud + spring-cloud-sleuth-otel-autoconfigure + + + io.opentelemetry + opentelemetry-exporter-otlp-trace + + + io.grpc + grpc-okhttp + ${grpc-okhttp.version} + + + org.junit.jupiter + junit-jupiter-engine + ${junit-jupiter.version} + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + repackage + + + + + + + + + + spring-milestones + https://repo.spring.io/milestone + + + + + spring-milestones + https://repo.spring.io/milestone + + + + + 2.5.7 + 2020.0.4 + 1.0.0-M12 + 1.42.1 + + + \ No newline at end of file diff --git a/spring-cloud-modules/spring-cloud-open-telemetry/spring-cloud-open-telemetry2/src/main/java/com/baeldung/opentelemetry/PriceApplication.java b/spring-cloud-modules/spring-cloud-open-telemetry/spring-cloud-open-telemetry2/src/main/java/com/baeldung/opentelemetry/PriceApplication.java new file mode 100644 index 0000000000..75db68c624 --- /dev/null +++ b/spring-cloud-modules/spring-cloud-open-telemetry/spring-cloud-open-telemetry2/src/main/java/com/baeldung/opentelemetry/PriceApplication.java @@ -0,0 +1,12 @@ +package com.baeldung.opentelemetry; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class PriceApplication { + + public static void main(String[] args) { + SpringApplication.run(PriceApplication.class, args); + } +} diff --git a/spring-cloud-modules/spring-cloud-open-telemetry/spring-cloud-open-telemetry2/src/main/java/com/baeldung/opentelemetry/controller/PriceController.java b/spring-cloud-modules/spring-cloud-open-telemetry/spring-cloud-open-telemetry2/src/main/java/com/baeldung/opentelemetry/controller/PriceController.java new file mode 100644 index 0000000000..6142d4da10 --- /dev/null +++ b/spring-cloud-modules/spring-cloud-open-telemetry/spring-cloud-open-telemetry2/src/main/java/com/baeldung/opentelemetry/controller/PriceController.java @@ -0,0 +1,29 @@ +package com.baeldung.opentelemetry.controller; + +import com.baeldung.opentelemetry.model.Price; +import com.baeldung.opentelemetry.repository.PriceRepository; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class PriceController { + + private static final Logger LOGGER = LoggerFactory.getLogger(PriceController.class); + + private final PriceRepository priceRepository; + + @Autowired + public PriceController(PriceRepository priceRepository) { + this.priceRepository = priceRepository; + } + + @GetMapping(path = "/price/{id}") + public Price getPrice(@PathVariable("id") long productId) { + LOGGER.info("Getting Price details for Product Id {}", productId); + return priceRepository.getPrice(productId); + } +} diff --git a/spring-cloud-modules/spring-cloud-open-telemetry/spring-cloud-open-telemetry2/src/main/java/com/baeldung/opentelemetry/exception/PriceNotFoundException.java b/spring-cloud-modules/spring-cloud-open-telemetry/spring-cloud-open-telemetry2/src/main/java/com/baeldung/opentelemetry/exception/PriceNotFoundException.java new file mode 100644 index 0000000000..6e1c5e0b5e --- /dev/null +++ b/spring-cloud-modules/spring-cloud-open-telemetry/spring-cloud-open-telemetry2/src/main/java/com/baeldung/opentelemetry/exception/PriceNotFoundException.java @@ -0,0 +1,7 @@ +package com.baeldung.opentelemetry.exception; + +public class PriceNotFoundException extends RuntimeException { + public PriceNotFoundException(String priceNotFound) { + super(priceNotFound); + } +} diff --git a/spring-cloud-modules/spring-cloud-open-telemetry/spring-cloud-open-telemetry2/src/main/java/com/baeldung/opentelemetry/exception/ProductControllerAdvice.java b/spring-cloud-modules/spring-cloud-open-telemetry/spring-cloud-open-telemetry2/src/main/java/com/baeldung/opentelemetry/exception/ProductControllerAdvice.java new file mode 100644 index 0000000000..fe7789ecf5 --- /dev/null +++ b/spring-cloud-modules/spring-cloud-open-telemetry/spring-cloud-open-telemetry2/src/main/java/com/baeldung/opentelemetry/exception/ProductControllerAdvice.java @@ -0,0 +1,16 @@ +package com.baeldung.opentelemetry.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +@RestControllerAdvice +public class ProductControllerAdvice { + + @ExceptionHandler(PriceNotFoundException.class) + public ResponseEntity handlePriceNotFoundException(PriceNotFoundException exception) { + return new ResponseEntity<>(exception.getMessage(), HttpStatus.NOT_FOUND); + } + +} diff --git a/spring-cloud-modules/spring-cloud-open-telemetry/spring-cloud-open-telemetry2/src/main/java/com/baeldung/opentelemetry/model/Price.java b/spring-cloud-modules/spring-cloud-open-telemetry/spring-cloud-open-telemetry2/src/main/java/com/baeldung/opentelemetry/model/Price.java new file mode 100644 index 0000000000..0e30c4e25b --- /dev/null +++ b/spring-cloud-modules/spring-cloud-open-telemetry/spring-cloud-open-telemetry2/src/main/java/com/baeldung/opentelemetry/model/Price.java @@ -0,0 +1,27 @@ +package com.baeldung.opentelemetry.model; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class Price { + + @JsonProperty("productId") + private long productId; + + @JsonProperty("price_amount") + private double priceAmount; + + @JsonProperty("discount") + private double discount; + + public void setDiscount(double discount) { + this.discount = discount; + } + + public void setProductId(long productId) { + this.productId = productId; + } + + public void setPriceAmount(double priceAmount) { + this.priceAmount = priceAmount; + } +} diff --git a/spring-cloud-modules/spring-cloud-open-telemetry/spring-cloud-open-telemetry2/src/main/java/com/baeldung/opentelemetry/repository/PriceRepository.java b/spring-cloud-modules/spring-cloud-open-telemetry/spring-cloud-open-telemetry2/src/main/java/com/baeldung/opentelemetry/repository/PriceRepository.java new file mode 100644 index 0000000000..63af7548d9 --- /dev/null +++ b/spring-cloud-modules/spring-cloud-open-telemetry/spring-cloud-open-telemetry2/src/main/java/com/baeldung/opentelemetry/repository/PriceRepository.java @@ -0,0 +1,53 @@ +package com.baeldung.opentelemetry.repository; + +import com.baeldung.opentelemetry.model.Price; +import com.baeldung.opentelemetry.exception.PriceNotFoundException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; + +import javax.annotation.PostConstruct; +import java.util.HashMap; +import java.util.Map; + +@Component +public class PriceRepository { + + private static final Logger LOGGER = LoggerFactory.getLogger(PriceRepository.class); + + private final Map priceMap = new HashMap<>(); + + public Price getPrice(Long productId){ + LOGGER.info("Getting Price from Price Repo With Product Id {}", productId); + + if(!priceMap.containsKey(productId)){ + LOGGER.error("Price Not Found for Product Id {}", productId); + throw new PriceNotFoundException("Product Not Found"); + } + + return priceMap.get(productId); + } + + @PostConstruct + private void setupRepo(){ + Price price1 = getPrice(100001L, 12.5, 2.5); + priceMap.put(100001L, price1); + + Price price2 = getPrice(100002L, 10.5, 2.1); + priceMap.put(100002L, price2); + + Price price3 = getPrice(100003L, 18.5, 2.0); + priceMap.put(100003L, price3); + + Price price4 = getPrice(100004L, 18.5, 2.0); + priceMap.put(100004L, price4); + } + + private static Price getPrice(long productId, double priceAmount, double discount) { + Price price = new Price(); + price.setProductId(productId); + price.setPriceAmount(priceAmount); + price.setDiscount(discount); + return price; + } +} \ No newline at end of file diff --git a/spring-cloud-modules/spring-cloud-open-telemetry/spring-cloud-open-telemetry2/src/main/resources/application.properties b/spring-cloud-modules/spring-cloud-open-telemetry/spring-cloud-open-telemetry2/src/main/resources/application.properties new file mode 100644 index 0000000000..03b80ae271 --- /dev/null +++ b/spring-cloud-modules/spring-cloud-open-telemetry/spring-cloud-open-telemetry2/src/main/resources/application.properties @@ -0,0 +1,4 @@ +server.port= 8081 +spring.application.name=price-service +spring.sleuth.otel.config.trace-id-ratio-based=1.0 +spring.sleuth.otel.exporter.otlp.endpoint=http://collector:4317 diff --git a/spring-cloud-modules/spring-cloud-open-telemetry/spring-cloud-open-telemetry2/src/test/java/com/baeldung/opentelemetry/SpringContextTest.java b/spring-cloud-modules/spring-cloud-open-telemetry/spring-cloud-open-telemetry2/src/test/java/com/baeldung/opentelemetry/SpringContextTest.java new file mode 100644 index 0000000000..524cc30567 --- /dev/null +++ b/spring-cloud-modules/spring-cloud-open-telemetry/spring-cloud-open-telemetry2/src/test/java/com/baeldung/opentelemetry/SpringContextTest.java @@ -0,0 +1,16 @@ +package com.baeldung.opentelemetry; + +import org.junit.jupiter.api.Test; + +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +@ExtendWith(SpringExtension.class) +@SpringBootTest(classes = PriceApplication.class) +class SpringContextTest { + + @Test + void whenSpringContextIsBootstrapped_thenNoException() { + } +} diff --git a/spring-cloud-modules/spring-cloud-open-telemetry/spring-cloud-open-telemetry2/src/test/java/com/baeldung/opentelemetry/controller/PriceControllerUnitTest.java b/spring-cloud-modules/spring-cloud-open-telemetry/spring-cloud-open-telemetry2/src/test/java/com/baeldung/opentelemetry/controller/PriceControllerUnitTest.java new file mode 100644 index 0000000000..7fd87c99d1 --- /dev/null +++ b/spring-cloud-modules/spring-cloud-open-telemetry/spring-cloud-open-telemetry2/src/test/java/com/baeldung/opentelemetry/controller/PriceControllerUnitTest.java @@ -0,0 +1,57 @@ +package com.baeldung.opentelemetry.controller; + + +import com.baeldung.opentelemetry.exception.PriceNotFoundException; +import com.baeldung.opentelemetry.model.Price; +import com.baeldung.opentelemetry.repository.PriceRepository; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.HttpStatus; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.test.web.servlet.MockMvc; + +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + + +@ExtendWith(SpringExtension.class) +@WebMvcTest(PriceController.class) +class PriceControllerUnitTest { + + @MockBean + private PriceRepository priceRepository; + + @Autowired + private MockMvc mockMvc; + + @Test + void givenProductandPriceAvailable_whenGetProductCalled_thenReturnProductDetails() throws Exception { + long productId = 100000L; + Price price = new Price(); + price.setProductId(productId); + price.setPriceAmount(12.00); + price.setDiscount(2.5); + + when(priceRepository.getPrice(productId)).thenReturn(price); + + mockMvc.perform(get("/price/" + productId)) + .andExpect(status().is(HttpStatus.OK.value())); + } + + + @Test + void givenProductNotFound_whenGetProductCalled_thenReturnInternalServerError() throws Exception { + long productId = 100000L; + + when(priceRepository.getPrice(productId)).thenThrow(PriceNotFoundException.class); + + mockMvc.perform(get("/price/" + productId)) + .andExpect(status().is(HttpStatus.NOT_FOUND.value())); + } + +}