├── .gitignore ├── .img ├── telemetry-dark.png ├── telemetry-light.png └── telemetry.drawio ├── CarBooking ├── Dockerfile ├── pom.xml └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── example │ │ │ └── demo │ │ │ ├── AvailableCars.java │ │ │ ├── DemoApplication.java │ │ │ └── MySimpleCamelRouter.java │ └── resources │ │ ├── application.properties │ │ └── logback.xml │ └── test │ └── java │ └── com │ └── example │ └── demo │ └── MySimpleCamelRouterTest.java ├── FlightBooking ├── Dockerfile ├── pom.xml └── src │ └── main │ ├── java │ └── com │ │ └── example │ │ └── demo │ │ ├── AvailableFlights.java │ │ ├── DemoApplication.java │ │ └── MySimpleCamelRouter.java │ └── resources │ ├── application.properties │ └── logback.xml ├── HotelBooking ├── Dockerfile ├── pom.xml └── src │ └── main │ ├── java │ └── com │ │ └── example │ │ └── demo │ │ ├── AvailableHotels.java │ │ ├── DemoApplication.java │ │ └── MySimpleCamelRouter.java │ └── resources │ ├── application.properties │ └── logback.xml ├── LICENSE ├── README.md ├── TripBooking ├── Demo.jmx ├── Dockerfile ├── pom.xml └── src │ └── main │ ├── java │ └── com │ │ └── example │ │ └── demo │ │ ├── DemoApplication.java │ │ ├── MergeAggregationStrategy.java │ │ └── MySimpleCamelRouter.java │ └── resources │ ├── application.properties │ └── logback.xml ├── compose.demo-apps.yml ├── compose.infra.yml ├── compose.yml └── infrastructure ├── filebeat └── filebeat.yml ├── grafana └── provisioning │ ├── dashboards │ ├── Logs.json │ ├── SpringBoot3ApacheCamel.json │ └── all.yml │ └── datasources │ └── datasource.yml ├── otel-collector └── otel-collector-config.yaml └── prometheus └── prometheusConfig.yml /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | pom.xml.tag 3 | pom.xml.releaseBackup 4 | pom.xml.versionsBackup 5 | pom.xml.next 6 | release.properties 7 | dependency-reduced-pom.xml 8 | buildNumber.properties 9 | .mvn/timing.properties 10 | # https://github.com/takari/maven-wrapper#usage-without-binary-jar 11 | .mvn/wrapper/maven-wrapper.jar 12 | .settings 13 | .project 14 | .classpath 15 | 16 | #Intellij 17 | .idea 18 | .idea/* 19 | !.idea/codeStyles 20 | !.idea/runConfigurations 21 | .idea/artifacts 22 | .idea/compiler.xml 23 | .idea/jarRepositories.xml 24 | .idea/modules.xml 25 | .idea/*.iml 26 | .idea/modules 27 | .idea/vcs.xml 28 | *.iml 29 | *.ipr 30 | 31 | # VSCode 32 | .vscode 33 | .vscode/* 34 | !.vscode/settings.json 35 | !.vscode/tasks.json 36 | !.vscode/launch.json 37 | !.vscode/extensions.json 38 | *.code-workspace 39 | # Local History for Visual Studio Code 40 | .history/ 41 | 42 | .DS_Store 43 | -------------------------------------------------------------------------------- /.img/telemetry-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stn1slv/demo-apps/78821035f5ee46627dffc65868e1ce3c6de82dfe/.img/telemetry-dark.png -------------------------------------------------------------------------------- /.img/telemetry-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stn1slv/demo-apps/78821035f5ee46627dffc65868e1ce3c6de82dfe/.img/telemetry-light.png -------------------------------------------------------------------------------- /.img/telemetry.drawio: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | -------------------------------------------------------------------------------- /CarBooking/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM maven:3.9-eclipse-temurin-17 as app-builder 2 | # Build app 3 | WORKDIR /var/src/app 4 | COPY . . 5 | RUN mvn -DskipTests clean package && mv target/*.jar target/app.jar 6 | # Download OpenTelemetry Java Agent 7 | ENV OTEL_AGENT_VERSION=v2.13.1 8 | RUN wget https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases/download/$OTEL_AGENT_VERSION/opentelemetry-javaagent.jar -P /var/src/app/target 9 | 10 | FROM eclipse-temurin:17-jre 11 | COPY --from=app-builder /var/src/app/target/app.jar /opt 12 | COPY --from=app-builder /var/src/app/target/opentelemetry-javaagent.jar /opt 13 | CMD ["java", "-javaagent:/opt/opentelemetry-javaagent.jar", "-jar", "/opt/app.jar"] -------------------------------------------------------------------------------- /CarBooking/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 3.4.3 9 | 10 | 11 | 12 | com.example 13 | CarBooking 14 | 0.0.1 15 | CarBooking 16 | Demo project for Spring Boot 17 | 18 | 17 19 | 4.10.0 20 | 21 | 22 | 23 | 24 | org.springframework.boot 25 | spring-boot-starter-web 26 | 27 | 28 | org.springframework.boot 29 | spring-boot-starter-actuator 30 | 31 | 32 | io.micrometer 33 | micrometer-registry-prometheus 34 | 35 | 36 | 37 | org.apache.camel.springboot 38 | camel-spring-boot-starter 39 | ${camel.version} 40 | 41 | 42 | org.apache.camel.springboot 43 | camel-rest-starter 44 | ${camel.version} 45 | 46 | 47 | org.apache.camel.springboot 48 | camel-servlet-starter 49 | ${camel.version} 50 | 51 | 52 | org.apache.camel.springboot 53 | camel-jackson-starter 54 | ${camel.version} 55 | 56 | 57 | org.apache.camel.springboot 58 | camel-micrometer-starter 59 | ${camel.version} 60 | 61 | 62 | org.apache.camel.springboot 63 | camel-seda-starter 64 | ${camel.version} 65 | 66 | 67 | org.apache.camel.springboot 68 | camel-http-starter 69 | ${camel.version} 70 | 71 | 72 | org.apache.camel.springboot 73 | camel-kafka-starter 74 | ${camel.version} 75 | 76 | 77 | org.apache.camel.springboot 78 | camel-bean-starter 79 | ${camel.version} 80 | 81 | 82 | 83 | 84 | org.apache.camel.springboot 85 | camel-opentelemetry-starter 86 | ${camel.version} 87 | 88 | 89 | 90 | io.micrometer 91 | micrometer-registry-otlp 92 | 93 | 94 | 95 | org.springframework.boot 96 | spring-boot-starter-test 97 | test 98 | 99 | 100 | org.apache.camel 101 | camel-test-spring-junit5 102 | ${camel.version} 103 | test 104 | 105 | 106 | 107 | 108 | 109 | org.springframework.boot 110 | spring-boot-maven-plugin 111 | 112 | 113 | 114 | -------------------------------------------------------------------------------- /CarBooking/src/main/java/com/example/demo/AvailableCars.java: -------------------------------------------------------------------------------- 1 | package com.example.demo; 2 | 3 | import java.util.Arrays; 4 | import java.util.List; 5 | import java.util.Random; 6 | 7 | import org.apache.camel.Handler; 8 | 9 | public class AvailableCars { 10 | private static final Random RANDOM = new Random(); 11 | private static final List CARS = Arrays.asList( 12 | "Toyota Corolla", 13 | "Honda Civic", 14 | "Mazda 3", 15 | "Hyundai Elantra", 16 | "Subaru Impreza", 17 | "Volkswagen Jetta", 18 | "Volkswagen Golf", 19 | "Ford Fiesta", 20 | "Ford Focus", 21 | "Chevrolet Cruze", 22 | "Kia Ceed", 23 | "Skoda Octavia", 24 | "Citroen C4", 25 | "Peugeot 308" 26 | ); 27 | 28 | @Handler 29 | public String getAvailableCar() { 30 | return String.format(""" 31 | { 32 | "bookingId": %d, 33 | "car": "%s", 34 | "startDate": "12-11-2018", 35 | "endDate": "15-11-2018", 36 | "price": %d 37 | }""", 38 | RANDOM.nextInt(1000), 39 | CARS.get(RANDOM.nextInt(CARS.size())), 40 | RANDOM.nextInt(25) + 140 41 | ); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /CarBooking/src/main/java/com/example/demo/DemoApplication.java: -------------------------------------------------------------------------------- 1 | package com.example.demo; 2 | 3 | import org.apache.camel.opentelemetry.starter.CamelOpenTelemetry; 4 | import org.springframework.boot.SpringApplication; 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; 6 | 7 | @CamelOpenTelemetry 8 | @SpringBootApplication 9 | public class DemoApplication { 10 | 11 | public static void main(String[] args) { 12 | SpringApplication.run(DemoApplication.class, args); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /CarBooking/src/main/java/com/example/demo/MySimpleCamelRouter.java: -------------------------------------------------------------------------------- 1 | package com.example.demo; 2 | 3 | import org.apache.camel.LoggingLevel; 4 | import org.apache.camel.builder.RouteBuilder; 5 | import org.apache.camel.model.dataformat.JsonLibrary; 6 | import org.apache.camel.model.rest.RestBindingMode; 7 | import org.springframework.stereotype.Component; 8 | 9 | @Component 10 | public class MySimpleCamelRouter extends RouteBuilder { 11 | @Override 12 | public void configure() throws Exception { 13 | restConfiguration() 14 | .component("servlet") 15 | .bindingMode(RestBindingMode.json); 16 | 17 | rest().get("/bookCar") 18 | .to("direct:bookCar"); 19 | 20 | from("direct:bookCar").routeId("bookCar-http") 21 | .log(LoggingLevel.INFO, "New book car request with traceId=${header.x-b3-traceid}") 22 | .bean(new AvailableCars(),"getAvailableCar") 23 | .unmarshal().json(JsonLibrary.Jackson); 24 | 25 | // kafka based 26 | from("kafka:car_input").routeId("bookCar-kafka") 27 | .log(LoggingLevel.INFO, "New book car request via Kafka topic") 28 | // .to("log:debug?showAll=true&multiline=true") 29 | .bean(new AvailableCars(),"getAvailableCar") 30 | .to("kafka:car_output"); 31 | 32 | } 33 | } -------------------------------------------------------------------------------- /CarBooking/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | camel.springboot.main-run-controller=true 2 | camel.springboot.use-mdc-logging = true 3 | camel.main.use-mdc-logging = true 4 | camel.component.micrometer.enabled=true 5 | camel.component.kafka.brokers=kafka:9092 6 | camel.metrics.enable-message-history=true 7 | management.endpoint.metrics.enabled=true 8 | management.endpoints.web.exposure.include=* 9 | management.otlp.metrics.export.step=10s 10 | spring.application.name=CarBooking 11 | -------------------------------------------------------------------------------- /CarBooking/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | %d{HH:mm:ss.SSS} %-5level [%X{trace_id:-},%X{span_id:-}] %logger{36} - %msg%n 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /CarBooking/src/test/java/com/example/demo/MySimpleCamelRouterTest.java: -------------------------------------------------------------------------------- 1 | package com.example.demo; 2 | 3 | import org.apache.camel.Exchange; 4 | import org.apache.camel.LoggingLevel; 5 | import org.apache.camel.builder.AdviceWith; 6 | import org.apache.camel.builder.RouteBuilder; 7 | import org.apache.camel.component.mock.MockEndpoint; 8 | import org.apache.camel.model.dataformat.JsonLibrary; 9 | import org.apache.camel.model.rest.RestBindingMode; 10 | import org.apache.camel.test.junit5.CamelTestSupport; 11 | import org.junit.jupiter.api.Test; 12 | 13 | import com.fasterxml.jackson.databind.JsonNode; 14 | import com.fasterxml.jackson.databind.ObjectMapper; 15 | 16 | import static org.junit.jupiter.api.Assertions.*; 17 | 18 | class MySimpleCamelRouterTest extends CamelTestSupport { 19 | 20 | private ObjectMapper objectMapper = new ObjectMapper(); 21 | 22 | @Override 23 | protected RouteBuilder createRouteBuilder() throws Exception { 24 | return new RouteBuilder() { 25 | @Override 26 | public void configure() throws Exception { 27 | // Create HTTP-only routes for testing 28 | restConfiguration() 29 | .component("servlet") 30 | .bindingMode(RestBindingMode.json); 31 | 32 | rest().get("/bookCar") 33 | .to("direct:bookCar"); 34 | 35 | from("direct:bookCar").routeId("bookCar-http") 36 | .log(LoggingLevel.INFO, "New book car request with traceId=${header.x-b3-traceid}") 37 | .bean(new AvailableCars(),"getAvailableCar") 38 | .unmarshal().json(JsonLibrary.Jackson); 39 | 40 | // Create a separate Kafka-like route for testing without actual Kafka 41 | from("direct:kafka-test-input").routeId("bookCar-kafka-test") 42 | .log(LoggingLevel.INFO, "New book car request via test Kafka topic") 43 | .bean(new AvailableCars(),"getAvailableCar") 44 | .to("mock:kafka-output"); 45 | } 46 | }; 47 | } 48 | 49 | @Override 50 | public boolean isUseAdviceWith() { 51 | return true; 52 | } 53 | 54 | /** 55 | * Test the direct bookCar route without external dependencies 56 | */ 57 | @Test 58 | void testDirectBookCarRoute() throws Exception { 59 | // Given 60 | context().start(); 61 | 62 | // When 63 | String response = template().requestBody("direct:bookCar", null, String.class); 64 | 65 | // Then 66 | assertNotNull(response); 67 | JsonNode jsonNode = objectMapper.readTree(response); 68 | assertTrue(jsonNode.has("bookingId")); 69 | assertTrue(jsonNode.has("car")); 70 | assertTrue(jsonNode.has("price")); 71 | assertTrue(jsonNode.has("startDate")); 72 | assertTrue(jsonNode.has("endDate")); 73 | 74 | // Verify field values 75 | assertTrue(jsonNode.get("bookingId").asInt() >= 0); 76 | assertTrue(jsonNode.get("bookingId").asInt() < 1000); 77 | assertFalse(jsonNode.get("car").asText().isEmpty()); 78 | assertEquals("12-11-2018", jsonNode.get("startDate").asText()); 79 | assertEquals("15-11-2018", jsonNode.get("endDate").asText()); 80 | assertTrue(jsonNode.get("price").asInt() >= 140); 81 | assertTrue(jsonNode.get("price").asInt() <= 164); 82 | } 83 | 84 | /** 85 | * Test Kafka-like route using AdviceWith to mock Kafka endpoints 86 | */ 87 | @Test 88 | void testKafkaRouteWithMockedEndpoints() throws Exception { 89 | // Given - Use AdviceWith to modify the test Kafka route 90 | AdviceWith.adviceWith(context(), "bookCar-kafka-test", a -> { 91 | a.replaceFromWith("direct:kafka-input"); 92 | a.interceptSendToEndpoint("mock:kafka-output") 93 | .skipSendToOriginalEndpoint() 94 | .to("mock:kafka-verified-output"); 95 | }); 96 | 97 | MockEndpoint mockOutput = context().getEndpoint("mock:kafka-verified-output", MockEndpoint.class); 98 | mockOutput.expectedMessageCount(1); 99 | 100 | context().start(); 101 | 102 | // When 103 | String response = template().requestBody("direct:kafka-input", null, String.class); 104 | 105 | // Then 106 | assertNotNull(response); 107 | JsonNode jsonNode = objectMapper.readTree(response); 108 | assertTrue(jsonNode.has("bookingId")); 109 | assertTrue(jsonNode.has("car")); 110 | assertTrue(jsonNode.has("price")); 111 | assertTrue(jsonNode.has("startDate")); 112 | assertTrue(jsonNode.has("endDate")); 113 | 114 | // Verify mock endpoint received the message 115 | mockOutput.assertIsSatisfied(); 116 | 117 | Exchange receivedExchange = mockOutput.getReceivedExchanges().get(0); 118 | String receivedResponse = receivedExchange.getIn().getBody(String.class); 119 | JsonNode receivedJson = objectMapper.readTree(receivedResponse); 120 | assertTrue(receivedJson.has("bookingId")); 121 | assertTrue(receivedJson.has("car")); 122 | } 123 | 124 | /** 125 | * Test Kafka route with headers propagation using AdviceWith 126 | */ 127 | @Test 128 | void testKafkaRouteWithHeaders() throws Exception { 129 | // Given 130 | AdviceWith.adviceWith(context(), "bookCar-kafka-test", a -> { 131 | a.replaceFromWith("direct:kafka-input-headers"); 132 | a.interceptSendToEndpoint("mock:kafka-output") 133 | .skipSendToOriginalEndpoint() 134 | .to("mock:kafka-output-headers"); 135 | }); 136 | 137 | MockEndpoint mockOutput = context().getEndpoint("mock:kafka-output-headers", MockEndpoint.class); 138 | mockOutput.expectedMessageCount(1); 139 | mockOutput.expectedHeaderReceived("x-b3-traceid", "test-trace-123"); 140 | 141 | context().start(); 142 | 143 | // When 144 | String response = template().requestBodyAndHeader( 145 | "direct:kafka-input-headers", 146 | null, 147 | "x-b3-traceid", 148 | "test-trace-123", 149 | String.class 150 | ); 151 | 152 | // Then 153 | assertNotNull(response); 154 | mockOutput.assertIsSatisfied(); 155 | 156 | // Verify header propagation 157 | Exchange exchange = mockOutput.getReceivedExchanges().get(0); 158 | assertEquals("test-trace-123", exchange.getIn().getHeader("x-b3-traceid")); 159 | } 160 | 161 | /** 162 | * Test error handling in routes 163 | */ 164 | @Test 165 | void testErrorHandling() throws Exception { 166 | // Given - Add a route with error handling capabilities 167 | context().addRoutes(new RouteBuilder() { 168 | @Override 169 | public void configure() throws Exception { 170 | onException(RuntimeException.class) 171 | .handled(true) 172 | .setBody(constant("{\"error\":\"Processing failed\"}")) 173 | .to("mock:error-output"); 174 | 175 | from("direct:error-test") 176 | .routeId("error-test-route") 177 | .process(exchange -> { 178 | String body = exchange.getIn().getBody(String.class); 179 | if ("ERROR".equals(body)) { 180 | throw new RuntimeException("Simulated error"); 181 | } 182 | }) 183 | .bean(new AvailableCars(), "getAvailableCar") 184 | .to("mock:success-output"); 185 | } 186 | }); 187 | 188 | MockEndpoint successMock = context().getEndpoint("mock:success-output", MockEndpoint.class); 189 | MockEndpoint errorMock = context().getEndpoint("mock:error-output", MockEndpoint.class); 190 | 191 | successMock.expectedMessageCount(1); 192 | errorMock.expectedMessageCount(1); 193 | 194 | context().start(); 195 | 196 | // When - Send success and error messages 197 | template().sendBody("direct:error-test", "NORMAL"); 198 | template().sendBody("direct:error-test", "ERROR"); 199 | 200 | // Then 201 | successMock.assertIsSatisfied(); 202 | errorMock.assertIsSatisfied(); 203 | 204 | // Verify success response 205 | String successResponse = successMock.getReceivedExchanges().get(0).getIn().getBody(String.class); 206 | JsonNode successJson = objectMapper.readTree(successResponse); 207 | assertTrue(successJson.has("bookingId")); 208 | 209 | // Verify error response 210 | String errorResponse = errorMock.getReceivedExchanges().get(0).getIn().getBody(String.class); 211 | JsonNode errorJson = objectMapper.readTree(errorResponse); 212 | assertTrue(errorJson.has("error")); 213 | assertEquals("Processing failed", errorJson.get("error").asText()); 214 | } 215 | 216 | /** 217 | * Test multiple requests to verify randomness and consistency 218 | */ 219 | @Test 220 | void testMultipleRequests() throws Exception { 221 | // Given 222 | context().start(); 223 | 224 | // When - make multiple requests 225 | String response1 = template().requestBody("direct:bookCar", null, String.class); 226 | String response2 = template().requestBody("direct:bookCar", null, String.class); 227 | String response3 = template().requestBody("direct:bookCar", null, String.class); 228 | 229 | // Then - verify all responses are valid 230 | JsonNode json1 = objectMapper.readTree(response1); 231 | JsonNode json2 = objectMapper.readTree(response2); 232 | JsonNode json3 = objectMapper.readTree(response3); 233 | 234 | // All should have required fields 235 | for (JsonNode json : new JsonNode[]{json1, json2, json3}) { 236 | assertTrue(json.has("bookingId")); 237 | assertTrue(json.has("car")); 238 | assertTrue(json.has("price")); 239 | assertEquals("12-11-2018", json.get("startDate").asText()); 240 | assertEquals("15-11-2018", json.get("endDate").asText()); 241 | } 242 | 243 | // At least one field should vary between requests (due to randomness) 244 | boolean hasDifference = 245 | !json1.get("bookingId").equals(json2.get("bookingId")) || 246 | !json1.get("car").equals(json2.get("car")) || 247 | !json1.get("price").equals(json2.get("price")); 248 | 249 | assertTrue(hasDifference, "Multiple requests should produce some variation due to randomness"); 250 | } 251 | 252 | /** 253 | * Test Kafka route performance with multiple messages using AdviceWith 254 | */ 255 | @Test 256 | void testKafkaRoutePerformance() throws Exception { 257 | // Given 258 | AdviceWith.adviceWith(context(), "bookCar-kafka-test", a -> { 259 | a.replaceFromWith("direct:perf-kafka-input"); 260 | a.interceptSendToEndpoint("mock:kafka-output") 261 | .skipSendToOriginalEndpoint() 262 | .to("mock:perf-kafka-output"); 263 | }); 264 | 265 | MockEndpoint mockOutput = context().getEndpoint("mock:perf-kafka-output", MockEndpoint.class); 266 | int messageCount = 10; 267 | mockOutput.expectedMessageCount(messageCount); 268 | 269 | context().start(); 270 | 271 | // When - Send multiple messages to test throughput 272 | long startTime = System.currentTimeMillis(); 273 | for (int i = 0; i < messageCount; i++) { 274 | template().sendBody("direct:perf-kafka-input", null); 275 | } 276 | long endTime = System.currentTimeMillis(); 277 | 278 | // Then 279 | mockOutput.assertIsSatisfied(); 280 | assertEquals(messageCount, mockOutput.getReceivedExchanges().size()); 281 | 282 | // Verify performance (should complete within reasonable time) 283 | long duration = endTime - startTime; 284 | assertTrue(duration < 5000, "Performance test should complete within 5 seconds"); 285 | 286 | // Verify all responses are valid 287 | for (Exchange exchange : mockOutput.getReceivedExchanges()) { 288 | String response = exchange.getIn().getBody(String.class); 289 | JsonNode jsonNode = objectMapper.readTree(response); 290 | assertTrue(jsonNode.has("bookingId")); 291 | assertTrue(jsonNode.has("car")); 292 | } 293 | } 294 | 295 | /** 296 | * Test route configuration and structure 297 | */ 298 | @Test 299 | void testRouteConfiguration() throws Exception { 300 | // Given 301 | context().start(); 302 | 303 | // Then - verify routes are configured correctly 304 | assertNotNull(context().getRoute("bookCar-http")); 305 | assertNotNull(context().getRoute("bookCar-kafka-test")); 306 | 307 | // Verify route IDs 308 | assertEquals("bookCar-http", context().getRoute("bookCar-http").getId()); 309 | assertEquals("bookCar-kafka-test", context().getRoute("bookCar-kafka-test").getId()); 310 | } 311 | 312 | /** 313 | * Test real Kafka route simulation with actual route structure 314 | */ 315 | @Test 316 | void testRealKafkaRouteSimulation() throws Exception { 317 | // Given - Create a route that mimics the real Kafka route structure 318 | context().addRoutes(new RouteBuilder() { 319 | @Override 320 | public void configure() throws Exception { 321 | from("direct:real-kafka-input").routeId("real-kafka-simulation") 322 | .log(LoggingLevel.INFO, "New book car request via simulated Kafka topic") 323 | .bean(new AvailableCars(),"getAvailableCar") 324 | .to("mock:real-kafka-output"); 325 | } 326 | }); 327 | 328 | MockEndpoint mockOutput = context().getEndpoint("mock:real-kafka-output", MockEndpoint.class); 329 | mockOutput.expectedMessageCount(1); 330 | 331 | context().start(); 332 | 333 | // When 334 | String response = template().requestBody("direct:real-kafka-input", null, String.class); 335 | 336 | // Then 337 | assertNotNull(response); 338 | JsonNode jsonNode = objectMapper.readTree(response); 339 | assertTrue(jsonNode.has("bookingId")); 340 | assertTrue(jsonNode.has("car")); 341 | assertTrue(jsonNode.has("price")); 342 | 343 | // Verify the message was sent to the output endpoint 344 | mockOutput.assertIsSatisfied(); 345 | 346 | Exchange receivedExchange = mockOutput.getReceivedExchanges().get(0); 347 | String receivedResponse = receivedExchange.getIn().getBody(String.class); 348 | assertEquals(response, receivedResponse); 349 | } 350 | } 351 | -------------------------------------------------------------------------------- /FlightBooking/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM maven:3.9-eclipse-temurin-17 as app-builder 2 | # Build app 3 | WORKDIR /var/src/app 4 | COPY . . 5 | RUN mvn -DskipTests clean package && mv target/*.jar target/app.jar 6 | # Download OpenTelemetry Java Agent 7 | ENV OTEL_AGENT_VERSION=v2.13.1 8 | RUN wget https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases/download/$OTEL_AGENT_VERSION/opentelemetry-javaagent.jar -P /var/src/app/target 9 | 10 | FROM eclipse-temurin:17-jre 11 | COPY --from=app-builder /var/src/app/target/app.jar /opt 12 | COPY --from=app-builder /var/src/app/target/opentelemetry-javaagent.jar /opt 13 | CMD ["java", "-javaagent:/opt/opentelemetry-javaagent.jar", "-jar", "/opt/app.jar"] -------------------------------------------------------------------------------- /FlightBooking/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | org.springframework.boot 6 | spring-boot-starter-parent 7 | 3.4.3 8 | 9 | 10 | 11 | com.example 12 | FlightBooking 13 | 0.0.1 14 | FlightBooking 15 | Demo project for Spring Boot 16 | 17 | 17 18 | 4.10.0 19 | 20 | 21 | 22 | 23 | org.springframework.boot 24 | spring-boot-starter-web 25 | 26 | 27 | org.springframework.boot 28 | spring-boot-starter-actuator 29 | 30 | 31 | io.micrometer 32 | micrometer-registry-prometheus 33 | 34 | 35 | 36 | org.apache.camel.springboot 37 | camel-spring-boot-starter 38 | ${camel.version} 39 | 40 | 41 | org.apache.camel.springboot 42 | camel-rest-starter 43 | ${camel.version} 44 | 45 | 46 | org.apache.camel.springboot 47 | camel-servlet-starter 48 | ${camel.version} 49 | 50 | 51 | org.apache.camel.springboot 52 | camel-jackson-starter 53 | ${camel.version} 54 | 55 | 56 | org.apache.camel.springboot 57 | camel-micrometer-starter 58 | ${camel.version} 59 | 60 | 61 | org.apache.camel.springboot 62 | camel-seda-starter 63 | ${camel.version} 64 | 65 | 66 | org.apache.camel.springboot 67 | camel-http-starter 68 | ${camel.version} 69 | 70 | 71 | org.apache.camel.springboot 72 | camel-kafka-starter 73 | ${camel.version} 74 | 75 | 76 | org.apache.camel.springboot 77 | camel-bean-starter 78 | ${camel.version} 79 | 80 | 81 | 82 | 83 | org.apache.camel.springboot 84 | camel-opentelemetry-starter 85 | ${camel.version} 86 | 87 | 88 | 89 | io.micrometer 90 | micrometer-registry-otlp 91 | 92 | 93 | 94 | org.springframework.boot 95 | spring-boot-starter-test 96 | test 97 | 98 | 99 | 100 | 101 | 102 | org.springframework.boot 103 | spring-boot-maven-plugin 104 | 105 | 106 | 107 | -------------------------------------------------------------------------------- /FlightBooking/src/main/java/com/example/demo/AvailableFlights.java: -------------------------------------------------------------------------------- 1 | package com.example.demo; 2 | 3 | import java.util.Arrays; 4 | import java.util.List; 5 | import java.util.Random; 6 | 7 | import org.apache.camel.Handler; 8 | 9 | public class AvailableFlights { 10 | private static final Random RANDOM = new Random(); 11 | private static final List FLIGHTS = Arrays.asList( 12 | "American Airlines", 13 | "Delta Air Lines", 14 | "Lufthansa", 15 | "United Airlines", 16 | "Air France–KLM", 17 | "IAG", 18 | "Southwest Airlines", 19 | "China Southern Airlines", 20 | "All Nippon Airways", 21 | "China Eastern Airlines", 22 | "Ryanair", 23 | "Air China", 24 | "British Airways", 25 | "Emirates", 26 | "Turkish Airlines", 27 | "Qatar Airways" 28 | ); 29 | 30 | @Handler 31 | public String getAvailableFlight() { 32 | return String.format(""" 33 | { 34 | "bookingId": %d, 35 | "flight": "%s %d", 36 | "startDate": "12-11-2018", 37 | "endDate": "15-11-2018", 38 | "price": %d 39 | }""", 40 | RANDOM.nextInt(1000), 41 | FLIGHTS.get(RANDOM.nextInt(FLIGHTS.size())), 42 | RANDOM.nextInt(10000), 43 | RANDOM.nextInt(100) + 100 44 | ); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /FlightBooking/src/main/java/com/example/demo/DemoApplication.java: -------------------------------------------------------------------------------- 1 | package com.example.demo; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.apache.camel.opentelemetry.starter.CamelOpenTelemetry; 6 | 7 | @CamelOpenTelemetry 8 | @SpringBootApplication 9 | public class DemoApplication { 10 | 11 | public static void main(String[] args) { 12 | SpringApplication.run(DemoApplication.class, args); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /FlightBooking/src/main/java/com/example/demo/MySimpleCamelRouter.java: -------------------------------------------------------------------------------- 1 | package com.example.demo; 2 | 3 | import org.apache.camel.LoggingLevel; 4 | import org.apache.camel.builder.RouteBuilder; 5 | import org.apache.camel.model.dataformat.JsonLibrary; 6 | import org.apache.camel.model.rest.RestBindingMode; 7 | import org.springframework.stereotype.Component; 8 | 9 | @Component 10 | public class MySimpleCamelRouter extends RouteBuilder { 11 | @Override 12 | public void configure() throws Exception { 13 | restConfiguration() 14 | .component("servlet") 15 | .port(8080).host("localhost") 16 | .bindingMode(RestBindingMode.json); 17 | 18 | rest().get("/bookFlight") 19 | .to("direct:bookFlight"); 20 | 21 | from("direct:bookFlight").routeId("bookFlight-http") 22 | .log(LoggingLevel.INFO, "New book flight request with traceId=${header.x-b3-traceid}") 23 | .bean(new AvailableFlights(),"getAvailableFlight") 24 | .unmarshal().json(JsonLibrary.Jackson); 25 | 26 | // kafka based 27 | from("kafka:flight_input").routeId("bookFlight-kafka") 28 | .log(LoggingLevel.INFO, "New book flight request via Kafka topic") 29 | // .to("log:debug?showAll=true&multiline=true") 30 | .bean(new AvailableFlights(),"getAvailableFlight") 31 | .to("kafka:flight_output"); 32 | 33 | } 34 | } -------------------------------------------------------------------------------- /FlightBooking/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | camel.springboot.main-run-controller=true 2 | camel.springboot.use-mdc-logging = true 3 | camel.main.use-mdc-logging = true 4 | camel.component.micrometer.enabled=true 5 | camel.component.kafka.brokers=kafka:9092 6 | camel.metrics.enable-message-history=true 7 | management.endpoint.metrics.enabled=true 8 | management.endpoints.web.exposure.include=* 9 | management.otlp.metrics.export.step=10s 10 | spring.application.name=FlightBooking -------------------------------------------------------------------------------- /FlightBooking/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | %d{HH:mm:ss.SSS} %-5level [%X{trace_id:-},%X{span_id:-}] %logger{36} - %msg%n 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /HotelBooking/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM maven:3.9-eclipse-temurin-17 as app-builder 2 | # Build app 3 | WORKDIR /var/src/app 4 | COPY . . 5 | RUN mvn -DskipTests clean package && mv target/*.jar target/app.jar 6 | # Download OpenTelemetry Java Agent 7 | ENV OTEL_AGENT_VERSION=v2.13.1 8 | RUN wget https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases/download/$OTEL_AGENT_VERSION/opentelemetry-javaagent.jar -P /var/src/app/target 9 | 10 | FROM eclipse-temurin:17-jre 11 | COPY --from=app-builder /var/src/app/target/app.jar /opt 12 | COPY --from=app-builder /var/src/app/target/opentelemetry-javaagent.jar /opt 13 | CMD ["java", "-javaagent:/opt/opentelemetry-javaagent.jar", "-jar", "/opt/app.jar"] -------------------------------------------------------------------------------- /HotelBooking/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | org.springframework.boot 6 | spring-boot-starter-parent 7 | 3.4.3 8 | 9 | 10 | 11 | com.example 12 | HotelBooking 13 | 0.0.1 14 | HotelBooking 15 | Demo project for Spring Boot 16 | 17 | 17 18 | 4.10.0 19 | 20 | 21 | 22 | 23 | org.springframework.boot 24 | spring-boot-starter-web 25 | 26 | 27 | org.springframework.boot 28 | spring-boot-starter-actuator 29 | 30 | 31 | io.micrometer 32 | micrometer-registry-prometheus 33 | 34 | 35 | 36 | org.apache.camel.springboot 37 | camel-spring-boot-starter 38 | ${camel.version} 39 | 40 | 41 | org.apache.camel.springboot 42 | camel-rest-starter 43 | ${camel.version} 44 | 45 | 46 | org.apache.camel.springboot 47 | camel-servlet-starter 48 | ${camel.version} 49 | 50 | 51 | org.apache.camel.springboot 52 | camel-jackson-starter 53 | ${camel.version} 54 | 55 | 56 | org.apache.camel.springboot 57 | camel-micrometer-starter 58 | ${camel.version} 59 | 60 | 61 | org.apache.camel.springboot 62 | camel-seda-starter 63 | ${camel.version} 64 | 65 | 66 | org.apache.camel.springboot 67 | camel-http-starter 68 | ${camel.version} 69 | 70 | 71 | org.apache.camel.springboot 72 | camel-kafka-starter 73 | ${camel.version} 74 | 75 | 76 | org.apache.camel.springboot 77 | camel-bean-starter 78 | ${camel.version} 79 | 80 | 81 | 82 | 83 | org.apache.camel.springboot 84 | camel-opentelemetry-starter 85 | ${camel.version} 86 | 87 | 88 | 89 | io.micrometer 90 | micrometer-registry-otlp 91 | 92 | 93 | 94 | 95 | org.springframework.boot 96 | spring-boot-starter-test 97 | test 98 | 99 | 100 | 101 | 102 | 103 | org.springframework.boot 104 | spring-boot-maven-plugin 105 | 106 | 107 | 108 | -------------------------------------------------------------------------------- /HotelBooking/src/main/java/com/example/demo/AvailableHotels.java: -------------------------------------------------------------------------------- 1 | package com.example.demo; 2 | 3 | import java.util.Arrays; 4 | import java.util.List; 5 | import java.util.Random; 6 | 7 | import org.apache.camel.Handler; 8 | 9 | public class AvailableHotels { 10 | private static final Random RANDOM = new Random(); 11 | private static final List HOTELS = Arrays.asList( 12 | "Four Seasons", 13 | "Sheraton", 14 | "The Ritz", 15 | "Marriott", 16 | "Hilton", 17 | "Accor", 18 | "Hyatt", 19 | "Radisson" 20 | ); 21 | 22 | @Handler 23 | public String getAvailableHotel() { 24 | return String.format(""" 25 | { 26 | "bookingId": %d, 27 | "hotel": "%s", 28 | "startDate": "12-11-2018", 29 | "endDate": "15-11-2018", 30 | "price": %d 31 | }""", 32 | RANDOM.nextInt(1000), 33 | HOTELS.get(RANDOM.nextInt(HOTELS.size())), 34 | RANDOM.nextInt(150) + 150 35 | ); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /HotelBooking/src/main/java/com/example/demo/DemoApplication.java: -------------------------------------------------------------------------------- 1 | package com.example.demo; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.apache.camel.opentelemetry.starter.CamelOpenTelemetry; 6 | 7 | @CamelOpenTelemetry 8 | @SpringBootApplication 9 | public class DemoApplication { 10 | 11 | public static void main(String[] args) { 12 | SpringApplication.run(DemoApplication.class, args); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /HotelBooking/src/main/java/com/example/demo/MySimpleCamelRouter.java: -------------------------------------------------------------------------------- 1 | package com.example.demo; 2 | 3 | import org.apache.camel.LoggingLevel; 4 | import org.apache.camel.builder.RouteBuilder; 5 | import org.apache.camel.model.dataformat.JsonLibrary; 6 | import org.apache.camel.model.rest.RestBindingMode; 7 | import org.springframework.stereotype.Component; 8 | 9 | @Component 10 | public class MySimpleCamelRouter extends RouteBuilder { 11 | @Override 12 | public void configure() throws Exception { 13 | restConfiguration() 14 | .component("servlet") 15 | .bindingMode(RestBindingMode.json); 16 | 17 | rest().get("/bookHotel") 18 | .to("direct:bookHotel"); 19 | 20 | from("direct:bookHotel").routeId("bookHotel-http") 21 | .log(LoggingLevel.INFO, "New book hotel request with traceId=${header.x-b3-traceid}") 22 | .bean(new AvailableHotels(),"getAvailableHotel") 23 | .unmarshal().json(JsonLibrary.Jackson); 24 | 25 | 26 | // kafka based 27 | from("kafka:hotel_input").routeId("bookHotel-kafka") 28 | .log(LoggingLevel.INFO, "New book hotel request via Kafka topic") 29 | // .to("log:debug?showAll=true&multiline=true") 30 | .bean(new AvailableHotels(),"getAvailableHotel") 31 | .to("kafka:hotel_output"); 32 | } 33 | } -------------------------------------------------------------------------------- /HotelBooking/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | camel.springboot.main-run-controller=true 2 | camel.springboot.use-mdc-logging = true 3 | camel.main.use-mdc-logging = true 4 | camel.component.micrometer.enabled=true 5 | camel.component.kafka.brokers=kafka:9092 6 | # camel.component.metrics.metric-registry=prometheusMeterRegistry 7 | camel.metrics.enable-message-history=true 8 | management.endpoint.metrics.enabled=true 9 | management.endpoints.web.exposure.include=* 10 | management.otlp.metrics.export.step=10s 11 | # management.otlp.metrics.export.url=http://localhost:4318/v1/metrics 12 | spring.application.name=HotelBooking 13 | -------------------------------------------------------------------------------- /HotelBooking/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | 10 | 11 | %d{HH:mm:ss.SSS} %-5level [%X{trace_id:-},%X{span_id:-}] %logger{36} - %msg%n 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 stn1slv 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Apache Camel: Telemetry demo 2 | This directory contains sources of demo apps based on Apache Camel with OpenTelemetry. 3 | ![Demo case](.img/telemetry-light.png#gh-light-mode-only) 4 | ![Demo case](.img/telemetry-dark.png#gh-dark-mode-only) 5 | 6 | The environment is the following: 7 | - Demo apps: 8 | - [Trip booking app](TripBooking) 9 | - [Flight booking app](FlightBooking) 10 | - [Hotel booking app](HotelBooking) 11 | - [Car booking app](CarBooking) 12 | - Apache Kafka 13 | - Jaeger 14 | - OpenTelemetry Collector 15 | - Prometheus 16 | - Grafana 17 | - FileBeat 18 | - ElasticSearch 19 | - Grafana, including: 20 | - Preconfigured datasources for Jaeger, Prometheus and ElasticSearch 21 | - Dashboard for Apache Camel apps 22 | - Dashboard for Logs from ElasticSearch 23 | - Dashboard for Jaeger 24 | 25 | ## Running 26 | You may want to remove any old containers to start clean: 27 | ``` 28 | docker rm -f kafka prometheus grafana elasticsearch jaeger otel-collector filebeat tripbooking carbooking flightbooking hotelbooking 29 | ``` 30 | We suggest using two terminal windows to start the following components: 31 | - infrastructure components 32 | - demo apps 33 | ### Startup infrastructure components 34 | ``` 35 | docker-compose -f compose.yml -f compose.infra.yml up --remove-orphans 36 | ``` 37 | ### Startup demo apps 38 | ``` 39 | docker-compose -f compose.yml -f compose.demo-apps.yml up 40 | ``` 41 | ## Testing 42 | Testing tools are following: 43 | - Any HTTP client (web browser, curl, httpie, postman etc.) 44 | - Apache JMeter for generation load 45 | #### cURL 46 | Sync communication (over HTTP): 47 | ``` 48 | curl http://127.0.0.1:8080/camel/bookTrip 49 | ``` 50 | Async communication (over Kafka): 51 | ``` 52 | curl http://127.0.0.1:8080/camel/asyncBookTrip 53 | ``` 54 | #### Apache JMeter 55 | You can find JMeter project by [the link](TripBooking/Demo.jmx). 56 | -------------------------------------------------------------------------------- /TripBooking/Demo.jmx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | false 7 | true 8 | false 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 1 17 | 1 18 | true 19 | continue 20 | 21 | -1 22 | false 23 | 24 | 25 | 26 | 27 | 500 28 | 500.0 29 | 30 | 31 | 32 | 127.0.0.1 33 | 8080 34 | http 35 | /camel/bookTrip 36 | true 37 | GET 38 | true 39 | false 40 | 41 | 42 | 43 | HttpClient4 44 | 45 | 46 | 47 | 100 48 | 100.0 49 | 50 | 51 | 52 | 127.0.0.1 53 | 8080 54 | http 55 | /camel/asyncBookTrip 56 | true 57 | GET 58 | true 59 | false 60 | 61 | 62 | 63 | HttpClient4 64 | 65 | 66 | 67 | false 68 | 69 | saveConfig 70 | 71 | 72 | true 73 | true 74 | true 75 | 76 | true 77 | true 78 | true 79 | true 80 | false 81 | true 82 | true 83 | false 84 | false 85 | false 86 | true 87 | false 88 | false 89 | false 90 | true 91 | 0 92 | true 93 | true 94 | true 95 | true 96 | true 97 | true 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | -------------------------------------------------------------------------------- /TripBooking/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM maven:3.9-eclipse-temurin-17 as app-builder 2 | # Build app 3 | WORKDIR /var/src/app 4 | COPY . . 5 | RUN mvn -DskipTests clean package && mv target/*.jar target/app.jar 6 | # Download OpenTelemetry Java Agent 7 | ENV OTEL_AGENT_VERSION=v2.13.1 8 | RUN wget https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases/download/$OTEL_AGENT_VERSION/opentelemetry-javaagent.jar -P /var/src/app/target 9 | 10 | FROM eclipse-temurin:17-jre 11 | COPY --from=app-builder /var/src/app/target/app.jar /opt 12 | COPY --from=app-builder /var/src/app/target/opentelemetry-javaagent.jar /opt 13 | CMD ["java", "-javaagent:/opt/opentelemetry-javaagent.jar", "-jar", "/opt/app.jar"] -------------------------------------------------------------------------------- /TripBooking/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | org.springframework.boot 6 | spring-boot-starter-parent 7 | 3.4.3 8 | 9 | 10 | 11 | com.example 12 | TripBooking 13 | 0.0.1 14 | TripBooking 15 | Demo project for Spring Boot 16 | 17 | 17 18 | 4.10.0 19 | 20 | 21 | 22 | 23 | org.springframework.boot 24 | spring-boot-starter-web 25 | 26 | 27 | org.springframework.boot 28 | spring-boot-starter-actuator 29 | 30 | 31 | io.micrometer 32 | micrometer-registry-prometheus 33 | 34 | 35 | 36 | org.apache.camel.springboot 37 | camel-spring-boot-starter 38 | ${camel.version} 39 | 40 | 41 | org.apache.camel.springboot 42 | camel-rest-starter 43 | ${camel.version} 44 | 45 | 46 | org.apache.camel.springboot 47 | camel-servlet-starter 48 | ${camel.version} 49 | 50 | 51 | org.apache.camel.springboot 52 | camel-jackson-starter 53 | ${camel.version} 54 | 55 | 56 | org.apache.camel.springboot 57 | camel-micrometer-starter 58 | ${camel.version} 59 | 60 | 61 | org.apache.camel.springboot 62 | camel-seda-starter 63 | ${camel.version} 64 | 65 | 66 | org.apache.camel.springboot 67 | camel-http-starter 68 | ${camel.version} 69 | 70 | 71 | org.apache.camel.springboot 72 | camel-kafka-starter 73 | ${camel.version} 74 | 75 | 76 | org.apache.camel.springboot 77 | camel-bean-starter 78 | ${camel.version} 79 | 80 | 81 | 82 | 83 | org.apache.camel.springboot 84 | camel-opentelemetry-starter 85 | ${camel.version} 86 | 87 | 88 | 89 | io.micrometer 90 | micrometer-registry-otlp 91 | 92 | 93 | 94 | 95 | org.springframework.boot 96 | spring-boot-starter-test 97 | test 98 | 99 | 100 | 101 | 102 | 103 | org.springframework.boot 104 | spring-boot-maven-plugin 105 | 106 | 107 | 108 | -------------------------------------------------------------------------------- /TripBooking/src/main/java/com/example/demo/DemoApplication.java: -------------------------------------------------------------------------------- 1 | package com.example.demo; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.apache.camel.opentelemetry.starter.CamelOpenTelemetry; 6 | 7 | @CamelOpenTelemetry 8 | @SpringBootApplication 9 | public class DemoApplication { 10 | 11 | public static void main(String[] args) { 12 | SpringApplication.run(DemoApplication.class, args); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /TripBooking/src/main/java/com/example/demo/MergeAggregationStrategy.java: -------------------------------------------------------------------------------- 1 | package com.example.demo; 2 | 3 | import org.apache.camel.Exchange; 4 | 5 | public class MergeAggregationStrategy implements org.apache.camel.AggregationStrategy { 6 | @Override 7 | public Exchange aggregate(Exchange oldExchange, Exchange newExchange) { 8 | if (oldExchange == null) 9 | { 10 | return newExchange; 11 | } 12 | String oldPayload = oldExchange.getIn().getBody(String.class); 13 | String newPayload = newExchange.getIn().getBody(String.class); 14 | String result = oldPayload + ", " + newPayload; 15 | oldExchange.getIn().setBody(result); 16 | return oldExchange; 17 | } 18 | 19 | @Override 20 | public void onCompletion(Exchange exchange) { 21 | String payload = exchange.getIn().getBody(String.class); 22 | String result = "["+payload+"]"; 23 | exchange.getIn().setBody(result); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /TripBooking/src/main/java/com/example/demo/MySimpleCamelRouter.java: -------------------------------------------------------------------------------- 1 | package com.example.demo; 2 | 3 | import org.apache.camel.LoggingLevel; 4 | import org.apache.camel.builder.RouteBuilder; 5 | import org.apache.camel.model.dataformat.JsonLibrary; 6 | import org.apache.camel.model.rest.RestBindingMode; 7 | import org.springframework.stereotype.Component; 8 | 9 | @Component 10 | public class MySimpleCamelRouter extends RouteBuilder { 11 | @Override 12 | public void configure() throws Exception { 13 | restConfiguration() 14 | .component("servlet") 15 | .bindingMode(RestBindingMode.json); 16 | 17 | rest() 18 | .get("/bookTrip").to("direct:bookTrip") 19 | .get("/asyncBookTrip").to("direct:asyncBookTrip"); 20 | 21 | from("direct:bookTrip") 22 | .routeId("bookTrip-http") 23 | .routeDescription("This is demo service for demonstration telemetry aspects") 24 | .log(LoggingLevel.INFO, "New book trip request via HTTP") 25 | .multicast(new MergeAggregationStrategy()).parallelProcessing() 26 | .to("{{car.booking.url}}?bridgeEndpoint=true") 27 | .to("{{flight.booking.url}}?bridgeEndpoint=true") 28 | .to("{{hotel.booking.url}}?bridgeEndpoint=true") 29 | .end() 30 | .log(LoggingLevel.INFO,"Response: ${body}") 31 | .unmarshal().json(JsonLibrary.Jackson); 32 | 33 | // kafka based 34 | from("direct:asyncBookTrip") 35 | .routeId("bookTrip-kafka-request") 36 | .routeDescription("This is demo service for demonstration telemetry aspects via Kafka") 37 | .log(LoggingLevel.INFO, "New book trip request via Kafka") 38 | .setBody(simple("New async request")) 39 | .multicast().parallelProcessing() 40 | .to("kafka:car_input") 41 | .to("kafka:flight_input") 42 | .to("kafka:hotel_input") 43 | .end(); 44 | 45 | from("kafka:car_output").to("seda:tripAggregator"); 46 | from("kafka:flight_output").to("seda:tripAggregator"); 47 | from("kafka:hotel_output").to("seda:tripAggregator"); 48 | 49 | from("seda:tripAggregator").routeId("bookTrip-kafka-response") 50 | .aggregate(constant(true), new MergeAggregationStrategy()) 51 | .completionSize(3) 52 | .log(LoggingLevel.INFO, "New book trip response: ${body}"); 53 | } 54 | } -------------------------------------------------------------------------------- /TripBooking/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | camel.springboot.main-run-controller=true 2 | camel.springboot.use-mdc-logging = true 3 | camel.main.use-mdc-logging = true 4 | camel.component.micrometer.enabled=true 5 | camel.component.kafka.brokers=kafka:9092 6 | camel.metrics.enable-message-history=true 7 | management.endpoint.metrics.enabled=true 8 | management.endpoints.web.exposure.include=* 9 | management.otlp.metrics.export.step=10s 10 | spring.application.name=TripBooking 11 | # ENDPOINTS 12 | car.booking.url=http://carbooking:8080/camel/bookCar 13 | flight.booking.url=http://flightbooking:8080/camel/bookFlight 14 | hotel.booking.url=http://hotelbooking:8080/camel/bookHotel -------------------------------------------------------------------------------- /TripBooking/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | %d{HH:mm:ss.SSS} %-5level [%X{trace_id:-},%X{span_id:-}] %logger{36} - %msg%n 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /compose.demo-apps.yml: -------------------------------------------------------------------------------- 1 | # version: '3.8' 2 | 3 | services: 4 | tripbooking: 5 | image: stn1slv/trip-booking-demo-app:otel 6 | container_name: trip-booking-app 7 | environment: 8 | - OTEL_SERVICE_NAME=trip-booking-app 9 | - OTEL_PROPAGATORS=b3multi 10 | - OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4317 11 | - OTEL_EXPORTER_OTLP_PROTOCOL=grpc 12 | - OTEL_TRACES_EXPORTER=otlp 13 | - OTEL_METRICS_EXPORTER=otlp 14 | - OTEL_LOGS_EXPORTER=none 15 | - MANAGEMENT_OTLP_METRICS_EXPORT_URL=http://otel-collector:4318/v1/metrics # Metrics Exporter URL 16 | - OTEL_RESOURCE_ATTRIBUTES=application=trip-booking-app 17 | ports: 18 | - 8080:8080 19 | carbooking: 20 | image: stn1slv/car-booking-demo-app:otel 21 | container_name: car-booking-app 22 | environment: 23 | - OTEL_SERVICE_NAME=car-booking-app 24 | - OTEL_PROPAGATORS=b3multi 25 | - OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4317 26 | - OTEL_EXPORTER_OTLP_PROTOCOL=grpc 27 | - OTEL_TRACES_EXPORTER=otlp 28 | - OTEL_METRICS_EXPORTER=otlp 29 | - OTEL_LOGS_EXPORTER=none 30 | - MANAGEMENT_OTLP_METRICS_EXPORT_URL=http://otel-collector:4318/v1/metrics # Metrics Exporter URL 31 | - OTEL_RESOURCE_ATTRIBUTES=application=car-booking-app 32 | ports: 33 | - 8081:8080 34 | flightbooking: 35 | image: stn1slv/flight-booking-demo-app:otel 36 | container_name: flight-booking-app 37 | environment: 38 | - OTEL_SERVICE_NAME=flight-booking-app 39 | - OTEL_PROPAGATORS=b3multi 40 | - OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4317 41 | - OTEL_EXPORTER_OTLP_PROTOCOL=grpc 42 | - OTEL_TRACES_EXPORTER=otlp 43 | - OTEL_METRICS_EXPORTER=otlp 44 | - OTEL_LOGS_EXPORTER=none 45 | - MANAGEMENT_OTLP_METRICS_EXPORT_URL=http://otel-collector:4318/v1/metrics # Metrics Exporter URL 46 | - OTEL_RESOURCE_ATTRIBUTES=application=flight-booking-app 47 | ports: 48 | - 8082:8080 49 | hotelbooking: 50 | image: stn1slv/hotel-booking-demo-app:otel 51 | container_name: hotel-booking-app 52 | environment: 53 | - OTEL_SERVICE_NAME=hotel-booking-app 54 | - OTEL_PROPAGATORS=b3multi 55 | - OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4317 56 | - OTEL_EXPORTER_OTLP_PROTOCOL=grpc 57 | - OTEL_TRACES_EXPORTER=otlp 58 | - OTEL_METRICS_EXPORTER=otlp 59 | - OTEL_LOGS_EXPORTER=none 60 | - MANAGEMENT_OTLP_METRICS_EXPORT_URL=http://otel-collector:4318/v1/metrics # Metrics Exporter URL 61 | - OTEL_RESOURCE_ATTRIBUTES=application=hotel-booking-app 62 | ports: 63 | - 8083:8080 -------------------------------------------------------------------------------- /compose.infra.yml: -------------------------------------------------------------------------------- 1 | # version: '3.8' 2 | 3 | services: 4 | kafka: 5 | image: apache/kafka:3.8.1 6 | container_name: kafka 7 | restart: always 8 | environment: 9 | KAFKA_NODE_ID: 1 10 | KAFKA_PROCESS_ROLES: broker,controller 11 | KAFKA_LISTENERS: PLAINTEXT://kafka:9092,CONTROLLER://kafka:9093 12 | KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9092 13 | KAFKA_CONTROLLER_LISTENER_NAMES: CONTROLLER 14 | KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT 15 | KAFKA_CONTROLLER_QUORUM_VOTERS: 1@kafka:9093 16 | KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1 17 | KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 1 18 | KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: 1 19 | KAFKA_GROUP_INITIAL_REBALANCE_DELAY_MS: 0 20 | KAFKA_NUM_PARTITIONS: 3 21 | ports: 22 | - 9092:9092 23 | jaeger: 24 | image: jaegertracing/jaeger:2.3.0 25 | container_name: jaeger 26 | restart: always 27 | ports: 28 | - 16686:16686 29 | # - 4317:4317 # OTLP gRPC receiver 30 | # - 4318:4318 # OTLP HTTP receiver 31 | - 5778:5778 32 | - 9411:9411 33 | otel-collector: 34 | image: otel/opentelemetry-collector:0.120.0 35 | container_name: otel-collector 36 | command: [ "--config=/etc/otel-collector-config.yaml" ] 37 | volumes: 38 | - ./infrastructure/otel-collector/otel-collector-config.yaml:/etc/otel-collector-config.yaml 39 | restart: always 40 | ports: 41 | - "1888:1888" # pprof extension 42 | - "8888:8888" # Prometheus metrics exposed by the collector 43 | - "8889:8889" # Prometheus exporter metrics 44 | - "13133:13133" # health_check extension 45 | - "4317:4317" # OTLP gRPC receiver 46 | - "4318:4318" # OTLP HTTP receiver 47 | - "55670:55679" # zpages extension 48 | prometheus: 49 | image: prom/prometheus:v2.37.7 50 | container_name: prometheus 51 | restart: unless-stopped 52 | volumes: 53 | - ./infrastructure/prometheus/prometheusConfig.yml:/etc/prometheus/prometheus.yml 54 | ports: 55 | - 9090:9090 56 | grafana: 57 | image: grafana/grafana:11.5.2 58 | container_name: grafana 59 | restart: unless-stopped 60 | environment: 61 | - GF_SECURITY_ADMIN_PASSWORD=admin 62 | 63 | # Install plugins 64 | #- GF_INSTALL_PLUGINS=vonage-status-panel,jdbranham-diagram-panel,agenty-flowcharting-panel,yesoreyeram-boomtable-panel 65 | 66 | # Log level 67 | #- GF_LOG_LEVEL=debug 68 | ports: 69 | - 3000:3000 70 | volumes: 71 | - ./infrastructure/grafana/provisioning/dashboards/:/etc/grafana/provisioning/dashboards/ 72 | - ./infrastructure/grafana/provisioning/datasources/:/etc/grafana/provisioning/datasources/ 73 | filebeat: 74 | image: docker.elastic.co/beats/filebeat:8.17.2 75 | container_name: filebeat 76 | user: root 77 | restart: always 78 | volumes: 79 | - /var/lib/docker:/var/lib/docker:ro 80 | - /var/run/docker.sock:/var/run/docker.sock 81 | - ./infrastructure/filebeat/filebeat.yml:/usr/share/filebeat/filebeat.yml 82 | command: filebeat -e --strict.perms=false -E output.elasticsearch.hosts=["elasticsearch:9200"] 83 | elasticsearch: 84 | image: docker.elastic.co/elasticsearch/elasticsearch:8.17.2 85 | container_name: elasticsearch 86 | restart: always 87 | deploy: 88 | resources: 89 | limits: 90 | memory: 1GB 91 | environment: 92 | - node.name=es01 93 | # - cluster.name=docker-cluster 94 | - discovery.type=single-node 95 | # - bootstrap.memory_lock=true 96 | - "ES_JAVA_OPTS=-Xms512m -Xmx512m" 97 | - xpack.security.enabled=false 98 | - ELASTIC_USERNAME=elastic 99 | - ELASTIC_PASSWORD=elastic 100 | - "cluster.routing.allocation.disk.threshold_enabled=false" 101 | - "node.roles=[data, master, ingest]" # Explicitly set node roles 102 | ulimits: 103 | memlock: 104 | soft: -1 105 | hard: -1 106 | nofile: 107 | soft: 65536 # maximum number of open files for the Elasticsearch user, set to at least 65536 on modern systems 108 | hard: 65536 109 | ports: 110 | - 9200:9200 111 | # - 9300:9300 -------------------------------------------------------------------------------- /compose.yml: -------------------------------------------------------------------------------- 1 | ## version: '3.8' 2 | 3 | # This files serves as filesystem anchor 4 | # Including it as first .yml file makes all build paths relative to this directory ('docker') 5 | # 6 | # Examples: 7 | # 8 | # docker-compose -f compose.yml -f prometheus/compose.yml -f grafana/compose.yml up --remove-orphans 9 | # -------------------------------------------------------------------------------- /infrastructure/filebeat/filebeat.yml: -------------------------------------------------------------------------------- 1 | setup.template.name: "filebeat" 2 | setup.template.pattern: "filebeat" 3 | setup.ilm.enabled: false 4 | 5 | filebeat.inputs: 6 | - type: container 7 | paths: 8 | - '/var/lib/docker/containers/*/*.log' 9 | 10 | multiline.type: pattern 11 | multiline.pattern: '^[[:space:]]' 12 | multiline.negate: false 13 | multiline.match: after 14 | 15 | logging.level: warning 16 | 17 | processors: 18 | - add_docker_metadata: 19 | host: "unix:///var/run/docker.sock" 20 | - dissect: 21 | tokenizer: "%{time} %{severity} [%{traceid},%{spanId}] %{class} - %{message}" 22 | field: "message" 23 | target_prefix: "msg" 24 | # - drop_event: 25 | # when: 26 | # not: 27 | # equals: 28 | # container.name: "trip-booking-app" 29 | 30 | #FOR DEBUG 31 | # output.console: 32 | # pretty: true 33 | 34 | output.elasticsearch: 35 | hosts: ["http://elasticsearch:9200"] 36 | username: "elastic" 37 | password: "elastic" 38 | # index: "filebeat-%{+yyyy.MM.dd}" 39 | index: "filebeat-%{+yyyy.MM.dd}" -------------------------------------------------------------------------------- /infrastructure/grafana/provisioning/dashboards/Logs.json: -------------------------------------------------------------------------------- 1 | { 2 | "annotations": { 3 | "list": [ 4 | { 5 | "builtIn": 1, 6 | "datasource": "-- Grafana --", 7 | "enable": true, 8 | "hide": true, 9 | "iconColor": "rgba(0, 211, 255, 1)", 10 | "name": "Annotations & Alerts", 11 | "type": "dashboard" 12 | } 13 | ] 14 | }, 15 | "editable": true, 16 | "gnetId": null, 17 | "graphTooltip": 0, 18 | "iteration": 1624882556730, 19 | "links": [], 20 | "panels": [ 21 | { 22 | "datasource": "Elasticsearch", 23 | "fieldConfig": { 24 | "defaults": { 25 | "color": { 26 | "mode": "thresholds" 27 | }, 28 | "custom": { 29 | "align": null, 30 | "filterable": false 31 | }, 32 | "mappings": [], 33 | "thresholds": { 34 | "mode": "absolute", 35 | "steps": [ 36 | { 37 | "color": "green", 38 | "value": null 39 | }, 40 | { 41 | "color": "red", 42 | "value": 80 43 | } 44 | ] 45 | } 46 | }, 47 | "overrides": [ 48 | { 49 | "matcher": { 50 | "id": "byName", 51 | "options": "Timestamp" 52 | }, 53 | "properties": [ 54 | { 55 | "id": "custom.width", 56 | "value": 147 57 | } 58 | ] 59 | }, 60 | { 61 | "matcher": { 62 | "id": "byName", 63 | "options": "Container" 64 | }, 65 | "properties": [ 66 | { 67 | "id": "custom.width", 68 | "value": 134 69 | } 70 | ] 71 | }, 72 | { 73 | "matcher": { 74 | "id": "byName", 75 | "options": "Message" 76 | }, 77 | "properties": [ 78 | { 79 | "id": "custom.width", 80 | "value": 910 81 | } 82 | ] 83 | }, 84 | { 85 | "matcher": { 86 | "id": "byName", 87 | "options": "Severity" 88 | }, 89 | "properties": [ 90 | { 91 | "id": "custom.width", 92 | "value": 72 93 | } 94 | ] 95 | }, 96 | { 97 | "matcher": { 98 | "id": "byName", 99 | "options": "Class" 100 | }, 101 | "properties": [ 102 | { 103 | "id": "custom.width", 104 | "value": 97 105 | } 106 | ] 107 | }, 108 | { 109 | "matcher": { 110 | "id": "byName", 111 | "options": "TraceId" 112 | }, 113 | "properties": [ 114 | { 115 | "id": "custom.width", 116 | "value": 144 117 | } 118 | ] 119 | } 120 | ] 121 | }, 122 | "gridPos": { 123 | "h": 17, 124 | "w": 24, 125 | "x": 0, 126 | "y": 0 127 | }, 128 | "id": 2, 129 | "options": { 130 | "showHeader": true, 131 | "sortBy": [ 132 | { 133 | "desc": false, 134 | "displayName": "TraceId" 135 | } 136 | ] 137 | }, 138 | "pluginVersion": "7.5.9", 139 | "targets": [ 140 | { 141 | "alias": "", 142 | "bucketAggs": [ 143 | { 144 | "field": "@timestamp", 145 | "id": "2", 146 | "settings": { 147 | "interval": "auto" 148 | }, 149 | "type": "date_histogram" 150 | } 151 | ], 152 | "metrics": [ 153 | { 154 | "id": "1", 155 | "type": "logs" 156 | } 157 | ], 158 | "query": "container.name:*-app msg.severity:* msg.traceId:*", 159 | "refId": "A", 160 | "timeField": "@timestamp" 161 | } 162 | ], 163 | "title": "Application logs", 164 | "transformations": [ 165 | { 166 | "id": "organize", 167 | "options": { 168 | "excludeByName": { 169 | "_id": true, 170 | "_index": true, 171 | "_source": true, 172 | "_type": true, 173 | "agent.ephemeral_id": true, 174 | "agent.hostname": true, 175 | "agent.id": true, 176 | "agent.name": true, 177 | "agent.type": true, 178 | "agent.version": true, 179 | "container.id": true, 180 | "container.image.name": true, 181 | "container.labels.com_docker_compose_config-hash": true, 182 | "container.labels.com_docker_compose_container-number": true, 183 | "container.labels.com_docker_compose_oneoff": true, 184 | "container.labels.com_docker_compose_project": true, 185 | "container.labels.com_docker_compose_project_config_files": true, 186 | "container.labels.com_docker_compose_project_working_dir": true, 187 | "container.labels.com_docker_compose_service": true, 188 | "container.labels.com_docker_compose_version": true, 189 | "container.labels.description": true, 190 | "container.labels.desktop_docker_io/binds/0/Source": true, 191 | "container.labels.desktop_docker_io/binds/0/SourceKind": true, 192 | "container.labels.desktop_docker_io/binds/0/Target": true, 193 | "container.labels.desktop_docker_io/binds/1/Source": true, 194 | "container.labels.desktop_docker_io/binds/1/SourceKind": true, 195 | "container.labels.desktop_docker_io/binds/1/Target": true, 196 | "container.labels.desktop_docker_io/binds/2/Source": true, 197 | "container.labels.desktop_docker_io/binds/2/SourceKind": true, 198 | "container.labels.desktop_docker_io/binds/2/Target": true, 199 | "container.labels.io_k8s_description": true, 200 | "container.labels.io_k8s_display-name": true, 201 | "container.labels.license": true, 202 | "container.labels.maintainer": true, 203 | "container.labels.name": true, 204 | "container.labels.org_label-schema_build-date": true, 205 | "container.labels.org_label-schema_license": true, 206 | "container.labels.org_label-schema_name": true, 207 | "container.labels.org_label-schema_schema-version": true, 208 | "container.labels.org_label-schema_url": true, 209 | "container.labels.org_label-schema_usage": true, 210 | "container.labels.org_label-schema_vcs-ref": true, 211 | "container.labels.org_label-schema_vcs-url": true, 212 | "container.labels.org_label-schema_vendor": true, 213 | "container.labels.org_label-schema_version": true, 214 | "container.labels.org_opencontainers_image_created": true, 215 | "container.labels.org_opencontainers_image_documentation": true, 216 | "container.labels.org_opencontainers_image_licenses": true, 217 | "container.labels.org_opencontainers_image_revision": true, 218 | "container.labels.org_opencontainers_image_source": true, 219 | "container.labels.org_opencontainers_image_title": true, 220 | "container.labels.org_opencontainers_image_url": true, 221 | "container.labels.org_opencontainers_image_vendor": true, 222 | "container.labels.org_opencontainers_image_version": true, 223 | "container.labels.release": true, 224 | "container.labels.summary": true, 225 | "container.labels.url": true, 226 | "container.labels.vendor": true, 227 | "container.labels.version": true, 228 | "container.name": false, 229 | "ecs.version": true, 230 | "highlight": true, 231 | "host.name": true, 232 | "input.type": true, 233 | "log.file.path": true, 234 | "log.flags": true, 235 | "log.offset": true, 236 | "message": true, 237 | "msg.time": true, 238 | "sort": true, 239 | "stream": true 240 | }, 241 | "indexByName": { 242 | "@timestamp": 0, 243 | "_id": 1, 244 | "_index": 2, 245 | "_source": 3, 246 | "_type": 4, 247 | "agent.ephemeral_id": 5, 248 | "agent.hostname": 6, 249 | "agent.id": 7, 250 | "agent.name": 8, 251 | "agent.type": 9, 252 | "agent.version": 10, 253 | "container.id": 11, 254 | "container.image.name": 12, 255 | "container.labels.com_docker_compose_config-hash": 13, 256 | "container.labels.com_docker_compose_container-number": 14, 257 | "container.labels.com_docker_compose_oneoff": 15, 258 | "container.labels.com_docker_compose_project": 16, 259 | "container.labels.com_docker_compose_project_config_files": 17, 260 | "container.labels.com_docker_compose_project_working_dir": 18, 261 | "container.labels.com_docker_compose_service": 19, 262 | "container.labels.com_docker_compose_version": 20, 263 | "container.name": 21, 264 | "ecs.version": 22, 265 | "highlight": 23, 266 | "host.name": 24, 267 | "input.type": 25, 268 | "log.file.path": 26, 269 | "log.flags": 27, 270 | "log.offset": 28, 271 | "message": 29, 272 | "msg.class": 31, 273 | "msg.message": 32, 274 | "msg.severity": 30, 275 | "msg.spanId": 34, 276 | "msg.time": 35, 277 | "msg.traceid": 33, 278 | "sort": 36, 279 | "stream": 37 280 | }, 281 | "renameByName": { 282 | "@timestamp": "Timestamp", 283 | "container.name": "Container", 284 | "ecs.version": "", 285 | "msg.class": "Class", 286 | "msg.message": "Message", 287 | "msg.severity": "Severity", 288 | "msg.spanId": "SpanId", 289 | "msg.traceid": "TraceId" 290 | } 291 | } 292 | } 293 | ], 294 | "type": "table" 295 | }, 296 | { 297 | "datasource": "Elasticsearch", 298 | "fieldConfig": { 299 | "defaults": { 300 | "color": { 301 | "mode": "thresholds" 302 | }, 303 | "custom": { 304 | "align": null, 305 | "filterable": false 306 | }, 307 | "mappings": [], 308 | "thresholds": { 309 | "mode": "absolute", 310 | "steps": [ 311 | { 312 | "color": "green", 313 | "value": null 314 | }, 315 | { 316 | "color": "red", 317 | "value": 80 318 | } 319 | ] 320 | } 321 | }, 322 | "overrides": [ 323 | { 324 | "matcher": { 325 | "id": "byName", 326 | "options": "Timestamp" 327 | }, 328 | "properties": [ 329 | { 330 | "id": "custom.width", 331 | "value": 147 332 | } 333 | ] 334 | }, 335 | { 336 | "matcher": { 337 | "id": "byName", 338 | "options": "Container" 339 | }, 340 | "properties": [ 341 | { 342 | "id": "custom.width", 343 | "value": 134 344 | } 345 | ] 346 | }, 347 | { 348 | "matcher": { 349 | "id": "byName", 350 | "options": "Message" 351 | }, 352 | "properties": [ 353 | { 354 | "id": "custom.width", 355 | "value": 1524 356 | } 357 | ] 358 | }, 359 | { 360 | "matcher": { 361 | "id": "byName", 362 | "options": "Severity" 363 | }, 364 | "properties": [ 365 | { 366 | "id": "custom.width", 367 | "value": 72 368 | } 369 | ] 370 | }, 371 | { 372 | "matcher": { 373 | "id": "byName", 374 | "options": "Class" 375 | }, 376 | "properties": [ 377 | { 378 | "id": "custom.width", 379 | "value": 97 380 | } 381 | ] 382 | }, 383 | { 384 | "matcher": { 385 | "id": "byName", 386 | "options": "TraceId" 387 | }, 388 | "properties": [ 389 | { 390 | "id": "custom.width", 391 | "value": 144 392 | } 393 | ] 394 | } 395 | ] 396 | }, 397 | "gridPos": { 398 | "h": 17, 399 | "w": 24, 400 | "x": 0, 401 | "y": 17 402 | }, 403 | "id": 3, 404 | "options": { 405 | "showHeader": true, 406 | "sortBy": [ 407 | { 408 | "desc": false, 409 | "displayName": "Container" 410 | } 411 | ] 412 | }, 413 | "pluginVersion": "7.5.9", 414 | "targets": [ 415 | { 416 | "alias": "", 417 | "bucketAggs": [ 418 | { 419 | "field": "@timestamp", 420 | "id": "2", 421 | "settings": { 422 | "interval": "auto" 423 | }, 424 | "type": "date_histogram" 425 | } 426 | ], 427 | "hide": false, 428 | "metrics": [ 429 | { 430 | "id": "1", 431 | "type": "logs" 432 | } 433 | ], 434 | "query": "-container.name:*-app", 435 | "refId": "A", 436 | "timeField": "@timestamp" 437 | } 438 | ], 439 | "title": "System logs", 440 | "transformations": [ 441 | { 442 | "id": "organize", 443 | "options": { 444 | "excludeByName": { 445 | "_id": true, 446 | "_index": true, 447 | "_source": true, 448 | "_type": true, 449 | "agent.ephemeral_id": true, 450 | "agent.hostname": true, 451 | "agent.id": true, 452 | "agent.name": true, 453 | "agent.type": true, 454 | "agent.version": true, 455 | "container.id": true, 456 | "container.image.name": true, 457 | "container.labels.architecture": true, 458 | "container.labels.build-date": true, 459 | "container.labels.com_docker_compose_config-hash": true, 460 | "container.labels.com_docker_compose_container-number": true, 461 | "container.labels.com_docker_compose_oneoff": true, 462 | "container.labels.com_docker_compose_project": true, 463 | "container.labels.com_docker_compose_project_config_files": true, 464 | "container.labels.com_docker_compose_project_working_dir": true, 465 | "container.labels.com_docker_compose_service": true, 466 | "container.labels.com_docker_compose_version": true, 467 | "container.labels.com_redhat_build-host": true, 468 | "container.labels.com_redhat_component": true, 469 | "container.labels.com_redhat_license_terms": true, 470 | "container.labels.description": true, 471 | "container.labels.desktop_docker_io/binds/0/Source": true, 472 | "container.labels.desktop_docker_io/binds/0/SourceKind": true, 473 | "container.labels.desktop_docker_io/binds/0/Target": true, 474 | "container.labels.desktop_docker_io/binds/1/Source": true, 475 | "container.labels.desktop_docker_io/binds/1/SourceKind": true, 476 | "container.labels.desktop_docker_io/binds/1/Target": true, 477 | "container.labels.desktop_docker_io/binds/2/Source": true, 478 | "container.labels.desktop_docker_io/binds/2/SourceKind": true, 479 | "container.labels.desktop_docker_io/binds/2/Target": true, 480 | "container.labels.distribution-scope": true, 481 | "container.labels.io_confluent_docker": true, 482 | "container.labels.io_confluent_docker_build_number": true, 483 | "container.labels.io_confluent_docker_git_id": true, 484 | "container.labels.io_confluent_docker_git_repo": true, 485 | "container.labels.io_k8s_description": true, 486 | "container.labels.io_k8s_display-name": true, 487 | "container.labels.io_openshift_expose-services": true, 488 | "container.labels.io_openshift_tags": true, 489 | "container.labels.license": true, 490 | "container.labels.maintainer": true, 491 | "container.labels.name": true, 492 | "container.labels.org_label-schema_build-date": true, 493 | "container.labels.org_label-schema_license": true, 494 | "container.labels.org_label-schema_name": true, 495 | "container.labels.org_label-schema_schema-version": true, 496 | "container.labels.org_label-schema_url": true, 497 | "container.labels.org_label-schema_usage": true, 498 | "container.labels.org_label-schema_vcs-ref": true, 499 | "container.labels.org_label-schema_vcs-url": true, 500 | "container.labels.org_label-schema_vendor": true, 501 | "container.labels.org_label-schema_version": true, 502 | "container.labels.org_opencontainers_image_created": true, 503 | "container.labels.org_opencontainers_image_documentation": true, 504 | "container.labels.org_opencontainers_image_licenses": true, 505 | "container.labels.org_opencontainers_image_revision": true, 506 | "container.labels.org_opencontainers_image_source": true, 507 | "container.labels.org_opencontainers_image_title": true, 508 | "container.labels.org_opencontainers_image_url": true, 509 | "container.labels.org_opencontainers_image_vendor": true, 510 | "container.labels.org_opencontainers_image_version": true, 511 | "container.labels.release": true, 512 | "container.labels.summary": true, 513 | "container.labels.url": true, 514 | "container.labels.vcs-ref": true, 515 | "container.labels.vcs-type": true, 516 | "container.labels.vendor": true, 517 | "container.labels.version": true, 518 | "container.name": false, 519 | "ecs.version": true, 520 | "highlight": true, 521 | "host.name": true, 522 | "input.type": true, 523 | "log.file.path": true, 524 | "log.flags": true, 525 | "log.offset": true, 526 | "message": false, 527 | "msg.spanId": true, 528 | "msg.time": true, 529 | "msg.traceid": true, 530 | "sort": true, 531 | "stream": true 532 | }, 533 | "indexByName": { 534 | "@timestamp": 0, 535 | "_id": 1, 536 | "_index": 2, 537 | "_source": 3, 538 | "_type": 4, 539 | "agent.ephemeral_id": 5, 540 | "agent.hostname": 6, 541 | "agent.id": 7, 542 | "agent.name": 8, 543 | "agent.type": 9, 544 | "agent.version": 10, 545 | "container.id": 11, 546 | "container.image.name": 12, 547 | "container.labels.com_docker_compose_config-hash": 13, 548 | "container.labels.com_docker_compose_container-number": 14, 549 | "container.labels.com_docker_compose_oneoff": 15, 550 | "container.labels.com_docker_compose_project": 16, 551 | "container.labels.com_docker_compose_project_config_files": 17, 552 | "container.labels.com_docker_compose_project_working_dir": 18, 553 | "container.labels.com_docker_compose_service": 19, 554 | "container.labels.com_docker_compose_version": 20, 555 | "container.name": 21, 556 | "ecs.version": 22, 557 | "highlight": 23, 558 | "host.name": 24, 559 | "input.type": 25, 560 | "log.file.path": 26, 561 | "log.flags": 27, 562 | "log.offset": 28, 563 | "message": 29, 564 | "msg.class": 31, 565 | "msg.message": 32, 566 | "msg.severity": 30, 567 | "msg.spanId": 34, 568 | "msg.time": 35, 569 | "msg.traceid": 33, 570 | "sort": 36, 571 | "stream": 37 572 | }, 573 | "renameByName": { 574 | "@timestamp": "Timestamp", 575 | "container.name": "Container", 576 | "ecs.version": "", 577 | "message": "Message", 578 | "msg.class": "Class", 579 | "msg.message": "Message", 580 | "msg.severity": "Severity", 581 | "msg.spanId": "SpanId", 582 | "msg.traceid": "TraceId" 583 | } 584 | } 585 | } 586 | ], 587 | "type": "table" 588 | } 589 | ], 590 | "refresh": false, 591 | "schemaVersion": 27, 592 | "style": "dark", 593 | "tags": [], 594 | "templating": { 595 | "list": [ 596 | { 597 | "datasource": "Elasticsearch", 598 | "description": null, 599 | "error": null, 600 | "filters": [], 601 | "hide": 0, 602 | "label": null, 603 | "name": "Filters", 604 | "skipUrlSync": false, 605 | "type": "adhoc" 606 | } 607 | ] 608 | }, 609 | "time": { 610 | "from": "now-15m", 611 | "to": "now" 612 | }, 613 | "timepicker": {}, 614 | "timezone": "", 615 | "title": "Logs", 616 | "uid": "CmJOR93Gk", 617 | "version": 1 618 | } -------------------------------------------------------------------------------- /infrastructure/grafana/provisioning/dashboards/all.yml: -------------------------------------------------------------------------------- 1 | apiVersion: 1 2 | providers: 3 | # an unique provider name. Required 4 | - name: 'default' 5 | # Org id. Default to 1 6 | orgId: 1 7 | # name of the dashboard folder. 8 | folder: '' 9 | # folder UID. will be automatically generated if not specified 10 | folderUid: '' 11 | # provider type. Default to 'file' 12 | type: file 13 | # disable dashboard deletion 14 | disableDeletion: false 15 | # how often Grafana will scan for changed dashboards 16 | updateIntervalSeconds: 10 17 | # allow updating provisioned dashboards from the UI 18 | allowUiUpdates: false 19 | options: 20 | # path to dashboard files on disk. Required when using the 'file' type 21 | path: /etc/grafana/provisioning/dashboards 22 | # use folder names from filesystem to create folders in Grafana 23 | foldersFromFilesStructure: true -------------------------------------------------------------------------------- /infrastructure/grafana/provisioning/datasources/datasource.yml: -------------------------------------------------------------------------------- 1 | # config file version 2 | apiVersion: 1 3 | 4 | # list of datasources that should be deleted from the database 5 | deleteDatasources: 6 | - name: Prometheus 7 | orgId: 1 8 | 9 | # list of datasources to insert/update depending 10 | # whats available in the database 11 | datasources: 12 | - name: Prometheus 13 | uid: gdev-prometheus 14 | type: prometheus 15 | access: proxy 16 | orgId: 1 17 | url: http://prometheus:9090 18 | password: admin 19 | user: admin 20 | database: 21 | basicAuth: true 22 | basicAuthUser: admin 23 | basicAuthPassword: admin 24 | withCredentials: 25 | isDefault: true 26 | jsonData: 27 | graphiteVersion: "1.1" 28 | tlsAuth: false 29 | tlsAuthWithCACert: false 30 | secureJsonData: 31 | tlsCACert: "..." 32 | tlsClientCert: "..." 33 | tlsClientKey: "..." 34 | version: 1 35 | editable: true 36 | - name: Elasticsearch 37 | uid: 'logs-elastic' 38 | orgId: 1 39 | type: elasticsearch 40 | typeName: Elasticsearch 41 | access: proxy 42 | url: http://elasticsearch:9200 43 | password: 'elastic' 44 | user: 'elastic' 45 | database: "[filebeat-]YYYY.MM.DD" 46 | basicAuth: true 47 | isDefault: false 48 | jsonData: 49 | esVersion: 70 50 | interval: Daily 51 | logLevelField: '' 52 | logMessageField: 'message' 53 | maxConcurrentShardRequests: 5 54 | timeField: "@timestamp" 55 | readOnly: false 56 | editable: true 57 | - name: Jaeger 58 | type: jaeger 59 | uid: gdev-jaeger 60 | access: proxy 61 | url: http://jaeger:16686 62 | editable: true 63 | jsonData: 64 | tlsSkipVerify: true 65 | tracesToLogsV2: 66 | customQuery: true 67 | datasourceUid: 'logs-elastic' 68 | spanStartTimeShift: '-2h' 69 | spanEndTimeShift: '2h' 70 | # Filtering by TraceID 71 | filterBySpanID: false 72 | filterByTraceID: true 73 | query: 'msg.traceid:"$${__span.traceId}"' 74 | 75 | # Filtering by SpanID 76 | #filterBySpanID: true 77 | #filterByTraceID: false 78 | #query: 'msg.spanId:"$${__span.spanId}"' 79 | tags: [] 80 | tracesToMetrics: 81 | datasourceUid: 'gdev-prometheus' 82 | queries: 83 | - name: CPU utilization 84 | query: 'system_cpu_usage{$$__tags}' 85 | spanEndTimeShift: '2m' 86 | spanStartTimeShift: '-2m' 87 | tags: 88 | - key: application 89 | value: exported_job -------------------------------------------------------------------------------- /infrastructure/otel-collector/otel-collector-config.yaml: -------------------------------------------------------------------------------- 1 | receivers: 2 | otlp: 3 | protocols: 4 | grpc: 5 | endpoint: 0.0.0.0:4317 6 | http: 7 | endpoint: 0.0.0.0:4318 8 | include_metadata: true 9 | cors: 10 | allowed_origins: 11 | - "*" 12 | 13 | exporters: 14 | prometheus: 15 | endpoint: "0.0.0.0:8889" 16 | # const_labels: 17 | # label1: value1 18 | 19 | otlp/jaeger: 20 | endpoint: jaeger:4317 21 | tls: 22 | insecure: true # Add this to disable TLS 23 | 24 | processors: 25 | batch: 26 | 27 | extensions: 28 | health_check: 29 | pprof: 30 | endpoint: :1888 31 | zpages: 32 | endpoint: :55679 33 | 34 | service: 35 | extensions: [pprof, zpages, health_check] 36 | pipelines: 37 | traces: 38 | receivers: [otlp] 39 | processors: [batch] 40 | exporters: [otlp/jaeger] 41 | metrics: 42 | # receivers: [otlp, prometheus] 43 | receivers: [otlp] 44 | processors: [batch] 45 | exporters: [prometheus] -------------------------------------------------------------------------------- /infrastructure/prometheus/prometheusConfig.yml: -------------------------------------------------------------------------------- 1 | #Global configurations 2 | global: 3 | scrape_interval: 5s # Set the scrape interval to every 5 seconds. 4 | evaluation_interval: 5s # Evaluate rules every 5 seconds. 5 | scrape_configs: 6 | # - job_name: 'jaeger' 7 | # metrics_path: '/metrics' 8 | # static_configs: 9 | # - targets: ['jaeger:14269'] 10 | # - job_name: 'jaeger-ingester' 11 | # metrics_path: '/metrics' 12 | # static_configs: 13 | # - targets: ['jaeger-ingester:14270'] 14 | # - job_name: 'jaeger-query' 15 | # metrics_path: '/metrics' 16 | # static_configs: 17 | # - targets: ['jaeger-query:16687'] 18 | - job_name: 'otel-collector' 19 | scrape_interval: 10s 20 | static_configs: 21 | - targets: ['otel-collector:8889'] 22 | # - targets: ['otel-collector:8888'] 23 | # - job_name: 'BookingTripService' 24 | # metrics_path: '/actuator/prometheus' 25 | # static_configs: 26 | # - targets: ['tripbooking:8080'] 27 | # - job_name: 'BookingCarService' 28 | # metrics_path: '/actuator/prometheus' 29 | # static_configs: 30 | # - targets: ['carbooking:8080'] 31 | # - job_name: 'BookingHotelService' 32 | # metrics_path: '/actuator/prometheus' 33 | # static_configs: 34 | # - targets: ['hotelbooking:8080'] 35 | # - job_name: 'BookingFlightService' 36 | # metrics_path: '/actuator/prometheus' 37 | # static_configs: 38 | # - targets: ['flightbooking:8080'] --------------------------------------------------------------------------------