This is an automated email from the ASF dual-hosted git repository.

squakez pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git

commit edc1252f3fea05d5831b4df3667bd5c2d4bca27b
Author: Pasquale Congiusti <[email protected]>
AuthorDate: Fri Oct 30 10:11:19 2026 +0100

    chore(main): expose metrics recordLastExchangeTimestamp parameter
    
    Ref CAMEL-26415
---
 .../prometheus/MicrometerPrometheusConfigurer.java |  6 ++++++
 .../prometheus/MicrometerPrometheus.java           | 15 ++++++++++++++
 .../MicrometerExchangeEventNotifier.java           |  3 ++-
 ...eterExchangeEventNotifierLastTimestampTest.java | 18 ++++++++++++++---
 .../MetricsConfigurationPropertiesConfigurer.java  |  7 +++++++
 .../camel-main-configuration-metadata.json         |  1 +
 core/camel-main/src/main/docs/main.adoc            |  3 ++-
 .../camel/main/MetricsConfigurationProperties.java | 23 ++++++++++++++++++++++
 8 files changed, 71 insertions(+), 5 deletions(-)

diff --git 
a/components/camel-micrometer-prometheus/src/generated/java/org/apache/camel/component/micrometer/prometheus/MicrometerPrometheusConfigurer.java
 
b/components/camel-micrometer-prometheus/src/generated/java/org/apache/camel/component/micrometer/prometheus/MicrometerPrometheusConfigurer.java
index d10ef882340c..73da4bd6a9a5 100644
--- 
a/components/camel-micrometer-prometheus/src/generated/java/org/apache/camel/component/micrometer/prometheus/MicrometerPrometheusConfigurer.java
+++ 
b/components/camel-micrometer-prometheus/src/generated/java/org/apache/camel/component/micrometer/prometheus/MicrometerPrometheusConfigurer.java
@@ -50,6 +50,8 @@ public class MicrometerPrometheusConfigurer extends 
org.apache.camel.support.com
         case "namingstrategy":
         case "namingStrategy": target.setNamingStrategy(property(camelContext, 
java.lang.String.class, value)); return true;
         case "path": target.setPath(property(camelContext, 
java.lang.String.class, value)); return true;
+        case "recordlastexchangetimestamp":
+        case "recordLastExchangeTimestamp": 
target.setRecordLastExchangeTimestamp(property(camelContext, boolean.class, 
value)); return true;
         case "routepolicylevel":
         case "routePolicyLevel": 
target.setRoutePolicyLevel(property(camelContext, java.lang.String.class, 
value)); return true;
         case "skipcamelinfo":
@@ -89,6 +91,8 @@ public class MicrometerPrometheusConfigurer extends 
org.apache.camel.support.com
         case "namingstrategy":
         case "namingStrategy": return java.lang.String.class;
         case "path": return java.lang.String.class;
+        case "recordlastexchangetimestamp":
+        case "recordLastExchangeTimestamp": return boolean.class;
         case "routepolicylevel":
         case "routePolicyLevel": return java.lang.String.class;
         case "skipcamelinfo":
@@ -129,6 +133,8 @@ public class MicrometerPrometheusConfigurer extends 
org.apache.camel.support.com
         case "namingstrategy":
         case "namingStrategy": return target.getNamingStrategy();
         case "path": return target.getPath();
+        case "recordlastexchangetimestamp":
+        case "recordLastExchangeTimestamp": return 
target.isRecordLastExchangeTimestamp();
         case "routepolicylevel":
         case "routePolicyLevel": return target.getRoutePolicyLevel();
         case "skipcamelinfo":
diff --git 
a/components/camel-micrometer-prometheus/src/main/java/org/apache/camel/component/micrometer/prometheus/MicrometerPrometheus.java
 
b/components/camel-micrometer-prometheus/src/main/java/org/apache/camel/component/micrometer/prometheus/MicrometerPrometheus.java
index d9df02a1ea43..0804f67b057a 100644
--- 
a/components/camel-micrometer-prometheus/src/main/java/org/apache/camel/component/micrometer/prometheus/MicrometerPrometheus.java
+++ 
b/components/camel-micrometer-prometheus/src/main/java/org/apache/camel/component/micrometer/prometheus/MicrometerPrometheus.java
@@ -96,6 +96,8 @@ public class MicrometerPrometheus extends ServiceSupport 
implements CamelMetrics
     @Metadata(defaultValue = "true")
     private boolean baseEndpointURIExchangeEventNotifier = true;
     @Metadata(defaultValue = "true")
