This is an automated email from the ASF dual-hosted git repository. squakez pushed a commit to branch camel-4.18.x in repository https://gitbox.apache.org/repos/asf/camel.git
commit 1390c4bc60575c8dd3e4f4d6f732ea67f948cfc9 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 | 34 ++++++++++++---------- .../camel/main/MetricsConfigurationProperties.java | 23 +++++++++++++++ 8 files changed, 88 insertions(+), 19 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 2b84fb571b78..f2e0a741d8ac 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 @@ -43,6 +43,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": @@ -76,6 +78,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": @@ -110,6 +114,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 602ad0bfbed2..312d0e16188c 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 @@ -97,6 +97,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; @@ -193,6 +195,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; } @@ -332,6 +346,7 @@ public class MicrometerPrometheus extends ServiceSupport implements CamelMetrics MicrometerExchangeEventNotifier notifier = new MicrometerExchangeEventNotifier(); notifier.setSkipCamelInfo(isSkipCamelInfo()); 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 7dcc2698b04b..76e347f17a7c 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 @@ -111,12 +111,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 9229784aee4f..735014aea1c5 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 @@ -33,6 +33,7 @@ public class MetricsConfigurationPropertiesConfigurer extends org.apache.camel.s map.put("Enabled", boolean.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); @@ -62,6 +63,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": @@ -99,6 +102,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": @@ -132,6 +137,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 eda37efa6916..ee37ed2f9a5f 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 @@ -236,6 +236,7 @@ { "name": "camel.metrics.enableRoutePolicy", "required": false, "description": "Set whether to enable the MicrometerRoutePolicyFactory for capturing metrics on route processing times.", "sourceType": "org.apache.camel.main.MetricsConfigurationProperties", "type": "boolean", "javaType": "boolean", "defaultValue": true, "secret": false }, { "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 428838b94a77..ae373e1db846 100644 --- a/core/camel-main/src/main/docs/main.adoc +++ b/core/camel-main/src/main/docs/main.adoc @@ -600,25 +600,29 @@ The camel.mdc supports 3 options, which are listed below. === Camel Micrometer Metrics configurations -The camel.metrics supports 14 options, which are listed below. +The camel.metrics supports 18 options, which are listed below. [width="100%",cols="2,5,^1,2",options="header"] |=== | Name | Description | Default | Type -| *camel.metrics.baseEndpointURI{zwsp}ExchangeEventNotifier* | Whether to use static or dynamic values for Endpoint Name tags in captured metrics. By default, static values are used. When using dynamic tags, then a dynamic to (toD) can compute many different endpoint URIs that, can lead to many tags as the URI is dynamic, so use this with care if setting this option to false. | true | boolean -| *camel.metrics.binders* | Additional Micrometer binders to include such as jvm-memory, processor, jvm-thread, and so forth. Multiple binders can be separated by comma. The following binders currently is available from Micrometer: class-loader, commons-object-pool2, file-descriptor, hystrix-metrics-binder, jvm-compilation, jvm-gc, jvm-heap-pressure, jvm-info, jvm-memory, jvm-thread, log4j2, logback, processor, uptime | | String -| *camel.metrics.clearOnReload* | Clear the captured metrics data when Camel is reloading routes such as when using Camel JBang. | true | boolean -| *camel.metrics.enabled* | To enable Micrometer metrics. | false | boolean -| *camel.metrics.enableExchange{zwsp}EventNotifier* | Set whether to enable the MicrometerExchangeEventNotifier for capturing metrics on exchange processing times. | true | boolean -| *camel.metrics.enable{zwsp}InstrumentedThreadPoolFactory* | Set whether to gather performance information about Camel Thread Pools by injecting an InstrumentedThreadPoolFactory. | false | boolean -| *camel.metrics.enableMessage{zwsp}History* | Set whether to enable the MicrometerMessageHistoryFactory for capturing metrics on individual route node processing times. Depending on the number of configured route nodes, there is the potential to create a large volume of metrics. Therefore, this option is disabled by default. | false | boolean -| *camel.metrics.enableRouteEvent{zwsp}Notifier* | Set whether to enable the MicrometerRouteEventNotifier for capturing metrics on the total number of routes and total number of routes running. | true | boolean -| *camel.metrics.enableRoute{zwsp}Policy* | Set whether to enable the MicrometerRoutePolicyFactory for capturing metrics on route processing times. | true | boolean -| *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.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.textFormat{zwsp}Version* | 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 +| `camel.metrics.baseEndpointURIExchangeEventNotifier` | Whether to use static or dynamic values for Endpoint Name tags in captured metrics. By default, static values are used. When using dynamic tags, then a dynamic to (toD) can compute many different endpoint URIs that, can lead to many tags as the URI is dynamic, so use this with care if setting this option to false. | true | boolean +| `camel.metrics.binders` | Additional Micrometer binders to include such as jvm-memory, processor, jvm-thread, and so forth. Multiple binders can be separated by comma. The following binders currently is available from Micrometer: class-loader, commons-object-pool2, file-descriptor, hystrix-metrics-binder, jvm-compilation, jvm-gc, jvm-heap-pressure, jvm-info, jvm-memory, jvm-thread, log4j2, logback, processor, uptime | | String +| `camel.metrics.clearOnReload` | Clear the captured metrics data when Camel is reloading routes such as when using Camel CLI. | true | boolean +| `camel.metrics.enabled` | To enable Micrometer metrics. | false | boolean +| `camel.metrics.enableExchangeEventNotifier` | Set whether to enable the MicrometerExchangeEventNotifier for capturing metrics on exchange processing times. | true | boolean +| `camel.metrics.enableInstrumentedThreadPoolFactory` | Set whether to gather performance information about Camel Thread Pools by injecting an InstrumentedThreadPoolFactory. | false | boolean +| `camel.metrics.enableMessageHistory` | Set whether to enable the MicrometerMessageHistoryFactory for capturing metrics on individual route node processing times. Depending on the number of configured route nodes, there is the potential to create a large volume of metrics. Therefore, this option is disabled by default. | false | boolean +| `camel.metrics.enableRouteEventNotifier` | Set whether to enable the MicrometerRouteEventNotifier for capturing metrics on the total number of routes and total number of routes running. | true | boolean +| `camel.metrics.enableRoutePolicy` | Set whether to enable the MicrometerRoutePolicyFactory for capturing metrics on route processing times. | true | boolean +| `camel.metrics.logMetricsOnShutdown` | Log metrics when application is shutting down. (default, false). | false | boolean +| `camel.metrics.logMetricsOnShutdownFilters` | List of metrics (comma separated) to log when application is shutting down. You can use character to log any metrics containing the wildcard, for example camel.exchanges. (default to all metrics available). | | String +| `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 b4241cec6764..50af6c9fbbab 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; @@ -151,6 +153,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; } @@ -355,4 +369,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; + } + }
