This is an automated email from the ASF dual-hosted git repository.
pcongiusti 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 634c94ff924f feat(micrometer): add meter logging on shutdown
634c94ff924f is described below
commit 634c94ff924f746edb7e384ab7569181a329a86c
Author: Pasquale Congiusti <[email protected]>
AuthorDate: Mon Mar 2 10:54:54 2026 +0100
feat(micrometer): add meter logging on shutdown
Closes CAMEL-23089
---
.../main/camel-main-configuration-metadata.json | 2 +
.../prometheus/MicrometerPrometheusConfigurer.java | 12 ++
.../prometheus/MicrometerPrometheus.java | 32 ++++
.../AbstractMicrometerEventNotifier.java | 24 ++-
.../micrometer/json/AbstractMicrometerService.java | 101 +++++++++++-
...ractMicrometerServiceConvertMeterToMapTest.java | 175 +++++++++++++++++++++
.../AbstractMicrometerServiceMatchFilterTest.java | 61 +++++++
.../MetricsConfigurationPropertiesConfigurer.java | 14 ++
.../camel-main-configuration-metadata.json | 2 +
core/camel-main/src/main/docs/main.adoc | 4 +-
.../camel/main/MetricsConfigurationProperties.java | 44 ++++++
11 files changed, 466 insertions(+), 5 deletions(-)
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/main/camel-main-configuration-metadata.json
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/main/camel-main-configuration-metadata.json
index cf5cacf653d8..e0e8c6b15390 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/main/camel-main-configuration-metadata.json
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/main/camel-main-configuration-metadata.json
@@ -231,6 +231,8 @@
{ "name": "camel.metrics.enableMessageHistory", "required": false,
"description": "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.",
"sourceType": "org.apache.camel.main.MetricsConfigurationProperties", "type":
"boolean", "javaType": "boolean", "defaultVa [...]
{ "name": "camel.metrics.enableRouteEventNotifier", "required": false,
"description": "Set whether to enable the MicrometerRouteEventNotifier for
capturing metrics on the total number of routes and total number of routes
running.", "sourceType":
"org.apache.camel.main.MetricsConfigurationProperties", "type": "boolean",
"javaType": "boolean", "defaultValue": true, "secret": false },
{ "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.logMetricsOnShutdown", "required": false,
"description": "Log metrics when application is shutting down. (default,
false).", "sourceType": "org.apache.camel.main.MetricsConfigurationProperties",
"type": "boolean", "javaType": "boolean", "defaultValue": false, "secret":
false },
+ { "name": "camel.metrics.logMetricsOnShutdownFilters", "required": false,
"description": "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).",
"sourceType": "org.apache.camel.main.MetricsConfigurationProperties", "type":
"string", "javaType": "java.lang.String", "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.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" ] },
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..f26413cabeb9 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
@@ -40,6 +40,10 @@ public class MicrometerPrometheusConfigurer extends
org.apache.camel.support.com
case "enableRouteEventNotifier":
target.setEnableRouteEventNotifier(property(camelContext, boolean.class,
value)); return true;
case "enableroutepolicy":
case "enableRoutePolicy":
target.setEnableRoutePolicy(property(camelContext, boolean.class, value));
return true;
+ case "logmetricsonshutdown":
+ case "logMetricsOnShutdown":
target.setLogMetricsOnShutdown(property(camelContext, boolean.class, value));
return true;
+ case "logmetricsonshutdownfilters":
+ case "logMetricsOnShutdownFilters":
target.setLogMetricsOnShutdownFilters(property(camelContext,
java.lang.String.class, value)); return true;
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;
@@ -73,6 +77,10 @@ public class MicrometerPrometheusConfigurer extends
org.apache.camel.support.com
case "enableRouteEventNotifier": return boolean.class;
case "enableroutepolicy":
case "enableRoutePolicy": return boolean.class;
+ case "logmetricsonshutdown":
+ case "logMetricsOnShutdown": return boolean.class;
+ case "logmetricsonshutdownfilters":
+ case "logMetricsOnShutdownFilters": return java.lang.String.class;
case "namingstrategy":
case "namingStrategy": return java.lang.String.class;
case "path": return java.lang.String.class;
@@ -107,6 +115,10 @@ public class MicrometerPrometheusConfigurer extends
org.apache.camel.support.com
case "enableRouteEventNotifier": return
target.isEnableRouteEventNotifier();
case "enableroutepolicy":
case "enableRoutePolicy": return target.isEnableRoutePolicy();
+ case "logmetricsonshutdown":
+ case "logMetricsOnShutdown": return target.isLogMetricsOnShutdown();
+ case "logmetricsonshutdownfilters":
+ case "logMetricsOnShutdownFilters": return
target.getLogMetricsOnShutdownFilters();
case "namingstrategy":
case "namingStrategy": return target.getNamingStrategy();
case "path": return target.getPath();
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..e9fbd7147777 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
@@ -104,10 +104,14 @@ public class MicrometerPrometheus extends ServiceSupport
implements CamelMetrics
private boolean clearOnReload = true;
@Metadata(defaultValue = "false")
private boolean skipCamelInfo = false;
+ @Metadata(defaultValue = "false")
+ private boolean logMetricsOnShutdown = false;
@Metadata(defaultValue = "0.0.4", enums = "0.0.4,1.0.0")
private String textFormatVersion = "0.0.4";
@Metadata
private String binders;
+ @Metadata
+ private String logMetricsOnShutdownFilters;
@Metadata(defaultValue = "/observe/metrics")
private String path = "/observe/metrics";
@@ -239,6 +243,17 @@ public class MicrometerPrometheus extends ServiceSupport
implements CamelMetrics
this.skipCamelInfo = skipCamelInfo;
}
+ public boolean isLogMetricsOnShutdown() {
+ return logMetricsOnShutdown;
+ }
+
+ /**
+ * Log metrics when application is shutting down. (default, `false`).
+ */
+ public void setLogMetricsOnShutdown(boolean logMetricsOnShutdown) {
+ this.logMetricsOnShutdown = logMetricsOnShutdown;
+ }
+
public String getTextFormatVersion() {
return textFormatVersion;
}
@@ -280,6 +295,18 @@ public class MicrometerPrometheus extends ServiceSupport
implements CamelMetrics
this.binders = binders;
}
+ public String getLogMetricsOnShutdownFilters() {
+ return 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).
+ */
+ public void setLogMetricsOnShutdownFilters(String
logMetricsOnShutdownFilters) {
+ this.logMetricsOnShutdownFilters = logMetricsOnShutdownFilters;
+ }
+
@Override
protected void doInit() throws Exception {
super.doInit();
@@ -331,6 +358,11 @@ public class MicrometerPrometheus extends ServiceSupport
implements CamelMetrics
if (isEnableExchangeEventNotifier()) {
MicrometerExchangeEventNotifier notifier = new
MicrometerExchangeEventNotifier();
notifier.setSkipCamelInfo(isSkipCamelInfo());
+ notifier.setLogMetricsOnShutdown(isLogMetricsOnShutdown());
+ if (getLogMetricsOnShutdownFilters() != null) {
+ String[] meterFilters =
getLogMetricsOnShutdownFilters().split(",");
+ notifier.setLogMetricsOnShutdownFilters(meterFilters);
+ }
notifier.setBaseEndpointURI(isBaseEndpointURIExchangeEventNotifier());
if ("legacy".equalsIgnoreCase(namingStrategy)) {
notifier.setNamingStrategy(
diff --git
a/components/camel-micrometer/src/main/java/org/apache/camel/component/micrometer/eventnotifier/AbstractMicrometerEventNotifier.java
b/components/camel-micrometer/src/main/java/org/apache/camel/component/micrometer/eventnotifier/AbstractMicrometerEventNotifier.java
index 898279a62fef..1666d7e67f14 100644
---
a/components/camel-micrometer/src/main/java/org/apache/camel/component/micrometer/eventnotifier/AbstractMicrometerEventNotifier.java
+++
b/components/camel-micrometer/src/main/java/org/apache/camel/component/micrometer/eventnotifier/AbstractMicrometerEventNotifier.java
@@ -21,7 +21,6 @@ import java.util.concurrent.TimeUnit;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Tags;
import org.apache.camel.CamelContext;
-import org.apache.camel.CamelContextAware;
import org.apache.camel.RuntimeCamelException;
import org.apache.camel.component.micrometer.MicrometerUtils;
import org.apache.camel.spi.CamelEvent;
@@ -32,8 +31,7 @@ import static
org.apache.camel.component.micrometer.MicrometerConstants.KIND;
import static
org.apache.camel.component.micrometer.MicrometerConstants.KIND_EXCHANGE;
import static
org.apache.camel.component.micrometer.MicrometerConstants.METRICS_REGISTRY_NAME;
-public abstract class AbstractMicrometerEventNotifier<T extends CamelEvent>
extends EventNotifierSupport
- implements CamelContextAware {
+public abstract class AbstractMicrometerEventNotifier<T extends CamelEvent>
extends EventNotifierSupport {
private final Class<T> eventType;
@@ -41,6 +39,8 @@ public abstract class AbstractMicrometerEventNotifier<T
extends CamelEvent> exte
private MeterRegistry meterRegistry;
private boolean prettyPrint;
private boolean skipCamelInfo = false;
+ private boolean logMetricsOnShutdown = false;
+ private String logMetricsOnShutdownFilters[];
private TimeUnit durationUnit = TimeUnit.MILLISECONDS;
protected AbstractMicrometerEventNotifier(Class<T> eventType) {
@@ -81,6 +81,22 @@ public abstract class AbstractMicrometerEventNotifier<T
extends CamelEvent> exte
this.skipCamelInfo = skipCamelInfo;
}
+ public boolean isLogMetricsOnShutdown() {
+ return logMetricsOnShutdown;
+ }
+
+ public void setLogMetricsOnShutdown(boolean logMetricsOnShutdown) {
+ this.logMetricsOnShutdown = logMetricsOnShutdown;
+ }
+
+ public String[] getLogMetricsOnShutdownFilters() {
+ return logMetricsOnShutdownFilters;
+ }
+
+ public void setLogMetricsOnShutdownFilters(String...
logMetricsOnShutdownFilters) {
+ this.logMetricsOnShutdownFilters = logMetricsOnShutdownFilters;
+ }
+
public TimeUnit getDurationUnit() {
return durationUnit;
}
@@ -109,6 +125,8 @@ public abstract class AbstractMicrometerEventNotifier<T
extends CamelEvent> exte
registryService.setMeterRegistry(getMeterRegistry());
registryService.setPrettyPrint(isPrettyPrint());
registryService.setSkipCamelInfo(isSkipCamelInfo());
+
registryService.setLogMetricsOnShutdown(isLogMetricsOnShutdown());
+
registryService.setLogMetricsOnShutdownFilters(getLogMetricsOnShutdownFilters());
registryService.setDurationUnit(getDurationUnit());
registryService.setMatchingTags(Tags.of(KIND, KIND_EXCHANGE));
camelContext.addService(registryService);
diff --git
a/components/camel-micrometer/src/main/java/org/apache/camel/component/micrometer/json/AbstractMicrometerService.java
b/components/camel-micrometer/src/main/java/org/apache/camel/component/micrometer/json/AbstractMicrometerService.java
index 64093f4914b5..82d382bc6a9c 100644
---
a/components/camel-micrometer/src/main/java/org/apache/camel/component/micrometer/json/AbstractMicrometerService.java
+++
b/components/camel-micrometer/src/main/java/org/apache/camel/component/micrometer/json/AbstractMicrometerService.java
@@ -17,31 +17,47 @@
package org.apache.camel.component.micrometer.json;
import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.function.Predicate;
+import java.util.stream.Collectors;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectWriter;
+import io.micrometer.core.instrument.Counter;
+import io.micrometer.core.instrument.DistributionSummary;
+import io.micrometer.core.instrument.FunctionCounter;
+import io.micrometer.core.instrument.FunctionTimer;
+import io.micrometer.core.instrument.Gauge;
+import io.micrometer.core.instrument.Meter;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Tag;
import io.micrometer.core.instrument.Tags;
+import io.micrometer.core.instrument.Timer;
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
import org.apache.camel.CamelContext;
import org.apache.camel.RuntimeCamelException;
import org.apache.camel.component.micrometer.MicrometerConstants;
import org.apache.camel.spi.Registry;
import org.apache.camel.support.service.ServiceSupport;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import static
org.apache.camel.component.micrometer.MicrometerConstants.APP_INFO_METER_NAME;
public class AbstractMicrometerService extends ServiceSupport {
+ private static final Logger LOG =
LoggerFactory.getLogger(AbstractMicrometerService.class);
+
private CamelContext camelContext;
private MeterRegistry meterRegistry;
private boolean prettyPrint = true;
private boolean skipCamelInfo = false;
+ private boolean logMetricsOnShutdown = false;
+ private String logMetricsOnShutdownFilters[];
private TimeUnit durationUnit = TimeUnit.MILLISECONDS;
private Iterable<Tag> matchingTags = Tags.empty();
private Predicate<String> matchingNames;
@@ -80,6 +96,22 @@ public class AbstractMicrometerService extends
ServiceSupport {
this.skipCamelInfo = skipCamelInfo;
}
+ public boolean isLogMetricsOnShutdown() {
+ return logMetricsOnShutdown;
+ }
+
+ public void setLogMetricsOnShutdown(boolean logMetricsOnShutdown) {
+ this.logMetricsOnShutdown = logMetricsOnShutdown;
+ }
+
+ public String[] getLogMetricsOnShutdownFilters() {
+ return logMetricsOnShutdownFilters;
+ }
+
+ public void setLogMetricsOnShutdownFilters(String...
logMetricsOnShutdownFilters) {
+ this.logMetricsOnShutdownFilters = logMetricsOnShutdownFilters;
+ }
+
public TimeUnit getDurationUnit() {
return durationUnit;
}
@@ -154,7 +186,74 @@ public class AbstractMicrometerService extends
ServiceSupport {
@Override
protected void doStop() {
- // noop
+ if (logMetricsOnShutdown) {
+ LOG.info("Micrometer component is stopping, here a list of metrics
collected so far.");
+ // Default: all metrics
+ logMetricsOnShutdown(logMetricsOnShutdownFilters == null ? new
String[] { "*" } : logMetricsOnShutdownFilters);
+ }
+ }
+
+ static boolean matchesFilter(String metricName, String... filters) {
+ for (String filter : filters) {
+ if (filter.contains("*")) {
+ if (metricName.contains(filter.replace("*", ""))) {
+ return true;
+ }
+ } else if (filter.equals(metricName)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ static Map<String, Object> convertMeterToMap(Meter meter) {
+ Map<String, Object> logEntry = new HashMap<>();
+ logEntry.put("name", meter.getId().getName());
+ logEntry.put("tags", meter.getId().getTags().stream()
+ .collect(Collectors.toMap(Tag::getKey, Tag::getValue)));
+
+ if (meter instanceof Gauge g) {
+ logEntry.put("type", "gauge");
+ logEntry.put("value", g.value());
+ } else if (meter instanceof Counter c) {
+ logEntry.put("type", "counter");
+ logEntry.put("value", c.count());
+ } else if (meter instanceof Timer t) {
+ logEntry.put("type", "timer");
+ logEntry.put("totalTimeMs", t.totalTime(TimeUnit.MILLISECONDS));
+ logEntry.put("count", t.count());
+ logEntry.put("maxTimeMs", t.max(TimeUnit.MILLISECONDS));
+ } else if (meter instanceof DistributionSummary ds) {
+ logEntry.put("type", "summary");
+ logEntry.put("total", ds.totalAmount());
+ logEntry.put("count", ds.count());
+ logEntry.put("max", ds.max());
+ } else if (meter instanceof FunctionCounter fc) {
+ logEntry.put("type", "functionCounter");
+ logEntry.put("value", fc.count());
+ } else if (meter instanceof FunctionTimer ft) {
+ logEntry.put("type", "functionTimer");
+ logEntry.put("count", ft.count());
+ logEntry.put("totalTimeMs", ft.totalTime(TimeUnit.MILLISECONDS));
+ logEntry.put("meanMs", ft.mean(TimeUnit.MILLISECONDS));
+ } else {
+ logEntry.put("type", meter.getId().getType().name());
+ }
+
+ return logEntry;
+ }
+
+ void logMetricsOnShutdown(String... filters) {
+ meterRegistry.getMeters().stream()
+ .filter(m ->
AbstractMicrometerService.matchesFilter(m.getId().getName(), filters))
+ .map(AbstractMicrometerService::convertMeterToMap)
+ .forEach(logEntry -> {
+ try {
+ LOG.info(mapper.writeValueAsString(logEntry));
+ } catch (Exception e) {
+ LOG.error("Error logging metric " +
logEntry.get("name"), e);
+ }
+ });
}
// This method does a best effort attempt to recover information about
versioning of the runtime.
diff --git
a/components/camel-micrometer/src/test/java/org/apache/camel/component/micrometer/json/AbstractMicrometerServiceConvertMeterToMapTest.java
b/components/camel-micrometer/src/test/java/org/apache/camel/component/micrometer/json/AbstractMicrometerServiceConvertMeterToMapTest.java
new file mode 100644
index 000000000000..3b0471345db5
--- /dev/null
+++
b/components/camel-micrometer/src/test/java/org/apache/camel/component/micrometer/json/AbstractMicrometerServiceConvertMeterToMapTest.java
@@ -0,0 +1,175 @@
+/*
+ * 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.component.micrometer.json;
+
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+import io.micrometer.core.instrument.*;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+class ConvertMeterToMapTest {
+
+ @Test
+ void shouldConvertCounter() {
+ Counter counter = mock(Counter.class);
+ Meter.Id id = new Meter.Id("my.counter", Tags.empty(), null, null,
Meter.Type.COUNTER);
+ when(counter.getId()).thenReturn(id);
+ when(counter.count()).thenReturn(5.0);
+
+ Map<String, Object> result =
AbstractMicrometerService.convertMeterToMap(counter);
+
+ assertEquals("my.counter", result.get("name"));
+ assertEquals("counter", result.get("type"));
+ assertEquals(5.0, result.get("value"));
+ }
+
+ @Test
+ void shouldConvertGauge() {
+ Gauge gauge = mock(Gauge.class);
+ Meter.Id id = new Meter.Id("my.gauge", Tags.empty(), null, null,
Meter.Type.GAUGE);
+ when(gauge.getId()).thenReturn(id);
+ when(gauge.value()).thenReturn(42.0);
+
+ Map<String, Object> result =
AbstractMicrometerService.convertMeterToMap(gauge);
+
+ assertEquals("my.gauge", result.get("name"));
+ assertEquals("gauge", result.get("type"));
+ assertEquals(42.0, result.get("value"));
+ }
+
+ @Test
+ void shouldConvertTimer() {
+ Timer timer = mock(Timer.class);
+ Meter.Id id = new Meter.Id("my.timer", Tags.empty(), null, null,
Meter.Type.TIMER);
+ when(timer.getId()).thenReturn(id);
+ when(timer.totalTime(TimeUnit.MILLISECONDS)).thenReturn(1000.0);
+ when(timer.count()).thenReturn(10L);
+ when(timer.max(TimeUnit.MILLISECONDS)).thenReturn(300.0);
+
+ Map<String, Object> result =
AbstractMicrometerService.convertMeterToMap(timer);
+
+ assertEquals("my.timer", result.get("name"));
+ assertEquals("timer", result.get("type"));
+ assertEquals(1000.0, result.get("totalTimeMs"));
+ assertEquals(10L, result.get("count"));
+ assertEquals(300.0, result.get("maxTimeMs"));
+ }
+
+ @Test
+ void shouldConvertDistributionSummary() {
+ DistributionSummary ds = mock(DistributionSummary.class);
+ Meter.Id id = new Meter.Id("my.summary", Tags.empty(), null, null,
Meter.Type.DISTRIBUTION_SUMMARY);
+ when(ds.getId()).thenReturn(id);
+ when(ds.totalAmount()).thenReturn(500.0);
+ when(ds.count()).thenReturn(5L);
+ when(ds.max()).thenReturn(200.0);
+
+ Map<String, Object> result =
AbstractMicrometerService.convertMeterToMap(ds);
+
+ assertEquals("my.summary", result.get("name"));
+ assertEquals("summary", result.get("type"));
+ assertEquals(500.0, result.get("total"));
+ assertEquals(5L, result.get("count"));
+ assertEquals(200.0, result.get("max"));
+ }
+
+ @Test
+ void shouldConvertFunctionCounter() {
+ FunctionCounter fc = mock(FunctionCounter.class);
+ Meter.Id id = new Meter.Id("my.fc", Tags.empty(), null, null,
Meter.Type.COUNTER);
+ when(fc.getId()).thenReturn(id);
+ when(fc.count()).thenReturn(3.0);
+
+ Map<String, Object> result =
AbstractMicrometerService.convertMeterToMap(fc);
+
+ assertEquals("my.fc", result.get("name"));
+ assertEquals("functionCounter", result.get("type"));
+ assertEquals(3.0, result.get("value"));
+ }
+
+ @Test
+ void shouldConvertFunctionTimer() {
+ FunctionTimer ft = mock(FunctionTimer.class);
+ Meter.Id id = new Meter.Id("my.ft", Tags.empty(), null, null,
Meter.Type.TIMER);
+ when(ft.getId()).thenReturn(id);
+ when(ft.count()).thenReturn(4.0);
+ when(ft.totalTime(TimeUnit.MILLISECONDS)).thenReturn(1000.0);
+ when(ft.mean(TimeUnit.MILLISECONDS)).thenReturn(250.0);
+
+ Map<String, Object> result =
AbstractMicrometerService.convertMeterToMap(ft);
+
+ assertEquals("my.ft", result.get("name"));
+ assertEquals("functionTimer", result.get("type"));
+ assertEquals(4.0, result.get("count"));
+ assertEquals(1000.0, result.get("totalTimeMs"));
+ assertEquals(250.0, result.get("meanMs"));
+ }
+
+ @Test
+ void shouldFallbackForUnknownMeterType() {
+ Meter meter = mock(Meter.class);
+ Meter.Id id = new Meter.Id("unknown.meter", Tags.empty(), null, null,
Meter.Type.OTHER);
+ when(meter.getId()).thenReturn(id);
+
+ Map<String, Object> result =
AbstractMicrometerService.convertMeterToMap(meter);
+
+ assertEquals("unknown.meter", result.get("name"));
+ assertEquals("OTHER", result.get("type"));
+ }
+
+ @Test
+ void shouldConvertGaugeWithNaNAndTags() {
+ Gauge gauge = mock(Gauge.class);
+
+ // Meter ID with tags
+ Meter.Id id = new Meter.Id(
+ "app.info",
+ Tags.of(
+ Tag.of("camel.runtime.provider", "Main"),
+ Tag.of("camel.runtime.version", "4.19.0-SNAPSHOT"),
+ Tag.of("camel.context", "camel-1"),
+ Tag.of("camel.version", "4.19.0-SNAPSHOT")),
+ null,
+ null,
+ Meter.Type.GAUGE);
+
+ when(gauge.getId()).thenReturn(id);
+ when(gauge.value()).thenReturn(Double.NaN);
+
+ Map<String, Object> result =
AbstractMicrometerService.convertMeterToMap(gauge);
+
+ // Assertions
+ assertEquals("app.info", result.get("name"));
+ assertEquals("gauge", result.get("type"));
+ assertTrue(result.get("value") instanceof Double);
+
+ // Check tags
+ @SuppressWarnings("unchecked")
+ Map<String, String> tags = (Map<String, String>) result.get("tags");
+ assertEquals(4, tags.size());
+ assertEquals("Main", tags.get("camel.runtime.provider"));
+ assertEquals("4.19.0-SNAPSHOT", tags.get("camel.runtime.version"));
+ assertEquals("camel-1", tags.get("camel.context"));
+ assertEquals("4.19.0-SNAPSHOT", tags.get("camel.version"));
+ }
+
+}
diff --git
a/components/camel-micrometer/src/test/java/org/apache/camel/component/micrometer/json/AbstractMicrometerServiceMatchFilterTest.java
b/components/camel-micrometer/src/test/java/org/apache/camel/component/micrometer/json/AbstractMicrometerServiceMatchFilterTest.java
new file mode 100644
index 000000000000..f391bd3cf8c9
--- /dev/null
+++
b/components/camel-micrometer/src/test/java/org/apache/camel/component/micrometer/json/AbstractMicrometerServiceMatchFilterTest.java
@@ -0,0 +1,61 @@
+/*
+ * 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.component.micrometer.json;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class AbstractMicrometerServiceMatchFilterTest {
+
+ @Test
+ void exactMatchReturnsTrue() {
+ assertTrue(AbstractMicrometerService.matchesFilter("app.info",
"app.info"));
+ }
+
+ @Test
+ void exactMismatchReturnsFalse() {
+ assertFalse(AbstractMicrometerService.matchesFilter("app.info",
"other.metric"));
+ }
+
+ @Test
+ void wildcardMatchReturnsTrue() {
+
assertTrue(AbstractMicrometerService.matchesFilter("camel.exchanges.completed",
"camel.exchanges.*"));
+
assertTrue(AbstractMicrometerService.matchesFilter("camel.exchanges.failed",
"camel.exchanges.*"));
+ }
+
+ @Test
+ void wildcardMismatchReturnsFalse() {
+
assertFalse(AbstractMicrometerService.matchesFilter("camel.routes.completed",
"camel.exchanges.*"));
+ }
+
+ @Test
+ void multipleFiltersAnyMatchReturnsTrue() {
+ assertTrue(AbstractMicrometerService.matchesFilter("app.info",
"foo.*", "app.info"));
+
assertTrue(AbstractMicrometerService.matchesFilter("camel.exchanges.completed",
"foo.*", "camel.exchanges.*"));
+ }
+
+ @Test
+ void multipleFiltersNoMatchReturnsFalse() {
+ assertFalse(AbstractMicrometerService.matchesFilter("random.metric",
"foo.*", "app.info"));
+ }
+
+ @Test
+ void emptyFiltersReturnsFalse() {
+ assertFalse(AbstractMicrometerService.matchesFilter("anything"));
+ }
+}
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..dafd87e4da31 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
@@ -31,6 +31,8 @@ public class MetricsConfigurationPropertiesConfigurer extends
org.apache.camel.s
map.put("EnableRouteEventNotifier", boolean.class);
map.put("EnableRoutePolicy", boolean.class);
map.put("Enabled", boolean.class);
+ map.put("LogMetricsOnShutdown", boolean.class);
+ map.put("LogMetricsOnShutdownFilters", java.lang.String.class);
map.put("NamingStrategy", java.lang.String.class);
map.put("Path", java.lang.String.class);
map.put("RoutePolicyLevel", java.lang.String.class);
@@ -59,6 +61,10 @@ public class MetricsConfigurationPropertiesConfigurer
extends org.apache.camel.s
case "enableroutepolicy":
case "enableRoutePolicy":
target.setEnableRoutePolicy(property(camelContext, boolean.class, value));
return true;
case "enabled": target.setEnabled(property(camelContext,
boolean.class, value)); return true;
+ case "logmetricsonshutdown":
+ case "logMetricsOnShutdown":
target.setLogMetricsOnShutdown(property(camelContext, boolean.class, value));
return true;
+ case "logmetricsonshutdownfilters":
+ case "logMetricsOnShutdownFilters":
target.setLogMetricsOnShutdownFilters(property(camelContext,
java.lang.String.class, value)); return true;
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;
@@ -96,6 +102,10 @@ public class MetricsConfigurationPropertiesConfigurer
extends org.apache.camel.s
case "enableroutepolicy":
case "enableRoutePolicy": return boolean.class;
case "enabled": return boolean.class;
+ case "logmetricsonshutdown":
+ case "logMetricsOnShutdown": return boolean.class;
+ case "logmetricsonshutdownfilters":
+ case "logMetricsOnShutdownFilters": return java.lang.String.class;
case "namingstrategy":
case "namingStrategy": return java.lang.String.class;
case "path": return java.lang.String.class;
@@ -129,6 +139,10 @@ public class MetricsConfigurationPropertiesConfigurer
extends org.apache.camel.s
case "enableroutepolicy":
case "enableRoutePolicy": return target.isEnableRoutePolicy();
case "enabled": return target.isEnabled();
+ case "logmetricsonshutdown":
+ case "logMetricsOnShutdown": return target.isLogMetricsOnShutdown();
+ case "logmetricsonshutdownfilters":
+ case "logMetricsOnShutdownFilters": return
target.getLogMetricsOnShutdownFilters();
case "namingstrategy":
case "namingStrategy": return target.getNamingStrategy();
case "path": return target.getPath();
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 cf5cacf653d8..e0e8c6b15390 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
@@ -231,6 +231,8 @@
{ "name": "camel.metrics.enableMessageHistory", "required": false,
"description": "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.",
"sourceType": "org.apache.camel.main.MetricsConfigurationProperties", "type":
"boolean", "javaType": "boolean", "defaultVa [...]
{ "name": "camel.metrics.enableRouteEventNotifier", "required": false,
"description": "Set whether to enable the MicrometerRouteEventNotifier for
capturing metrics on the total number of routes and total number of routes
running.", "sourceType":
"org.apache.camel.main.MetricsConfigurationProperties", "type": "boolean",
"javaType": "boolean", "defaultValue": true, "secret": false },
{ "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.logMetricsOnShutdown", "required": false,
"description": "Log metrics when application is shutting down. (default,
false).", "sourceType": "org.apache.camel.main.MetricsConfigurationProperties",
"type": "boolean", "javaType": "boolean", "defaultValue": false, "secret":
false },
+ { "name": "camel.metrics.logMetricsOnShutdownFilters", "required": false,
"description": "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).",
"sourceType": "org.apache.camel.main.MetricsConfigurationProperties", "type":
"string", "javaType": "java.lang.String", "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.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" ] },
diff --git a/core/camel-main/src/main/docs/main.adoc
b/core/camel-main/src/main/docs/main.adoc
index 801ea9d02b37..179bc82bb8e3 100644
--- a/core/camel-main/src/main/docs/main.adoc
+++ b/core/camel-main/src/main/docs/main.adoc
@@ -597,7 +597,7 @@ 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 16 options, which are listed below.
[width="100%",cols="2,5,^1,2",options="header"]
|===
@@ -611,6 +611,8 @@ The camel.metrics supports 14 options, which are listed
below.
| *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.logMetricsOn{zwsp}Shutdown* | Log metrics when application is
shutting down. (default, false). | false | boolean
+| *camel.metrics.logMetricsOn{zwsp}ShutdownFilters* | 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.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
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..8f1c20b599b4 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
@@ -48,10 +48,14 @@ public class MetricsConfigurationProperties implements
BootstrapCloseable {
private boolean clearOnReload = true;
@Metadata(defaultValue = "false")
private boolean skipCamelInfo = false;
+ @Metadata(defaultValue = "false")
+ private boolean logMetricsOnShutdown = false;
@Metadata(defaultValue = "0.0.4", enums = "0.0.4,1.0.0")
private String textFormatVersion = "0.0.4";
@Metadata
private String binders;
+ @Metadata
+ private String logMetricsOnShutdownFilters;
@Metadata(defaultValue = "/observe/metrics")
private String path = "/observe/metrics";
@@ -197,6 +201,17 @@ public class MetricsConfigurationProperties implements
BootstrapCloseable {
this.skipCamelInfo = skipCamelInfo;
}
+ public boolean isLogMetricsOnShutdown() {
+ return logMetricsOnShutdown;
+ }
+
+ /**
+ * Log metrics when application is shutting down. (default, `false`).
+ */
+ public void setLogMetricsOnShutdown(boolean logMetricsOnShutdown) {
+ this.logMetricsOnShutdown = logMetricsOnShutdown;
+ }
+
public String getTextFormatVersion() {
return textFormatVersion;
}
@@ -227,6 +242,18 @@ public class MetricsConfigurationProperties implements
BootstrapCloseable {
this.binders = binders;
}
+ public String getLogMetricsOnShutdownFilters() {
+ return 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).
+ */
+ public void setLogMetricsOnShutdownFilters(String
logMetricsOnShutdownFilters) {
+ this.logMetricsOnShutdownFilters = logMetricsOnShutdownFilters;
+ }
+
public String getPath() {
return path;
}
@@ -331,6 +358,14 @@ public class MetricsConfigurationProperties implements
BootstrapCloseable {
return this;
}
+ /**
+ * Log metrics when application is shutting down. (default, `false`).
+ */
+ public MetricsConfigurationProperties withLogMetricsOnShutdown(boolean
logMetricsOnShutdown) {
+ this.logMetricsOnShutdown = logMetricsOnShutdown;
+ return this;
+ }
+
/**
* The text-format version to use with Prometheus scraping.
*
@@ -355,4 +390,13 @@ public class MetricsConfigurationProperties implements
BootstrapCloseable {
return this;
}
+ /**
+ * 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).
+ */
+ public MetricsConfigurationProperties
withLogMetricsOnShutdownFilters(String logMetricsOnShutdownFilters) {
+ this.logMetricsOnShutdownFilters = logMetricsOnShutdownFilters;
+ return this;
+ }
+
}