You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by zb...@apache.org on 2023/08/31 14:51:41 UTC
[camel-quarkus-examples] 17/24: Add Micrometer features to Observability example
This is an automated email from the ASF dual-hosted git repository.
zbendhiba pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel-quarkus-examples.git
commit 7426f140db482196a1ea0509dbfb65d88a8bf08c
Author: Lukas Lowinger <ll...@redhat.com>
AuthorDate: Thu Jun 29 13:31:27 2023 +0200
Add Micrometer features to Observability example
---
observability/README.adoc | 74 +++++++++++++++++++++-
observability/pom.xml | 4 ++
.../main/java/org/acme/observability/Routes.java | 19 ++++++
.../java/org/acme/observability/TimerRoute.java | 1 +
.../health/camel/CustomLivenessCheck.java | 4 +-
.../TimerCounter.java} | 16 +++--
.../src/main/resources/application.properties | 22 +++----
.../org/acme/observability/ObservabilityIT.java | 6 ++
.../org/acme/observability/ObservabilityTest.java | 33 +++++-----
9 files changed, 138 insertions(+), 41 deletions(-)
diff --git a/observability/README.adoc b/observability/README.adoc
index 48bbb77..c403256 100644
--- a/observability/README.adoc
+++ b/observability/README.adoc
@@ -19,18 +19,88 @@ workspace. Any modifications in your project will automatically take effect in t
TIP: Please refer to the Development mode section of
https://camel.apache.org/camel-quarkus/latest/first-steps.html#_development_mode[Camel Quarkus User guide] for more details.
+=== How to enable metrics
+To enable observability features in Camel Quarkus, we need to add some additional dependencies to the project's pom.xml file.
+The most important one (see link:pom.xml#L97-L100[pom.xml]):
+
+[source, xml]
+----
+<dependency>
+ <groupId>org.apache.camel.quarkus</groupId>
+ <artifactId>camel-quarkus-micrometer</artifactId>
+</dependency>
+----
+
+After adding this dependency, you can benefit from both https://camel.apache.org/components/next/micrometer-component.html[Camel Micrometer] and https://quarkus.io/guides/micrometer[Quarkus Micrometer] worlds.
+We are able to use multiple ways to achieve create meters for our custom metrics.
+
+First of them is using Camel micrometer component (see link:src/main/java/org/acme/observability/Routes.java[Routes.java]):
+
+[source, java]
+----
+.to("micrometer:counter:org.acme.observability.greeting-provider?tags=type=events,purpose=example")
+----
+
+which will count each call to `platform-http:/greeting-provider` endpoint.
+
+Second approach is to benefit from auto-injected `MeterRegistry` (see link:src/main/java/org/acme/observability/Routes.java#L28[injection]) and use it directly (see link:src/main/java/org/acme/observability/Routes.java#L36[registry call]):
+
+[source, java]
+----
+registry.counter("org.acme.observability.greeting", "type", "events", "purpose", "example").increment();
+----
+
+which will count each call to `from("platform-http:/greeting")` endpoint.
+
+Finally last approach is to use Micrometer annotations (see https://quarkus.io/guides/micrometer#does-micrometer-support-annotations[which] are supported by Quarkus) by defining bean link:src/main/java/org/acme/observability/micrometer/TimerCounter.java[TimerCounter.java] as follows:
+
+[source, java]
+----
+@ApplicationScoped
+@Named("timerCounter")
+public class TimerCounter {
+
+ @Counted(value = "org.acme.observability.timer-counter", extraTags = { "purpose", "example" })
+ public void count() {
+ }
+}
+----
+
+and invoking it from Camel via (see link:src/main/java/org/acme/observability/TimerRoute.java[TimerRoute.java]):
+
+[source, java]
+----
+.bean("timerCounter", "count")
+----
+It will count each time the timer is fired.
+
+How to explore our custom metrics will be shown in the next chapter.
=== Metrics endpoint
-Metrics are exposed on an HTTP endpoint at `/q/metrics`. You can also browse application specific metrics from the `/q/metrics/application` endpoint.
+Metrics are exposed on an HTTP endpoint at `/q/metrics` on port `9000`.
+
+NOTE: Note we are using different port (9000) for the management endpoint then our application (8080) is listening on.
+This is caused by using link:src/main/resources/application.properties#L22[`quarkus.management.enabled = true`] (see https://quarkus.io/guides/management-interface-reference for more information).
To view all Camel metrics do:
[source,shell]
----
-$ curl localhost:8080/q/metrics/application
+$ curl localhost:9000/q/metrics
+----
+
+To view only our previously created metrics, use:
+
+[source,shell]
+----
+$ curl -s localhost:9000/q/metrics | grep -i 'purpose="example"'
----
+and you should see 3 lines of different metrics (with the same value, as they are all triggered by the timer).
+
+NOTE: Maybe you've noticed the Prometheus output format. If you would rather use JSON format, please follow https://quarkus.io/guides/micrometer#management-interface.
+
=== Health endpoint
Camel provides some out of the box liveness and readiness checks. To see this working, interrogate the `/q/health/live` and `/q/health/ready` endpoints:
diff --git a/observability/pom.xml b/observability/pom.xml
index baeba7d..d52f6f2 100644
--- a/observability/pom.xml
+++ b/observability/pom.xml
@@ -82,6 +82,10 @@
<groupId>org.apache.camel.quarkus</groupId>
<artifactId>camel-quarkus-platform-http</artifactId>
</dependency>
+ <dependency>
+ <groupId>org.apache.camel.quarkus</groupId>
+ <artifactId>camel-quarkus-bean</artifactId>
+ </dependency>
<dependency>
<groupId>org.apache.camel.quarkus</groupId>
<artifactId>camel-quarkus-http</artifactId>
diff --git a/observability/src/main/java/org/acme/observability/Routes.java b/observability/src/main/java/org/acme/observability/Routes.java
index 27bf12d..86730d7 100644
--- a/observability/src/main/java/org/acme/observability/Routes.java
+++ b/observability/src/main/java/org/acme/observability/Routes.java
@@ -16,19 +16,38 @@
*/
package org.acme.observability;
+import io.micrometer.core.instrument.MeterRegistry;
+import jakarta.enterprise.context.ApplicationScoped;
+import org.apache.camel.Exchange;
import org.apache.camel.builder.RouteBuilder;
+@ApplicationScoped
public class Routes extends RouteBuilder {
+ // Quarkus will inject this automatically for us
+ private final MeterRegistry registry;
+
+ public Routes(MeterRegistry registry) {
+ this.registry = registry;
+ }
+
+ private void countGreeting(Exchange exchange) {
+ // This is our custom metric: just counting how many times the method is called
+ registry.counter("org.acme.observability.greeting", "type", "events", "purpose", "example").increment();
+ }
+
@Override
public void configure() throws Exception {
from("platform-http:/greeting")
.removeHeaders("*")
+ .process(this::countGreeting)
.to("http://localhost:{{greeting-provider-app.service.port}}/greeting-provider");
from("platform-http:/greeting-provider")
// Random delay to simulate latency
+ .to("micrometer:counter:org.acme.observability.greeting-provider?tags=type=events,purpose=example")
.delay(simple("${random(1000, 5000)}"))
.setBody(constant("Hello From Camel Quarkus!"));
}
+
}
diff --git a/observability/src/main/java/org/acme/observability/TimerRoute.java b/observability/src/main/java/org/acme/observability/TimerRoute.java
index 0b8fd0f..2d237bc 100644
--- a/observability/src/main/java/org/acme/observability/TimerRoute.java
+++ b/observability/src/main/java/org/acme/observability/TimerRoute.java
@@ -23,6 +23,7 @@ public class TimerRoute extends RouteBuilder {
@Override
public void configure() throws Exception {
from("timer:greeting?period=10000")
+ .bean("timerCounter", "count")
.to("http://{{greeting-app.service.host}}:{{greeting-app.service.port}}/greeting");
}
}
diff --git a/observability/src/main/java/org/acme/observability/health/camel/CustomLivenessCheck.java b/observability/src/main/java/org/acme/observability/health/camel/CustomLivenessCheck.java
index 17e5ca9..6294ccd 100644
--- a/observability/src/main/java/org/acme/observability/health/camel/CustomLivenessCheck.java
+++ b/observability/src/main/java/org/acme/observability/health/camel/CustomLivenessCheck.java
@@ -39,8 +39,8 @@ public class CustomLivenessCheck extends AbstractHealthCheck {
protected void doCall(HealthCheckResultBuilder builder, Map<String, Object> options) {
int hits = hitCount.incrementAndGet();
- // Flag the check as DOWN on every 5th invocation, else it is UP
- if (hits % 5 == 0) {
+ // Flag the check as DOWN on every 5th invocation (but not on Kubernetes), else it is UP
+ if (hits % 5 == 0 && System.getenv("KUBERNETES_NAMESPACE") == null) {
builder.down();
} else {
builder.up();
diff --git a/observability/src/main/java/org/acme/observability/TimerRoute.java b/observability/src/main/java/org/acme/observability/micrometer/TimerCounter.java
similarity index 68%
copy from observability/src/main/java/org/acme/observability/TimerRoute.java
copy to observability/src/main/java/org/acme/observability/micrometer/TimerCounter.java
index 0b8fd0f..54ce6ad 100644
--- a/observability/src/main/java/org/acme/observability/TimerRoute.java
+++ b/observability/src/main/java/org/acme/observability/micrometer/TimerCounter.java
@@ -14,15 +14,17 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.acme.observability;
+package org.acme.observability.micrometer;
-import org.apache.camel.builder.RouteBuilder;
+import io.micrometer.core.annotation.Counted;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Named;
-public class TimerRoute extends RouteBuilder {
+@ApplicationScoped
+@Named("timerCounter")
+public class TimerCounter {
- @Override
- public void configure() throws Exception {
- from("timer:greeting?period=10000")
- .to("http://{{greeting-app.service.host}}:{{greeting-app.service.port}}/greeting");
+ @Counted(value = "org.acme.observability.timer-counter", extraTags = { "purpose", "example" })
+ public void count() {
}
}
diff --git a/observability/src/main/resources/application.properties b/observability/src/main/resources/application.properties
index a0956de..1007fa2 100644
--- a/observability/src/main/resources/application.properties
+++ b/observability/src/main/resources/application.properties
@@ -19,25 +19,23 @@
# Quarkus
#
quarkus.banner.enabled = false
+quarkus.management.enabled = true
# Identifier for the origin of spans created by the application
-quarkus.application.name=camel-quarkus-observability
+quarkus.application.name = camel-quarkus-observability
# For OTLP
-quarkus.otel.exporter.otlp.traces.endpoint=http://${TELEMETRY_COLLECTOR_COLLECTOR_SERVICE_HOST:localhost}:4317
+quarkus.otel.exporter.otlp.traces.endpoint = http://${TELEMETRY_COLLECTOR_COLLECTOR_SERVICE_HOST:localhost}:4317
# For Jaeger
-# quarkus.otel.exporter.jaeger.traces.endpoint=http://${MY_JAEGER_COLLECTOR_SERVICE_HOST:localhost}:14250
-
-# Allow metrics to be exported as JSON. Not strictly required and is disabled by default
-quarkus.micrometer.export.json.enabled = true
+# quarkus.otel.exporter.jaeger.traces.endpoint = http://${MY_JAEGER_COLLECTOR_SERVICE_HOST:localhost}:14250
#
# Camel
#
camel.context.name = camel-quarkus-observability
-greeting-app.service.host=${CAMEL_QUARKUS_OBSERVABILITY_SERVICE_HOST:localhost}
-greeting-app.service.port=${CAMEL_QUARKUS_OBSERVABILITY_SERVICE_PORT_HTTP:${quarkus.http.port}}
-%test.greeting-app.service.port=${quarkus.http.test-port}
-greeting-provider-app.service.host=localhost
-greeting-provider-app.service.port=${quarkus.http.port}
-%test.greeting-provider-app.service.port=${quarkus.http.test-port}
+greeting-app.service.host = ${CAMEL_QUARKUS_OBSERVABILITY_SERVICE_HOST:localhost}
+greeting-app.service.port = ${CAMEL_QUARKUS_OBSERVABILITY_SERVICE_PORT_HTTP:${quarkus.http.port}}
+%test.greeting-app.service.port = ${quarkus.http.test-port}
+greeting-provider-app.service.host = localhost
+greeting-provider-app.service.port = ${quarkus.http.port}
+%test.greeting-provider-app.service.port = ${quarkus.http.test-port}
diff --git a/observability/src/test/java/org/acme/observability/ObservabilityIT.java b/observability/src/test/java/org/acme/observability/ObservabilityIT.java
index ea6c2b4..e69623e 100644
--- a/observability/src/test/java/org/acme/observability/ObservabilityIT.java
+++ b/observability/src/test/java/org/acme/observability/ObservabilityIT.java
@@ -20,4 +20,10 @@ import io.quarkus.test.junit.QuarkusIntegrationTest;
@QuarkusIntegrationTest
public class ObservabilityIT extends ObservabilityTest {
+
+ // Is run in prod mode
+ @Override
+ protected String getManagementPrefix() {
+ return "http://localhost:9000";
+ }
}
diff --git a/observability/src/test/java/org/acme/observability/ObservabilityTest.java b/observability/src/test/java/org/acme/observability/ObservabilityTest.java
index d207f0c..8db44ea 100644
--- a/observability/src/test/java/org/acme/observability/ObservabilityTest.java
+++ b/observability/src/test/java/org/acme/observability/ObservabilityTest.java
@@ -16,20 +16,24 @@
*/
package org.acme.observability;
+import java.util.Arrays;
+
import io.quarkus.test.junit.QuarkusTest;
import io.restassured.RestAssured;
-import io.restassured.http.ContentType;
-import io.restassured.path.json.JsonPath;
import org.hamcrest.Matchers;
import org.junit.jupiter.api.Test;
-import static io.restassured.RestAssured.given;
import static org.hamcrest.CoreMatchers.is;
-import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.assertEquals;
@QuarkusTest
public class ObservabilityTest {
+ // Management interface is listening on 9001
+ protected String getManagementPrefix() {
+ return "http://localhost:9001";
+ }
+
@Test
public void greeting() {
RestAssured.get("/greeting")
@@ -40,35 +44,28 @@ public class ObservabilityTest {
@Test
public void metrics() {
// Verify Camel metrics are available
- JsonPath path = given()
- .when().accept(ContentType.JSON)
- .get("/q/metrics")
+ String prometheusMetrics = RestAssured
+ .get(getManagementPrefix() + "/q/metrics")
.then()
.statusCode(200)
.extract()
- .body()
- .jsonPath();
-
- long camelMetricCount = path.getMap("$.")
- .keySet()
- .stream()
- .filter(key -> key.toString().toLowerCase().startsWith("camel"))
- .count();
+ .body().asString();
- assertTrue(camelMetricCount > 0);
+ assertEquals(3,
+ Arrays.stream(prometheusMetrics.split("\n")).filter(line -> line.contains("purpose=\"example\"")).count());
}
@Test
public void health() {
// Verify liveness
- RestAssured.get("/q/health/live")
+ RestAssured.get(getManagementPrefix() + "/q/health/live")
.then()
.statusCode(200)
.body("status", is("UP"),
"checks.findAll { it.name == 'custom-liveness-check' }.status", Matchers.contains("UP"));
// Verify readiness
- RestAssured.get("/q/health/ready")
+ RestAssured.get(getManagementPrefix() + "/q/health/ready")
.then()
.statusCode(200)
.body("status", is("UP"),