+    private boolean recordLastExchangeTimestamp = true;
+    @Metadata(defaultValue = "true")
     private boolean enableRouteEventNotifier = true;
     @Metadata(defaultValue = "false")
     private boolean enableInstrumentedThreadPoolFactory;
@@ -198,6 +200,18 @@ public class MicrometerPrometheus extends ServiceSupport 
implements CamelMetrics
         this.baseEndpointURIExchangeEventNotifier = 
baseEndpointURIExchangeEventNotifier;
     }
 
+    public boolean isRecordLastExchangeTimestamp() {
+        return recordLastExchangeTimestamp;
+    }
+
+    /**
+     * Whether to record the last exchange processed timestamp as a Gauge 
metric (camel.exchanges.last.timestamp). Set
+     * to false to disable this metric entirely.
+     */
+    public void setRecordLastExchangeTimestamp(boolean 
recordLastExchangeTimestamp) {
+        this.recordLastExchangeTimestamp = recordLastExchangeTimestamp;
+    }
+
     public boolean isEnableRouteEventNotifier() {
         return enableRouteEventNotifier;
     }
@@ -380,6 +394,7 @@ public class MicrometerPrometheus extends ServiceSupport 
implements CamelMetrics
                 notifier.setLogMetricsOnShutdownFilters(meterFilters);
             }
             
