├── .github └── FUNDING.yml ├── .gitattributes ├── documentation ├── kibana.jpeg ├── prometheus.jpeg ├── project-diagram.jpeg ├── movies-api-grafana-dashboard.jpeg └── project-diagram.excalidraw ├── remove-docker-images.sh ├── movies-api ├── src │ ├── main │ │ ├── java │ │ │ └── com │ │ │ │ └── ivanfranchin │ │ │ │ └── moviesapi │ │ │ │ ├── movie │ │ │ │ ├── model │ │ │ │ │ ├── Genre.java │ │ │ │ │ └── Movie.java │ │ │ │ ├── MovieRepository.java │ │ │ │ ├── exception │ │ │ │ │ └── MovieNotFoundException.java │ │ │ │ ├── dto │ │ │ │ │ ├── CreateMovieRequest.java │ │ │ │ │ └── MovieResponse.java │ │ │ │ ├── MovieService.java │ │ │ │ └── MovieController.java │ │ │ │ ├── MoviesApiApplication.java │ │ │ │ └── config │ │ │ │ └── ErrorAttributesConfig.java │ │ └── resources │ │ │ ├── application.properties │ │ │ └── logback-spring.xml │ └── test │ │ └── java │ │ └── com │ │ └── ivanfranchin │ │ └── moviesapi │ │ └── MoviesApiApplicationTests.java └── pom.xml ├── grafana └── provisioning │ ├── datasources │ └── datasource.yml │ └── dashboards │ ├── dashboard.yml │ └── movies-api-dashboard.json ├── prometheus └── prometheus.yml ├── logstash └── pipeline │ └── logstash.conf ├── filebeat ├── Dockerfile └── filebeat.yml ├── .gitignore ├── .mvn └── wrapper │ └── maven-wrapper.properties ├── pom.xml ├── docker-compose.yml ├── mvnw.cmd ├── README.md └── mvnw /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: ivangfr 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | /mvnw text eol=lf 2 | *.cmd text eol=crlf 3 | -------------------------------------------------------------------------------- /documentation/kibana.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ivangfr/springboot-elk-prometheus-grafana/HEAD/documentation/kibana.jpeg -------------------------------------------------------------------------------- /documentation/prometheus.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ivangfr/springboot-elk-prometheus-grafana/HEAD/documentation/prometheus.jpeg -------------------------------------------------------------------------------- /documentation/project-diagram.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ivangfr/springboot-elk-prometheus-grafana/HEAD/documentation/project-diagram.jpeg -------------------------------------------------------------------------------- /remove-docker-images.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | docker rmi ivanfranchin/movies-api:1.0.0 4 | docker rmi springboot-elk-prometheus-grafana-filebeat:latest 5 | -------------------------------------------------------------------------------- /documentation/movies-api-grafana-dashboard.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ivangfr/springboot-elk-prometheus-grafana/HEAD/documentation/movies-api-grafana-dashboard.jpeg -------------------------------------------------------------------------------- /movies-api/src/main/java/com/ivanfranchin/moviesapi/movie/model/Genre.java: -------------------------------------------------------------------------------- 1 | package com.ivanfranchin.moviesapi.movie.model; 2 | 3 | public enum Genre { 4 | 5 | Biography, Comedy, Drama, Sport, Horror, Romance 6 | } 7 | -------------------------------------------------------------------------------- /grafana/provisioning/datasources/datasource.yml: -------------------------------------------------------------------------------- 1 | apiVersion: 1 2 | 3 | datasources: 4 | - name: Prometheus 5 | type: prometheus 6 | access: proxy 7 | orgId: 1 8 | url: http://prometheus:9090 9 | basicAuth: false 10 | isDefault: true 11 | editable: true -------------------------------------------------------------------------------- /grafana/provisioning/dashboards/dashboard.yml: -------------------------------------------------------------------------------- 1 | apiVersion: 1 2 | 3 | providers: 4 | - name: 'Prometheus' 5 | orgId: 1 6 | folder: '' 7 | type: file 8 | disableDeletion: false 9 | editable: true 10 | options: 11 | path: /etc/grafana/provisioning/dashboards -------------------------------------------------------------------------------- /prometheus/prometheus.yml: -------------------------------------------------------------------------------- 1 | global: 2 | scrape_interval: 5s 3 | evaluation_interval: 5s 4 | 5 | scrape_configs: 6 | 7 | - job_name: 'movies-api' 8 | metrics_path: '/actuator/prometheus' 9 | static_configs: 10 | - targets: ['host.docker.internal:8080'] 11 | labels: 12 | application: movies-api 13 | -------------------------------------------------------------------------------- /logstash/pipeline/logstash.conf: -------------------------------------------------------------------------------- 1 | input { 2 | beats { 3 | port => 5044 4 | host => "0.0.0.0" 5 | } 6 | } 7 | 8 | output { 9 | elasticsearch { 10 | hosts => elasticsearch 11 | manage_template => false 12 | index => "%{[@metadata][beat]}-%{[@metadata][version]}-%{+YYYY.MM.dd}" 13 | } 14 | 15 | stdout { codec => rubydebug } 16 | } -------------------------------------------------------------------------------- /filebeat/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM docker.elastic.co/beats/filebeat:7.17.28 2 | 3 | # Copy our custom configuration file 4 | COPY filebeat.yml /usr/share/filebeat/filebeat.yml 5 | 6 | USER root 7 | # Create a directory to map volume with all docker log files 8 | RUN mkdir /usr/share/filebeat/dockerlogs 9 | RUN chown -R root /usr/share/filebeat/ 10 | RUN chmod -R go-w /usr/share/filebeat/ -------------------------------------------------------------------------------- /movies-api/src/main/java/com/ivanfranchin/moviesapi/movie/MovieRepository.java: -------------------------------------------------------------------------------- 1 | package com.ivanfranchin.moviesapi.movie; 2 | 3 | import com.ivanfranchin.moviesapi.movie.model.Movie; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | import org.springframework.stereotype.Repository; 6 | 7 | @Repository 8 | public interface MovieRepository extends JpaRepository { 9 | } 10 | -------------------------------------------------------------------------------- /movies-api/src/test/java/com/ivanfranchin/moviesapi/MoviesApiApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.ivanfranchin.moviesapi; 2 | 3 | import org.junit.jupiter.api.Disabled; 4 | import org.junit.jupiter.api.Test; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | 7 | @Disabled 8 | @SpringBootTest 9 | class MoviesApiApplicationTests { 10 | 11 | @Test 12 | void contextLoads() { 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /movies-api/src/main/java/com/ivanfranchin/moviesapi/MoviesApiApplication.java: -------------------------------------------------------------------------------- 1 | package com.ivanfranchin.moviesapi; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class MoviesApiApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(MoviesApiApplication.class, args); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /movies-api/src/main/java/com/ivanfranchin/moviesapi/movie/exception/MovieNotFoundException.java: -------------------------------------------------------------------------------- 1 | package com.ivanfranchin.moviesapi.movie.exception; 2 | 3 | import org.springframework.http.HttpStatus; 4 | import org.springframework.web.bind.annotation.ResponseStatus; 5 | 6 | @ResponseStatus(HttpStatus.NOT_FOUND) 7 | public class MovieNotFoundException extends RuntimeException { 8 | 9 | public MovieNotFoundException(String imdbId) { 10 | super(String.format("Movie with imdbId %s not found", imdbId)); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /movies-api/src/main/java/com/ivanfranchin/moviesapi/movie/dto/CreateMovieRequest.java: -------------------------------------------------------------------------------- 1 | package com.ivanfranchin.moviesapi.movie.dto; 2 | 3 | import com.ivanfranchin.moviesapi.movie.model.Genre; 4 | import jakarta.validation.constraints.NotBlank; 5 | import jakarta.validation.constraints.NotNull; 6 | import jakarta.validation.constraints.Positive; 7 | 8 | public record CreateMovieRequest( 9 | @NotBlank String imdbId, 10 | @NotBlank String title, 11 | @Positive Short year, 12 | @NotNull Genre genre, 13 | @NotBlank String country) { 14 | } 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | !**/src/main/**/target/ 4 | !**/src/test/**/target/ 5 | 6 | ### STS ### 7 | .apt_generated 8 | .classpath 9 | .factorypath 10 | .project 11 | .settings 12 | .springBeans 13 | .sts4-cache 14 | 15 | ### IntelliJ IDEA ### 16 | .idea 17 | *.iws 18 | *.iml 19 | *.ipr 20 | 21 | ### NetBeans ### 22 | /nbproject/private/ 23 | /nbbuild/ 24 | /dist/ 25 | /nbdist/ 26 | /.nb-gradle/ 27 | build/ 28 | !**/src/main/**/build/ 29 | !**/src/test/**/build/ 30 | 31 | ### VS Code ### 32 | .vscode/ 33 | 34 | ### MAC OS ### 35 | *.DS_store 36 | -------------------------------------------------------------------------------- /movies-api/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring.application.name=movies-api 2 | 3 | spring.main.banner-mode=OFF 4 | 5 | spring.jpa.hibernate.ddl-auto=update 6 | spring.datasource.url=jdbc:mysql://${MYSQL_HOST:localhost}:${MYSQL_PORT:3306}/moviesdb?characterEncoding=UTF-8&serverTimezone=UTC 7 | spring.datasource.username=root 8 | spring.datasource.password=secret 9 | 10 | management.endpoints.web.exposure.include=beans,env,health,info,metrics,mappings,prometheus 11 | management.endpoint.health.show-details=always 12 | management.health.probes.enabled=true 13 | 14 | logging.level.com.ivanfranchin.moviesapi=DEBUG 15 | -------------------------------------------------------------------------------- /movies-api/src/main/java/com/ivanfranchin/moviesapi/movie/dto/MovieResponse.java: -------------------------------------------------------------------------------- 1 | package com.ivanfranchin.moviesapi.movie.dto; 2 | 3 | import com.ivanfranchin.moviesapi.movie.model.Movie; 4 | 5 | public record MovieResponse(String imdbId, String title, Short year, String genre, String country) { 6 | 7 | public static MovieResponse from(Movie movie) { 8 | String genre = null; 9 | if (movie.getGenre() != null) { 10 | genre = movie.getGenre().name(); 11 | } 12 | return new MovieResponse(movie.getImdbId(), movie.getTitle(), movie.getYear(), genre, movie.getCountry()); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /movies-api/src/main/java/com/ivanfranchin/moviesapi/movie/MovieService.java: -------------------------------------------------------------------------------- 1 | package com.ivanfranchin.moviesapi.movie; 2 | 3 | import com.ivanfranchin.moviesapi.movie.exception.MovieNotFoundException; 4 | import com.ivanfranchin.moviesapi.movie.model.Movie; 5 | import lombok.RequiredArgsConstructor; 6 | import org.springframework.stereotype.Service; 7 | 8 | import java.util.List; 9 | 10 | @RequiredArgsConstructor 11 | @Service 12 | public class MovieService { 13 | 14 | private final MovieRepository movieRepository; 15 | 16 | public Movie validateAndGetMovie(String imdbId) { 17 | return movieRepository.findById(imdbId).orElseThrow(() -> new MovieNotFoundException(imdbId)); 18 | } 19 | 20 | public List getMovies() { 21 | return movieRepository.findAll(); 22 | } 23 | 24 | public Movie createMovie(Movie movie) { 25 | return movieRepository.save(movie); 26 | } 27 | 28 | public void deleteMovie(Movie movie) { 29 | movieRepository.delete(movie); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, 12 | # software distributed under the License is distributed on an 13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | # KIND, either express or implied. See the License for the 15 | # specific language governing permissions and limitations 16 | # under the License. 17 | wrapperVersion=3.3.2 18 | distributionType=only-script 19 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip 20 | -------------------------------------------------------------------------------- /movies-api/src/main/java/com/ivanfranchin/moviesapi/config/ErrorAttributesConfig.java: -------------------------------------------------------------------------------- 1 | package com.ivanfranchin.moviesapi.config; 2 | 3 | import org.springframework.boot.web.error.ErrorAttributeOptions; 4 | import org.springframework.boot.web.error.ErrorAttributeOptions.Include; 5 | import org.springframework.boot.web.servlet.error.DefaultErrorAttributes; 6 | import org.springframework.boot.web.servlet.error.ErrorAttributes; 7 | import org.springframework.context.annotation.Bean; 8 | import org.springframework.context.annotation.Configuration; 9 | import org.springframework.web.context.request.WebRequest; 10 | 11 | import java.util.Map; 12 | 13 | @Configuration 14 | public class ErrorAttributesConfig { 15 | 16 | @Bean 17 | ErrorAttributes errorAttributes() { 18 | return new DefaultErrorAttributes() { 19 | @Override 20 | public Map getErrorAttributes(WebRequest webRequest, ErrorAttributeOptions options) { 21 | return super.getErrorAttributes(webRequest, 22 | options.including(Include.EXCEPTION, Include.MESSAGE, Include.BINDING_ERRORS)); 23 | } 24 | }; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /movies-api/src/main/java/com/ivanfranchin/moviesapi/movie/model/Movie.java: -------------------------------------------------------------------------------- 1 | package com.ivanfranchin.moviesapi.movie.model; 2 | 3 | import com.ivanfranchin.moviesapi.movie.dto.CreateMovieRequest; 4 | import jakarta.persistence.Column; 5 | import jakarta.persistence.Entity; 6 | import jakarta.persistence.Id; 7 | import jakarta.persistence.Table; 8 | import lombok.Data; 9 | 10 | @Data 11 | @Entity 12 | @Table(name = "movies") 13 | public class Movie { 14 | 15 | @Id 16 | private String imdbId; 17 | 18 | @Column(nullable = false) 19 | private String title; 20 | 21 | @Column(nullable = false) 22 | private Short year; 23 | 24 | @Column(nullable = false) 25 | private Genre genre; 26 | 27 | @Column(nullable = false) 28 | private String country; 29 | 30 | public static Movie from(CreateMovieRequest createMovieRequest) { 31 | Movie movie = new Movie(); 32 | movie.setImdbId(createMovieRequest.imdbId()); 33 | movie.setTitle(createMovieRequest.title()); 34 | movie.setYear(createMovieRequest.year()); 35 | movie.setGenre(createMovieRequest.genre()); 36 | movie.setCountry(createMovieRequest.country()); 37 | return movie; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 3.5.0 9 | 10 | 11 | com.ivanfranchin 12 | springboot-elk-prometheus-grafana 13 | 1.0.0 14 | pom 15 | springboot-elk-prometheus-grafana 16 | Demo project for Spring Boot 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 21 32 | 33 | 34 | movies-api 35 | 36 | 37 | -------------------------------------------------------------------------------- /filebeat/filebeat.yml: -------------------------------------------------------------------------------- 1 | filebeat.inputs: 2 | - type: docker 3 | combine_partial: true 4 | containers: 5 | path: "/usr/share/dockerlogs/data" 6 | stream: "stdout" 7 | ids: 8 | - "*" 9 | exclude_files: ['\.gz$'] 10 | ignore_older: 10m 11 | 12 | processors: 13 | # decode the log field (sub JSON document) if JSON encoded, then maps it's fields to elasticsearch fields 14 | - decode_json_fields: 15 | fields: ["log", "message", "msg"] 16 | target: "" 17 | # overwrite existing target elasticsearch fields while decoding json fields 18 | overwrite_keys: true 19 | - add_docker_metadata: 20 | # annotates each event with relevant metadata from Docker containers 21 | host: "unix:///var/run/docker.sock" 22 | - drop_fields: 23 | # drop the fields below in order to have a cleaner 24 | fields: ["container.labels.io_buildpacks_build_metadata", "container.labels.io_buildpacks_lifecycle_metadata", "container.labels.io_buildpacks_stack_id"] 25 | 26 | filebeat.config.modules: 27 | path: ${path.config}/modules.d/*.yml 28 | reload.enabled: false 29 | 30 | # setup filebeat to send output to logstash 31 | output.logstash: 32 | hosts: ["logstash"] 33 | 34 | # Write Filebeat own logs only to file to avoid catching them with itself in docker log files 35 | logging.level: info 36 | logging.to_files: false 37 | logging.to_syslog: false 38 | loggins.metrice.enabled: false 39 | logging.files: 40 | path: /var/log/filebeat 41 | name: filebeat 42 | keepfiles: 7 43 | permissions: 0644 44 | ssl.verification_mode: none -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | 3 | mysql: 4 | image: 'mysql:9.2.0' 5 | container_name: 'mysql' 6 | restart: 'unless-stopped' 7 | ports: 8 | - '3306:3306' 9 | environment: 10 | - 'MYSQL_DATABASE=moviesdb' 11 | - 'MYSQL_ROOT_PASSWORD=secret' 12 | healthcheck: 13 | test: "mysqladmin ping -u root -p$${MYSQL_ROOT_PASSWORD}" 14 | 15 | elasticsearch: 16 | image: 'docker.elastic.co/elasticsearch/elasticsearch:7.17.28' 17 | container_name: 'elasticsearch' 18 | restart: 'unless-stopped' 19 | ports: 20 | - '9200:9200' 21 | - '9300:9300' 22 | environment: 23 | - 'discovery.type=single-node' 24 | - 'xpack.security.enabled=false' 25 | - 'ES_JAVA_OPTS=-Xms512m -Xmx512m' 26 | healthcheck: 27 | test: 'curl -f http://localhost:9200 || exit 1' 28 | 29 | logstash: 30 | image: 'docker.elastic.co/logstash/logstash:7.17.28' 31 | container_name: 'logstash' 32 | restart: 'unless-stopped' 33 | ports: 34 | - '5044:5044' 35 | volumes: 36 | - './logstash/pipeline:/usr/share/logstash/pipeline/' 37 | depends_on: 38 | - 'elasticsearch' 39 | healthcheck: 40 | test: 'curl -f http://localhost:9600 || exit 1' 41 | 42 | kibana: 43 | image: 'docker.elastic.co/kibana/kibana:7.17.28' 44 | container_name: 'kibana' 45 | restart: 'unless-stopped' 46 | ports: 47 | - '5601:5601' 48 | environment: 49 | - 'ELASTICSEARCH_HOSTS=http://elasticsearch:9200' 50 | depends_on: 51 | - 'elasticsearch' 52 | healthcheck: 53 | test: 'curl -f http://localhost:5601 || exit 1' 54 | 55 | filebeat: 56 | build: 'filebeat' 57 | container_name: 'filebeat' 58 | restart: 'unless-stopped' 59 | volumes: 60 | - '/var/run/docker.sock:/var/run/docker.sock' 61 | - '/var/lib/docker/containers:/usr/share/dockerlogs/data:ro' 62 | depends_on: 63 | - 'elasticsearch' 64 | - 'logstash' 65 | 66 | prometheus: 67 | image: 'prom/prometheus:v3.4.0' 68 | container_name: 'prometheus' 69 | restart: 'unless-stopped' 70 | volumes: 71 | - './prometheus/prometheus.yml:/etc/prometheus/prometheus.yml' 72 | ports: 73 | - '9090:9090' 74 | healthcheck: 75 | test: [ "CMD", "nc", "-z", "localhost", "9090" ] 76 | 77 | grafana: 78 | image: 'grafana/grafana:12.0.1' 79 | container_name: 'grafana' 80 | restart: 'unless-stopped' 81 | ports: 82 | - '3000:3000' 83 | environment: 84 | - 'GF_USERS_ALLOW_SIGN_UP=false' 85 | volumes: 86 | - './grafana/provisioning:/etc/grafana/provisioning' 87 | depends_on: 88 | - 'prometheus' 89 | healthcheck: 90 | test: [ "CMD", "nc", "-z", "localhost", "3000" ] 91 | -------------------------------------------------------------------------------- /movies-api/src/main/java/com/ivanfranchin/moviesapi/movie/MovieController.java: -------------------------------------------------------------------------------- 1 | package com.ivanfranchin.moviesapi.movie; 2 | 3 | import com.ivanfranchin.moviesapi.movie.model.Movie; 4 | import com.ivanfranchin.moviesapi.movie.dto.CreateMovieRequest; 5 | import com.ivanfranchin.moviesapi.movie.dto.MovieResponse; 6 | import jakarta.validation.Valid; 7 | import lombok.RequiredArgsConstructor; 8 | import lombok.extern.slf4j.Slf4j; 9 | import org.springframework.http.HttpStatus; 10 | import org.springframework.web.bind.annotation.DeleteMapping; 11 | import org.springframework.web.bind.annotation.GetMapping; 12 | import org.springframework.web.bind.annotation.PathVariable; 13 | import org.springframework.web.bind.annotation.PostMapping; 14 | import org.springframework.web.bind.annotation.RequestBody; 15 | import org.springframework.web.bind.annotation.RequestMapping; 16 | import org.springframework.web.bind.annotation.ResponseStatus; 17 | import org.springframework.web.bind.annotation.RestController; 18 | 19 | import java.util.List; 20 | 21 | import static net.logstash.logback.argument.StructuredArguments.kv; 22 | 23 | @Slf4j 24 | @RequiredArgsConstructor 25 | @RestController 26 | @RequestMapping("/api/movies") 27 | public class MovieController { 28 | 29 | private final MovieService movieService; 30 | 31 | @GetMapping 32 | public List getMovies() { 33 | log.debug("Get all movies"); 34 | return movieService.getMovies().stream() 35 | .map(MovieResponse::from) 36 | .toList(); 37 | } 38 | 39 | @GetMapping("/{imdbId}") 40 | public MovieResponse getMovie(@PathVariable("imdbId") String imdbId) { 41 | log.debug("Get movie {}", kv("imdbId", imdbId)); 42 | if (imdbId.equals("111")) { 43 | throw new NullPointerException("It's known that there is a bug with this movie"); 44 | } 45 | Movie movie = movieService.validateAndGetMovie(imdbId); 46 | return MovieResponse.from(movie); 47 | } 48 | 49 | @ResponseStatus(HttpStatus.CREATED) 50 | @PostMapping 51 | public MovieResponse createMovie(@Valid @RequestBody CreateMovieRequest createMovieRequest) { 52 | log.debug("Movie created {}", kv("imdbId", createMovieRequest.imdbId())); 53 | Movie movie = Movie.from(createMovieRequest); 54 | movie = movieService.createMovie(movie); 55 | return MovieResponse.from(movie); 56 | } 57 | 58 | @DeleteMapping("/{imdbId}") 59 | public String deleteMovie(@PathVariable("imdbId") String imdbId) { 60 | log.debug("Movie deleted {}", kv("imdbId", imdbId)); 61 | Movie movie = movieService.validateAndGetMovie(imdbId); 62 | movieService.deleteMovie(movie); 63 | return movie.getImdbId(); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /movies-api/src/main/resources/logback-spring.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ${spring.application.name} 5 | 6 | 7 | 8 | 9 | 10 | 11 | { 12 | "trace": "%X{traceId:-}", 13 | "span": "%X{spanId:-}", 14 | "parent": "%X{parentId:-}", 15 | "exportable": "%X{sampled:-}", 16 | "pid": "${PID:-}" 17 | } 18 | 19 | 20 | 21 | app 22 | 23 | 24 | ts 25 | UTC 26 | 27 | 28 | logger 29 | 30 | 31 | level 32 | 33 | 34 | class 35 | method 36 | line 37 | file 38 | 39 | 40 | thread 41 | 42 | 43 | 44 | false 45 | 46 | 47 | stack 48 | 49 | 50 | msg 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /movies-api/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | com.ivanfranchin 7 | springboot-elk-prometheus-grafana 8 | 1.0.0 9 | ../pom.xml 10 | 11 | movies-api 12 | movies-api 13 | Demo project for Spring Boot 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 8.1 29 | 30 | 31 | 32 | org.springframework.boot 33 | spring-boot-starter-actuator 34 | 35 | 36 | org.springframework.boot 37 | spring-boot-starter-data-jpa 38 | 39 | 40 | org.springframework.boot 41 | spring-boot-starter-validation 42 | 43 | 44 | org.springframework.boot 45 | spring-boot-starter-web 46 | 47 | 48 | io.micrometer 49 | micrometer-tracing-bridge-brave 50 | 51 | 52 | 53 | 54 | net.logstash.logback 55 | logstash-logback-encoder 56 | ${logstash-logback-encoder.version} 57 | 58 | 59 | 60 | com.mysql 61 | mysql-connector-j 62 | runtime 63 | 64 | 65 | io.micrometer 66 | micrometer-registry-prometheus 67 | runtime 68 | 69 | 70 | org.projectlombok 71 | lombok 72 | true 73 | 74 | 75 | org.springframework.boot 76 | spring-boot-starter-test 77 | test 78 | 79 | 80 | 81 | 82 | 83 | 84 | org.apache.maven.plugins 85 | maven-compiler-plugin 86 | 87 | 88 | 89 | org.projectlombok 90 | lombok 91 | 92 | 93 | 94 | 95 | 96 | org.hibernate.orm.tooling 97 | hibernate-enhance-maven-plugin 98 | ${hibernate.version} 99 | 100 | 101 | enhance 102 | 103 | enhance 104 | 105 | 106 | true 107 | true 108 | true 109 | 110 | 111 | 112 | 113 | 114 | org.graalvm.buildtools 115 | native-maven-plugin 116 | 117 | 118 | org.springframework.boot 119 | spring-boot-maven-plugin 120 | 121 | 122 | 123 | org.projectlombok 124 | lombok 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | -------------------------------------------------------------------------------- /mvnw.cmd: -------------------------------------------------------------------------------- 1 | <# : batch portion 2 | @REM ---------------------------------------------------------------------------- 3 | @REM Licensed to the Apache Software Foundation (ASF) under one 4 | @REM or more contributor license agreements. See the NOTICE file 5 | @REM distributed with this work for additional information 6 | @REM regarding copyright ownership. The ASF licenses this file 7 | @REM to you under the Apache License, Version 2.0 (the 8 | @REM "License"); you may not use this file except in compliance 9 | @REM with the License. You may obtain a copy of the License at 10 | @REM 11 | @REM http://www.apache.org/licenses/LICENSE-2.0 12 | @REM 13 | @REM Unless required by applicable law or agreed to in writing, 14 | @REM software distributed under the License is distributed on an 15 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | @REM KIND, either express or implied. See the License for the 17 | @REM specific language governing permissions and limitations 18 | @REM under the License. 19 | @REM ---------------------------------------------------------------------------- 20 | 21 | @REM ---------------------------------------------------------------------------- 22 | @REM Apache Maven Wrapper startup batch script, version 3.3.2 23 | @REM 24 | @REM Optional ENV vars 25 | @REM MVNW_REPOURL - repo url base for downloading maven distribution 26 | @REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven 27 | @REM MVNW_VERBOSE - true: enable verbose log; others: silence the output 28 | @REM ---------------------------------------------------------------------------- 29 | 30 | @IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0) 31 | @SET __MVNW_CMD__= 32 | @SET __MVNW_ERROR__= 33 | @SET __MVNW_PSMODULEP_SAVE=%PSModulePath% 34 | @SET PSModulePath= 35 | @FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @( 36 | IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B) 37 | ) 38 | @SET PSModulePath=%__MVNW_PSMODULEP_SAVE% 39 | @SET __MVNW_PSMODULEP_SAVE= 40 | @SET __MVNW_ARG0_NAME__= 41 | @SET MVNW_USERNAME= 42 | @SET MVNW_PASSWORD= 43 | @IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*) 44 | @echo Cannot start maven from wrapper >&2 && exit /b 1 45 | @GOTO :EOF 46 | : end batch / begin powershell #> 47 | 48 | $ErrorActionPreference = "Stop" 49 | if ($env:MVNW_VERBOSE -eq "true") { 50 | $VerbosePreference = "Continue" 51 | } 52 | 53 | # calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties 54 | $distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl 55 | if (!$distributionUrl) { 56 | Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" 57 | } 58 | 59 | switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) { 60 | "maven-mvnd-*" { 61 | $USE_MVND = $true 62 | $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip" 63 | $MVN_CMD = "mvnd.cmd" 64 | break 65 | } 66 | default { 67 | $USE_MVND = $false 68 | $MVN_CMD = $script -replace '^mvnw','mvn' 69 | break 70 | } 71 | } 72 | 73 | # apply MVNW_REPOURL and calculate MAVEN_HOME 74 | # maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ 75 | if ($env:MVNW_REPOURL) { 76 | $MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" } 77 | $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')" 78 | } 79 | $distributionUrlName = $distributionUrl -replace '^.*/','' 80 | $distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$','' 81 | $MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain" 82 | if ($env:MAVEN_USER_HOME) { 83 | $MAVEN_HOME_PARENT = "$env:MAVEN_USER_HOME/wrapper/dists/$distributionUrlNameMain" 84 | } 85 | $MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join '' 86 | $MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME" 87 | 88 | if (Test-Path -Path "$MAVEN_HOME" -PathType Container) { 89 | Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME" 90 | Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" 91 | exit $? 92 | } 93 | 94 | if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) { 95 | Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl" 96 | } 97 | 98 | # prepare tmp dir 99 | $TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile 100 | $TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir" 101 | $TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null 102 | trap { 103 | if ($TMP_DOWNLOAD_DIR.Exists) { 104 | try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } 105 | catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } 106 | } 107 | } 108 | 109 | New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null 110 | 111 | # Download and Install Apache Maven 112 | Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." 113 | Write-Verbose "Downloading from: $distributionUrl" 114 | Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" 115 | 116 | $webclient = New-Object System.Net.WebClient 117 | if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) { 118 | $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD) 119 | } 120 | [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 121 | $webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null 122 | 123 | # If specified, validate the SHA-256 sum of the Maven distribution zip file 124 | $distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum 125 | if ($distributionSha256Sum) { 126 | if ($USE_MVND) { 127 | Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." 128 | } 129 | Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash 130 | if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) { 131 | Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property." 132 | } 133 | } 134 | 135 | # unzip and move 136 | Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null 137 | Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null 138 | try { 139 | Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null 140 | } catch { 141 | if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) { 142 | Write-Error "fail to move MAVEN_HOME" 143 | } 144 | } finally { 145 | try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } 146 | catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } 147 | } 148 | 149 | Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" 150 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # springboot-elk-prometheus-grafana 2 | 3 | The goal of this project is to implement a [`Spring Boot`](https://docs.spring.io/spring-boot/index.html) application, called `movies-api`, and use [`Filebeat`](https://www.elastic.co/beats/filebeat) & `ELK Stack` ([`Elasticsearch`](https://www.elastic.co/elasticsearch), [`Logstash`](https://www.elastic.co/logstash) and [`Kibana`](https://www.elastic.co/kibana)) to collect and visualize application's **logs** and [`Prometheus`](https://prometheus.io/) & [`Grafana`](https://grafana.com/) to monitor application's **metrics**. 4 | 5 | > **Note**: In [`kubernetes-minikube-environment`](https://github.com/ivangfr/kubernetes-minikube-environment/tree/master/movies-api-elk-prometheus-grafana) repository, it shows how to deploy this project in `Kubernetes` (`Minikube`) 6 | 7 | ## Proof-of-Concepts & Articles 8 | 9 | On [ivangfr.github.io](https://ivangfr.github.io), I have compiled my Proof-of-Concepts (PoCs) and articles. You can easily search for the technology you are interested in by using the filter. Who knows, perhaps I have already implemented a PoC or written an article about what you are looking for. 10 | 11 | ## Additional Readings 12 | 13 | - \[**Medium**\] [**Exposing Metrics of a Spring Boot API that uses Spring Data JPA and PostgreSQL**](https://medium.com/@ivangfr/exposing-metrics-of-a-spring-boot-api-that-uses-spring-data-jpa-and-postgresql-5ff188097b0f) 14 | - \[**Medium**\] [**Running Prometheus and Grafana to monitor a Spring Boot API application**](https://medium.com/@ivangfr/running-prometheus-and-grafana-to-monitor-a-spring-boot-api-application-e6a3313563f2) 15 | 16 | ## Project Diagram 17 | 18 | ![project-diagram](documentation/project-diagram.jpeg) 19 | 20 | ## Application 21 | 22 | - ### movies-api 23 | 24 | `Spring Boot` Web Java application that exposes a REST API for managing movies. It provides the following endpoints: 25 | ```text 26 | POST /api/movies -d {"imdbId","title","year","genre","country"} 27 | GET /api/movies 28 | GET /api/movies/{imdbId} 29 | DELETE /api/movies/{imdbId} 30 | ``` 31 | 32 | ## Prerequisites 33 | 34 | - [`Java 21`](https://www.oracle.com/java/technologies/downloads/#java21) or higher; 35 | - A containerization tool (e.g., [`Docker`](https://www.docker.com), [`Podman`](https://podman.io), etc.) 36 | 37 | ## Start Environment 38 | 39 | - Open a terminal and inside the `springboot-elk-prometheus-grafana` root folder run: 40 | ```bash 41 | docker compose up -d 42 | ``` 43 | 44 | - Wait for the Docker containers to be up and running. To check it, run: 45 | ```bash 46 | docker ps -a 47 | ``` 48 | 49 | ## Running application with Maven 50 | 51 | - Open a terminal and make sure you are inside the `springboot-elk-prometheus-grafana` folder; 52 | 53 | - Run the following command: 54 | ```bash 55 | ./mvnw clean spring-boot:run --projects movies-api 56 | ``` 57 | > **Note**: If you want to switch to the "non-json-logs" profile (which may be useful during development), run: 58 | > ```bash 59 | > ./mvnw clean spring-boot:run --projects movies-api -Dspring-boot.run.jvmArguments="-Dspring.profiles.active=non-json-logs" 60 | > ``` 61 | 62 | ## Running application as Docker container 63 | 64 | - ### Build Docker image 65 | 66 | - In a terminal, make sure you are inside the `springboot-elk-prometheus-grafana` root folder; 67 | - Run the following script to build the image: 68 | - JVM 69 | ```bash 70 | ./build-docker-images.sh 71 | ``` 72 | - Native 73 | ```bash 74 | ./build-docker-images.sh native 75 | ``` 76 | 77 | - ### Environment variables 78 | 79 | | Environment Variable | Description | 80 | |----------------------|-------------------------------------------------------------------| 81 | | `MYSQL_HOST` | Specify host of the `MySQL` database to use (default `localhost`) | 82 | | `MYSQL_PORT` | Specify port of the `MySQL` database to use (default `3306`) | 83 | 84 | - ### Start Docker container 85 | 86 | - In a terminal, run the following command to start the Docker container: 87 | ```bash 88 | docker run --rm --name movies-api -p 8080:8080 \ 89 | -e MYSQL_HOST=mysql \ 90 | --network=springboot-elk-prometheus-grafana_default \ 91 | ivanfranchin/movies-api:1.0.0 92 | ``` 93 | > **Note**: If you want to change to "non-json-logs", add `-e SPRING_PROFILES_ACTIVE=non-json-logs` to the command above 94 | 95 | ## Calling the Application Endpoints 96 | 97 | - **Create movie** 98 | 99 | ```bash 100 | curl -X POST http://localhost:8080/api/movies \ 101 | -H "Content-Type: application/json" \ 102 | -d '{"imdbId": "tt5580036", "title": "I, Tonya", "year": 2017, "genre": "Biography", "country": "USA"}' 103 | ``` 104 | 105 | - **Get all movies** 106 | 107 | ```bash 108 | curl http://localhost:8080/api/movies 109 | ``` 110 | - **Get movie** 111 | 112 | ```bash 113 | curl http://localhost:8080/api/movies/tt5580036 114 | ``` 115 | 116 | - **Delete movie** 117 | 118 | ```bash 119 | curl -X DELETE http://localhost:8080/api/movies/tt5580036 120 | ``` 121 | 122 | ## Services URLs 123 | 124 | - **MySQL** 125 | 126 | ```bash 127 | docker exec -it -e MYSQL_PWD=secret mysql mysql -uroot --database moviesdb 128 | SELECT * FROM movies; 129 | ``` 130 | > Type `exit` to leave the `MySQL monitor` 131 | 132 | - **Prometheus** 133 | 134 | `Prometheus` can be accessed at http://localhost:9090 135 | 136 | ![prometheus](documentation/prometheus.jpeg) 137 | 138 | - **Grafana** 139 | 140 | `Grafana` can be accessed at http://localhost:3000 141 | 142 | - In order to login, type `admin` for both `username` and `password`. 143 | - You can skip the next screen that asks you to provide a new password. 144 | - Select `Dashboards` on the left-menu. 145 | - Click `movies-api-dashboard`. 146 | 147 | ![grafana](documentation/movies-api-grafana-dashboard.jpeg) 148 | 149 | - **Kibana** 150 | 151 | `Kibana` can be accessed at http://localhost:5601 152 | 153 | > **Note**: in order to see movies-api logs in Kibana, you must run the application as Docker container 154 | 155 | _Configuration_ 156 | 157 | - Access `Kibana` website; 158 | - Click `Explore on my own`. 159 | - On the main page, click the _"burger"_ menu icon, then click `Discover`. 160 | - Click `Create index pattern` button. 161 | - In the `Create index pattern` form: 162 | - Set `filebeat-*` fot the `Name` field; 163 | - Select `@timestamp` for the `Timestamp field` combo-box. 164 | - Click `Create index pattern` button. 165 | - Click the _"burger"_ menu icon again, and then click `Discover` to start performing searches. 166 | 167 | ![kibana](documentation/kibana.jpeg) 168 | 169 | - **Elasticsearch** 170 | 171 | `Elasticsearch` URL is http://localhost:9200 172 | 173 | _Useful queries_ 174 | ```text 175 | # Check it's up and running 176 | curl localhost:9200 177 | 178 | # Check indexes 179 | curl "localhost:9200/_cat/indices?v" 180 | 181 | # Check filebeat index mapping 182 | curl "localhost:9200/filebeat-*/_mapping" 183 | 184 | # Simple search 185 | curl "localhost:9200/filebeat-*/_search?pretty" 186 | ``` 187 | 188 | ## Shutdown 189 | 190 | - To stop the application, go to the terminal where it is running and press `Ctrl+C`. 191 | - To stop and remove docker compose containers, network, and volumes, go to a terminal and, inside the `springboot-elk-prometheus-grafana` root folder, run the following command: 192 | ```bash 193 | docker compose down -v 194 | ``` 195 | 196 | ## Cleanup 197 | 198 | To remove the Docker images created by this project, go to a terminal and, inside the `springboot-elk-prometheus-grafana` root folder, run the script below: 199 | ```bash 200 | ./remove-docker-images.sh 201 | ``` 202 | 203 | ## References 204 | 205 | https://medium.com/@sece.cosmin/docker-logs-with-elastic-stack-elk-filebeat-50e2b20a27c6 206 | -------------------------------------------------------------------------------- /mvnw: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # ---------------------------------------------------------------------------- 3 | # Licensed to the Apache Software Foundation (ASF) under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. The ASF licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, 14 | # software distributed under the License is distributed on an 15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | # KIND, either express or implied. See the License for the 17 | # specific language governing permissions and limitations 18 | # under the License. 19 | # ---------------------------------------------------------------------------- 20 | 21 | # ---------------------------------------------------------------------------- 22 | # Apache Maven Wrapper startup batch script, version 3.3.2 23 | # 24 | # Optional ENV vars 25 | # ----------------- 26 | # JAVA_HOME - location of a JDK home dir, required when download maven via java source 27 | # MVNW_REPOURL - repo url base for downloading maven distribution 28 | # MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven 29 | # MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output 30 | # ---------------------------------------------------------------------------- 31 | 32 | set -euf 33 | [ "${MVNW_VERBOSE-}" != debug ] || set -x 34 | 35 | # OS specific support. 36 | native_path() { printf %s\\n "$1"; } 37 | case "$(uname)" in 38 | CYGWIN* | MINGW*) 39 | [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")" 40 | native_path() { cygpath --path --windows "$1"; } 41 | ;; 42 | esac 43 | 44 | # set JAVACMD and JAVACCMD 45 | set_java_home() { 46 | # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched 47 | if [ -n "${JAVA_HOME-}" ]; then 48 | if [ -x "$JAVA_HOME/jre/sh/java" ]; then 49 | # IBM's JDK on AIX uses strange locations for the executables 50 | JAVACMD="$JAVA_HOME/jre/sh/java" 51 | JAVACCMD="$JAVA_HOME/jre/sh/javac" 52 | else 53 | JAVACMD="$JAVA_HOME/bin/java" 54 | JAVACCMD="$JAVA_HOME/bin/javac" 55 | 56 | if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then 57 | echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2 58 | echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2 59 | return 1 60 | fi 61 | fi 62 | else 63 | JAVACMD="$( 64 | 'set' +e 65 | 'unset' -f command 2>/dev/null 66 | 'command' -v java 67 | )" || : 68 | JAVACCMD="$( 69 | 'set' +e 70 | 'unset' -f command 2>/dev/null 71 | 'command' -v javac 72 | )" || : 73 | 74 | if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then 75 | echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2 76 | return 1 77 | fi 78 | fi 79 | } 80 | 81 | # hash string like Java String::hashCode 82 | hash_string() { 83 | str="${1:-}" h=0 84 | while [ -n "$str" ]; do 85 | char="${str%"${str#?}"}" 86 | h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296)) 87 | str="${str#?}" 88 | done 89 | printf %x\\n $h 90 | } 91 | 92 | verbose() { :; } 93 | [ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; } 94 | 95 | die() { 96 | printf %s\\n "$1" >&2 97 | exit 1 98 | } 99 | 100 | trim() { 101 | # MWRAPPER-139: 102 | # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds. 103 | # Needed for removing poorly interpreted newline sequences when running in more 104 | # exotic environments such as mingw bash on Windows. 105 | printf "%s" "${1}" | tr -d '[:space:]' 106 | } 107 | 108 | # parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties 109 | while IFS="=" read -r key value; do 110 | case "${key-}" in 111 | distributionUrl) distributionUrl=$(trim "${value-}") ;; 112 | distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;; 113 | esac 114 | done <"${0%/*}/.mvn/wrapper/maven-wrapper.properties" 115 | [ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties" 116 | 117 | case "${distributionUrl##*/}" in 118 | maven-mvnd-*bin.*) 119 | MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ 120 | case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in 121 | *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;; 122 | :Darwin*x86_64) distributionPlatform=darwin-amd64 ;; 123 | :Darwin*arm64) distributionPlatform=darwin-aarch64 ;; 124 | :Linux*x86_64*) distributionPlatform=linux-amd64 ;; 125 | *) 126 | echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2 127 | distributionPlatform=linux-amd64 128 | ;; 129 | esac 130 | distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip" 131 | ;; 132 | maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;; 133 | *) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;; 134 | esac 135 | 136 | # apply MVNW_REPOURL and calculate MAVEN_HOME 137 | # maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ 138 | [ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}" 139 | distributionUrlName="${distributionUrl##*/}" 140 | distributionUrlNameMain="${distributionUrlName%.*}" 141 | distributionUrlNameMain="${distributionUrlNameMain%-bin}" 142 | MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}" 143 | MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")" 144 | 145 | exec_maven() { 146 | unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || : 147 | exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD" 148 | } 149 | 150 | if [ -d "$MAVEN_HOME" ]; then 151 | verbose "found existing MAVEN_HOME at $MAVEN_HOME" 152 | exec_maven "$@" 153 | fi 154 | 155 | case "${distributionUrl-}" in 156 | *?-bin.zip | *?maven-mvnd-?*-?*.zip) ;; 157 | *) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;; 158 | esac 159 | 160 | # prepare tmp dir 161 | if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then 162 | clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; } 163 | trap clean HUP INT TERM EXIT 164 | else 165 | die "cannot create temp dir" 166 | fi 167 | 168 | mkdir -p -- "${MAVEN_HOME%/*}" 169 | 170 | # Download and Install Apache Maven 171 | verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." 172 | verbose "Downloading from: $distributionUrl" 173 | verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" 174 | 175 | # select .zip or .tar.gz 176 | if ! command -v unzip >/dev/null; then 177 | distributionUrl="${distributionUrl%.zip}.tar.gz" 178 | distributionUrlName="${distributionUrl##*/}" 179 | fi 180 | 181 | # verbose opt 182 | __MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR='' 183 | [ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v 184 | 185 | # normalize http auth 186 | case "${MVNW_PASSWORD:+has-password}" in 187 | '') MVNW_USERNAME='' MVNW_PASSWORD='' ;; 188 | has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;; 189 | esac 190 | 191 | if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then 192 | verbose "Found wget ... using wget" 193 | wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl" 194 | elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then 195 | verbose "Found curl ... using curl" 196 | curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl" 197 | elif set_java_home; then 198 | verbose "Falling back to use Java to download" 199 | javaSource="$TMP_DOWNLOAD_DIR/Downloader.java" 200 | targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName" 201 | cat >"$javaSource" <<-END 202 | public class Downloader extends java.net.Authenticator 203 | { 204 | protected java.net.PasswordAuthentication getPasswordAuthentication() 205 | { 206 | return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() ); 207 | } 208 | public static void main( String[] args ) throws Exception 209 | { 210 | setDefault( new Downloader() ); 211 | java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() ); 212 | } 213 | } 214 | END 215 | # For Cygwin/MinGW, switch paths to Windows format before running javac and java 216 | verbose " - Compiling Downloader.java ..." 217 | "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java" 218 | verbose " - Running Downloader.java ..." 219 | "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")" 220 | fi 221 | 222 | # If specified, validate the SHA-256 sum of the Maven distribution zip file 223 | if [ -n "${distributionSha256Sum-}" ]; then 224 | distributionSha256Result=false 225 | if [ "$MVN_CMD" = mvnd.sh ]; then 226 | echo "Checksum validation is not supported for maven-mvnd." >&2 227 | echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 228 | exit 1 229 | elif command -v sha256sum >/dev/null; then 230 | if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c >/dev/null 2>&1; then 231 | distributionSha256Result=true 232 | fi 233 | elif command -v shasum >/dev/null; then 234 | if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then 235 | distributionSha256Result=true 236 | fi 237 | else 238 | echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2 239 | echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 240 | exit 1 241 | fi 242 | if [ $distributionSha256Result = false ]; then 243 | echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2 244 | echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2 245 | exit 1 246 | fi 247 | fi 248 | 249 | # unzip and move 250 | if command -v unzip >/dev/null; then 251 | unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip" 252 | else 253 | tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar" 254 | fi 255 | printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url" 256 | mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME" 257 | 258 | clean || : 259 | exec_maven "$@" 260 | -------------------------------------------------------------------------------- /grafana/provisioning/dashboards/movies-api-dashboard.json: -------------------------------------------------------------------------------- 1 | { 2 | "annotations": { 3 | "list": [ 4 | { 5 | "builtIn": 1, 6 | "datasource": { 7 | "type": "datasource", 8 | "uid": "grafana" 9 | }, 10 | "enable": true, 11 | "hide": true, 12 | "iconColor": "rgba(0, 211, 255, 1)", 13 | "name": "Annotations & Alerts", 14 | "target": { 15 | "limit": 100, 16 | "matchAny": false, 17 | "tags": [], 18 | "type": "dashboard" 19 | }, 20 | "type": "dashboard" 21 | } 22 | ] 23 | }, 24 | "editable": true, 25 | "fiscalYearStartMonth": 0, 26 | "graphTooltip": 0, 27 | "id": 1, 28 | "links": [], 29 | "liveNow": false, 30 | "panels": [ 31 | { 32 | "datasource": { 33 | "type": "prometheus", 34 | "uid": "PBFA97CFB590B2093" 35 | }, 36 | "description": "", 37 | "fieldConfig": { 38 | "defaults": { 39 | "color": { 40 | "mode": "thresholds" 41 | }, 42 | "mappings": [], 43 | "thresholds": { 44 | "mode": "absolute", 45 | "steps": [ 46 | { 47 | "color": "green", 48 | "value": null 49 | }, 50 | { 51 | "color": "red", 52 | "value": 80 53 | } 54 | ] 55 | } 56 | }, 57 | "overrides": [] 58 | }, 59 | "gridPos": { 60 | "h": 5, 61 | "w": 12, 62 | "x": 0, 63 | "y": 0 64 | }, 65 | "id": 10, 66 | "options": { 67 | "colorMode": "value", 68 | "graphMode": "area", 69 | "justifyMode": "auto", 70 | "orientation": "horizontal", 71 | "reduceOptions": { 72 | "calcs": [ 73 | "lastNotNull" 74 | ], 75 | "fields": "", 76 | "values": false 77 | }, 78 | "text": {}, 79 | "textMode": "auto" 80 | }, 81 | "pluginVersion": "9.5.15", 82 | "targets": [ 83 | { 84 | "datasource": { 85 | "type": "prometheus", 86 | "uid": "PBFA97CFB590B2093" 87 | }, 88 | "exemplar": true, 89 | "expr": "sum(increase(http_server_requests_seconds_count{job=\"movies-api\",method=~\".+\",status=~\"2.+\",uri=~\"/api/movies.*\"}[1m]))", 90 | "format": "time_series", 91 | "instant": false, 92 | "interval": "", 93 | "legendFormat": "SUCCESS", 94 | "refId": "A" 95 | }, 96 | { 97 | "datasource": { 98 | "type": "prometheus", 99 | "uid": "PBFA97CFB590B2093" 100 | }, 101 | "exemplar": true, 102 | "expr": "sum(increase(http_server_requests_seconds_count{job=\"movies-api\",method=~\".+\",status!~\"2.+\",uri=~\"/api/movies.*\"}[1m]))", 103 | "instant": false, 104 | "interval": "", 105 | "legendFormat": "ERROR", 106 | "refId": "B" 107 | } 108 | ], 109 | "title": "API Calls", 110 | "transparent": true, 111 | "type": "stat" 112 | }, 113 | { 114 | "datasource": { 115 | "type": "prometheus", 116 | "uid": "PBFA97CFB590B2093" 117 | }, 118 | "fieldConfig": { 119 | "defaults": { 120 | "mappings": [], 121 | "thresholds": { 122 | "mode": "absolute", 123 | "steps": [ 124 | { 125 | "color": "green", 126 | "value": null 127 | }, 128 | { 129 | "color": "red", 130 | "value": 80 131 | } 132 | ] 133 | } 134 | }, 135 | "overrides": [] 136 | }, 137 | "gridPos": { 138 | "h": 5, 139 | "w": 12, 140 | "x": 12, 141 | "y": 0 142 | }, 143 | "id": 2, 144 | "options": { 145 | "colorMode": "value", 146 | "graphMode": "area", 147 | "justifyMode": "auto", 148 | "orientation": "auto", 149 | "reduceOptions": { 150 | "calcs": [ 151 | "mean" 152 | ], 153 | "fields": "", 154 | "values": false 155 | }, 156 | "text": {}, 157 | "textMode": "auto" 158 | }, 159 | "pluginVersion": "9.5.15", 160 | "targets": [ 161 | { 162 | "expr": "process_cpu_usage{job=\"movies-api\"} * 100", 163 | "instant": false, 164 | "interval": "", 165 | "legendFormat": "movies-api", 166 | "refId": "A" 167 | } 168 | ], 169 | "title": "CPU", 170 | "transparent": true, 171 | "type": "stat" 172 | }, 173 | { 174 | "datasource": { 175 | "type": "prometheus", 176 | "uid": "PBFA97CFB590B2093" 177 | }, 178 | "description": "", 179 | "fieldConfig": { 180 | "defaults": { 181 | "color": { 182 | "mode": "palette-classic" 183 | }, 184 | "custom": { 185 | "axisCenteredZero": false, 186 | "axisColorMode": "text", 187 | "axisLabel": "", 188 | "axisPlacement": "auto", 189 | "barAlignment": 0, 190 | "drawStyle": "line", 191 | "fillOpacity": 50, 192 | "gradientMode": "opacity", 193 | "hideFrom": { 194 | "legend": false, 195 | "tooltip": false, 196 | "viz": false 197 | }, 198 | "lineInterpolation": "smooth", 199 | "lineWidth": 2, 200 | "pointSize": 5, 201 | "scaleDistribution": { 202 | "type": "linear" 203 | }, 204 | "showPoints": "never", 205 | "spanNulls": true, 206 | "stacking": { 207 | "group": "A", 208 | "mode": "none" 209 | }, 210 | "thresholdsStyle": { 211 | "mode": "off" 212 | } 213 | }, 214 | "links": [], 215 | "mappings": [], 216 | "min": 0, 217 | "thresholds": { 218 | "mode": "absolute", 219 | "steps": [ 220 | { 221 | "color": "green", 222 | "value": null 223 | }, 224 | { 225 | "color": "red", 226 | "value": 80 227 | } 228 | ] 229 | }, 230 | "unit": "short" 231 | }, 232 | "overrides": [] 233 | }, 234 | "gridPos": { 235 | "h": 5, 236 | "w": 12, 237 | "x": 0, 238 | "y": 5 239 | }, 240 | "id": 6, 241 | "options": { 242 | "legend": { 243 | "calcs": [], 244 | "displayMode": "list", 245 | "placement": "right", 246 | "showLegend": true 247 | }, 248 | "tooltip": { 249 | "mode": "multi", 250 | "sort": "none" 251 | } 252 | }, 253 | "pluginVersion": "8.4.6", 254 | "targets": [ 255 | { 256 | "datasource": { 257 | "type": "prometheus", 258 | "uid": "PBFA97CFB590B2093" 259 | }, 260 | "exemplar": true, 261 | "expr": "sum(increase(http_server_requests_seconds_count{job=\"movies-api\",method=\"GET\",status=\"200\",uri=\"/api/movies\"}[1m]))", 262 | "format": "time_series", 263 | "instant": false, 264 | "interval": "", 265 | "legendFormat": "SUCCESS", 266 | "refId": "A" 267 | }, 268 | { 269 | "datasource": { 270 | "type": "prometheus", 271 | "uid": "PBFA97CFB590B2093" 272 | }, 273 | "exemplar": true, 274 | "expr": "sum(increase(http_server_requests_seconds_count{job=\"movies-api\",method=\"GET\",status!=\"200\",uri=\"/api/movies\"}[1m]))", 275 | "interval": "", 276 | "legendFormat": "ERROR", 277 | "refId": "B" 278 | } 279 | ], 280 | "title": "GET /api/movies", 281 | "transformations": [], 282 | "transparent": true, 283 | "type": "timeseries" 284 | }, 285 | { 286 | "datasource": { 287 | "type": "prometheus", 288 | "uid": "PBFA97CFB590B2093" 289 | }, 290 | "description": "", 291 | "fieldConfig": { 292 | "defaults": { 293 | "color": { 294 | "mode": "palette-classic" 295 | }, 296 | "custom": { 297 | "axisCenteredZero": false, 298 | "axisColorMode": "text", 299 | "axisLabel": "", 300 | "axisPlacement": "auto", 301 | "barAlignment": 0, 302 | "drawStyle": "line", 303 | "fillOpacity": 50, 304 | "gradientMode": "opacity", 305 | "hideFrom": { 306 | "legend": false, 307 | "tooltip": false, 308 | "viz": false 309 | }, 310 | "lineInterpolation": "linear", 311 | "lineWidth": 2, 312 | "pointSize": 5, 313 | "scaleDistribution": { 314 | "type": "linear" 315 | }, 316 | "showPoints": "never", 317 | "spanNulls": true, 318 | "stacking": { 319 | "group": "A", 320 | "mode": "none" 321 | }, 322 | "thresholdsStyle": { 323 | "mode": "off" 324 | } 325 | }, 326 | "links": [], 327 | "mappings": [], 328 | "min": 0, 329 | "thresholds": { 330 | "mode": "absolute", 331 | "steps": [ 332 | { 333 | "color": "green", 334 | "value": null 335 | }, 336 | { 337 | "color": "red", 338 | "value": 80 339 | } 340 | ] 341 | }, 342 | "unit": "short" 343 | }, 344 | "overrides": [] 345 | }, 346 | "gridPos": { 347 | "h": 5, 348 | "w": 12, 349 | "x": 12, 350 | "y": 5 351 | }, 352 | "id": 9, 353 | "options": { 354 | "legend": { 355 | "calcs": [], 356 | "displayMode": "list", 357 | "placement": "right", 358 | "showLegend": true 359 | }, 360 | "tooltip": { 361 | "mode": "multi", 362 | "sort": "none" 363 | } 364 | }, 365 | "pluginVersion": "8.4.6", 366 | "targets": [ 367 | { 368 | "datasource": { 369 | "type": "prometheus", 370 | "uid": "PBFA97CFB590B2093" 371 | }, 372 | "exemplar": true, 373 | "expr": "sum(increase(http_server_requests_seconds_count{job=\"movies-api\",method=\"GET\",status=\"200\",uri=\"/api/movies/{imdbId}\"}[1m]))", 374 | "format": "time_series", 375 | "instant": false, 376 | "interval": "", 377 | "legendFormat": "SUCCESS", 378 | "refId": "A" 379 | }, 380 | { 381 | "datasource": { 382 | "type": "prometheus", 383 | "uid": "PBFA97CFB590B2093" 384 | }, 385 | "exemplar": true, 386 | "expr": "sum(increase(http_server_requests_seconds_count{job=\"movies-api\",method=\"GET\",status!=\"200\",uri=\"/api/movies/{imdbId}\"}[1m]))", 387 | "interval": "", 388 | "legendFormat": "ERROR", 389 | "refId": "B" 390 | } 391 | ], 392 | "title": "GET /api/movies/{imdbId}", 393 | "transparent": true, 394 | "type": "timeseries" 395 | }, 396 | { 397 | "datasource": { 398 | "type": "prometheus", 399 | "uid": "PBFA97CFB590B2093" 400 | }, 401 | "description": "", 402 | "fieldConfig": { 403 | "defaults": { 404 | "color": { 405 | "mode": "palette-classic" 406 | }, 407 | "custom": { 408 | "axisCenteredZero": false, 409 | "axisColorMode": "text", 410 | "axisLabel": "", 411 | "axisPlacement": "auto", 412 | "barAlignment": 0, 413 | "drawStyle": "line", 414 | "fillOpacity": 50, 415 | "gradientMode": "opacity", 416 | "hideFrom": { 417 | "legend": false, 418 | "tooltip": false, 419 | "viz": false 420 | }, 421 | "lineInterpolation": "smooth", 422 | "lineWidth": 2, 423 | "pointSize": 5, 424 | "scaleDistribution": { 425 | "type": "linear" 426 | }, 427 | "showPoints": "never", 428 | "spanNulls": true, 429 | "stacking": { 430 | "group": "A", 431 | "mode": "none" 432 | }, 433 | "thresholdsStyle": { 434 | "mode": "off" 435 | } 436 | }, 437 | "links": [], 438 | "mappings": [], 439 | "min": 0, 440 | "thresholds": { 441 | "mode": "absolute", 442 | "steps": [ 443 | { 444 | "color": "green", 445 | "value": null 446 | }, 447 | { 448 | "color": "red", 449 | "value": 80 450 | } 451 | ] 452 | }, 453 | "unit": "short" 454 | }, 455 | "overrides": [] 456 | }, 457 | "gridPos": { 458 | "h": 5, 459 | "w": 12, 460 | "x": 0, 461 | "y": 10 462 | }, 463 | "id": 8, 464 | "options": { 465 | "legend": { 466 | "calcs": [], 467 | "displayMode": "list", 468 | "placement": "right", 469 | "showLegend": true 470 | }, 471 | "tooltip": { 472 | "mode": "multi", 473 | "sort": "none" 474 | } 475 | }, 476 | "pluginVersion": "8.4.6", 477 | "targets": [ 478 | { 479 | "datasource": { 480 | "type": "prometheus", 481 | "uid": "PBFA97CFB590B2093" 482 | }, 483 | "expr": "sum(increase(http_server_requests_seconds_count{job=\"movies-api\",method=\"POST\",status=\"201\",uri=\"/api/movies\"}[1m]))", 484 | "format": "time_series", 485 | "instant": false, 486 | "interval": "", 487 | "legendFormat": "SUCCESS", 488 | "refId": "A" 489 | }, 490 | { 491 | "datasource": { 492 | "type": "prometheus", 493 | "uid": "PBFA97CFB590B2093" 494 | }, 495 | "exemplar": true, 496 | "expr": "sum(increase(http_server_requests_seconds_count{job=\"movies-api\",method=\"POST\",status!=\"201\",uri=\"/api/movies\"}[1m]))", 497 | "interval": "", 498 | "legendFormat": "ERROR", 499 | "refId": "B" 500 | } 501 | ], 502 | "title": "POST /api/movies", 503 | "transparent": true, 504 | "type": "timeseries" 505 | }, 506 | { 507 | "datasource": { 508 | "type": "prometheus", 509 | "uid": "PBFA97CFB590B2093" 510 | }, 511 | "description": "", 512 | "fieldConfig": { 513 | "defaults": { 514 | "color": { 515 | "mode": "palette-classic" 516 | }, 517 | "custom": { 518 | "axisCenteredZero": false, 519 | "axisColorMode": "text", 520 | "axisLabel": "", 521 | "axisPlacement": "auto", 522 | "barAlignment": 0, 523 | "drawStyle": "line", 524 | "fillOpacity": 50, 525 | "gradientMode": "opacity", 526 | "hideFrom": { 527 | "legend": false, 528 | "tooltip": false, 529 | "viz": false 530 | }, 531 | "lineInterpolation": "smooth", 532 | "lineWidth": 2, 533 | "pointSize": 5, 534 | "scaleDistribution": { 535 | "type": "linear" 536 | }, 537 | "showPoints": "never", 538 | "spanNulls": true, 539 | "stacking": { 540 | "group": "A", 541 | "mode": "none" 542 | }, 543 | "thresholdsStyle": { 544 | "mode": "off" 545 | } 546 | }, 547 | "links": [], 548 | "mappings": [], 549 | "min": 0, 550 | "thresholds": { 551 | "mode": "absolute", 552 | "steps": [ 553 | { 554 | "color": "green", 555 | "value": null 556 | }, 557 | { 558 | "color": "red", 559 | "value": 80 560 | } 561 | ] 562 | }, 563 | "unit": "short" 564 | }, 565 | "overrides": [] 566 | }, 567 | "gridPos": { 568 | "h": 5, 569 | "w": 12, 570 | "x": 12, 571 | "y": 10 572 | }, 573 | "id": 7, 574 | "options": { 575 | "legend": { 576 | "calcs": [], 577 | "displayMode": "list", 578 | "placement": "right", 579 | "showLegend": true 580 | }, 581 | "tooltip": { 582 | "mode": "multi", 583 | "sort": "none" 584 | } 585 | }, 586 | "pluginVersion": "8.4.6", 587 | "targets": [ 588 | { 589 | "datasource": { 590 | "type": "prometheus", 591 | "uid": "PBFA97CFB590B2093" 592 | }, 593 | "exemplar": true, 594 | "expr": "sum(increase(http_server_requests_seconds_count{job=\"movies-api\",method=\"DELETE\",status=\"200\",uri=\"/api/movies/{imdbId}\"}[1m]))", 595 | "format": "time_series", 596 | "instant": false, 597 | "interval": "", 598 | "legendFormat": "SUCCESS", 599 | "refId": "A" 600 | }, 601 | { 602 | "datasource": { 603 | "type": "prometheus", 604 | "uid": "PBFA97CFB590B2093" 605 | }, 606 | "exemplar": true, 607 | "expr": "sum(increase(http_server_requests_seconds_count{job=\"movies-api\",method=\"DELETE\",status!=\"200\",uri=\"/api/movies/{imdbId}\"}[1m]))", 608 | "interval": "", 609 | "legendFormat": "ERROR", 610 | "refId": "B" 611 | } 612 | ], 613 | "title": "DELETE /api/movies/{imdbId}", 614 | "transparent": true, 615 | "type": "timeseries" 616 | } 617 | ], 618 | "refresh": "5s", 619 | "schemaVersion": 38, 620 | "style": "dark", 621 | "tags": [], 622 | "templating": { 623 | "list": [ 624 | { 625 | "auto": false, 626 | "auto_count": 30, 627 | "auto_min": "10s", 628 | "current": { 629 | "selected": false, 630 | "text": "1m", 631 | "value": "1m" 632 | }, 633 | "hide": 0, 634 | "name": "timeRate", 635 | "options": [ 636 | { 637 | "selected": true, 638 | "text": "1m", 639 | "value": "1m" 640 | }, 641 | { 642 | "selected": false, 643 | "text": "5m", 644 | "value": "5m" 645 | }, 646 | { 647 | "selected": false, 648 | "text": "10m", 649 | "value": "10m" 650 | }, 651 | { 652 | "selected": false, 653 | "text": "30m", 654 | "value": "30m" 655 | }, 656 | { 657 | "selected": false, 658 | "text": "1h", 659 | "value": "1h" 660 | }, 661 | { 662 | "selected": false, 663 | "text": "6h", 664 | "value": "6h" 665 | }, 666 | { 667 | "selected": false, 668 | "text": "12h", 669 | "value": "12h" 670 | }, 671 | { 672 | "selected": false, 673 | "text": "1d", 674 | "value": "1d" 675 | }, 676 | { 677 | "selected": false, 678 | "text": "7d", 679 | "value": "7d" 680 | }, 681 | { 682 | "selected": false, 683 | "text": "14d", 684 | "value": "14d" 685 | }, 686 | { 687 | "selected": false, 688 | "text": "30d", 689 | "value": "30d" 690 | } 691 | ], 692 | "query": "1m,5m,10m,30m,1h,6h,12h,1d,7d,14d,30d", 693 | "queryValue": "", 694 | "refresh": 2, 695 | "skipUrlSync": false, 696 | "type": "interval" 697 | } 698 | ] 699 | }, 700 | "time": { 701 | "from": "now-5m", 702 | "to": "now" 703 | }, 704 | "timepicker": { 705 | "refresh_intervals": [ 706 | "10s", 707 | "30s", 708 | "1m", 709 | "5m", 710 | "15m", 711 | "30m", 712 | "1h", 713 | "2h", 714 | "1d" 715 | ] 716 | }, 717 | "timezone": "", 718 | "title": "movies-api-dashboard", 719 | "uid": "wcsvkAwZt", 720 | "version": 1, 721 | "weekStart": "" 722 | } -------------------------------------------------------------------------------- /documentation/project-diagram.excalidraw: -------------------------------------------------------------------------------- 1 | { 2 | "type": "excalidraw", 3 | "version": 2, 4 | "source": "https://excalidraw.com", 5 | "elements": [ 6 | { 7 | "type": "rectangle", 8 | "version": 225, 9 | "versionNonce": 836506597, 10 | "isDeleted": false, 11 | "id": "AdzBvdnFgEvFafpE8X9bC", 12 | "fillStyle": "hachure", 13 | "strokeWidth": 1, 14 | "strokeStyle": "solid", 15 | "roughness": 1, 16 | "opacity": 100, 17 | "angle": 0, 18 | "x": 154.86442305402068, 19 | "y": -513.2122013743339, 20 | "strokeColor": "#000000", 21 | "backgroundColor": "#ced4da", 22 | "width": 1184, 23 | "height": 657, 24 | "seed": 1942754958, 25 | "groupIds": [], 26 | "roundness": { 27 | "type": 3 28 | }, 29 | "boundElements": [ 30 | { 31 | "type": "text", 32 | "id": "AodyCozBNx_QhPq7YfKPA" 33 | } 34 | ], 35 | "updated": 1679390722496, 36 | "link": null, 37 | "locked": false 38 | }, 39 | { 40 | "type": "text", 41 | "version": 64, 42 | "versionNonce": 237512718, 43 | "isDeleted": false, 44 | "id": "AodyCozBNx_QhPq7YfKPA", 45 | "fillStyle": "hachure", 46 | "strokeWidth": 1, 47 | "strokeStyle": "solid", 48 | "roughness": 1, 49 | "opacity": 100, 50 | "angle": 0, 51 | "x": 159.86442305402068, 52 | "y": -508.21220137433386, 53 | "strokeColor": "#000000", 54 | "backgroundColor": "transparent", 55 | "width": 97.66798400878906, 56 | "height": 43.199999999999996, 57 | "seed": 1490824206, 58 | "groupIds": [], 59 | "roundness": null, 60 | "boundElements": [], 61 | "updated": 1679353518297, 62 | "link": null, 63 | "locked": false, 64 | "fontSize": 36, 65 | "fontFamily": 1, 66 | "text": " Host", 67 | "textAlign": "left", 68 | "verticalAlign": "top", 69 | "containerId": "AdzBvdnFgEvFafpE8X9bC", 70 | "originalText": " Host" 71 | }, 72 | { 73 | "type": "rectangle", 74 | "version": 444, 75 | "versionNonce": 1102471179, 76 | "isDeleted": false, 77 | "id": "mokyN9VL1gjv8bq2RVlMT", 78 | "fillStyle": "cross-hatch", 79 | "strokeWidth": 1, 80 | "strokeStyle": "solid", 81 | "roughness": 1, 82 | "opacity": 100, 83 | "angle": 0, 84 | "x": 176.85036561547258, 85 | "y": -441.0610091046596, 86 | "strokeColor": "#000000", 87 | "backgroundColor": "#ced4da", 88 | "width": 1139, 89 | "height": 450, 90 | "seed": 136006030, 91 | "groupIds": [], 92 | "roundness": { 93 | "type": 3 94 | }, 95 | "boundElements": [ 96 | { 97 | "type": "text", 98 | "id": "2Y_n0rM9RpoM8YzBqmzFy" 99 | } 100 | ], 101 | "updated": 1679390737866, 102 | "link": null, 103 | "locked": false 104 | }, 105 | { 106 | "type": "text", 107 | "version": 149, 108 | "versionNonce": 594000421, 109 | "isDeleted": false, 110 | "id": "2Y_n0rM9RpoM8YzBqmzFy", 111 | "fillStyle": "hachure", 112 | "strokeWidth": 1, 113 | "strokeStyle": "solid", 114 | "roughness": 1, 115 | "opacity": 100, 116 | "angle": 0, 117 | "x": 181.85036561547258, 118 | "y": -436.0610091046596, 119 | "strokeColor": "#000000", 120 | "backgroundColor": "transparent", 121 | "width": 136.79995727539062, 122 | "height": 43.199999999999996, 123 | "seed": 909910350, 124 | "groupIds": [], 125 | "roundness": null, 126 | "boundElements": [], 127 | "updated": 1679390737866, 128 | "link": null, 129 | "locked": false, 130 | "fontSize": 36, 131 | "fontFamily": 1, 132 | "text": " Docker", 133 | "textAlign": "left", 134 | "verticalAlign": "top", 135 | "containerId": "mokyN9VL1gjv8bq2RVlMT", 136 | "originalText": " Docker" 137 | }, 138 | { 139 | "type": "rectangle", 140 | "version": 2355, 141 | "versionNonce": 1095902222, 142 | "isDeleted": false, 143 | "id": "DZXRIamNpCFFiyWEaviJ-", 144 | "fillStyle": "cross-hatch", 145 | "strokeWidth": 1, 146 | "strokeStyle": "solid", 147 | "roughness": 1, 148 | "opacity": 100, 149 | "angle": 0, 150 | "x": 495.0414024860955, 151 | "y": -132.1653192334059, 152 | "strokeColor": "#000000", 153 | "backgroundColor": "#7950f2", 154 | "width": 209, 155 | "height": 100, 156 | "seed": 2098553042, 157 | "groupIds": [], 158 | "roundness": { 159 | "type": 3 160 | }, 161 | "boundElements": [ 162 | { 163 | "type": "text", 164 | "id": "zzjvTAYn04BtKBzCi6Jhu" 165 | }, 166 | { 167 | "id": "8xO0oIWCA6KdAMxjq6D2R", 168 | "type": "arrow" 169 | }, 170 | { 171 | "id": "ad3TukdKD6lS5obL9RDxj", 172 | "type": "arrow" 173 | }, 174 | { 175 | "id": "UeETspdhwuokcwLbcVJe6", 176 | "type": "arrow" 177 | } 178 | ], 179 | "updated": 1679353618992, 180 | "link": null, 181 | "locked": false 182 | }, 183 | { 184 | "type": "text", 185 | "version": 2446, 186 | "versionNonce": 1941333202, 187 | "isDeleted": false, 188 | "id": "zzjvTAYn04BtKBzCi6Jhu", 189 | "fillStyle": "hachure", 190 | "strokeWidth": 1, 191 | "strokeStyle": "solid", 192 | "roughness": 1, 193 | "opacity": 100, 194 | "angle": 0, 195 | "x": 532.2154366047477, 196 | "y": -98.96531923340594, 197 | "strokeColor": "#000000", 198 | "backgroundColor": "transparent", 199 | "width": 134.6519317626953, 200 | "height": 33.6, 201 | "seed": 302730574, 202 | "groupIds": [], 203 | "roundness": null, 204 | "boundElements": [], 205 | "updated": 1679353617753, 206 | "link": null, 207 | "locked": false, 208 | "fontSize": 28, 209 | "fontFamily": 1, 210 | "text": "movies-api", 211 | "textAlign": "center", 212 | "verticalAlign": "middle", 213 | "containerId": "DZXRIamNpCFFiyWEaviJ-", 214 | "originalText": "movies-api" 215 | }, 216 | { 217 | "type": "rectangle", 218 | "version": 2536, 219 | "versionNonce": 1244704014, 220 | "isDeleted": false, 221 | "id": "RwL7tyNKnalwIXHi3y7vA", 222 | "fillStyle": "cross-hatch", 223 | "strokeWidth": 1, 224 | "strokeStyle": "solid", 225 | "roughness": 1, 226 | "opacity": 100, 227 | "angle": 0, 228 | "x": 1072.8882645548633, 229 | "y": -132.15199942705107, 230 | "strokeColor": "#000000", 231 | "backgroundColor": "#4c6ef5", 232 | "width": 209.18356323242188, 233 | "height": 99.67071533203125, 234 | "seed": 1628796562, 235 | "groupIds": [], 236 | "roundness": { 237 | "type": 3 238 | }, 239 | "boundElements": [ 240 | { 241 | "type": "text", 242 | "id": "YRH266FQNRor_35C7fcAe" 243 | }, 244 | { 245 | "id": "IQcv-HmHctBfoKzVXkyXb", 246 | "type": "arrow" 247 | }, 248 | { 249 | "id": "RthTH0BeUu2YS08npxAz5", 250 | "type": "arrow" 251 | } 252 | ], 253 | "updated": 1679353629310, 254 | "link": null, 255 | "locked": false 256 | }, 257 | { 258 | "type": "text", 259 | "version": 1492, 260 | "versionNonce": 749162962, 261 | "isDeleted": false, 262 | "id": "YRH266FQNRor_35C7fcAe", 263 | "fillStyle": "hachure", 264 | "strokeWidth": 1, 265 | "strokeStyle": "solid", 266 | "roughness": 1, 267 | "opacity": 100, 268 | "angle": 0, 269 | "x": 1084.8560837687305, 270 | "y": -99.11664176103547, 271 | "strokeColor": "#000000", 272 | "backgroundColor": "transparent", 273 | "width": 185.2479248046875, 274 | "height": 33.6, 275 | "seed": 2115007374, 276 | "groupIds": [], 277 | "roundness": null, 278 | "boundElements": [], 279 | "updated": 1679353518299, 280 | "link": null, 281 | "locked": false, 282 | "fontSize": 28, 283 | "fontFamily": 1, 284 | "text": "Elasticsearch", 285 | "textAlign": "center", 286 | "verticalAlign": "middle", 287 | "containerId": "RwL7tyNKnalwIXHi3y7vA", 288 | "originalText": "Elasticsearch" 289 | }, 290 | { 291 | "type": "rectangle", 292 | "version": 2217, 293 | "versionNonce": 490792590, 294 | "isDeleted": false, 295 | "id": "3nUMuhHFRkjR6rnmo5_HF", 296 | "fillStyle": "cross-hatch", 297 | "strokeWidth": 1, 298 | "strokeStyle": "solid", 299 | "roughness": 1, 300 | "opacity": 100, 301 | "angle": 0, 302 | "x": 205.7992060440563, 303 | "y": -132.15199942705107, 304 | "strokeColor": "#000000", 305 | "backgroundColor": "#e64980", 306 | "width": 209.18356323242188, 307 | "height": 99.67071533203125, 308 | "seed": 1930875342, 309 | "groupIds": [], 310 | "roundness": { 311 | "type": 3 312 | }, 313 | "boundElements": [ 314 | { 315 | "type": "text", 316 | "id": "GrLuc29xfNM1bifGYbjNL" 317 | }, 318 | { 319 | "id": "_vhb-7O91TLyRuQ4sTdb0", 320 | "type": "arrow" 321 | } 322 | ], 323 | "updated": 1679353614734, 324 | "link": null, 325 | "locked": false 326 | }, 327 | { 328 | "type": "text", 329 | "version": 1155, 330 | "versionNonce": 973484686, 331 | "isDeleted": false, 332 | "id": "GrLuc29xfNM1bifGYbjNL", 333 | "fillStyle": "hachure", 334 | "strokeWidth": 1, 335 | "strokeStyle": "solid", 336 | "roughness": 1, 337 | "opacity": 100, 338 | "angle": 0, 339 | "x": 251.96901347813832, 340 | "y": -99.11664176103547, 341 | "strokeColor": "#000000", 342 | "backgroundColor": "transparent", 343 | "width": 116.84394836425781, 344 | "height": 33.6, 345 | "seed": 198343186, 346 | "groupIds": [], 347 | "roundness": null, 348 | "boundElements": [], 349 | "updated": 1679353518299, 350 | "link": null, 351 | "locked": false, 352 | "fontSize": 28, 353 | "fontFamily": 1, 354 | "text": "Grafana", 355 | "textAlign": "center", 356 | "verticalAlign": "middle", 357 | "containerId": "3nUMuhHFRkjR6rnmo5_HF", 358 | "originalText": "Grafana" 359 | }, 360 | { 361 | "type": "rectangle", 362 | "version": 2214, 363 | "versionNonce": 319692878, 364 | "isDeleted": false, 365 | "id": "dzep2w5H19hH42cysACh2", 366 | "fillStyle": "cross-hatch", 367 | "strokeWidth": 1, 368 | "strokeStyle": "solid", 369 | "roughness": 1, 370 | "opacity": 100, 371 | "angle": 0, 372 | "x": 206.37029413713935, 373 | "y": -378.33841865818675, 374 | "strokeColor": "#000000", 375 | "backgroundColor": "#fd7e14", 376 | "width": 209.18356323242188, 377 | "height": 99.67071533203125, 378 | "seed": 741828622, 379 | "groupIds": [], 380 | "roundness": { 381 | "type": 3 382 | }, 383 | "boundElements": [ 384 | { 385 | "type": "text", 386 | "id": "khjgBe4RDwGqhj4WxtPdF" 387 | }, 388 | { 389 | "id": "8xO0oIWCA6KdAMxjq6D2R", 390 | "type": "arrow" 391 | }, 392 | { 393 | "id": "_vhb-7O91TLyRuQ4sTdb0", 394 | "type": "arrow" 395 | } 396 | ], 397 | "updated": 1679353612381, 398 | "link": null, 399 | "locked": false 400 | }, 401 | { 402 | "type": "text", 403 | "version": 1167, 404 | "versionNonce": 1207742670, 405 | "isDeleted": false, 406 | "id": "khjgBe4RDwGqhj4WxtPdF", 407 | "fillStyle": "hachure", 408 | "strokeWidth": 1, 409 | "strokeStyle": "solid", 410 | "roughness": 1, 411 | "opacity": 100, 412 | "angle": 0, 413 | "x": 233.37410883440498, 414 | "y": -345.30306099217114, 415 | "strokeColor": "#000000", 416 | "backgroundColor": "transparent", 417 | "width": 155.17593383789062, 418 | "height": 33.6, 419 | "seed": 1667065810, 420 | "groupIds": [], 421 | "roundness": null, 422 | "boundElements": [], 423 | "updated": 1679353518299, 424 | "link": null, 425 | "locked": false, 426 | "fontSize": 28, 427 | "fontFamily": 1, 428 | "text": "Prometheus", 429 | "textAlign": "center", 430 | "verticalAlign": "middle", 431 | "containerId": "dzep2w5H19hH42cysACh2", 432 | "originalText": "Prometheus" 433 | }, 434 | { 435 | "type": "rectangle", 436 | "version": 2198, 437 | "versionNonce": 640088206, 438 | "isDeleted": false, 439 | "id": "6KWkQ5SAz7ptFIqBt5I7q", 440 | "fillStyle": "cross-hatch", 441 | "strokeWidth": 1, 442 | "strokeStyle": "solid", 443 | "roughness": 1, 444 | "opacity": 100, 445 | "angle": 0, 446 | "x": 783.7973906404537, 447 | "y": -132.15199942705107, 448 | "strokeColor": "#000000", 449 | "backgroundColor": "#fab005", 450 | "width": 209.18356323242188, 451 | "height": 99.67071533203125, 452 | "seed": 2026467214, 453 | "groupIds": [], 454 | "roundness": { 455 | "type": 3 456 | }, 457 | "boundElements": [ 458 | { 459 | "type": "text", 460 | "id": "EAIBQ2hjkM6rejG2XVxyn" 461 | }, 462 | { 463 | "id": "CF-xfV5WRvSJr9GHmJ_J6", 464 | "type": "arrow" 465 | }, 466 | { 467 | "id": "Cgmhms42VznXU0xHq7mGp", 468 | "type": "arrow" 469 | } 470 | ], 471 | "updated": 1679353623643, 472 | "link": null, 473 | "locked": false 474 | }, 475 | { 476 | "type": "text", 477 | "version": 1164, 478 | "versionNonce": 1663662990, 479 | "isDeleted": false, 480 | "id": "EAIBQ2hjkM6rejG2XVxyn", 481 | "fillStyle": "hachure", 482 | "strokeWidth": 1, 483 | "strokeStyle": "solid", 484 | "roughness": 1, 485 | "opacity": 100, 486 | "angle": 0, 487 | "x": 833.9291960603756, 488 | "y": -99.11664176103547, 489 | "strokeColor": "#000000", 490 | "backgroundColor": "transparent", 491 | "width": 108.91995239257812, 492 | "height": 33.6, 493 | "seed": 1718720594, 494 | "groupIds": [], 495 | "roundness": null, 496 | "boundElements": [], 497 | "updated": 1679353518299, 498 | "link": null, 499 | "locked": false, 500 | "fontSize": 28, 501 | "fontFamily": 1, 502 | "text": "Filebeat", 503 | "textAlign": "center", 504 | "verticalAlign": "middle", 505 | "containerId": "6KWkQ5SAz7ptFIqBt5I7q", 506 | "originalText": "Filebeat" 507 | }, 508 | { 509 | "type": "rectangle", 510 | "version": 2132, 511 | "versionNonce": 869167694, 512 | "isDeleted": false, 513 | "id": "X4Op79JUgTiBk6v0n1_cd", 514 | "fillStyle": "cross-hatch", 515 | "strokeWidth": 1, 516 | "strokeStyle": "solid", 517 | "roughness": 1, 518 | "opacity": 100, 519 | "angle": 0, 520 | "x": 784.6447194987271, 521 | "y": -378.33841865818675, 522 | "strokeColor": "#000000", 523 | "backgroundColor": "#15aabf", 524 | "width": 209.18356323242188, 525 | "height": 99.67071533203125, 526 | "seed": 1509019730, 527 | "groupIds": [], 528 | "roundness": { 529 | "type": 3 530 | }, 531 | "boundElements": [ 532 | { 533 | "type": "text", 534 | "id": "j_YXnc3FrDVingTiDXzcU" 535 | }, 536 | { 537 | "id": "IQcv-HmHctBfoKzVXkyXb", 538 | "type": "arrow" 539 | }, 540 | { 541 | "id": "Cgmhms42VznXU0xHq7mGp", 542 | "type": "arrow" 543 | } 544 | ], 545 | "updated": 1679353621185, 546 | "link": null, 547 | "locked": false 548 | }, 549 | { 550 | "type": "text", 551 | "version": 1104, 552 | "versionNonce": 1607391694, 553 | "isDeleted": false, 554 | "id": "j_YXnc3FrDVingTiDXzcU", 555 | "fillStyle": "hachure", 556 | "strokeWidth": 1, 557 | "strokeStyle": "solid", 558 | "roughness": 1, 559 | "opacity": 100, 560 | "angle": 0, 561 | "x": 826.7125295573209, 562 | "y": -345.30306099217114, 563 | "strokeColor": "#000000", 564 | "backgroundColor": "transparent", 565 | "width": 125.04794311523438, 566 | "height": 33.6, 567 | "seed": 1393530318, 568 | "groupIds": [], 569 | "roundness": null, 570 | "boundElements": [], 571 | "updated": 1679353518299, 572 | "link": null, 573 | "locked": false, 574 | "fontSize": 28, 575 | "fontFamily": 1, 576 | "text": "Logstash", 577 | "textAlign": "center", 578 | "verticalAlign": "middle", 579 | "containerId": "X4Op79JUgTiBk6v0n1_cd", 580 | "originalText": "Logstash" 581 | }, 582 | { 583 | "type": "rectangle", 584 | "version": 2533, 585 | "versionNonce": 1505813710, 586 | "isDeleted": false, 587 | "id": "A9-r0FwuyyUoCZ0u85s-G", 588 | "fillStyle": "cross-hatch", 589 | "strokeWidth": 1, 590 | "strokeStyle": "solid", 591 | "roughness": 1, 592 | "opacity": 100, 593 | "angle": 0, 594 | "x": 495.50750681793323, 595 | "y": -378.33841865818675, 596 | "strokeColor": "#000000", 597 | "backgroundColor": "#82c91e", 598 | "width": 209.18356323242188, 599 | "height": 99.67071533203125, 600 | "seed": 432426830, 601 | "groupIds": [], 602 | "roundness": { 603 | "type": 3 604 | }, 605 | "boundElements": [ 606 | { 607 | "type": "text", 608 | "id": "izx-o4ILzP3QO6RgC8yVq" 609 | }, 610 | { 611 | "id": "ad3TukdKD6lS5obL9RDxj", 612 | "type": "arrow" 613 | } 614 | ], 615 | "updated": 1679353616764, 616 | "link": null, 617 | "locked": false 618 | }, 619 | { 620 | "type": "text", 621 | "version": 1498, 622 | "versionNonce": 1420718094, 623 | "isDeleted": false, 624 | "id": "izx-o4ILzP3QO6RgC8yVq", 625 | "fillStyle": "hachure", 626 | "strokeWidth": 1, 627 | "strokeStyle": "solid", 628 | "roughness": 1, 629 | "opacity": 100, 630 | "angle": 0, 631 | "x": 556.0413126651013, 632 | "y": -345.3030609921711, 633 | "strokeColor": "#000000", 634 | "backgroundColor": "transparent", 635 | "width": 88.11595153808594, 636 | "height": 33.6, 637 | "seed": 134452370, 638 | "groupIds": [], 639 | "roundness": null, 640 | "boundElements": [], 641 | "updated": 1679353518299, 642 | "link": null, 643 | "locked": false, 644 | "fontSize": 28, 645 | "fontFamily": 1, 646 | "text": "MySQL", 647 | "textAlign": "center", 648 | "verticalAlign": "middle", 649 | "containerId": "A9-r0FwuyyUoCZ0u85s-G", 650 | "originalText": "MySQL" 651 | }, 652 | { 653 | "type": "rectangle", 654 | "version": 2467, 655 | "versionNonce": 1075099342, 656 | "isDeleted": false, 657 | "id": "OuAm6T1N2Hi6p_UVN-VCB", 658 | "fillStyle": "cross-hatch", 659 | "strokeWidth": 1, 660 | "strokeStyle": "solid", 661 | "roughness": 1, 662 | "opacity": 100, 663 | "angle": 0, 664 | "x": 1073.781932179521, 665 | "y": -378.33841865818675, 666 | "strokeColor": "#000000", 667 | "backgroundColor": "#228be6", 668 | "width": 209.18356323242188, 669 | "height": 99.67071533203125, 670 | "seed": 1526770706, 671 | "groupIds": [], 672 | "roundness": { 673 | "type": 3 674 | }, 675 | "boundElements": [ 676 | { 677 | "type": "text", 678 | "id": "IAVnmbsj3I0jUZxxThjgR" 679 | }, 680 | { 681 | "id": "RthTH0BeUu2YS08npxAz5", 682 | "type": "arrow" 683 | } 684 | ], 685 | "updated": 1679353626859, 686 | "link": null, 687 | "locked": false 688 | }, 689 | { 690 | "type": "text", 691 | "version": 1451, 692 | "versionNonce": 815698770, 693 | "isDeleted": false, 694 | "id": "IAVnmbsj3I0jUZxxThjgR", 695 | "fillStyle": "hachure", 696 | "strokeWidth": 1, 697 | "strokeStyle": "solid", 698 | "roughness": 1, 699 | "opacity": 100, 700 | "angle": 0, 701 | "x": 1134.399737660478, 702 | "y": -345.30306099217114, 703 | "strokeColor": "#000000", 704 | "backgroundColor": "transparent", 705 | "width": 87.94795227050781, 706 | "height": 33.6, 707 | "seed": 1404917262, 708 | "groupIds": [], 709 | "roundness": null, 710 | "boundElements": [], 711 | "updated": 1679353518300, 712 | "link": null, 713 | "locked": false, 714 | "fontSize": 28, 715 | "fontFamily": 1, 716 | "text": "Kibana", 717 | "textAlign": "center", 718 | "verticalAlign": "middle", 719 | "containerId": "OuAm6T1N2Hi6p_UVN-VCB", 720 | "originalText": "Kibana" 721 | }, 722 | { 723 | "type": "rectangle", 724 | "version": 390, 725 | "versionNonce": 1686383621, 726 | "isDeleted": false, 727 | "id": "JZSHyCyYlrzg9oGlaCj7K", 728 | "fillStyle": "cross-hatch", 729 | "strokeWidth": 1, 730 | "strokeStyle": "solid", 731 | "roughness": 1, 732 | "opacity": 100, 733 | "angle": 0, 734 | "x": 603.086069990949, 735 | "y": 75.28338474180487, 736 | "strokeColor": "#000000", 737 | "backgroundColor": "#ced4da", 738 | "width": 331, 739 | "height": 58, 740 | "seed": 1611928978, 741 | "groupIds": [], 742 | "roundness": { 743 | "type": 3 744 | }, 745 | "boundElements": [ 746 | { 747 | "type": "text", 748 | "id": "X016AmkW43_ta-ZdPMLkL" 749 | }, 750 | { 751 | "id": "UeETspdhwuokcwLbcVJe6", 752 | "type": "arrow" 753 | }, 754 | { 755 | "id": "CF-xfV5WRvSJr9GHmJ_J6", 756 | "type": "arrow" 757 | } 758 | ], 759 | "updated": 1679390718336, 760 | "link": null, 761 | "locked": false 762 | }, 763 | { 764 | "type": "text", 765 | "version": 330, 766 | "versionNonce": 908608203, 767 | "isDeleted": false, 768 | "id": "X016AmkW43_ta-ZdPMLkL", 769 | "fillStyle": "hachure", 770 | "strokeWidth": 1, 771 | "strokeStyle": "solid", 772 | "roughness": 1, 773 | "opacity": 100, 774 | "angle": 0, 775 | "x": 641.9861706989568, 776 | "y": 80.28338474180487, 777 | "strokeColor": "#000000", 778 | "backgroundColor": "transparent", 779 | "width": 253.19979858398438, 780 | "height": 48, 781 | "seed": 1779817938, 782 | "groupIds": [], 783 | "roundness": null, 784 | "boundElements": [], 785 | "updated": 1679390718336, 786 | "link": null, 787 | "locked": false, 788 | "fontSize": 20, 789 | "fontFamily": 1, 790 | "text": "/var/run/docker.sock\n/var/lib/docker/containers", 791 | "textAlign": "center", 792 | "verticalAlign": "middle", 793 | "containerId": "JZSHyCyYlrzg9oGlaCj7K", 794 | "originalText": "/var/run/docker.sock\n/var/lib/docker/containers" 795 | }, 796 | { 797 | "type": "arrow", 798 | "version": 1088, 799 | "versionNonce": 1743221966, 800 | "isDeleted": false, 801 | "id": "8xO0oIWCA6KdAMxjq6D2R", 802 | "fillStyle": "hachure", 803 | "strokeWidth": 1, 804 | "strokeStyle": "solid", 805 | "roughness": 1, 806 | "opacity": 100, 807 | "angle": 0, 808 | "x": 385.34132015773935, 809 | "y": -269.2197122089847, 810 | "strokeColor": "#000000", 811 | "backgroundColor": "transparent", 812 | "width": 105.51365449211994, 813 | "height": 160.55508023769622, 814 | "seed": 1931579154, 815 | "groupIds": [], 816 | "roundness": { 817 | "type": 2 818 | }, 819 | "boundElements": [ 820 | { 821 | "type": "text", 822 | "id": "18t_LZBPrcsm_f9iHF3bX" 823 | } 824 | ], 825 | "updated": 1679354037732, 826 | "link": null, 827 | "locked": false, 828 | "startBinding": { 829 | "elementId": "dzep2w5H19hH42cysACh2", 830 | "focus": -0.2578907626730556, 831 | "gap": 9.44799111717083 832 | }, 833 | "endBinding": { 834 | "elementId": "DZXRIamNpCFFiyWEaviJ-", 835 | "focus": -0.6644747313252518, 836 | "gap": 4.186427836236163 837 | }, 838 | "lastCommittedPoint": null, 839 | "startArrowhead": null, 840 | "endArrowhead": "arrow", 841 | "points": [ 842 | [ 843 | 0, 844 | 0 845 | ], 846 | [ 847 | 105.51365449211994, 848 | 160.55508023769622 849 | ] 850 | ] 851 | }, 852 | { 853 | "type": "text", 854 | "version": 30, 855 | "versionNonce": 1865265938, 856 | "isDeleted": false, 857 | "id": "18t_LZBPrcsm_f9iHF3bX", 858 | "fillStyle": "hachure", 859 | "strokeWidth": 1, 860 | "strokeStyle": "solid", 861 | "roughness": 1, 862 | "opacity": 100, 863 | "angle": 0, 864 | "x": 355.37084020045995, 865 | "y": -201.20512568555387, 866 | "strokeColor": "#000000", 867 | "backgroundColor": "transparent", 868 | "width": 116.53988647460938, 869 | "height": 24, 870 | "seed": 1380000014, 871 | "groupIds": [], 872 | "roundness": null, 873 | "boundElements": [], 874 | "updated": 1679353689898, 875 | "link": null, 876 | "locked": false, 877 | "fontSize": 20, 878 | "fontFamily": 1, 879 | "text": "Pull Metrics", 880 | "textAlign": "center", 881 | "verticalAlign": "middle", 882 | "containerId": "8xO0oIWCA6KdAMxjq6D2R", 883 | "originalText": "Pull Metrics" 884 | }, 885 | { 886 | "type": "arrow", 887 | "version": 866, 888 | "versionNonce": 1835355090, 889 | "isDeleted": false, 890 | "id": "_vhb-7O91TLyRuQ4sTdb0", 891 | "fillStyle": "hachure", 892 | "strokeWidth": 1, 893 | "strokeStyle": "solid", 894 | "roughness": 1, 895 | "opacity": 100, 896 | "angle": 0, 897 | "x": 277.02566076620764, 898 | "y": -136.26487694717042, 899 | "strokeColor": "#000000", 900 | "backgroundColor": "transparent", 901 | "width": 0.8221222926266023, 902 | "height": 140.56349993915092, 903 | "seed": 56931086, 904 | "groupIds": [], 905 | "roundness": { 906 | "type": 2 907 | }, 908 | "boundElements": [ 909 | { 910 | "type": "text", 911 | "id": "bXhNjt86n6XfRzZG7gYbF" 912 | } 913 | ], 914 | "updated": 1679354051205, 915 | "link": null, 916 | "locked": false, 917 | "startBinding": { 918 | "elementId": "3nUMuhHFRkjR6rnmo5_HF", 919 | "focus": -0.32112711101169933, 920 | "gap": 4.112877520119355 921 | }, 922 | "endBinding": { 923 | "elementId": "dzep2w5H19hH42cysACh2", 924 | "focus": 0.3128436402272153, 925 | "gap": 1.839326439834167 926 | }, 927 | "lastCommittedPoint": null, 928 | "startArrowhead": null, 929 | "endArrowhead": "arrow", 930 | "points": [ 931 | [ 932 | 0, 933 | 0 934 | ], 935 | [ 936 | 0.8221222926266023, 937 | -140.56349993915092 938 | ] 939 | ] 940 | }, 941 | { 942 | "type": "text", 943 | "version": 94, 944 | "versionNonce": 302439954, 945 | "isDeleted": false, 946 | "id": "bXhNjt86n6XfRzZG7gYbF", 947 | "fillStyle": "hachure", 948 | "strokeWidth": 1, 949 | "strokeStyle": "solid", 950 | "roughness": 1, 951 | "opacity": 100, 952 | "angle": 0, 953 | "x": 209.52678691496237, 954 | "y": -218.54662691674588, 955 | "strokeColor": "#000000", 956 | "backgroundColor": "transparent", 957 | "width": 135.8198699951172, 958 | "height": 24, 959 | "seed": 1330181330, 960 | "groupIds": [], 961 | "roundness": null, 962 | "boundElements": [], 963 | "updated": 1679354050666, 964 | "link": null, 965 | "locked": false, 966 | "fontSize": 20, 967 | "fontFamily": 1, 968 | "text": "Query Metrics", 969 | "textAlign": "center", 970 | "verticalAlign": "middle", 971 | "containerId": "_vhb-7O91TLyRuQ4sTdb0", 972 | "originalText": "Query Metrics" 973 | }, 974 | { 975 | "type": "arrow", 976 | "version": 756, 977 | "versionNonce": 1027397714, 978 | "isDeleted": false, 979 | "id": "ad3TukdKD6lS5obL9RDxj", 980 | "fillStyle": "hachure", 981 | "strokeWidth": 1, 982 | "strokeStyle": "solid", 983 | "roughness": 1, 984 | "opacity": 100, 985 | "angle": 0, 986 | "x": 597.7688163792427, 987 | "y": -135.9810601909, 988 | "strokeColor": "#000000", 989 | "backgroundColor": "transparent", 990 | "width": 0.8876442734388093, 991 | "height": 132.9018011325743, 992 | "seed": 1231999698, 993 | "groupIds": [], 994 | "roundness": { 995 | "type": 2 996 | }, 997 | "boundElements": [], 998 | "updated": 1679353617754, 999 | "link": null, 1000 | "locked": false, 1001 | "startBinding": { 1002 | "elementId": "DZXRIamNpCFFiyWEaviJ-", 1003 | "focus": -0.014264416076877505, 1004 | "gap": 3.8157409574940857 1005 | }, 1006 | "endBinding": { 1007 | "elementId": "A9-r0FwuyyUoCZ0u85s-G", 1008 | "focus": 0.0344658502666866, 1009 | "gap": 9.784842002681216 1010 | }, 1011 | "lastCommittedPoint": null, 1012 | "startArrowhead": null, 1013 | "endArrowhead": null, 1014 | "points": [ 1015 | [ 1016 | 0, 1017 | 0 1018 | ], 1019 | [ 1020 | -0.8876442734388093, 1021 | -132.9018011325743 1022 | ] 1023 | ] 1024 | }, 1025 | { 1026 | "type": "arrow", 1027 | "version": 1137, 1028 | "versionNonce": 1368023909, 1029 | "isDeleted": false, 1030 | "id": "UeETspdhwuokcwLbcVJe6", 1031 | "fillStyle": "hachure", 1032 | "strokeWidth": 1, 1033 | "strokeStyle": "solid", 1034 | "roughness": 1, 1035 | "opacity": 100, 1036 | "angle": 0, 1037 | "x": 726.8465166707625, 1038 | "y": 8.402920164711247, 1039 | "strokeColor": "#000000", 1040 | "backgroundColor": "transparent", 1041 | "width": 0.20883983740907297, 1042 | "height": 56.859087385782516, 1043 | "seed": 775365842, 1044 | "groupIds": [], 1045 | "roundness": { 1046 | "type": 2 1047 | }, 1048 | "boundElements": [], 1049 | "updated": 1679390718336, 1050 | "link": null, 1051 | "locked": false, 1052 | "startBinding": null, 1053 | "endBinding": { 1054 | "elementId": "JZSHyCyYlrzg9oGlaCj7K", 1055 | "focus": -0.24991402292554932, 1056 | "gap": 10.021377191311103 1057 | }, 1058 | "lastCommittedPoint": null, 1059 | "startArrowhead": "dot", 1060 | "endArrowhead": "arrow", 1061 | "points": [ 1062 | [ 1063 | 0, 1064 | 0 1065 | ], 1066 | [ 1067 | 0.20883983740907297, 1068 | 56.859087385782516 1069 | ] 1070 | ] 1071 | }, 1072 | { 1073 | "type": "arrow", 1074 | "version": 857, 1075 | "versionNonce": 1670557381, 1076 | "isDeleted": false, 1077 | "id": "CF-xfV5WRvSJr9GHmJ_J6", 1078 | "fillStyle": "hachure", 1079 | "strokeWidth": 1, 1080 | "strokeStyle": "solid", 1081 | "roughness": 1, 1082 | "opacity": 100, 1083 | "angle": 0, 1084 | "x": 786.7969319945821, 1085 | "y": 67.01699159203349, 1086 | "strokeColor": "#000000", 1087 | "backgroundColor": "transparent", 1088 | "width": 84.84995633988763, 1089 | "height": 91.20505312012452, 1090 | "seed": 1841243922, 1091 | "groupIds": [], 1092 | "roundness": { 1093 | "type": 2 1094 | }, 1095 | "boundElements": [ 1096 | { 1097 | "type": "text", 1098 | "id": "-5ALfJ8ncqBpE8-agEj0w" 1099 | } 1100 | ], 1101 | "updated": 1679390718336, 1102 | "link": null, 1103 | "locked": false, 1104 | "startBinding": { 1105 | "elementId": "JZSHyCyYlrzg9oGlaCj7K", 1106 | "focus": -0.08544910603312071, 1107 | "gap": 8.266393149771375 1108 | }, 1109 | "endBinding": { 1110 | "elementId": "6KWkQ5SAz7ptFIqBt5I7q", 1111 | "focus": -0.24733217668131638, 1112 | "gap": 8.293222566928783 1113 | }, 1114 | "lastCommittedPoint": null, 1115 | "startArrowhead": null, 1116 | "endArrowhead": "arrow", 1117 | "points": [ 1118 | [ 1119 | 0, 1120 | 0 1121 | ], 1122 | [ 1123 | 84.84995633988763, 1124 | -91.20505312012452 1125 | ] 1126 | ] 1127 | }, 1128 | { 1129 | "type": "text", 1130 | "version": 47, 1131 | "versionNonce": 598861842, 1132 | "isDeleted": false, 1133 | "id": "-5ALfJ8ncqBpE8-agEj0w", 1134 | "fillStyle": "hachure", 1135 | "strokeWidth": 1, 1136 | "strokeStyle": "solid", 1137 | "roughness": 1, 1138 | "opacity": 100, 1139 | "angle": 0, 1140 | "x": 784.5572733885754, 1141 | "y": 19.38474091087744, 1142 | "strokeColor": "#000000", 1143 | "backgroundColor": "transparent", 1144 | "width": 88.75991821289062, 1145 | "height": 24, 1146 | "seed": 218359054, 1147 | "groupIds": [], 1148 | "roundness": null, 1149 | "boundElements": [], 1150 | "updated": 1679353979554, 1151 | "link": null, 1152 | "locked": false, 1153 | "fontSize": 20, 1154 | "fontFamily": 1, 1155 | "text": "Poll Logs", 1156 | "textAlign": "center", 1157 | "verticalAlign": "middle", 1158 | "containerId": "CF-xfV5WRvSJr9GHmJ_J6", 1159 | "originalText": "Poll Logs" 1160 | }, 1161 | { 1162 | "type": "arrow", 1163 | "version": 658, 1164 | "versionNonce": 790254798, 1165 | "isDeleted": false, 1166 | "id": "Cgmhms42VznXU0xHq7mGp", 1167 | "fillStyle": "hachure", 1168 | "strokeWidth": 1, 1169 | "strokeStyle": "solid", 1170 | "roughness": 1, 1171 | "opacity": 100, 1172 | "angle": 0, 1173 | "x": 881.0085243924486, 1174 | "y": -143.64838688548184, 1175 | "strokeColor": "#000000", 1176 | "backgroundColor": "transparent", 1177 | "width": 1.43526378891886, 1178 | "height": 123.94613870760287, 1179 | "seed": 1966816338, 1180 | "groupIds": [], 1181 | "roundness": { 1182 | "type": 2 1183 | }, 1184 | "boundElements": [ 1185 | { 1186 | "type": "text", 1187 | "id": "WOTCLj7OgJEyeGFVBgRE0" 1188 | } 1189 | ], 1190 | "updated": 1679353518300, 1191 | "link": null, 1192 | "locked": false, 1193 | "startBinding": { 1194 | "elementId": "6KWkQ5SAz7ptFIqBt5I7q", 1195 | "focus": -0.07693202378557829, 1196 | "gap": 11.496387458430775 1197 | }, 1198 | "endBinding": { 1199 | "elementId": "X4Op79JUgTiBk6v0n1_cd", 1200 | "focus": 0.057882228141615, 1201 | "gap": 11.073177733070793 1202 | }, 1203 | "lastCommittedPoint": null, 1204 | "startArrowhead": null, 1205 | "endArrowhead": "arrow", 1206 | "points": [ 1207 | [ 1208 | 0, 1209 | 0 1210 | ], 1211 | [ 1212 | 1.43526378891886, 1213 | -123.94613870760287 1214 | ] 1215 | ] 1216 | }, 1217 | { 1218 | "type": "text", 1219 | "version": 206, 1220 | "versionNonce": 2025957522, 1221 | "isDeleted": false, 1222 | "id": "WOTCLj7OgJEyeGFVBgRE0", 1223 | "fillStyle": "hachure", 1224 | "strokeWidth": 1, 1225 | "strokeStyle": "solid", 1226 | "roughness": 1, 1227 | "opacity": 100, 1228 | "angle": 0, 1229 | "x": 832.0962048098573, 1230 | "y": -217.62145623928336, 1231 | "strokeColor": "#000000", 1232 | "backgroundColor": "transparent", 1233 | "width": 99.25990295410156, 1234 | "height": 24, 1235 | "seed": 881271502, 1236 | "groupIds": [], 1237 | "roundness": null, 1238 | "boundElements": [], 1239 | "updated": 1679354183637, 1240 | "link": null, 1241 | "locked": false, 1242 | "fontSize": 20, 1243 | "fontFamily": 1, 1244 | "text": "Push Logs", 1245 | "textAlign": "center", 1246 | "verticalAlign": "middle", 1247 | "containerId": "Cgmhms42VznXU0xHq7mGp", 1248 | "originalText": "Push Logs" 1249 | }, 1250 | { 1251 | "type": "arrow", 1252 | "version": 1169, 1253 | "versionNonce": 1121094414, 1254 | "isDeleted": false, 1255 | "id": "IQcv-HmHctBfoKzVXkyXb", 1256 | "fillStyle": "hachure", 1257 | "strokeWidth": 1, 1258 | "strokeStyle": "solid", 1259 | "roughness": 1, 1260 | "opacity": 100, 1261 | "angle": 0, 1262 | "x": 943.605050827121, 1263 | "y": -276.23865557879884, 1264 | "strokeColor": "#000000", 1265 | "backgroundColor": "transparent", 1266 | "width": 169.74873243726665, 1267 | "height": 138.78500762453103, 1268 | "seed": 213004750, 1269 | "groupIds": [], 1270 | "roundness": { 1271 | "type": 2 1272 | }, 1273 | "boundElements": [ 1274 | { 1275 | "type": "text", 1276 | "id": "7xIY-m-pqM-apETpS9GD2" 1277 | } 1278 | ], 1279 | "updated": 1679353518300, 1280 | "link": null, 1281 | "locked": false, 1282 | "startBinding": { 1283 | "elementId": "X4Op79JUgTiBk6v0n1_cd", 1284 | "focus": 0.0572951571794921, 1285 | "gap": 2.4290477473566625 1286 | }, 1287 | "endBinding": { 1288 | "elementId": "RwL7tyNKnalwIXHi3y7vA", 1289 | "focus": 0.05335221129722037, 1290 | "gap": 9.81483628456931 1291 | }, 1292 | "lastCommittedPoint": null, 1293 | "startArrowhead": null, 1294 | "endArrowhead": "arrow", 1295 | "points": [ 1296 | [ 1297 | 0, 1298 | 0 1299 | ], 1300 | [ 1301 | 169.74873243726665, 1302 | 138.78500762453103 1303 | ] 1304 | ] 1305 | }, 1306 | { 1307 | "type": "text", 1308 | "version": 113, 1309 | "versionNonce": 2096103118, 1310 | "isDeleted": false, 1311 | "id": "7xIY-m-pqM-apETpS9GD2", 1312 | "fillStyle": "hachure", 1313 | "strokeWidth": 1, 1314 | "strokeStyle": "solid", 1315 | "roughness": 1, 1316 | "opacity": 100, 1317 | "angle": 0, 1318 | "x": 978.339463432473, 1319 | "y": -254.84615176653324, 1320 | "strokeColor": "#000000", 1321 | "backgroundColor": "transparent", 1322 | "width": 100.2799072265625, 1323 | "height": 96, 1324 | "seed": 1863428750, 1325 | "groupIds": [], 1326 | "roundness": null, 1327 | "boundElements": [], 1328 | "updated": 1679354024091, 1329 | "link": null, 1330 | "locked": false, 1331 | "fontSize": 20, 1332 | "fontFamily": 1, 1333 | "text": "Parse,\nFilter &\nTransform\nLogs", 1334 | "textAlign": "center", 1335 | "verticalAlign": "middle", 1336 | "containerId": "IQcv-HmHctBfoKzVXkyXb", 1337 | "originalText": "Parse,\nFilter &\nTransform\nLogs" 1338 | }, 1339 | { 1340 | "type": "arrow", 1341 | "version": 1735, 1342 | "versionNonce": 380238158, 1343 | "isDeleted": false, 1344 | "id": "RthTH0BeUu2YS08npxAz5", 1345 | "fillStyle": "hachure", 1346 | "strokeWidth": 1, 1347 | "strokeStyle": "solid", 1348 | "roughness": 1, 1349 | "opacity": 100, 1350 | "angle": 0, 1351 | "x": 1177.529579631323, 1352 | "y": -272.3807510050314, 1353 | "strokeColor": "#000000", 1354 | "backgroundColor": "transparent", 1355 | "width": 0.7444638179485992, 1356 | "height": 133.8138758263902, 1357 | "seed": 405657038, 1358 | "groupIds": [], 1359 | "roundness": { 1360 | "type": 2 1361 | }, 1362 | "boundElements": [ 1363 | { 1364 | "type": "text", 1365 | "id": "SzVNMHbM4csSXuDm-YK1z" 1366 | } 1367 | ], 1368 | "updated": 1679353518300, 1369 | "link": null, 1370 | "locked": false, 1371 | "startBinding": { 1372 | "elementId": "OuAm6T1N2Hi6p_UVN-VCB", 1373 | "focus": 0.005075889921273774, 1374 | "gap": 6.28695232112409 1375 | }, 1376 | "endBinding": { 1377 | "elementId": "RwL7tyNKnalwIXHi3y7vA", 1378 | "focus": -0.009850221619726662, 1379 | "gap": 10.928063508942728 1380 | }, 1381 | "lastCommittedPoint": null, 1382 | "startArrowhead": null, 1383 | "endArrowhead": "arrow", 1384 | "points": [ 1385 | [ 1386 | 0, 1387 | 0 1388 | ], 1389 | [ 1390 | -0.7444638179485992, 1391 | 133.8138758263902 1392 | ] 1393 | ] 1394 | }, 1395 | { 1396 | "type": "text", 1397 | "version": 191, 1398 | "versionNonce": 802587666, 1399 | "isDeleted": false, 1400 | "id": "SzVNMHbM4csSXuDm-YK1z", 1401 | "fillStyle": "hachure", 1402 | "strokeWidth": 1, 1403 | "strokeStyle": "solid", 1404 | "roughness": 1, 1405 | "opacity": 100, 1406 | "angle": 0, 1407 | "x": 1109.0374136403175, 1408 | "y": -217.4738130918363, 1409 | "strokeColor": "#000000", 1410 | "backgroundColor": "transparent", 1411 | "width": 136.2398681640625, 1412 | "height": 24, 1413 | "seed": 1019385362, 1414 | "groupIds": [], 1415 | "roundness": null, 1416 | "boundElements": [], 1417 | "updated": 1679354027242, 1418 | "link": null, 1419 | "locked": false, 1420 | "fontSize": 20, 1421 | "fontFamily": 1, 1422 | "text": "Visualize Logs", 1423 | "textAlign": "center", 1424 | "verticalAlign": "middle", 1425 | "containerId": "RthTH0BeUu2YS08npxAz5", 1426 | "originalText": "Visualize Logs" 1427 | } 1428 | ], 1429 | "appState": { 1430 | "gridSize": null, 1431 | "viewBackgroundColor": "#ffffff" 1432 | }, 1433 | "files": {} 1434 | } --------------------------------------------------------------------------------