This is an automated email from the ASF dual-hosted git repository.
davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/main by this push:
new 351c58e8c75b CAMEL-16866 add route event-notifier (#19867)
351c58e8c75b is described below
commit 351c58e8c75b948f31165e6a6889e1f0321ec2de
Author: Jono Morris <[email protected]>
AuthorDate: Tue Nov 11 02:14:50 2025 +1300
CAMEL-16866 add route event-notifier (#19867)
---
.../main/docs/opentelemetry-metrics-component.adoc | 29 +++++
.../metrics/OpenTelemetryConstants.java | 11 ++
.../OpenTelemetryRouteEventNotifier.java | 144 +++++++++++++++++++++
...nTelemetryRouteEventNotifierNamingStrategy.java | 57 ++++++++
.../metrics/AbstractOpenTelemetryTest.java | 82 ++++++++++++
.../OpenTelemetryRouteEventNotifierTest.java | 80 ++++++++++++
6 files changed, 403 insertions(+)
diff --git
a/components/camel-opentelemetry-metrics/src/main/docs/opentelemetry-metrics-component.adoc
b/components/camel-opentelemetry-metrics/src/main/docs/opentelemetry-metrics-component.adoc
index d8638a50cd1e..cf3b38744b92 100644
---
a/components/camel-opentelemetry-metrics/src/main/docs/opentelemetry-metrics-component.adoc
+++
b/components/camel-opentelemetry-metrics/src/main/docs/opentelemetry-metrics-component.adoc
@@ -292,6 +292,35 @@ from("direct:in")
.to("direct:out");
----
+=== OpenTelemetry Event Notification
+
+==== Camel Route Event Notifier
+
+The Route Event Notifier counts the number of added and running routes, and
can be added to the CamelContext as follows:
+
+[source,java]
+----
+camelContext.getManagementStrategy().addEventNotifier(new
OpenTelemetryRouteEventNotifier());
+----
+
+Camel specific metrics that are available:
+
+[width="100%",options="header"]
+|=====================================================
+|Default Name |Type |Description
+|camel.routes.added | LongUpDownCounter | Number of routes in total
+|camel.routes.reloaded | LongCounter | Number of routes that have been reloaded
+|camel.routes.running | LongUpDownCounter | Number of routes currently running
+|=====================================================
+
+The following options are supported:
+
+[width="100%",options="header"]
+|=======================================================================
+|Name |Default |Description
+| namingStrategy | OpenTelemetryRouteEventNotifierNamingStrategy.DEFAULT | The
strategy to use for overriding default metric names.
+|=======================================================================
+
== OpenTelemetry Configuration
Applications can export collected metrics to various backends using different
exporters, including the OpenTelemetry Protocol (OTLP) exporter,
diff --git
a/components/camel-opentelemetry-metrics/src/main/java/org/apache/camel/opentelemetry/metrics/OpenTelemetryConstants.java
b/components/camel-opentelemetry-metrics/src/main/java/org/apache/camel/opentelemetry/metrics/OpenTelemetryConstants.java
index 7d3b3ce7251b..5514cc29af99 100644
---
a/components/camel-opentelemetry-metrics/src/main/java/org/apache/camel/opentelemetry/metrics/OpenTelemetryConstants.java
+++
b/components/camel-opentelemetry-metrics/src/main/java/org/apache/camel/opentelemetry/metrics/OpenTelemetryConstants.java
@@ -39,8 +39,19 @@ public class OpenTelemetryConstants {
public static final String HEADER_METRIC_ATTRIBUTES = HEADER_PREFIX +
"Attributes";
+ // Route-event metrics
+ public static final String DEFAULT_CAMEL_ROUTES_ADDED =
"camel.routes.added";
+ public static final String DEFAULT_CAMEL_ROUTES_RUNNING =
"camel.routes.running";
+ public static final String DEFAULT_CAMEL_ROUTES_RELOADED =
"camel.routes.reloaded";
+
// OpenTelemetry Attribute keys
public static final String CAMEL_CONTEXT_ATTRIBUTE = "camelContext";
+ public static final String ROUTE_ID_ATTRIBUTE = "routeId";
+ public static final String EVENT_TYPE_ATTRIBUTE = "eventType";
+ public static final String KIND_ATTRIBUTE = "kind";
+
+ // OpenTelemetry Attribute values
+ public static final String KIND_ROUTE = "CamelRoute";
private OpenTelemetryConstants() {
// no-op
diff --git
a/components/camel-opentelemetry-metrics/src/main/java/org/apache/camel/opentelemetry/metrics/eventnotifier/OpenTelemetryRouteEventNotifier.java
b/components/camel-opentelemetry-metrics/src/main/java/org/apache/camel/opentelemetry/metrics/eventnotifier/OpenTelemetryRouteEventNotifier.java
new file mode 100644
index 000000000000..7a7c92e52cdf
--- /dev/null
+++
b/components/camel-opentelemetry-metrics/src/main/java/org/apache/camel/opentelemetry/metrics/eventnotifier/OpenTelemetryRouteEventNotifier.java
@@ -0,0 +1,144 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.opentelemetry.metrics.eventnotifier;
+
+import io.opentelemetry.api.GlobalOpenTelemetry;
+import io.opentelemetry.api.common.AttributeKey;
+import io.opentelemetry.api.common.Attributes;
+import io.opentelemetry.api.metrics.LongCounter;
+import io.opentelemetry.api.metrics.LongUpDownCounter;
+import io.opentelemetry.api.metrics.Meter;
+import org.apache.camel.CamelContext;
+import org.apache.camel.CamelContextAware;
+import org.apache.camel.RuntimeCamelException;
+import org.apache.camel.spi.CamelEvent;
+import org.apache.camel.spi.CamelEvent.RouteEvent;
+import org.apache.camel.spi.ManagementStrategy;
+import org.apache.camel.support.CamelContextHelper;
+import org.apache.camel.support.EventNotifierSupport;
+
+import static
org.apache.camel.opentelemetry.metrics.OpenTelemetryConstants.CAMEL_CONTEXT_ATTRIBUTE;
+import static
org.apache.camel.opentelemetry.metrics.OpenTelemetryConstants.EVENT_TYPE_ATTRIBUTE;
+import static
org.apache.camel.opentelemetry.metrics.OpenTelemetryConstants.KIND_ATTRIBUTE;
+import static
org.apache.camel.opentelemetry.metrics.OpenTelemetryConstants.KIND_ROUTE;
+
+public class OpenTelemetryRouteEventNotifier extends EventNotifierSupport
implements CamelContextAware {
+
+ private final Class<RouteEvent> eventType = RouteEvent.class;
+ private Meter meter;
+ boolean registerKamelets;
+ boolean registerTemplates = true;
+ private Attributes attributes = Attributes.of(
+ AttributeKey.stringKey(KIND_ATTRIBUTE), KIND_ROUTE,
+ AttributeKey.stringKey(EVENT_TYPE_ATTRIBUTE),
RouteEvent.class.getSimpleName());
+
+ // Event Notifier options
+ private OpenTelemetryRouteEventNotifierNamingStrategy namingStrategy
+ = OpenTelemetryRouteEventNotifierNamingStrategy.DEFAULT;
+
+ // OpenTelemetry instruments
+ private LongUpDownCounter addedCounter;
+ private LongUpDownCounter runningCounter;
+ private LongCounter reloadedCounter;
+
+ public OpenTelemetryRouteEventNotifier() {
+ // no-op
+ }
+
+ public void
setNamingStrategy(OpenTelemetryRouteEventNotifierNamingStrategy namingStrategy)
{
+ this.namingStrategy = namingStrategy;
+ }
+
+ public OpenTelemetryRouteEventNotifierNamingStrategy getNamingStrategy() {
+ return namingStrategy;
+ }
+
+ public void setMeter(Meter meter) {
+ this.meter = meter;
+ }
+
+ public Meter getMeter() {
+ return meter;
+ }
+
+ @Override
+ public void setCamelContext(CamelContext camelContext) {
+ super.setCamelContext(camelContext);
+ this.attributes = Attributes.of(
+ AttributeKey.stringKey(KIND_ATTRIBUTE), KIND_ROUTE,
+ AttributeKey.stringKey(CAMEL_CONTEXT_ATTRIBUTE),
camelContext.getName(),
+ AttributeKey.stringKey(EVENT_TYPE_ATTRIBUTE),
RouteEvent.class.getSimpleName());
+ }
+
+ @Override
+ public boolean isEnabled(CamelEvent eventObject) {
+ return eventType.isAssignableFrom(eventObject.getClass());
+ }
+
+ @Override
+ protected void doInit() throws Exception {
+ super.doInit();
+ if (meter == null) {
+ this.meter =
CamelContextHelper.findSingleByType(getCamelContext(), Meter.class);
+ }
+ if (meter == null) {
+ this.meter = GlobalOpenTelemetry.get().getMeter("camel");
+ }
+ if (meter == null) {
+ throw new RuntimeCamelException("Could not find any OpenTelemetry
meter!");
+ }
+ ManagementStrategy ms = getCamelContext().getManagementStrategy();
+ if (ms != null && ms.getManagementAgent() != null) {
+ registerKamelets =
ms.getManagementAgent().getRegisterRoutesCreateByKamelet();
+ registerTemplates =
ms.getManagementAgent().getRegisterRoutesCreateByTemplate();
+ }
+ }
+
+ @Override
+ protected void doStart() throws Exception {
+ super.doStart();
+ addedCounter =
meter.upDownCounterBuilder(namingStrategy.getRouteAddedName())
+ .setUnit("routes").build();
+ runningCounter =
meter.upDownCounterBuilder(namingStrategy.getRouteRunningName())
+ .setUnit("routes").build();
+ reloadedCounter =
meter.counterBuilder(namingStrategy.getRouteReloadedName())
+ .setUnit("routes").build();
+ }
+
+ @Override
+ public void notify(CamelEvent event) {
+ if (event instanceof RouteEvent re) {
+ // skip routes that should not be included
+ boolean skip = (re.getRoute().isCreatedByKamelet() &&
!registerKamelets)
+ || (re.getRoute().isCreatedByRouteTemplate() &&
!registerTemplates);
+ if (skip) {
+ return;
+ }
+ }
+ if (event instanceof CamelEvent.RouteAddedEvent) {
+ addedCounter.add(1L, attributes);
+ } else if (event instanceof CamelEvent.RouteRemovedEvent) {
+ addedCounter.add(-1L, attributes);
+ } else if (event instanceof CamelEvent.RouteStartedEvent) {
+ runningCounter.add(1L, attributes);
+ } else if (event instanceof CamelEvent.RouteStoppedEvent) {
+ runningCounter.add(-1L, attributes);
+ } else if (event instanceof CamelEvent.RouteReloadedEvent) {
+ reloadedCounter.add(1L, attributes);
+ }
+ }
+}
diff --git
a/components/camel-opentelemetry-metrics/src/main/java/org/apache/camel/opentelemetry/metrics/eventnotifier/OpenTelemetryRouteEventNotifierNamingStrategy.java
b/components/camel-opentelemetry-metrics/src/main/java/org/apache/camel/opentelemetry/metrics/eventnotifier/OpenTelemetryRouteEventNotifierNamingStrategy.java
new file mode 100644
index 000000000000..87d99fffdea1
--- /dev/null
+++
b/components/camel-opentelemetry-metrics/src/main/java/org/apache/camel/opentelemetry/metrics/eventnotifier/OpenTelemetryRouteEventNotifierNamingStrategy.java
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.opentelemetry.metrics.eventnotifier;
+
+import static
org.apache.camel.opentelemetry.metrics.OpenTelemetryConstants.DEFAULT_CAMEL_ROUTES_ADDED;
+import static
org.apache.camel.opentelemetry.metrics.OpenTelemetryConstants.DEFAULT_CAMEL_ROUTES_RELOADED;
+import static
org.apache.camel.opentelemetry.metrics.OpenTelemetryConstants.DEFAULT_CAMEL_ROUTES_RUNNING;
+
+public interface OpenTelemetryRouteEventNotifierNamingStrategy {
+
+ /**
+ * Default naming strategy.
+ */
+ OpenTelemetryRouteEventNotifierNamingStrategy DEFAULT = new
OpenTelemetryRouteEventNotifierNamingStrategy() {
+ @Override
+ public String formatName(String name) {
+ return name;
+ }
+
+ @Override
+ public String getRouteAddedName() {
+ return DEFAULT_CAMEL_ROUTES_ADDED;
+ }
+
+ @Override
+ public String getRouteRunningName() {
+ return DEFAULT_CAMEL_ROUTES_RUNNING;
+ }
+
+ @Override
+ public String getRouteReloadedName() {
+ return DEFAULT_CAMEL_ROUTES_RELOADED;
+ }
+ };
+
+ String formatName(String name);
+
+ String getRouteAddedName();
+
+ String getRouteRunningName();
+
+ String getRouteReloadedName();
+}
diff --git
a/components/camel-opentelemetry-metrics/src/test/java/org/apache/camel/opentelemetry/metrics/AbstractOpenTelemetryTest.java
b/components/camel-opentelemetry-metrics/src/test/java/org/apache/camel/opentelemetry/metrics/AbstractOpenTelemetryTest.java
new file mode 100644
index 000000000000..06c32d7a8470
--- /dev/null
+++
b/components/camel-opentelemetry-metrics/src/test/java/org/apache/camel/opentelemetry/metrics/AbstractOpenTelemetryTest.java
@@ -0,0 +1,82 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.opentelemetry.metrics;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import io.opentelemetry.api.common.AttributeKey;
+import io.opentelemetry.sdk.metrics.data.LongPointData;
+import io.opentelemetry.sdk.metrics.data.MetricData;
+import io.opentelemetry.sdk.metrics.data.PointData;
+import org.apache.camel.test.junit5.CamelTestSupport;
+
+import static
org.apache.camel.opentelemetry.metrics.OpenTelemetryConstants.ROUTE_ID_ATTRIBUTE;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class AbstractOpenTelemetryTest extends CamelTestSupport {
+
+ public final CamelOpenTelemetryExtension otelExtension =
CamelOpenTelemetryExtension.create();
+
+ protected List<MetricData> getMetricData(String metricName) {
+ return otelExtension.getMetrics().stream()
+ .filter(d -> d.getName().equals(metricName))
+ .collect(Collectors.toList());
+ }
+
+ protected PointData getPointDataForRouteId(String metricName, String
routeId) {
+ List<PointData> pointDataList = getAllPointDataForRouteId(metricName,
routeId);
+ assertEquals(1, pointDataList.size(),
+ "Should have one metric for routeId " + routeId + " and
metricName " + metricName);
+ return pointDataList.get(0);
+ }
+
+ protected LongPointData getSingleLongPointData(String meterName, String
routeId) {
+ List<PointData> pdList = getAllPointDataForRouteId(meterName, routeId);
+ assertEquals(1, pdList.size(), "Should have one metric for routeId " +
routeId + " and meterName " + meterName);
+ PointData pd = pdList.get(0);
+ assertInstanceOf(LongPointData.class, pd);
+ return (LongPointData) pd;
+ }
+
+ protected List<PointData> getAllPointDataForRouteId(String metricName,
String routeId) {
+ return otelExtension.getMetrics().stream()
+ .filter(d -> d.getName().equals(metricName))
+ .map(metricData -> metricData.getData().getPoints())
+ .flatMap(Collection::stream)
+ .filter(point -> routeId.equals(getRouteId(point)))
+ .collect(Collectors.toList());
+ }
+
+ protected List<PointData> getAllPointData(String metricName) {
+ return otelExtension.getMetrics().stream()
+ .filter(d -> d.getName().equals(metricName))
+ .map(metricData -> metricData.getData().getPoints())
+ .flatMap(Collection::stream)
+ .collect(Collectors.toList());
+ }
+
+ protected String getRouteId(PointData pd) {
+ Map<AttributeKey<?>, Object> m = pd.getAttributes().asMap();
+ assertTrue(m.containsKey(AttributeKey.stringKey(ROUTE_ID_ATTRIBUTE)));
+ return (String) m.get(AttributeKey.stringKey(ROUTE_ID_ATTRIBUTE));
+ }
+}
diff --git
a/components/camel-opentelemetry-metrics/src/test/java/org/apache/camel/opentelemetry/metrics/eventnotifier/OpenTelemetryRouteEventNotifierTest.java
b/components/camel-opentelemetry-metrics/src/test/java/org/apache/camel/opentelemetry/metrics/eventnotifier/OpenTelemetryRouteEventNotifierTest.java
new file mode 100644
index 000000000000..8807ebaabc1c
--- /dev/null
+++
b/components/camel-opentelemetry-metrics/src/test/java/org/apache/camel/opentelemetry/metrics/eventnotifier/OpenTelemetryRouteEventNotifierTest.java
@@ -0,0 +1,80 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.opentelemetry.metrics.eventnotifier;
+
+import java.util.List;
+
+import io.opentelemetry.sdk.metrics.data.LongPointData;
+import io.opentelemetry.sdk.metrics.data.PointData;
+import org.apache.camel.CamelContext;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.opentelemetry.metrics.AbstractOpenTelemetryTest;
+import org.junit.jupiter.api.Test;
+
+import static
org.apache.camel.opentelemetry.metrics.OpenTelemetryConstants.DEFAULT_CAMEL_ROUTES_ADDED;
+import static
org.apache.camel.opentelemetry.metrics.OpenTelemetryConstants.DEFAULT_CAMEL_ROUTES_RUNNING;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+
+public class OpenTelemetryRouteEventNotifierTest extends
AbstractOpenTelemetryTest {
+
+ @Override
+ protected CamelContext createCamelContext() throws Exception {
+ CamelContext context = super.createCamelContext();
+ OpenTelemetryRouteEventNotifier ren = new
OpenTelemetryRouteEventNotifier();
+ ren.setMeter(otelExtension.getOpenTelemetry().getMeter("meterTest"));
+ context.getManagementStrategy().addEventNotifier(ren);
+ ren.init();
+ return context;
+ }
+
+ @Test
+ public void testCamelRouteEvents() throws Exception {
+
+ verifyMetric(DEFAULT_CAMEL_ROUTES_ADDED, 1L);
+ verifyMetric(DEFAULT_CAMEL_ROUTES_RUNNING, 1L);
+
+ context.getRouteController().stopRoute("test");
+
+ verifyMetric(DEFAULT_CAMEL_ROUTES_ADDED, 1L);
+ verifyMetric(DEFAULT_CAMEL_ROUTES_RUNNING, 0L);
+
+ context.removeRoute("test");
+
+ verifyMetric(DEFAULT_CAMEL_ROUTES_ADDED, 0L);
+ verifyMetric(DEFAULT_CAMEL_ROUTES_RUNNING, 0L);
+ }
+
+ private void verifyMetric(String metricName, long expected) {
+ List<PointData> ls = getAllPointData(metricName);
+ assertEquals(1, ls.size(), "Expected one point data");
+ PointData pd = ls.get(0);
+
+ assertInstanceOf(LongPointData.class, pd);
+ assertEquals(expected, ((LongPointData) pd).getValue());
+ }
+
+ @Override
+ protected RouteBuilder createRouteBuilder() {
+ return new RouteBuilder() {
+ @Override
+ public void configure() {
+ from("direct:in").routeId("test").to("mock:out");
+ }
+ };
+ }
+}