notifier.setBaseEndpointURI(isBaseEndpointURIExchangeEventNotifier());
+            
notifier.setRecordLastExchangeTimestamp(isRecordLastExchangeTimestamp());
             if ("legacy".equalsIgnoreCase(namingStrategy)) {
                 notifier.setNamingStrategy(
                         new 
MicrometerExchangeEventNotifierNamingStrategyLegacy(isBaseEndpointURIExchangeEventNotifier()));
diff --git 
a/components/camel-micrometer/src/main/java/org/apache/camel/component/micrometer/eventnotifier/MicrometerExchangeEventNotifier.java
 
b/components/camel-micrometer/src/main/java/org/apache/camel/component/micrometer/eventnotifier/MicrometerExchangeEventNotifier.java
index 7937c8ca1115..ebf3980110df 100644
--- 
a/components/camel-micrometer/src/main/java/org/apache/camel/component/micrometer/eventnotifier/MicrometerExchangeEventNotifier.java
+++ 
b/components/camel-micrometer/src/main/java/org/apache/camel/component/micrometer/eventnotifier/MicrometerExchangeEventNotifier.java
@@ -114,12 +114,13 @@ public class MicrometerExchangeEventNotifier extends 
AbstractMicrometerEventNoti
 
         // Register once at startup — backing value updated via AtomicLong on 
each exchange
         if (recordLastExchangeTimestamp) {
-            Gauge.builder(
+            Meter meter = Gauge.builder(
                     MicrometerConstants.CAMEL_EXCHANGE_LAST_TIME_METER_NAME,
                     lastExchangeTimestampHolder,
                     AtomicLong::get)
                     .description("Last exchange processed time in milliseconds 
since the Unix epoch")
                     .register(getMeterRegistry());
+            
meterMap.put(MicrometerConstants.CAMEL_EXCHANGE_LAST_TIME_METER_NAME, meter);
         }
 
         // need to be able to add/remove meter accordingly to route changes
diff --git 
a/components/camel-micrometer/src/test/java/org/apache/camel/component/micrometer/eventnotifier/MicrometerExchangeEventNotifierLastTimestampTest.java
 
b/components/camel-micrometer/src/test/java/org/apache/camel/component/micrometer/eventnotifier/MicrometerExchangeEventNotifierLastTimestampTest.java
index b215b7ce616e..8a1e8a5cf4e1 100644
--- 
a/components/camel-micrometer/src/test/java/org/apache/camel/component/micrometer/eventnotifier/MicrometerExchangeEventNotifierLastTimestampTest.java
+++ 
b/components/camel-micrometer/src/test/java/org/apache/camel/component/micrometer/eventnotifier/MicrometerExchangeEventNotifierLastTimestampTest.java
@@ -19,7 +19,9 @@ package org.apache.camel.component.micrometer.eventnotifier;
 import java.util.concurrent.atomic.AtomicInteger;
 
 import io.micrometer.core.instrument.Gauge;
+import io.micrometer.core.instrument.composite.CompositeMeterRegistry;
 import io.micrometer.core.instrument.search.RequiredSearch;
+import io.micrometer.core.instrument.search.Search;
 import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
 import org.apache.camel.builder.RouteBuilder;
 import org.apache.camel.component.micrometer.MicrometerConstants;
@@ -67,7 +69,6 @@ public class MicrometerExchangeEventNotifierLastTimestampTest 
extends AbstractMi
         mock.assertIsSatisfied();
 
         // The gauge should have been registered once at doStart() — never 
queried again per exchange.
-        // With the current implementation this assertion FAILS because find() 
is called `count` times.
         assertThat(findCallsAfterStart.get())
                 .as("MeterRegistry.find() for the last-timestamp gauge must 
not be called on every exchange")
                 .isEqualTo(0);
@@ -78,6 +79,17 @@ public class 
MicrometerExchangeEventNotifierLastTimestampTest extends AbstractMi
         assertThat(gauge.value()).as("last-timestamp gauge must hold a 
non-zero timestamp").isGreaterThan(0);
     }
 
+    @Test
+    public void testLastTimestampGaugeRemovedOnStop() {
+        Gauge gauge = 
meterRegistry.find(MicrometerConstants.CAMEL_EXCHANGE_LAST_TIME_METER_NAME).gauge();
+        assertThat(gauge).as("last-timestamp gauge must be registered after 
start").isNotNull();
+
+        context.stop();
+
+        gauge = 
meterRegistry.find(MicrometerConstants.CAMEL_EXCHANGE_LAST_TIME_METER_NAME).gauge();
+        assertThat(gauge).as("last-timestamp gauge must be removed after 
stop").isNull();
+    }
+
     @Override
     protected RouteBuilder createRouteBuilder() {
         return new RouteBuilder() {
@@ -92,7 +104,7 @@ public class 
MicrometerExchangeEventNotifierLastTimestampTest extends AbstractMi
     // Spy registry that intercepts find() calls for the last-timestamp meter 
name
     // 
---------------------------------------------------------------------------
 
-    private static class CountingCompositeMeterRegistry extends 
io.micrometer.core.instrument.composite.CompositeMeterRegistry {
+    private static class CountingCompositeMeterRegistry extends 
CompositeMeterRegistry {
 
         private final AtomicInteger counter;
 
@@ -111,7 +123,7 @@ public class 
MicrometerExchangeEventNotifierLastTimestampTest extends AbstractMi
         }
 
         @Override
-        public io.micrometer.core.instrument.search.Search find(String name) {
+        public Search find(String name) {
             if 
(MicrometerConstants.CAMEL_EXCHANGE_LAST_TIME_METER_NAME.equals(name)) {
                 counter.incrementAndGet();
             }
diff --git 
a/core/camel-main/src/generated/java/org/apache/camel/main/MetricsConfigurationPropertiesConfigurer.java
 
b/core/camel-main/src/generated/java/org/apache/camel/main/MetricsConfigurationPropertiesConfigurer.java
index b0470c3bc4ec..cb887727dc66 100644
--- 
a/core/camel-main/src/generated/java/org/apache/camel/main/MetricsConfigurationPropertiesConfigurer.java
+++ 
b/core/camel-main/src/generated/java/org/apache/camel/main/MetricsConfigurationPropertiesConfigurer.java
@@ -37,6 +37,7 @@ public class MetricsConfigurationPropertiesConfigurer extends 
org.apache.camel.s
         map.put("LogMetricsOnShutdownFormat", java.lang.String.class);
         map.put("NamingStrategy", java.lang.String.class);
         map.put("Path", java.lang.String.class);
+        map.put("RecordLastExchangeTimestamp", boolean.class);
         map.put("RoutePolicyLevel", java.lang.String.class);
         map.put("SkipCamelInfo", boolean.class);
         map.put("TextFormatVersion", java.lang.String.class);
@@ -72,6 +73,8 @@ public class MetricsConfigurationPropertiesConfigurer extends 
org.apache.camel.s
         case "namingstrategy":
         case "namingStrategy": target.setNamingStrategy(property(camelContext, 
java.lang.String.class, value)); return true;
         case "path": target.setPath(property(camelContext, 
java.lang.String.class, value)); return true;
+        case "recordlastexchangetimestamp":
+        case "recordLastExchangeTimestamp": 
target.setRecordLastExchangeTimestamp(property(camelContext, boolean.class, 
value)); return true;
         case "routepolicylevel":
         case "routePolicyLevel": 
target.setRoutePolicyLevel(property(camelContext, java.lang.String.class, 
value)); return true;
         case "skipcamelinfo":
@@ -115,6 +118,8 @@ public class MetricsConfigurationPropertiesConfigurer 
extends org.apache.camel.s
         case "namingstrategy":
         case "namingStrategy": return java.lang.String.class;
         case "path": return java.lang.String.class;
+        case "recordlastexchangetimestamp":
+        case "recordLastExchangeTimestamp": return boolean.class;
         case "routepolicylevel":
         case "routePolicyLevel": return java.lang.String.class;
         case "skipcamelinfo":
@@ -154,6 +159,8 @@ public class MetricsConfigurationPropertiesConfigurer 
extends org.apache.camel.s
         case "namingstrategy":
         case "namingStrategy": return target.getNamingStrategy();
         case "path": return target.getPath();
+        case "recordlastexchangetimestamp":
+        case "recordLastExchangeTimestamp": return 
target.isRecordLastExchangeTimestamp();
         case "routepolicylevel":
         case "routePolicyLevel": return target.getRoutePolicyLevel();
         case "skipcamelinfo":
diff --git 
a/core/camel-main/src/generated/resources/META-INF/camel-main-configuration-metadata.json
 
b/core/camel-main/src/generated/resources/META-INF/camel-main-configuration-metadata.json
index 96dd30c090c1..f75856d4b773 100644
--- 
a/core/camel-main/src/generated/resources/META-INF/camel-main-configuration-metadata.json
+++ 
b/core/camel-main/src/generated/resources/META-INF/camel-main-configuration-metadata.json
@@ -351,6 +351,7 @@
     { "name": "camel.metrics.logMetricsOnShutdownFormat", "required": false, 
"description": "Format used to log metrics when application is shutting down. 
Either json (default) or prometheus format.", "sourceType": 
"org.apache.camel.main.MetricsConfigurationProperties", "type": "enum", 
"javaType": "java.lang.String", "defaultValue": "json", "secret": false, 
"enum": [ "json", "prometheus" ] },
     { "name": "camel.metrics.namingStrategy", "required": false, 
"description": "Controls the name style to use for metrics. Default = uses 
micrometer naming convention. Legacy = uses the classic naming style 
(camelCase)", "sourceType": 
"org.apache.camel.main.MetricsConfigurationProperties", "type": "enum", 
"javaType": "java.lang.String", "defaultValue": "default", "secret": false, 
"enum": [ "default", "legacy" ] },
     { "name": "camel.metrics.path", "required": false, "description": "The 
path endpoint used to expose the metrics.", "sourceType": 
"org.apache.camel.main.MetricsConfigurationProperties", "type": "string", 
"javaType": "java.lang.String", "defaultValue": "\/observe\/metrics", "secret": 
false },
+    { "name": "camel.metrics.recordLastExchangeTimestamp", "required": false, 
"description": "Whether to record the last exchange processed timestamp as a 
Gauge metric (camel.exchanges.last.timestamp). Set to false to disable this 
metric entirely.", "sourceType": 
"org.apache.camel.main.MetricsConfigurationProperties", "type": "boolean", 
"javaType": "boolean", "defaultValue": true, "secret": false },
     { "name": "camel.metrics.routePolicyLevel", "required": false, 
"description": "Sets the level of information to capture. all = both context 
and routes.", "sourceType": 
"org.apache.camel.main.MetricsConfigurationProperties", "type": "enum", 
"javaType": "java.lang.String", "defaultValue": "all", "secret": false, "enum": 
[ "all", "route", "context" ] },
     { "name": "camel.metrics.skipCamelInfo", "required": false, "description": 
"Skip the evaluation of app.info metric which contains runtime provider 
information (default, false).", "sourceType": 
"org.apache.camel.main.MetricsConfigurationProperties", "type": "boolean", 
"javaType": "boolean", "defaultValue": false, "secret": false },
     { "name": "camel.metrics.textFormatVersion", "required": false, 
"description": "The text-format version to use with Prometheus scraping. 0.0.4 
= text\/plain; version=0.0.4; charset=utf-8 1.0.0 = 
application\/openmetrics-text; version=1.0.0; charset=utf-8", "sourceType": 
"org.apache.camel.main.MetricsConfigurationProperties", "type": "enum", 
"javaType": "java.lang.String", "defaultValue": "0.0.4", "secret": false, 
"enum": [ "0.0.4", "1.0.0" ] },
diff --git a/core/camel-main/src/main/docs/main.adoc 
b/core/camel-main/src/main/docs/main.adoc
index 30168fb26589..b433a673a690 100644
--- a/core/camel-main/src/main/docs/main.adoc
+++ b/core/camel-main/src/main/docs/main.adoc
@@ -655,7 +655,7 @@ The camel.mdc supports 3 options, which are listed below.
 
 
 === Camel Micrometer Metrics configurations
-The camel.metrics supports 17 options, which are listed below.
+The camel.metrics supports 18 options, which are listed below.
 
 [width="100%",cols="2,5,^1,2",options="header"]
 |===
@@ -674,6 +674,7 @@ The camel.metrics supports 17 options, which are listed 
below.
 | `camel.metrics.logMetricsOnShutdownFormat` | Format used to log metrics when 
application is shutting down. Either json (default) or prometheus format. | 
json | String
 | `camel.metrics.namingStrategy` | Controls the name style to use for metrics. 
Default = uses micrometer naming convention. Legacy = uses the classic naming 
style (camelCase) | default | String
 | `camel.metrics.path` | The path endpoint used to expose the metrics. | 
/observe/metrics | String
+| `camel.metrics.recordLastExchangeTimestamp` | Whether to record the last 
exchange processed timestamp as a Gauge metric 
(camel.exchanges.last.timestamp). Set to false to disable this metric entirely. 
| true | boolean
 | `camel.metrics.routePolicyLevel` | Sets the level of information to capture. 
all = both context and routes. | all | String
 | `camel.metrics.skipCamelInfo` | Skip the evaluation of app.info metric which 
contains runtime provider information (default, false). | false | boolean
 | `camel.metrics.textFormatVersion` | The text-format version to use with 
Prometheus scraping. 0.0.4 = text/plain; version=0.0.4; charset=utf-8 1.0.0 = 
application/openmetrics-text; version=1.0.0; charset=utf-8 | 0.0.4 | String
diff --git 
a/core/camel-main/src/main/java/org/apache/camel/main/MetricsConfigurationProperties.java
 
b/core/camel-main/src/main/java/org/apache/camel/main/MetricsConfigurationProperties.java
index a58bd6ee3f78..556e9d0d4691 100644
--- 
a/core/camel-main/src/main/java/org/apache/camel/main/MetricsConfigurationProperties.java
+++ 
b/core/camel-main/src/main/java/org/apache/camel/main/MetricsConfigurationProperties.java
@@ -41,6 +41,8 @@ public class MetricsConfigurationProperties implements 
BootstrapCloseable {
     @Metadata(defaultValue = "true")
     private boolean baseEndpointURIExchangeEventNotifier = true;
     @Metadata(defaultValue = "true")
+    private boolean recordLastExchangeTimestamp = true;
+    @Metadata(defaultValue = "true")
     private boolean enableRouteEventNotifier = true;
     @Metadata(defaultValue = "false")
     private boolean enableInstrumentedThreadPoolFactory;
@@ -157,6 +159,18 @@ public class MetricsConfigurationProperties implements 
BootstrapCloseable {
         this.baseEndpointURIExchangeEventNotifier = 
baseEndpointURIExchangeEventNotifier;
     }
 
+    public boolean isRecordLastExchangeTimestamp() {
+        return recordLastExchangeTimestamp;
+    }
+
+    /**
+     * Whether to record the last exchange processed timestamp as a Gauge 
metric (camel.exchanges.last.timestamp). Set
+     * to false to disable this metric entirely.
+     */
+    public void setRecordLastExchangeTimestamp(boolean 
recordLastExchangeTimestamp) {
+        this.recordLastExchangeTimestamp = recordLastExchangeTimestamp;
+    }
+
     public boolean isEnableRouteEventNotifier() {
         return enableRouteEventNotifier;
     }
@@ -420,4 +434,13 @@ public class MetricsConfigurationProperties implements 
BootstrapCloseable {
         return this;
     }
 
+    /**
+     * Whether to record the last exchange processed timestamp as a Gauge 
metric (camel.exchanges.last.timestamp). Set
+     * to false to disable this metric entirely.
+     */
+    public MetricsConfigurationProperties 
withRecordLastExchangeTimestamp(boolean recordLastExchangeTimestamp) {
+        this.recordLastExchangeTimestamp = recordLastExchangeTimestamp;
+        return this;
+    }
+
 }

Reply via email to