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"),