Skip to content

Commit 634c94f

Browse files
committed
feat(micrometer): add meter logging on shutdown
Closes CAMEL-23089
1 parent 7f9f16f commit 634c94f

11 files changed

Lines changed: 466 additions & 5 deletions

File tree

catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/main/camel-main-configuration-metadata.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,8 @@
231231
{ "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", "defaultValue": false, "secret": false },
232232
{ "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 },
233233
{ "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 },
234+
{ "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 },
235+
{ "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 },
234236
{ "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" ] },
235237
{ "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 },
236238
{ "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" ] },

components/camel-micrometer-prometheus/src/generated/java/org/apache/camel/component/micrometer/prometheus/MicrometerPrometheusConfigurer.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@ public boolean configure(CamelContext camelContext, Object obj, String name, Obj
4040
case "enableRouteEventNotifier": target.setEnableRouteEventNotifier(property(camelContext, boolean.class, value)); return true;
4141
case "enableroutepolicy":
4242
case "enableRoutePolicy": target.setEnableRoutePolicy(property(camelContext, boolean.class, value)); return true;
43+
case "logmetricsonshutdown":
44+
case "logMetricsOnShutdown": target.setLogMetricsOnShutdown(property(camelContext, boolean.class, value)); return true;
45+
case "logmetricsonshutdownfilters":
46+
case "logMetricsOnShutdownFilters": target.setLogMetricsOnShutdownFilters(property(camelContext, java.lang.String.class, value)); return true;
4347
case "namingstrategy":
4448
case "namingStrategy": target.setNamingStrategy(property(camelContext, java.lang.String.class, value)); return true;
4549
case "path": target.setPath(property(camelContext, java.lang.String.class, value)); return true;
@@ -73,6 +77,10 @@ public Class<?> getOptionType(String name, boolean ignoreCase) {
7377
case "enableRouteEventNotifier": return boolean.class;
7478
case "enableroutepolicy":
7579
case "enableRoutePolicy": return boolean.class;
80+
case "logmetricsonshutdown":
81+
case "logMetricsOnShutdown": return boolean.class;
82+
case "logmetricsonshutdownfilters":
83+
case "logMetricsOnShutdownFilters": return java.lang.String.class;
7684
case "namingstrategy":
7785
case "namingStrategy": return java.lang.String.class;
7886
case "path": return java.lang.String.class;
@@ -107,6 +115,10 @@ public Object getOptionValue(Object obj, String name, boolean ignoreCase) {
107115
case "enableRouteEventNotifier": return target.isEnableRouteEventNotifier();
108116
case "enableroutepolicy":
109117
case "enableRoutePolicy": return target.isEnableRoutePolicy();
118+
case "logmetricsonshutdown":
119+
case "logMetricsOnShutdown": return target.isLogMetricsOnShutdown();
120+
case "logmetricsonshutdownfilters":
121+
case "logMetricsOnShutdownFilters": return target.getLogMetricsOnShutdownFilters();
110122
case "namingstrategy":
111123
case "namingStrategy": return target.getNamingStrategy();
112124
case "path": return target.getPath();

components/camel-micrometer-prometheus/src/main/java/org/apache/camel/component/micrometer/prometheus/MicrometerPrometheus.java

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,10 +104,14 @@ public class MicrometerPrometheus extends ServiceSupport implements CamelMetrics
104104
private boolean clearOnReload = true;
105105
@Metadata(defaultValue = "false")
106106
private boolean skipCamelInfo = false;
107+
@Metadata(defaultValue = "false")
108+
private boolean logMetricsOnShutdown = false;
107109
@Metadata(defaultValue = "0.0.4", enums = "0.0.4,1.0.0")
108110
private String textFormatVersion = "0.0.4";
109111
@Metadata
110112
private String binders;
113+
@Metadata
114+
private String logMetricsOnShutdownFilters;
111115
@Metadata(defaultValue = "/observe/metrics")
112116
private String path = "/observe/metrics";
113117

@@ -239,6 +243,17 @@ public void setSkipCamelInfo(boolean skipCamelInfo) {
239243
this.skipCamelInfo = skipCamelInfo;
240244
}
241245

246+
public boolean isLogMetricsOnShutdown() {
247+
return logMetricsOnShutdown;
248+
}
249+
250+
/**
251+
* Log metrics when application is shutting down. (default, `false`).
252+
*/
253+
public void setLogMetricsOnShutdown(boolean logMetricsOnShutdown) {
254+
this.logMetricsOnShutdown = logMetricsOnShutdown;
255+
}
256+
242257
public String getTextFormatVersion() {
243258
return textFormatVersion;
244259
}
@@ -280,6 +295,18 @@ public void setBinders(String binders) {
280295
this.binders = binders;
281296
}
282297

298+
public String getLogMetricsOnShutdownFilters() {
299+
return logMetricsOnShutdownFilters;
300+
}
301+
302+
/**
303+
* List of metrics (comma separated) to log when application is shutting down. You can use `*` character to log any
304+
* metrics containing the wildcard, for example `camel.exchanges.*` (default to all metrics available).
305+
*/
306+
public void setLogMetricsOnShutdownFilters(String logMetricsOnShutdownFilters) {
307+
this.logMetricsOnShutdownFilters = logMetricsOnShutdownFilters;
308+
}
309+
283310
@Override
284311
protected void doInit() throws Exception {
285312
super.doInit();
@@ -331,6 +358,11 @@ protected void doInit() throws Exception {
331358
if (isEnableExchangeEventNotifier()) {
332359
MicrometerExchangeEventNotifier notifier = new MicrometerExchangeEventNotifier();
333360
notifier.setSkipCamelInfo(isSkipCamelInfo());
361+
notifier.setLogMetricsOnShutdown(isLogMetricsOnShutdown());
362+
if (getLogMetricsOnShutdownFilters() != null) {
363+
String[] meterFilters = getLogMetricsOnShutdownFilters().split(",");
364+
notifier.setLogMetricsOnShutdownFilters(meterFilters);
365+
}
334366
notifier.setBaseEndpointURI(isBaseEndpointURIExchangeEventNotifier());
335367
if ("legacy".equalsIgnoreCase(namingStrategy)) {
336368
notifier.setNamingStrategy(

components/camel-micrometer/src/main/java/org/apache/camel/component/micrometer/eventnotifier/AbstractMicrometerEventNotifier.java

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121
import io.micrometer.core.instrument.MeterRegistry;
2222
import io.micrometer.core.instrument.Tags;
2323
import org.apache.camel.CamelContext;
24-
import org.apache.camel.CamelContextAware;
2524
import org.apache.camel.RuntimeCamelException;
2625
import org.apache.camel.component.micrometer.MicrometerUtils;
2726
import org.apache.camel.spi.CamelEvent;
@@ -32,15 +31,16 @@
3231
import static org.apache.camel.component.micrometer.MicrometerConstants.KIND_EXCHANGE;
3332
import static org.apache.camel.component.micrometer.MicrometerConstants.METRICS_REGISTRY_NAME;
3433

35-
public abstract class AbstractMicrometerEventNotifier<T extends CamelEvent> extends EventNotifierSupport
36-
implements CamelContextAware {
34+
public abstract class AbstractMicrometerEventNotifier<T extends CamelEvent> extends EventNotifierSupport {
3735

3836
private final Class<T> eventType;
3937

4038
private CamelContext camelContext;
4139
private MeterRegistry meterRegistry;
4240
private boolean prettyPrint;
4341
private boolean skipCamelInfo = false;
42+
private boolean logMetricsOnShutdown = false;
43+
private String logMetricsOnShutdownFilters[];
4444
private TimeUnit durationUnit = TimeUnit.MILLISECONDS;
4545

4646
protected AbstractMicrometerEventNotifier(Class<T> eventType) {
@@ -81,6 +81,22 @@ public void setSkipCamelInfo(boolean skipCamelInfo) {
8181
this.skipCamelInfo = skipCamelInfo;
8282
}
8383

84+
public boolean isLogMetricsOnShutdown() {
85+
return logMetricsOnShutdown;
86+
}
87+
88+
public void setLogMetricsOnShutdown(boolean logMetricsOnShutdown) {
89+
this.logMetricsOnShutdown = logMetricsOnShutdown;
90+
}
91+
92+
public String[] getLogMetricsOnShutdownFilters() {
93+
return logMetricsOnShutdownFilters;
94+
}
95+
96+
public void setLogMetricsOnShutdownFilters(String... logMetricsOnShutdownFilters) {
97+
this.logMetricsOnShutdownFilters = logMetricsOnShutdownFilters;
98+
}
99+
84100
public TimeUnit getDurationUnit() {
85101
return durationUnit;
86102
}
@@ -109,6 +125,8 @@ protected void doStart() throws Exception {
109125
registryService.setMeterRegistry(getMeterRegistry());
110126
registryService.setPrettyPrint(isPrettyPrint());
111127
registryService.setSkipCamelInfo(isSkipCamelInfo());
128+
registryService.setLogMetricsOnShutdown(isLogMetricsOnShutdown());
129+
registryService.setLogMetricsOnShutdownFilters(getLogMetricsOnShutdownFilters());
112130
registryService.setDurationUnit(getDurationUnit());
113131
registryService.setMatchingTags(Tags.of(KIND, KIND_EXCHANGE));
114132
camelContext.addService(registryService);

components/camel-micrometer/src/main/java/org/apache/camel/component/micrometer/json/AbstractMicrometerService.java

Lines changed: 100 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,31 +17,47 @@
1717
package org.apache.camel.component.micrometer.json;
1818

1919
import java.util.ArrayList;
20+
import java.util.HashMap;
21+
import java.util.Map;
2022
import java.util.Optional;
2123
import java.util.concurrent.TimeUnit;
2224
import java.util.function.Predicate;
25+
import java.util.stream.Collectors;
2326

2427
import com.fasterxml.jackson.core.JsonProcessingException;
2528
import com.fasterxml.jackson.databind.ObjectMapper;
2629
import com.fasterxml.jackson.databind.ObjectWriter;
30+
import io.micrometer.core.instrument.Counter;
31+
import io.micrometer.core.instrument.DistributionSummary;
32+
import io.micrometer.core.instrument.FunctionCounter;
33+
import io.micrometer.core.instrument.FunctionTimer;
34+
import io.micrometer.core.instrument.Gauge;
35+
import io.micrometer.core.instrument.Meter;
2736
import io.micrometer.core.instrument.MeterRegistry;
2837
import io.micrometer.core.instrument.Tag;
2938
import io.micrometer.core.instrument.Tags;
39+
import io.micrometer.core.instrument.Timer;
3040
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
3141
import org.apache.camel.CamelContext;
3242
import org.apache.camel.RuntimeCamelException;
3343
import org.apache.camel.component.micrometer.MicrometerConstants;
3444
import org.apache.camel.spi.Registry;
3545
import org.apache.camel.support.service.ServiceSupport;
46+
import org.slf4j.Logger;
47+
import org.slf4j.LoggerFactory;
3648

3749
import static org.apache.camel.component.micrometer.MicrometerConstants.APP_INFO_METER_NAME;
3850

3951
public class AbstractMicrometerService extends ServiceSupport {
4052

53+
private static final Logger LOG = LoggerFactory.getLogger(AbstractMicrometerService.class);
54+
4155
private CamelContext camelContext;
4256
private MeterRegistry meterRegistry;
4357
private boolean prettyPrint = true;
4458
private boolean skipCamelInfo = false;
59+
private boolean logMetricsOnShutdown = false;
60+
private String logMetricsOnShutdownFilters[];
4561
private TimeUnit durationUnit = TimeUnit.MILLISECONDS;
4662
private Iterable<Tag> matchingTags = Tags.empty();
4763
private Predicate<String> matchingNames;
@@ -80,6 +96,22 @@ public void setSkipCamelInfo(boolean skipCamelInfo) {
8096
this.skipCamelInfo = skipCamelInfo;
8197
}
8298

99+
public boolean isLogMetricsOnShutdown() {
100+
return logMetricsOnShutdown;
101+
}
102+
103+
public void setLogMetricsOnShutdown(boolean logMetricsOnShutdown) {
104+
this.logMetricsOnShutdown = logMetricsOnShutdown;
105+
}
106+
107+
public String[] getLogMetricsOnShutdownFilters() {
108+
return logMetricsOnShutdownFilters;
109+
}
110+
111+
public void setLogMetricsOnShutdownFilters(String... logMetricsOnShutdownFilters) {
112+
this.logMetricsOnShutdownFilters = logMetricsOnShutdownFilters;
113+
}
114+
83115
public TimeUnit getDurationUnit() {
84116
return durationUnit;
85117
}
@@ -154,7 +186,74 @@ protected void doStart() {
154186

155187
@Override
156188
protected void doStop() {
157-
// noop
189+
if (logMetricsOnShutdown) {
190+
LOG.info("Micrometer component is stopping, here a list of metrics collected so far.");
191+
// Default: all metrics
192+
logMetricsOnShutdown(logMetricsOnShutdownFilters == null ? new String[] { "*" } : logMetricsOnShutdownFilters);
193+
}
194+
}
195+
196+
static boolean matchesFilter(String metricName, String... filters) {
197+
for (String filter : filters) {
198+
if (filter.contains("*")) {
199+
if (metricName.contains(filter.replace("*", ""))) {
200+
return true;
201+
}
202+
} else if (filter.equals(metricName)) {
203+
return true;
204+
}
205+
}
206+
return false;
207+
}
208+
209+
static Map<String, Object> convertMeterToMap(Meter meter) {
210+
Map<String, Object> logEntry = new HashMap<>();
211+
logEntry.put("name", meter.getId().getName());
212+
logEntry.put("tags", meter.getId().getTags().stream()
213+
.collect(Collectors.toMap(Tag::getKey, Tag::getValue)));
214+
215+
if (meter instanceof Gauge g) {
216+
logEntry.put("type", "gauge");
217+
logEntry.put("value", g.value());
218+
} else if (meter instanceof Counter c) {
219+
logEntry.put("type", "counter");
220+
logEntry.put("value", c.count());
221+
} else if (meter instanceof Timer t) {
222+
logEntry.put("type", "timer");
223+
logEntry.put("totalTimeMs", t.totalTime(TimeUnit.MILLISECONDS));
224+
logEntry.put("count", t.count());
225+
logEntry.put("maxTimeMs", t.max(TimeUnit.MILLISECONDS));
226+
} else if (meter instanceof DistributionSummary ds) {
227+
logEntry.put("type", "summary");
228+
logEntry.put("total", ds.totalAmount());
229+
logEntry.put("count", ds.count());
230+
logEntry.put("max", ds.max());
231+
} else if (meter instanceof FunctionCounter fc) {
232+
logEntry.put("type", "functionCounter");
233+
logEntry.put("value", fc.count());
234+
} else if (meter instanceof FunctionTimer ft) {
235+
logEntry.put("type", "functionTimer");
236+
logEntry.put("count", ft.count());
237+
logEntry.put("totalTimeMs", ft.totalTime(TimeUnit.MILLISECONDS));
238+
logEntry.put("meanMs", ft.mean(TimeUnit.MILLISECONDS));
239+
} else {
240+
logEntry.put("type", meter.getId().getType().name());
241+
}
242+
243+
return logEntry;
244+
}
245+
246+
void logMetricsOnShutdown(String... filters) {
247+
meterRegistry.getMeters().stream()
248+
.filter(m -> AbstractMicrometerService.matchesFilter(m.getId().getName(), filters))
249+
.map(AbstractMicrometerService::convertMeterToMap)
250+
.forEach(logEntry -> {
251+
try {
252+
LOG.info(mapper.writeValueAsString(logEntry));
253+
} catch (Exception e) {
254+
LOG.error("Error logging metric " + logEntry.get("name"), e);
255+
}
256+
});
158257
}
159258

160259
// This method does a best effort attempt to recover information about versioning of the runtime.

0 commit comments

Comments
 (0)