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 <saikatcse03y@gmail.com>
This commit is contained in:
parent
35e516d31e
commit
382e9255fc
|
@ -55,6 +55,7 @@
|
||||||
<module>spring-cloud-data-flow</module>
|
<module>spring-cloud-data-flow</module>
|
||||||
<module>spring-cloud-sleuth</module>
|
<module>spring-cloud-sleuth</module>
|
||||||
<module>spring-cloud-openfeign-2</module>
|
<module>spring-cloud-openfeign-2</module>
|
||||||
|
<module>spring-cloud-open-telemetry</module>
|
||||||
</modules>
|
</modules>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
|
|
@ -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
|
|
@ -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 ]
|
|
@ -0,0 +1,22 @@
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<groupId>com.baeldung.spring.cloud</groupId>
|
||||||
|
<artifactId>spring-cloud-open-telemetry</artifactId>
|
||||||
|
<version>1.0.0-SNAPSHOT</version>
|
||||||
|
<name>spring-cloud-open-telemetry</name>
|
||||||
|
<packaging>pom</packaging>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<groupId>com.baeldung.spring.cloud</groupId>
|
||||||
|
<artifactId>spring-cloud-modules</artifactId>
|
||||||
|
<version>1.0.0-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<modules>
|
||||||
|
<module>spring-cloud-open-telemetry1</module>
|
||||||
|
<module>spring-cloud-open-telemetry2</module>
|
||||||
|
</modules>
|
||||||
|
|
||||||
|
</project>
|
|
@ -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"]
|
|
@ -0,0 +1,116 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<artifactId>spring-cloud-open-telemetry1</artifactId>
|
||||||
|
<groupId>com.baeldung.spring.cloud</groupId>
|
||||||
|
<version>1.0.0-SNAPSHOT</version>
|
||||||
|
<name>spring-cloud-open-telemetry1</name>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<groupId>com.baeldung.spring.cloud</groupId>
|
||||||
|
<artifactId>spring-cloud-open-telemetry</artifactId>
|
||||||
|
<version>1.0.0-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<dependencyManagement>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-dependencies</artifactId>
|
||||||
|
<version>${spring-boot-dependencies.version}</version>
|
||||||
|
<type>pom</type>
|
||||||
|
<scope>import</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.cloud</groupId>
|
||||||
|
<artifactId>spring-cloud-dependencies</artifactId>
|
||||||
|
<version>${release.train.version}</version>
|
||||||
|
<type>pom</type>
|
||||||
|
<scope>import</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.cloud</groupId>
|
||||||
|
<artifactId>spring-cloud-sleuth-otel-dependencies</artifactId>
|
||||||
|
<version>${spring-cloud-sleuth-otel.version}</version>
|
||||||
|
<scope>import</scope>
|
||||||
|
<type>pom</type>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</dependencyManagement>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-web</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.cloud</groupId>
|
||||||
|
<artifactId>spring-cloud-starter-sleuth</artifactId>
|
||||||
|
<exclusions>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>org.springframework.cloud</groupId>
|
||||||
|
<artifactId>spring-cloud-sleuth-brave</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
</exclusions>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.cloud</groupId>
|
||||||
|
<artifactId>spring-cloud-sleuth-otel-autoconfigure</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.opentelemetry</groupId>
|
||||||
|
<artifactId>opentelemetry-exporter-otlp-trace</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.grpc</groupId>
|
||||||
|
<artifactId>grpc-okhttp</artifactId>
|
||||||
|
<version>${grpc-okhttp.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.junit.jupiter</groupId>
|
||||||
|
<artifactId>junit-jupiter-engine</artifactId>
|
||||||
|
<version>${junit-jupiter.version}</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<goals>
|
||||||
|
<goal>repackage</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
<repositories>
|
||||||
|
<repository>
|
||||||
|
<id>spring-milestones</id>
|
||||||
|
<url>https://repo.spring.io/milestone</url>
|
||||||
|
</repository>
|
||||||
|
</repositories>
|
||||||
|
<pluginRepositories>
|
||||||
|
<pluginRepository>
|
||||||
|
<id>spring-milestones</id>
|
||||||
|
<url>https://repo.spring.io/milestone</url>
|
||||||
|
</pluginRepository>
|
||||||
|
</pluginRepositories>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<spring-boot-dependencies.version>2.5.7</spring-boot-dependencies.version>
|
||||||
|
<release.train.version>2020.0.4</release.train.version>
|
||||||
|
<spring-cloud-sleuth-otel.version>1.0.0-M12</spring-cloud-sleuth-otel.version>
|
||||||
|
<grpc-okhttp.version>1.42.1</grpc-okhttp.version>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
</project>
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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> price = restTemplate.getForEntity(url, Price.class);
|
||||||
|
return price.getBody();
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<Object> handleProductNotFoundException(ProductNotFoundException exception) {
|
||||||
|
return new ResponseEntity<>(exception.getMessage(), HttpStatus.NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ExceptionHandler(HttpServerErrorException.ServiceUnavailable.class)
|
||||||
|
public ResponseEntity<Object> handleException(HttpServerErrorException.ServiceUnavailable exception) {
|
||||||
|
return new ResponseEntity<>(exception.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
package com.baeldung.opentelemetry.exception;
|
||||||
|
|
||||||
|
public class ProductNotFoundException extends RuntimeException {
|
||||||
|
public ProductNotFoundException(String productNotFound) {
|
||||||
|
super(productNotFound);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<Long, Product> 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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
|
@ -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() {
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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"]
|
|
@ -0,0 +1,115 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<artifactId>spring-cloud-open-telemetry2</artifactId>
|
||||||
|
<groupId>com.baeldung.spring.cloud</groupId>
|
||||||
|
<version>1.0.0-SNAPSHOT</version>
|
||||||
|
<name>spring-cloud-open-telemetry2</name>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<groupId>com.baeldung.spring.cloud</groupId>
|
||||||
|
<artifactId>spring-cloud-open-telemetry</artifactId>
|
||||||
|
<version>1.0.0-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<dependencyManagement>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-dependencies</artifactId>
|
||||||
|
<version>${spring-boot-dependencies.version}</version>
|
||||||
|
<type>pom</type>
|
||||||
|
<scope>import</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.cloud</groupId>
|
||||||
|
<artifactId>spring-cloud-dependencies</artifactId>
|
||||||
|
<version>${release.train.version}</version>
|
||||||
|
<type>pom</type>
|
||||||
|
<scope>import</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.cloud</groupId>
|
||||||
|
<artifactId>spring-cloud-sleuth-otel-dependencies</artifactId>
|
||||||
|
<version>${spring-cloud-sleuth-otel.version}</version>
|
||||||
|
<scope>import</scope>
|
||||||
|
<type>pom</type>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</dependencyManagement>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-web</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.cloud</groupId>
|
||||||
|
<artifactId>spring-cloud-starter-sleuth</artifactId>
|
||||||
|
<exclusions>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>org.springframework.cloud</groupId>
|
||||||
|
<artifactId>spring-cloud-sleuth-brave</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
</exclusions>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.cloud</groupId>
|
||||||
|
<artifactId>spring-cloud-sleuth-otel-autoconfigure</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.opentelemetry</groupId>
|
||||||
|
<artifactId>opentelemetry-exporter-otlp-trace</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.grpc</groupId>
|
||||||
|
<artifactId>grpc-okhttp</artifactId>
|
||||||
|
<version>${grpc-okhttp.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.junit.jupiter</groupId>
|
||||||
|
<artifactId>junit-jupiter-engine</artifactId>
|
||||||
|
<version>${junit-jupiter.version}</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<goals>
|
||||||
|
<goal>repackage</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
<repositories>
|
||||||
|
<repository>
|
||||||
|
<id>spring-milestones</id>
|
||||||
|
<url>https://repo.spring.io/milestone</url>
|
||||||
|
</repository>
|
||||||
|
</repositories>
|
||||||
|
<pluginRepositories>
|
||||||
|
<pluginRepository>
|
||||||
|
<id>spring-milestones</id>
|
||||||
|
<url>https://repo.spring.io/milestone</url>
|
||||||
|
</pluginRepository>
|
||||||
|
</pluginRepositories>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<spring-boot-dependencies.version>2.5.7</spring-boot-dependencies.version>
|
||||||
|
<release.train.version>2020.0.4</release.train.version>
|
||||||
|
<spring-cloud-sleuth-otel.version>1.0.0-M12</spring-cloud-sleuth-otel.version>
|
||||||
|
<grpc-okhttp.version>1.42.1</grpc-okhttp.version>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
</project>
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
package com.baeldung.opentelemetry.exception;
|
||||||
|
|
||||||
|
public class PriceNotFoundException extends RuntimeException {
|
||||||
|
public PriceNotFoundException(String priceNotFound) {
|
||||||
|
super(priceNotFound);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<Object> handlePriceNotFoundException(PriceNotFoundException exception) {
|
||||||
|
return new ResponseEntity<>(exception.getMessage(), HttpStatus.NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<Long, Price> 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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
|
@ -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() {
|
||||||
|
}
|
||||||
|
}
|
|
@ -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()));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue