diff --git a/README.md b/README.md index 4057c7673434fe2fc683e412a3187d52c66d3f5e..8d131ff2be2f455da1a0b88ee639e8057a34a78d 100644 --- a/README.md +++ b/README.md @@ -85,17 +85,42 @@ ENV SPRING_PROFILES_ACTIVE docker,mysql In the `mysql section` of the `application.yml` from the [Configuration repository], you have to change the host and port of your MySQL JDBC connection string. +## Custom metrics monitoring + +@todo Add default custom dashboards to grafana + +Grafana and Prometheus are included in the `docker-compose.yml` configuration, and the public facing applications have been instrumented with [MicroMeter](https://micrometer.io) to collect JVM and custom business metrics. + +### Using Prometheus + +* Prometheus can be accessed from your local machine at http://localhost:9091 + +### Using Grafana with Prometheus + +* Login to Grafana at http://localhost:3000, the default user/pass is `admin:admin`, you will be prompted to change your password. +* Setup a prometheus datasource and point the URL to `http://prometheus-server:9090`, leave all the other options set to their default. +* Add the [Micrometer/SpringBoot dashboard](https://grafana.com/dashboards/4701) via the Import Dashboard menu item. The id for the dashboard is `4701` + +### Custom metrics implementation + +* `customers-service` application has the following custom metrics enabled: + * counter: `create.owner` + * counter: `update.owner` + * counter: `create.pet` + * counter: `update.pet` +* `visits-service` application has the following custom metrics enabled: + * counter: `create.visit` ## Looking for something in particular? -| Spring Cloud components | Resources | -|-------------------------|------------| -| Configuration server | [Config server properties](spring-petclinic-config-server/src/main/resources/application.yml) and [Configuration repository] | -| Service Discovery | [Eureka server](spring-petclinic-discovery-server) and [Service discovery client](spring-petclinic-vets-service/src/main/java/org/springframework/samples/petclinic/vets/VetsServiceApplication.java) | -| API Gateway | [Zuul reverse proxy](spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/ApiGatewayApplication.java) and [Routing configuration](https://github.com/spring-petclinic/spring-petclinic-microservices-config/blob/master/api-gateway.yml) | -| Docker Compose | [Spring Boot with Docker guide](https://spring.io/guides/gs/spring-boot-docker/) and [docker-compose file](docker-compose.yml) | -| Circuit Breaker | [Circuit Breaker with Hystrix guide](https://spring.io/guides/gs/circuit-breaker/) and [fallback method configuration](spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/application/VisitsServiceClient.java) | -| Graphite Monitoring | TBD | +| Spring Cloud components | Resources | +|---------------------------------|------------| +| Configuration server | [Config server properties](spring-petclinic-config-server/src/main/resources/application.yml) and [Configuration repository] | +| Service Discovery | [Eureka server](spring-petclinic-discovery-server) and [Service discovery client](spring-petclinic-vets-service/src/main/java/org/springframework/samples/petclinic/vets/VetsServiceApplication.java) | +| API Gateway | [Zuul reverse proxy](spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/ApiGatewayApplication.java) and [Routing configuration](https://github.com/spring-petclinic/spring-petclinic-microservices-config/blob/master/api-gateway.yml) | +| Docker Compose | [Spring Boot with Docker guide](https://spring.io/guides/gs/spring-boot-docker/) and [docker-compose file](docker-compose.yml) | +| Circuit Breaker | TBD | +| Grafana / Prometheus Monitoring | [Micrometer implementation](https://micrometer.io/) | Front-end module | Files | |-------------------|-------| diff --git a/docker-compose.yml b/docker-compose.yml index 50efcee448ecb95d530bdf6b153d6b5e9eb05b5a..1209bd7b4d87be0ae1cdc76d3e077193e021aa7e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,4 +1,8 @@ version: '2' + +volumes: + graf-data: + services: config-server: image: mszarlinski/spring-petclinic-config-server @@ -91,3 +95,22 @@ services: entrypoint: ["./dockerize","-wait=tcp://discovery-server:8761","-timeout=60s","--","java", "-XX:+UnlockExperimentalVMOptions", "-XX:+UseCGroupMemoryLimitForHeap", "-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"] ports: - 7979:7979 + + ## Grafana / Prometheus + + grafana-server: + image: grafana/grafana:5.2.4 + container_name: grafana-server + mem_limit: 256M + ports: + - 3000:3000 + volumes: + - graf-data:/var/lib/grafana + + prometheus-server: + build: ./docker/prometheus + image: prometheus-local:v2.4.2 + container_name: prometheus-server + mem_limit: 256M + ports: + - 9091:9090 diff --git a/docker/prometheus/Dockerfile b/docker/prometheus/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..58626f638cbe8799377e2389668077db2f35c4a4 --- /dev/null +++ b/docker/prometheus/Dockerfile @@ -0,0 +1,2 @@ +FROM prom/prometheus:v2.4.2 +ADD prometheus.yml /etc/prometheus/ diff --git a/docker/prometheus/prometheus.yml b/docker/prometheus/prometheus.yml new file mode 100644 index 0000000000000000000000000000000000000000..30f089b72b1fea34e73f61bb4ae2418d05f679d7 --- /dev/null +++ b/docker/prometheus/prometheus.yml @@ -0,0 +1,32 @@ +# my global config +global: + scrape_interval: 15s # Set the scrape interval to every 15 seconds. Default is every 1 minute. + evaluation_interval: 15s # Evaluate rules every 15 seconds. The default is every 1 minute. + # scrape_timeout is set to the global default (10s). + +# A scrape configuration containing exactly one endpoint to scrape: +# Here it's Prometheus itself. +scrape_configs: +- job_name: prometheus + static_configs: + - targets: ['localhost:9090'] + +- job_name: api-gateway + metrics_path: /actuator/prometheus + static_configs: + - targets: ['api-gateway:8080'] + +- job_name: customers-service + metrics_path: /actuator/prometheus + static_configs: + - targets: ['customers-service:8081'] + +- job_name: visits-service + metrics_path: /actuator/prometheus + static_configs: + - targets: ['visits-service:8082'] + +- job_name: vets-service + metrics_path: /actuator/prometheus + static_configs: + - targets: ['vets-service:8083'] diff --git a/pom.xml b/pom.xml index 72c02e268390c8d1dea3fa8809a83a94a59ce441..1958e248768cf502a2b84bb34f7e1a554e1672f9 100644 --- a/pom.xml +++ b/pom.xml @@ -33,6 +33,7 @@ <spring-boot.version>2.0.4.RELEASE</spring-boot.version> <spring-cloud.version>Finchley.SR2</spring-cloud.version> <sleuth.version>2.0.0.RC2</sleuth.version> + <micrometer.version>1.0.5</micrometer.version> <maven-surefire-plugin.version>2.22.0</maven-surefire-plugin.version> @@ -77,6 +78,19 @@ <version>${assertj.version}</version> <scope>test</scope> </dependency> + + <!-- Micrometer core dependency --> + <dependency> + <groupId>io.micrometer</groupId> + <artifactId>micrometer-core</artifactId> + <version>${micrometer.version}</version> + </dependency> + <!-- Micrometer Prometheus registry --> + <dependency> + <groupId>io.micrometer</groupId> + <artifactId>micrometer-registry-prometheus</artifactId> + <version>${micrometer.version}</version> + </dependency> </dependencies> </dependencyManagement> diff --git a/spring-petclinic-api-gateway/pom.xml b/spring-petclinic-api-gateway/pom.xml index 72370d2c3a6278fbf013fd2e97ab1134be47a43c..ae40e34eb04dc67f455d2d70230d02ab627cf1ea 100644 --- a/spring-petclinic-api-gateway/pom.xml +++ b/spring-petclinic-api-gateway/pom.xml @@ -87,6 +87,14 @@ <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> + <dependency> + <groupId>io.micrometer</groupId> + <artifactId>micrometer-core</artifactId> + </dependency> + <dependency> + <groupId>io.micrometer</groupId> + <artifactId>micrometer-registry-prometheus</artifactId> + </dependency> <!-- Webjars --> <dependency> diff --git a/spring-petclinic-customers-service/pom.xml b/spring-petclinic-customers-service/pom.xml index 6786f8346532e754c32c9fac3b21811f427c70ec..dc5928e477bcc4cda5d72006c66700d5d07c1ac7 100644 --- a/spring-petclinic-customers-service/pom.xml +++ b/spring-petclinic-customers-service/pom.xml @@ -77,6 +77,14 @@ <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> + <dependency> + <groupId>io.micrometer</groupId> + <artifactId>micrometer-core</artifactId> + </dependency> + <dependency> + <groupId>io.micrometer</groupId> + <artifactId>micrometer-registry-prometheus</artifactId> + </dependency> <!-- Testing --> <dependency> diff --git a/spring-petclinic-customers-service/src/main/java/org/springframework/samples/petclinic/customers/web/OwnerResource.java b/spring-petclinic-customers-service/src/main/java/org/springframework/samples/petclinic/customers/web/OwnerResource.java index da748d0fa59a4cb75f74e7ddd8161ebec01100fc..80916f3efd0e60341bacff931fe56be06edfa759 100644 --- a/spring-petclinic-customers-service/src/main/java/org/springframework/samples/petclinic/customers/web/OwnerResource.java +++ b/spring-petclinic-customers-service/src/main/java/org/springframework/samples/petclinic/customers/web/OwnerResource.java @@ -15,6 +15,7 @@ */ package org.springframework.samples.petclinic.customers.web; +import io.micrometer.core.instrument.MeterRegistry; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; @@ -40,6 +41,7 @@ import java.util.Optional; class OwnerResource { private final OwnerRepository ownerRepository; + private final MeterRegistry registry; /** * Create Owner @@ -47,6 +49,7 @@ class OwnerResource { @PostMapping @ResponseStatus(HttpStatus.CREATED) public void createOwner(@Valid @RequestBody Owner owner) { + registry.counter("create.owner").increment(); ownerRepository.save(owner); } @@ -81,6 +84,7 @@ class OwnerResource { ownerModel.setAddress(ownerRequest.getAddress()); ownerModel.setTelephone(ownerRequest.getTelephone()); log.info("Saving owner {}", ownerModel); + registry.counter("update.owner").increment(); return ownerRepository.save(ownerModel); } } diff --git a/spring-petclinic-customers-service/src/main/java/org/springframework/samples/petclinic/customers/web/PetResource.java b/spring-petclinic-customers-service/src/main/java/org/springframework/samples/petclinic/customers/web/PetResource.java index c657ca370fb09c8f65f6acb46690419b5fb21d73..34c8c70d6da7487791803f8fc8b6638ce6c3fdc7 100644 --- a/spring-petclinic-customers-service/src/main/java/org/springframework/samples/petclinic/customers/web/PetResource.java +++ b/spring-petclinic-customers-service/src/main/java/org/springframework/samples/petclinic/customers/web/PetResource.java @@ -15,6 +15,7 @@ */ package org.springframework.samples.petclinic.customers.web; +import io.micrometer.core.instrument.MeterRegistry; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; @@ -36,8 +37,9 @@ import java.util.Optional; class PetResource { private final PetRepository petRepository; - private final OwnerRepository ownerRepository; + private final MeterRegistry registry; + @GetMapping("/petTypes") public List<PetType> getPetTypes() { @@ -55,6 +57,7 @@ class PetResource { Owner owner = optionalOwner.orElseThrow(() -> new ResourceNotFoundException("Owner "+ownerId+" not found")); owner.addPet(pet); + registry.counter("create.pet").increment(); save(pet, petRequest); } @@ -63,6 +66,7 @@ class PetResource { public void processUpdateForm(@RequestBody PetRequest petRequest) { int petId = petRequest.getId(); Pet pet = findPetById(petId); + registry.counter("update.pet").increment(); save(pet, petRequest); } diff --git a/spring-petclinic-customers-service/src/test/java/org/springframework/samples/petclinic/customers/web/PetResourceTest.java b/spring-petclinic-customers-service/src/test/java/org/springframework/samples/petclinic/customers/web/PetResourceTest.java index 99313d23ebaf97a6c2ca2fc31ec9e035ff093dfb..5b2b3303c556b0efd3f1e1fd17a9d50a56cc3379 100644 --- a/spring-petclinic-customers-service/src/test/java/org/springframework/samples/petclinic/customers/web/PetResourceTest.java +++ b/spring-petclinic-customers-service/src/test/java/org/springframework/samples/petclinic/customers/web/PetResourceTest.java @@ -1,6 +1,8 @@ package org.springframework.samples.petclinic.customers.web; import java.util.Optional; + +import io.micrometer.core.instrument.MeterRegistry; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; @@ -40,6 +42,9 @@ class PetResourceTest { @MockBean OwnerRepository ownerRepository; + @MockBean + MeterRegistry registry; + @Test void shouldGetAPetInJSonFormat() throws Exception { diff --git a/spring-petclinic-vets-service/pom.xml b/spring-petclinic-vets-service/pom.xml index e7705a18d3f53a335acf20c4c93c879738b0498b..99b95770388e3001f753f5c1a1349d137fb523e7 100644 --- a/spring-petclinic-vets-service/pom.xml +++ b/spring-petclinic-vets-service/pom.xml @@ -93,6 +93,14 @@ <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> + <dependency> + <groupId>io.micrometer</groupId> + <artifactId>micrometer-core</artifactId> + </dependency> + <dependency> + <groupId>io.micrometer</groupId> + <artifactId>micrometer-registry-prometheus</artifactId> + </dependency> <!-- Testing --> <dependency> diff --git a/spring-petclinic-visits-service/pom.xml b/spring-petclinic-visits-service/pom.xml index ea9184d472b51f648aadb598c95732fe3c8e8f66..c60737406048892caf24887750a0a576bc6bbc50 100644 --- a/spring-petclinic-visits-service/pom.xml +++ b/spring-petclinic-visits-service/pom.xml @@ -76,6 +76,14 @@ <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> + <dependency> + <groupId>io.micrometer</groupId> + <artifactId>micrometer-core</artifactId> + </dependency> + <dependency> + <groupId>io.micrometer</groupId> + <artifactId>micrometer-registry-prometheus</artifactId> + </dependency> <!-- Testing --> <dependency> diff --git a/spring-petclinic-visits-service/src/main/java/org/springframework/samples/petclinic/visits/web/VisitResource.java b/spring-petclinic-visits-service/src/main/java/org/springframework/samples/petclinic/visits/web/VisitResource.java index 2ebab12e1b4fbc518d2d085e440d8c7161e4635f..5905f3715392fac0d3d7b59047ad84817caffd34 100644 --- a/spring-petclinic-visits-service/src/main/java/org/springframework/samples/petclinic/visits/web/VisitResource.java +++ b/spring-petclinic-visits-service/src/main/java/org/springframework/samples/petclinic/visits/web/VisitResource.java @@ -17,6 +17,8 @@ package org.springframework.samples.petclinic.visits.web; import java.util.List; import javax.validation.Valid; + +import io.micrometer.core.instrument.MeterRegistry; import lombok.RequiredArgsConstructor; import lombok.Value; import lombok.extern.slf4j.Slf4j; @@ -44,6 +46,7 @@ import org.springframework.web.bind.annotation.RestController; class VisitResource { private final VisitRepository visitRepository; + private final MeterRegistry registry; @PostMapping("owners/*/pets/{petId}/visits") @ResponseStatus(HttpStatus.NO_CONTENT) @@ -53,6 +56,7 @@ class VisitResource { visit.setPetId(petId); log.info("Saving visit {}", visit); + registry.counter("create.visit").increment(); visitRepository.save(visit); } diff --git a/spring-petclinic-visits-service/src/test/java/org/springframework/samples/petclinic/visits/web/VisitResourceTest.java b/spring-petclinic-visits-service/src/test/java/org/springframework/samples/petclinic/visits/web/VisitResourceTest.java index 25d33f24ba7bba4bbd962752b6f05a98acd786d3..3409b30abd3b848b888586e422a7cb84bc294481 100644 --- a/spring-petclinic-visits-service/src/test/java/org/springframework/samples/petclinic/visits/web/VisitResourceTest.java +++ b/spring-petclinic-visits-service/src/test/java/org/springframework/samples/petclinic/visits/web/VisitResourceTest.java @@ -1,5 +1,6 @@ package org.springframework.samples.petclinic.visits.web; +import io.micrometer.core.instrument.MeterRegistry; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; @@ -29,6 +30,9 @@ class VisitResourceTest { @MockBean VisitRepository visitRepository; + @MockBean + MeterRegistry registry; + @Test void shouldFetchVisits() throws Exception { given(visitRepository.findByPetIdIn(asList(111, 222)))