├── .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 | 
4 | 
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']
--------------------------------------------------------------------------------