diff --git a/spring-cloud-stream-metrics/pom.xml b/spring-cloud-stream-metrics/pom.xml index 037b3410f..115e6b2fa 100644 --- a/spring-cloud-stream-metrics/pom.xml +++ b/spring-cloud-stream-metrics/pom.xml @@ -29,6 +29,11 @@ spring-cloud-stream-test-support test + + org.springframework.boot + spring-boot-configuration-processor + true + diff --git a/spring-cloud-stream-metrics/src/main/java/org/springframework/cloud/stream/config/metrics/StreamMetricsProperties.java b/spring-cloud-stream-metrics/src/main/java/org/springframework/cloud/stream/config/metrics/StreamMetricsProperties.java deleted file mode 100644 index 7becb0f37..000000000 --- a/spring-cloud-stream-metrics/src/main/java/org/springframework/cloud/stream/config/metrics/StreamMetricsProperties.java +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright 2017 the original author or authors. - * - * Licensed 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.springframework.cloud.stream.config.metrics; - -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.util.StringUtils; - -/** - * @author Vinicius Carvalho - */ -@ConfigurationProperties(prefix = "spring.cloud.stream.metrics") -public class StreamMetricsProperties { - - private String prefix; - - @Value("${spring.application.name:${vcap.application.name:${spring.config.name:application}}}") - private String key; - - private String metricName; - - private String[] includes = new String[] { "integration**" }; - - private String[] excludes; - - private String[] properties; - - private Long delayMillis; - - public String[] getIncludes() { - return includes; - } - - public void setIncludes(String[] includes) { - this.includes = includes; - } - - public String[] getExcludes() { - return excludes; - } - - public void setExcludes(String[] excludes) { - this.excludes = excludes; - } - - public Long getDelayMillis() { - return delayMillis; - } - - public void setDelayMillis(Long delayMillis) { - this.delayMillis = delayMillis; - } - - public String getPrefix() { - return prefix; - } - - public void setPrefix(String prefix) { - this.prefix = prefix; - } - - public String getKey() { - return key; - } - - public void setKey(String key) { - this.key = key; - } - - public String[] getProperties() { - return properties; - } - - public void setProperties(String[] properties) { - this.properties = properties; - } - - public String getMetricName() { - if (this.metricName == null) { - this.metricName = resolveMetricName(); - } - return metricName; - } - - private String resolveMetricName() { - StringBuffer name = new StringBuffer(this.key); - if (!StringUtils.isEmpty(this.prefix)) { - String prefix = this.prefix; - if (prefix.lastIndexOf(".") == -1) { - prefix += "."; - } - name.insert(0, prefix); - } - return name.toString(); - } -} diff --git a/spring-cloud-stream-metrics/src/main/java/org/springframework/cloud/stream/metrics/ApplicationMetrics.java b/spring-cloud-stream-metrics/src/main/java/org/springframework/cloud/stream/metrics/ApplicationMetrics.java index 646364d40..97ea7cbd2 100644 --- a/spring-cloud-stream-metrics/src/main/java/org/springframework/cloud/stream/metrics/ApplicationMetrics.java +++ b/spring-cloud-stream-metrics/src/main/java/org/springframework/cloud/stream/metrics/ApplicationMetrics.java @@ -38,13 +38,14 @@ public class ApplicationMetrics { private int instanceIndex; - private Collection metrics; + private Collection> metrics; private Map properties; @JsonCreator - public ApplicationMetrics(@JsonProperty("name") String name, @JsonProperty("instanceIndex") int instanceIndex, - @JsonProperty("metrics") Collection metrics) { + public ApplicationMetrics(@JsonProperty("name") String name, + @JsonProperty("instanceIndex") int instanceIndex, + @JsonProperty("metrics") Collection> metrics) { this.name = name; this.instanceIndex = instanceIndex; this.metrics = metrics; @@ -67,11 +68,11 @@ public class ApplicationMetrics { this.instanceIndex = instanceIndex; } - public Collection getMetrics() { + public Collection> getMetrics() { return metrics; } - public void setMetrics(Collection metrics) { + public void setMetrics(Collection> metrics) { this.metrics = metrics; } diff --git a/spring-cloud-stream-metrics/src/main/java/org/springframework/cloud/stream/metrics/ApplicationMetricsExporter.java b/spring-cloud-stream-metrics/src/main/java/org/springframework/cloud/stream/metrics/ApplicationMetricsExporter.java new file mode 100644 index 000000000..8a2b8f39f --- /dev/null +++ b/spring-cloud-stream-metrics/src/main/java/org/springframework/cloud/stream/metrics/ApplicationMetricsExporter.java @@ -0,0 +1,92 @@ +/* + * Copyright 2017 the original author or authors. + * + * Licensed 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.springframework.cloud.stream.metrics; + +import java.util.ArrayList; +import java.util.Collection; + +import org.springframework.boot.actuate.endpoint.MetricsEndpoint; +import org.springframework.boot.actuate.endpoint.MetricsEndpointMetricReader; +import org.springframework.boot.actuate.metrics.Metric; +import org.springframework.boot.actuate.metrics.export.Exporter; +import org.springframework.boot.actuate.metrics.export.MetricCopyExporter; +import org.springframework.integration.support.MessageBuilder; +import org.springframework.messaging.MessageChannel; +import org.springframework.util.ObjectUtils; +import org.springframework.util.PatternMatchUtils; + +/** + * + * Component that sends {@link ApplicationMetrics} from + * {@link MetricsEndpointMetricReader} downstream via the configured metrics channel. + * + * It uses the Spring Boot support for {@link Exporter} to periodically emit messages + * polled from the endpoint. + * + * @author Vinicius Carvalho + */ +public class ApplicationMetricsExporter implements Exporter { + + private MessageChannel source; + + private ApplicationMetricsProperties properties; + + private MetricsEndpointMetricReader metricsReader; + + public ApplicationMetricsExporter(MetricsEndpoint endpoint, MessageChannel source, + ApplicationMetricsProperties properties) { + this.source = source; + this.properties = properties; + this.metricsReader = new MetricsEndpointMetricReader(endpoint); + } + + @Override + public void export() { + ApplicationMetrics appMetrics = new ApplicationMetrics( + this.properties.getMetricName(), this.properties.getInstanceIndex(), + filter()); + appMetrics.setProperties(this.properties.getExportProperties()); + source.send(MessageBuilder.withPayload(appMetrics).build()); + } + + /** + * Copy of similarly named method in {@link MetricCopyExporter}. + */ + protected Collection> filter() { + Collection> result = new ArrayList<>(); + Iterable> metrics = metricsReader.findAll(); + for (Metric metric : metrics) { + if (isMatch(metric.getName(), this.properties.getTrigger().getIncludes(), + this.properties.getTrigger().getExcludes())) { + result.add(metric); + } + } + return result; + } + + /** + * Copy of similarly named method in {@link MetricCopyExporter}. + */ + private boolean isMatch(String name, String[] includes, String[] excludes) { + if (ObjectUtils.isEmpty(includes) + || PatternMatchUtils.simpleMatch(includes, name)) { + return !PatternMatchUtils.simpleMatch(excludes, name); + } + return false; + } + +} diff --git a/spring-cloud-stream-metrics/src/main/java/org/springframework/cloud/stream/metrics/ApplicationMetricsProperties.java b/spring-cloud-stream-metrics/src/main/java/org/springframework/cloud/stream/metrics/ApplicationMetricsProperties.java new file mode 100644 index 000000000..59139ce7a --- /dev/null +++ b/spring-cloud-stream-metrics/src/main/java/org/springframework/cloud/stream/metrics/ApplicationMetricsProperties.java @@ -0,0 +1,158 @@ +/* + * Copyright 2017 the original author or authors. + * + * Licensed 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.springframework.cloud.stream.metrics; + +import java.util.HashMap; +import java.util.Map; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.actuate.metrics.export.MetricExportProperties; +import org.springframework.boot.actuate.metrics.export.TriggerProperties; +import org.springframework.boot.bind.RelaxedNames; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.ApplicationListener; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.event.ContextRefreshedEvent; +import org.springframework.core.env.EnumerablePropertySource; +import org.springframework.core.env.PropertySource; +import org.springframework.util.ObjectUtils; +import org.springframework.util.PatternMatchUtils; + +/** + * @author Vinicius Carvalho + */ +@ConfigurationProperties(prefix = "spring.cloud.stream.metrics") +public class ApplicationMetricsProperties + implements ApplicationListener { + + private String prefix = ""; + + @Value("${spring.application.name:${vcap.application.name:${spring.config.name:application}}}") + private String key; + + @Value("${INSTANCE_INDEX:${CF_INSTANCE_INDEX:0}}") + private int instanceIndex; + + private String metricName; + + private String[] properties; + + private MetricExportProperties export; + + public TriggerProperties getTrigger() { + return export.findTrigger("application"); + } + + public ApplicationMetricsProperties(MetricExportProperties export) { + this.export = export; + } + + public String getPrefix() { + return prefix; + } + + public void setPrefix(String prefix) { + if (!prefix.endsWith(".")) { + prefix += "."; + } + this.prefix = prefix; + } + + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } + + public int getInstanceIndex() { + return instanceIndex; + } + + public void setInstanceIndex(int instanceIndex) { + this.instanceIndex = instanceIndex; + } + + public String[] getProperties() { + return properties; + } + + public void setProperties(String[] properties) { + this.properties = properties; + } + + /** + * List of properties that are going to be appended to each message. This gets + * populate by onApplicationEvent, once the context refreshes to avoid overhead of + * doing per message basis. + */ + private Map exportProperties = new HashMap<>(); + + public Map getExportProperties() { + return exportProperties; + } + + public String getMetricName() { + if (this.metricName == null) { + this.metricName = resolveMetricName(); + } + return metricName; + } + + private String resolveMetricName() { + return this.prefix + this.key; + } + + /** + * Iterates over all property sources from this application context and copies the + * ones listed in {@link ApplicationMetricsProperties} includes. + */ + @Override + public void onApplicationEvent(ContextRefreshedEvent event) { + ConfigurableApplicationContext ctx = (ConfigurableApplicationContext) event + .getSource(); + if (!ObjectUtils.isEmpty(this.properties)) { + for (PropertySource source : ctx.getEnvironment().getPropertySources()) { + if (source instanceof EnumerablePropertySource) { + EnumerablePropertySource e = (EnumerablePropertySource) source; + for (String propertyName : e.getPropertyNames()) { + RelaxedNames relaxedNames = new RelaxedNames(propertyName); + relaxedLoop: for (String relaxedPropertyName : relaxedNames) { + if (isMatch(relaxedPropertyName, this.properties, null)) { + this.exportProperties.put( + RelaxedPropertiesUtils + .findCanonicalFormat(relaxedNames), + source.getProperty(propertyName)); + break relaxedLoop; + } + } + } + } + } + } + } + + private boolean isMatch(String name, String[] includes, String[] excludes) { + if (ObjectUtils.isEmpty(includes) + || PatternMatchUtils.simpleMatch(includes, name)) { + return !PatternMatchUtils.simpleMatch(excludes, name); + } + return false; + } + +} diff --git a/spring-cloud-stream-metrics/src/main/java/org/springframework/cloud/stream/metrics/BinderMetricsEmitter.java b/spring-cloud-stream-metrics/src/main/java/org/springframework/cloud/stream/metrics/BinderMetricsEmitter.java deleted file mode 100644 index 516302002..000000000 --- a/spring-cloud-stream-metrics/src/main/java/org/springframework/cloud/stream/metrics/BinderMetricsEmitter.java +++ /dev/null @@ -1,144 +0,0 @@ -/* - * Copyright 2017 the original author or authors. - * - * Licensed 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.springframework.cloud.stream.metrics; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.Map; - -import org.springframework.beans.BeansException; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.actuate.endpoint.MetricsEndpoint; -import org.springframework.boot.actuate.endpoint.MetricsEndpointMetricReader; -import org.springframework.boot.actuate.metrics.Metric; -import org.springframework.boot.actuate.metrics.export.MetricCopyExporter; -import org.springframework.boot.bind.RelaxedNames; -import org.springframework.cloud.stream.config.BindingServiceProperties; -import org.springframework.cloud.stream.config.metrics.StreamMetricsProperties; -import org.springframework.context.ApplicationContext; -import org.springframework.context.ApplicationContextAware; -import org.springframework.context.ApplicationListener; -import org.springframework.context.ConfigurableApplicationContext; -import org.springframework.context.event.ContextRefreshedEvent; -import org.springframework.core.env.EnumerablePropertySource; -import org.springframework.core.env.PropertySource; -import org.springframework.integration.support.MessageBuilder; -import org.springframework.scheduling.annotation.Scheduled; -import org.springframework.util.ObjectUtils; -import org.springframework.util.PatternMatchUtils; - -/** - * - * Component that sends metrics from {@link MetricsEndpointMetricReader} downstream via - * the configured metrics channel. - * - * It uses {@link Scheduled} support to periodially emit messages polled from the - * endpoint. - * - * @author Vinicius Carvalho - */ -public class BinderMetricsEmitter implements ApplicationListener, ApplicationContextAware { - - @Autowired - private Emitter source; - - @Autowired - private StreamMetricsProperties properties; - - @Autowired - private BindingServiceProperties bindingServiceProperties; - - private MetricsEndpointMetricReader metricsReader; - - private ApplicationContext applicationContext; - - /** - * List of properties that are going to be appended to each message. This gets - * populate by onApplicationEvent, once the context refreshes to avoid overhead of - * doing per message basis. - */ - private Map whitelistedProperties; - - public BinderMetricsEmitter(MetricsEndpoint endpoint) { - this.metricsReader = new MetricsEndpointMetricReader(endpoint); - this.whitelistedProperties = new HashMap<>(); - } - - @Scheduled(fixedRateString = "${spring.cloud.stream.metrics.delay-millis:5000}") - public void sendMetrics() { - ApplicationMetrics appMetrics = new ApplicationMetrics(this.properties.getMetricName(), - this.bindingServiceProperties.getInstanceIndex(), filter()); - appMetrics.setProperties(whitelistedProperties); - source.metrics().send(MessageBuilder.withPayload(appMetrics).build()); - } - - /** - * Copy of similarly named method in {@link MetricCopyExporter}. - */ - protected Collection filter() { - Collection result = new ArrayList<>(); - Iterable> metrics = metricsReader.findAll(); - for (Metric metric : metrics) { - if (isMatch(metric.getName(), this.properties.getIncludes(), this.properties.getExcludes())) { - result.add(metric); - } - } - return result; - } - - /** - * Copy of similarly named method in {@link MetricCopyExporter}. - */ - private boolean isMatch(String name, String[] includes, String[] excludes) { - if (ObjectUtils.isEmpty(includes) || PatternMatchUtils.simpleMatch(includes, name)) { - return !PatternMatchUtils.simpleMatch(excludes, name); - } - return false; - } - - /** - * Iterates over all property sources from this application context and copies the - * ones listed in {@link StreamMetricsProperties} includes. - */ - @Override - public void onApplicationEvent(ContextRefreshedEvent event) { - ConfigurableApplicationContext ctx = (ConfigurableApplicationContext) event.getSource(); - if (!ObjectUtils.isEmpty(this.properties.getProperties())) { - for (PropertySource source : ctx.getEnvironment().getPropertySources()) { - if (source instanceof EnumerablePropertySource) { - EnumerablePropertySource e = (EnumerablePropertySource) source; - for (String propertyName : e.getPropertyNames()) { - RelaxedNames relaxedNames = new RelaxedNames(propertyName); - relaxedLoop: for (String relaxedPropertyName : relaxedNames) { - if (isMatch(relaxedPropertyName, this.properties.getProperties(), null)) { - whitelistedProperties.put(RelaxedPropertiesUtils.findCanonicalFormat(relaxedNames), - source.getProperty(propertyName)); - break relaxedLoop; - } - } - } - } - } - } - } - - @Override - public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { - this.applicationContext = applicationContext; - } -} diff --git a/spring-cloud-stream-metrics/src/main/java/org/springframework/cloud/stream/metrics/RelaxedPropertiesUtils.java b/spring-cloud-stream-metrics/src/main/java/org/springframework/cloud/stream/metrics/RelaxedPropertiesUtils.java index 91f7d8d1f..fc03ccdef 100644 --- a/spring-cloud-stream-metrics/src/main/java/org/springframework/cloud/stream/metrics/RelaxedPropertiesUtils.java +++ b/spring-cloud-stream-metrics/src/main/java/org/springframework/cloud/stream/metrics/RelaxedPropertiesUtils.java @@ -27,19 +27,19 @@ import org.springframework.util.StringUtils; * * @author Vinicius Carvalho */ -public class RelaxedPropertiesUtils { +class RelaxedPropertiesUtils { private static final Pattern HYPHEN_LOWER = Pattern.compile("-_|_-|__|\\.-|\\._"); - private static final Pattern SEPARATED_TO_CAMEL_CASE_PATTERN = Pattern.compile("[_\\-.]"); + private static final Pattern SEPARATED_TO_CAMEL_CASE_PATTERN = Pattern + .compile("[_\\-.]"); private static final char[] SUFFIXES = new char[] { '_', '-', '.' }; /** * Searches relaxed names and tries to find a best match for a canonical form using - * dot notation. - * For example, if a new list was built with JAVA_HOME as property, the return of this - * method would be {@code java.home}. + * dot notation. For example, if a new list was built with JAVA_HOME as property, the + * return of this method would be {@code java.home}. * * Relaxed names generate a long list of variations of a property, it can be tricky * trying to infer the correct format, which sometimes may not even exist in dot diff --git a/spring-cloud-stream-metrics/src/main/java/org/springframework/cloud/stream/config/metrics/BinderMetricsAutoConfiguration.java b/spring-cloud-stream-metrics/src/main/java/org/springframework/cloud/stream/metrics/config/BinderMetricsAutoConfiguration.java similarity index 67% rename from spring-cloud-stream-metrics/src/main/java/org/springframework/cloud/stream/config/metrics/BinderMetricsAutoConfiguration.java rename to spring-cloud-stream-metrics/src/main/java/org/springframework/cloud/stream/metrics/config/BinderMetricsAutoConfiguration.java index 498f8a8ae..2c6f261a9 100644 --- a/spring-cloud-stream-metrics/src/main/java/org/springframework/cloud/stream/config/metrics/BinderMetricsAutoConfiguration.java +++ b/spring-cloud-stream-metrics/src/main/java/org/springframework/cloud/stream/metrics/config/BinderMetricsAutoConfiguration.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.cloud.stream.config.metrics; +package org.springframework.cloud.stream.metrics.config; import org.springframework.boot.actuate.endpoint.MetricsEndpoint; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; @@ -22,32 +22,31 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.cloud.stream.annotation.EnableBinding; import org.springframework.cloud.stream.binder.Binder; -import org.springframework.cloud.stream.metrics.BinderMetricsEmitter; -import org.springframework.cloud.stream.metrics.BootMetricJsonSerializer; -import org.springframework.cloud.stream.metrics.Emitter; +import org.springframework.cloud.stream.metrics.ApplicationMetricsExporter; +import org.springframework.cloud.stream.metrics.ApplicationMetricsProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.scheduling.annotation.EnableScheduling; /** * @author Vinicius Carvalho */ @Configuration @ConditionalOnClass(Binder.class) -@EnableScheduling @EnableBinding(Emitter.class) -@EnableConfigurationProperties(StreamMetricsProperties.class) -@ConditionalOnProperty("spring.cloud.stream.bindings." + Emitter.METRICS_CHANNEL_NAME + ".destination") +@EnableConfigurationProperties(ApplicationMetricsProperties.class) +@ConditionalOnProperty("spring.cloud.stream.bindings." + Emitter.METRICS_CHANNEL_NAME + + ".destination") public class BinderMetricsAutoConfiguration { @Bean - public BinderMetricsEmitter binderMetricsExporter(MetricsEndpoint endpoint) { - return new BinderMetricsEmitter(endpoint); + public ApplicationMetricsExporter aggregateMetricsExporter(MetricsEndpoint endpoint, + Emitter emitter, ApplicationMetricsProperties properties) { + return new ApplicationMetricsExporter(endpoint, emitter.metrics(), properties); } @Bean - public BootMetricJsonSerializer metricJsonSerializer() { - return new BootMetricJsonSerializer(); + public MetricJsonSerializer metricJsonSerializer() { + return new MetricJsonSerializer(); } } diff --git a/spring-cloud-stream-metrics/src/main/java/org/springframework/cloud/stream/config/metrics/BinderMetricsEnvironmentPostProcessor.java b/spring-cloud-stream-metrics/src/main/java/org/springframework/cloud/stream/metrics/config/BinderMetricsEnvironmentPostProcessor.java similarity index 71% rename from spring-cloud-stream-metrics/src/main/java/org/springframework/cloud/stream/config/metrics/BinderMetricsEnvironmentPostProcessor.java rename to spring-cloud-stream-metrics/src/main/java/org/springframework/cloud/stream/metrics/config/BinderMetricsEnvironmentPostProcessor.java index cb2d32da5..2b53b1506 100644 --- a/spring-cloud-stream-metrics/src/main/java/org/springframework/cloud/stream/config/metrics/BinderMetricsEnvironmentPostProcessor.java +++ b/spring-cloud-stream-metrics/src/main/java/org/springframework/cloud/stream/metrics/config/BinderMetricsEnvironmentPostProcessor.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.cloud.stream.config.metrics; +package org.springframework.cloud.stream.metrics.config; import java.util.HashMap; import java.util.Map; @@ -29,10 +29,15 @@ import org.springframework.core.env.MapPropertySource; */ public class BinderMetricsEnvironmentPostProcessor implements EnvironmentPostProcessor { @Override - public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) { + public void postProcessEnvironment(ConfigurableEnvironment environment, + SpringApplication application) { Map propertiesToAdd = new HashMap<>(); - propertiesToAdd.put("spring.cloud.stream.bindings.streamMetrics.contentType", "application/json"); - environment.getPropertySources() - .addLast(new MapPropertySource("binderMetricsDefaultProperties", propertiesToAdd)); + propertiesToAdd.put("spring.cloud.stream.bindings.streamMetrics.contentType", + "application/json"); + propertiesToAdd.put("spring.cloud.stream.metrics.instanceIndex", + "${spring.cloud.stream.instanceIndex:${INSTANCE_INDEX:${CF_INSTANCE_INDEX:0}}}"); + propertiesToAdd.put("spring.metrics.export.includes", "integration**"); + environment.getPropertySources().addLast( + new MapPropertySource("binderMetricsDefaultProperties", propertiesToAdd)); } } diff --git a/spring-cloud-stream-metrics/src/main/java/org/springframework/cloud/stream/metrics/Emitter.java b/spring-cloud-stream-metrics/src/main/java/org/springframework/cloud/stream/metrics/config/Emitter.java similarity index 94% rename from spring-cloud-stream-metrics/src/main/java/org/springframework/cloud/stream/metrics/Emitter.java rename to spring-cloud-stream-metrics/src/main/java/org/springframework/cloud/stream/metrics/config/Emitter.java index cc19c1614..138f67d7c 100644 --- a/spring-cloud-stream-metrics/src/main/java/org/springframework/cloud/stream/metrics/Emitter.java +++ b/spring-cloud-stream-metrics/src/main/java/org/springframework/cloud/stream/metrics/config/Emitter.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.cloud.stream.metrics; +package org.springframework.cloud.stream.metrics.config; import org.springframework.cloud.stream.annotation.Output; import org.springframework.messaging.MessageChannel; diff --git a/spring-cloud-stream-metrics/src/main/java/org/springframework/cloud/stream/metrics/BootMetricJsonSerializer.java b/spring-cloud-stream-metrics/src/main/java/org/springframework/cloud/stream/metrics/config/MetricJsonSerializer.java similarity index 82% rename from spring-cloud-stream-metrics/src/main/java/org/springframework/cloud/stream/metrics/BootMetricJsonSerializer.java rename to spring-cloud-stream-metrics/src/main/java/org/springframework/cloud/stream/metrics/config/MetricJsonSerializer.java index 81be6a0f6..918a11dd3 100644 --- a/spring-cloud-stream-metrics/src/main/java/org/springframework/cloud/stream/metrics/BootMetricJsonSerializer.java +++ b/spring-cloud-stream-metrics/src/main/java/org/springframework/cloud/stream/metrics/config/MetricJsonSerializer.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.cloud.stream.metrics; +package org.springframework.cloud.stream.metrics.config; import java.io.IOException; import java.text.DateFormat; @@ -38,15 +38,15 @@ import org.springframework.boot.jackson.JsonComponent; * @author Vinicius Carvalho */ @JsonComponent -public class BootMetricJsonSerializer { +public class MetricJsonSerializer { final static DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); - public static class Serializer extends JsonSerializer { + public static class Serializer extends JsonSerializer> { @Override - public void serialize(Metric metric, JsonGenerator json, SerializerProvider serializerProvider) - throws IOException { + public void serialize(Metric metric, JsonGenerator json, + SerializerProvider serializerProvider) throws IOException { json.writeStartObject(); json.writeStringField("name", metric.getName()); json.writeNumberField("value", metric.getValue().doubleValue()); @@ -55,10 +55,10 @@ public class BootMetricJsonSerializer { } } - public static class Deserializer extends JsonDeserializer { + public static class Deserializer extends JsonDeserializer> { @Override - public Metric deserialize(JsonParser p, DeserializationContext ctxt) + public Metric deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException { JsonNode node = p.getCodec().readTree(p); String name = node.get("name").asText(); @@ -70,7 +70,7 @@ public class BootMetricJsonSerializer { } catch (ParseException e) { } - Metric metric = new Metric(name, value, timestamp); + Metric metric = new Metric(name, value, timestamp); return metric; } diff --git a/spring-cloud-stream-metrics/src/main/resources/META-INF/spring.factories b/spring-cloud-stream-metrics/src/main/resources/META-INF/spring.factories index 2555b7f54..9b9369892 100644 --- a/spring-cloud-stream-metrics/src/main/resources/META-INF/spring.factories +++ b/spring-cloud-stream-metrics/src/main/resources/META-INF/spring.factories @@ -1,4 +1,4 @@ org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ - org.springframework.cloud.stream.config.metrics.BinderMetricsAutoConfiguration + org.springframework.cloud.stream.metrics.config.BinderMetricsAutoConfiguration org.springframework.boot.env.EnvironmentPostProcessor=\ - org.springframework.cloud.stream.config.metrics.BinderMetricsEnvironmentPostProcessor \ No newline at end of file + org.springframework.cloud.stream.metrics.config.BinderMetricsEnvironmentPostProcessor \ No newline at end of file diff --git a/spring-cloud-stream-metrics/src/test/java/org/springframework/cloud/stream/metrics/BinderMetricsEmitterTests.java b/spring-cloud-stream-metrics/src/test/java/org/springframework/cloud/stream/metrics/ApplicationMetricsExporterTests.java similarity index 59% rename from spring-cloud-stream-metrics/src/test/java/org/springframework/cloud/stream/metrics/BinderMetricsEmitterTests.java rename to spring-cloud-stream-metrics/src/test/java/org/springframework/cloud/stream/metrics/ApplicationMetricsExporterTests.java index e9c34e518..17748c5fe 100644 --- a/spring-cloud-stream-metrics/src/test/java/org/springframework/cloud/stream/metrics/BinderMetricsEmitterTests.java +++ b/spring-cloud-stream-metrics/src/test/java/org/springframework/cloud/stream/metrics/ApplicationMetricsExporterTests.java @@ -20,6 +20,7 @@ import java.util.Collection; import java.util.concurrent.TimeUnit; import com.fasterxml.jackson.databind.ObjectMapper; + import org.junit.AfterClass; import org.junit.Assert; import org.junit.BeforeClass; @@ -29,6 +30,7 @@ import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.boot.SpringApplication; import org.springframework.boot.actuate.metrics.Metric; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.cloud.stream.metrics.config.Emitter; import org.springframework.cloud.stream.test.binder.MessageCollector; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.messaging.Message; @@ -37,7 +39,7 @@ import org.springframework.util.CollectionUtils; /** * @author Vinicius Carvalho */ -public class BinderMetricsEmitterTests { +public class ApplicationMetricsExporterTests { @BeforeClass public static void setSystemProps() { @@ -51,8 +53,9 @@ public class BinderMetricsEmitterTests { @Test(expected = NoSuchBeanDefinitionException.class) public void checkDisabledConfiguration() throws Exception { - ConfigurableApplicationContext applicationContext = SpringApplication.run(BinderExporterApplication.class, - "--server.port=0", "--spring.jmx.enabled=false", "--spring.cloud.stream.metrics.delay-millis=500"); + ConfigurableApplicationContext applicationContext = SpringApplication.run( + BinderExporterApplication.class, "--server.port=0", + "--spring.jmx.enabled=false"); try { applicationContext.getBean(Emitter.class); } @@ -67,17 +70,20 @@ public class BinderMetricsEmitterTests { @Test public void defaultIncludes() throws Exception { - ConfigurableApplicationContext applicationContext = SpringApplication.run(BinderExporterApplication.class, - "--server.port=0", "--spring.jmx.enabled=false", "--spring.cloud.stream.metrics.delay-millis=500", + ConfigurableApplicationContext applicationContext = SpringApplication.run( + BinderExporterApplication.class, "--server.port=0", + "--spring.jmx.enabled=false", "--spring.metrics.export.delay-millis=500", "--spring.cloud.stream.bindings.streamMetrics.destination=foo"); Emitter emitterSource = applicationContext.getBean(Emitter.class); MessageCollector collector = applicationContext.getBean(MessageCollector.class); - Message message = collector.forChannel(emitterSource.metrics()).poll(1000, TimeUnit.MILLISECONDS); + Message message = collector.forChannel(emitterSource.metrics()).poll(1000, + TimeUnit.MILLISECONDS); Assert.assertNotNull(message); ObjectMapper mapper = applicationContext.getBean(ObjectMapper.class); - ApplicationMetrics applicationMetrics = mapper.readValue((String) message.getPayload(), - ApplicationMetrics.class); - Assert.assertTrue(contains("integration.channel.errorChannel.errorRate.mean", applicationMetrics.getMetrics())); + ApplicationMetrics applicationMetrics = mapper + .readValue((String) message.getPayload(), ApplicationMetrics.class); + Assert.assertTrue(contains("integration.channel.errorChannel.errorRate.mean", + applicationMetrics.getMetrics())); Assert.assertFalse(contains("mem", applicationMetrics.getMetrics())); Assert.assertEquals("application", applicationMetrics.getName()); Assert.assertEquals(0, applicationMetrics.getInstanceIndex()); @@ -87,18 +93,21 @@ public class BinderMetricsEmitterTests { @Test public void customAppNameAndIndex() throws Exception { - ConfigurableApplicationContext applicationContext = SpringApplication.run(BinderExporterApplication.class, - "--server.port=0", "--spring.jmx.enabled=false", "--spring.cloud.stream.metrics.delay-millis=500", + ConfigurableApplicationContext applicationContext = SpringApplication.run( + BinderExporterApplication.class, "--server.port=0", + "--spring.jmx.enabled=false", "--spring.metrics.export.delay-millis=500", "--spring.application.name=foo", "--spring.cloud.stream.instanceIndex=1", "--spring.cloud.stream.bindings.streamMetrics.destination=foo"); Emitter emitterSource = applicationContext.getBean(Emitter.class); MessageCollector collector = applicationContext.getBean(MessageCollector.class); - Message message = collector.forChannel(emitterSource.metrics()).poll(1000, TimeUnit.MILLISECONDS); + Message message = collector.forChannel(emitterSource.metrics()).poll(1000, + TimeUnit.MILLISECONDS); Assert.assertNotNull(message); ObjectMapper mapper = applicationContext.getBean(ObjectMapper.class); - ApplicationMetrics applicationMetrics = mapper.readValue((String) message.getPayload(), - ApplicationMetrics.class); - Assert.assertTrue(contains("integration.channel.errorChannel.errorRate.mean", applicationMetrics.getMetrics())); + ApplicationMetrics applicationMetrics = mapper + .readValue((String) message.getPayload(), ApplicationMetrics.class); + Assert.assertTrue(contains("integration.channel.errorChannel.errorRate.mean", + applicationMetrics.getMetrics())); Assert.assertFalse(contains("mem", applicationMetrics.getMetrics())); Assert.assertEquals("foo", applicationMetrics.getName()); Assert.assertEquals(1, applicationMetrics.getInstanceIndex()); @@ -108,18 +117,22 @@ public class BinderMetricsEmitterTests { @Test public void usingPrefix() throws Exception { - ConfigurableApplicationContext applicationContext = SpringApplication.run(BinderExporterApplication.class, - "--server.port=0", "--spring.jmx.enabled=false", "--spring.cloud.stream.metrics.delay-millis=500", - "--spring.cloud.stream.metrics.prefix=foo", "--spring.cloud.stream.instanceIndex=1", + ConfigurableApplicationContext applicationContext = SpringApplication.run( + BinderExporterApplication.class, "--server.port=0", + "--spring.jmx.enabled=false", "--spring.metrics.export.delay-millis=500", + "--spring.cloud.stream.metrics.prefix=foo", + "--spring.cloud.stream.instanceIndex=1", "--spring.cloud.stream.bindings.streamMetrics.destination=foo"); Emitter emitterSource = applicationContext.getBean(Emitter.class); MessageCollector collector = applicationContext.getBean(MessageCollector.class); - Message message = collector.forChannel(emitterSource.metrics()).poll(1000, TimeUnit.MILLISECONDS); + Message message = collector.forChannel(emitterSource.metrics()).poll(1000, + TimeUnit.MILLISECONDS); Assert.assertNotNull(message); ObjectMapper mapper = applicationContext.getBean(ObjectMapper.class); - ApplicationMetrics applicationMetrics = mapper.readValue((String) message.getPayload(), - ApplicationMetrics.class); - Assert.assertTrue(contains("integration.channel.errorChannel.errorRate.mean", applicationMetrics.getMetrics())); + ApplicationMetrics applicationMetrics = mapper + .readValue((String) message.getPayload(), ApplicationMetrics.class); + Assert.assertTrue(contains("integration.channel.errorChannel.errorRate.mean", + applicationMetrics.getMetrics())); Assert.assertFalse(contains("mem", applicationMetrics.getMetrics())); Assert.assertEquals("foo.application", applicationMetrics.getName()); Assert.assertEquals(1, applicationMetrics.getInstanceIndex()); @@ -129,19 +142,22 @@ public class BinderMetricsEmitterTests { @Test public void includesExcludes() throws Exception { - ConfigurableApplicationContext applicationContext = SpringApplication.run(BinderExporterApplication.class, - "--server.port=0", "--spring.jmx.enabled=false", "--spring.cloud.stream.metrics.delay-millis=500", + ConfigurableApplicationContext applicationContext = SpringApplication.run( + BinderExporterApplication.class, "--server.port=0", + "--spring.jmx.enabled=false", "--spring.metrics.export.delay-millis=500", "--spring.cloud.stream.bindings.streamMetrics.destination=foo", - "--spring.cloud.stream.metrics.includes=mem**", "--spring.cloud.stream.metrics.excludes=integration**"); + "--spring.metrics.export.includes=mem**", + "--spring.metrics.export.excludes=integration**"); Emitter emitterSource = applicationContext.getBean(Emitter.class); MessageCollector collector = applicationContext.getBean(MessageCollector.class); - Message message = collector.forChannel(emitterSource.metrics()).poll(1000, TimeUnit.MILLISECONDS); + Message message = collector.forChannel(emitterSource.metrics()).poll(1000, + TimeUnit.MILLISECONDS); Assert.assertNotNull(message); ObjectMapper mapper = applicationContext.getBean(ObjectMapper.class); - ApplicationMetrics applicationMetrics = mapper.readValue((String) message.getPayload(), - ApplicationMetrics.class); - Assert.assertFalse( - contains("integration.channel.errorChannel.errorRate.mean", applicationMetrics.getMetrics())); + ApplicationMetrics applicationMetrics = mapper + .readValue((String) message.getPayload(), ApplicationMetrics.class); + Assert.assertFalse(contains("integration.channel.errorChannel.errorRate.mean", + applicationMetrics.getMetrics())); Assert.assertTrue(contains("mem", applicationMetrics.getMetrics())); Assert.assertTrue(CollectionUtils.isEmpty(applicationMetrics.getProperties())); applicationContext.close(); @@ -149,50 +165,57 @@ public class BinderMetricsEmitterTests { @Test public void includesExcludesWithProperties() throws Exception { - ConfigurableApplicationContext applicationContext = SpringApplication.run(BinderExporterApplication.class, - "--server.port=0", "--spring.jmx.enabled=false", "--spring.cloud.stream.metrics.delay-millis=500", + ConfigurableApplicationContext applicationContext = SpringApplication.run( + BinderExporterApplication.class, "--server.port=0", + "--spring.jmx.enabled=false", "--spring.metrics.export.delay-millis=500", "--spring.cloud.stream.bindings.streamMetrics.destination=foo", - "--spring.cloud.stream.metrics.includes=integration**", + "--spring.metrics.export.includes=integration**", "--spring.cloud.stream.metrics.properties=java**,spring.test.env**"); Emitter emitterSource = applicationContext.getBean(Emitter.class); MessageCollector collector = applicationContext.getBean(MessageCollector.class); - Message message = collector.forChannel(emitterSource.metrics()).poll(1000, TimeUnit.MILLISECONDS); + Message message = collector.forChannel(emitterSource.metrics()).poll(1000, + TimeUnit.MILLISECONDS); Assert.assertNotNull(message); ObjectMapper mapper = applicationContext.getBean(ObjectMapper.class); - ApplicationMetrics applicationMetrics = mapper.readValue((String) message.getPayload(), - ApplicationMetrics.class); + ApplicationMetrics applicationMetrics = mapper + .readValue((String) message.getPayload(), ApplicationMetrics.class); Assert.assertFalse(contains("mem", applicationMetrics.getMetrics())); - Assert.assertTrue(contains("integration.channel.errorChannel.errorRate.mean", applicationMetrics.getMetrics())); + Assert.assertTrue(contains("integration.channel.errorChannel.errorRate.mean", + applicationMetrics.getMetrics())); Assert.assertFalse(CollectionUtils.isEmpty(applicationMetrics.getProperties())); - Assert.assertTrue(applicationMetrics.getProperties().get("spring.test.env.syntax").equals("testing")); + Assert.assertTrue(applicationMetrics.getProperties().get("spring.test.env.syntax") + .equals("testing")); applicationContext.close(); } - @Test - public void overrideAppName() throws Exception { - ConfigurableApplicationContext applicationContext = SpringApplication.run(BinderExporterApplication.class, - "--server.port=0", "--spring.jmx.enabled=false", "--spring.cloud.stream.metrics.delay-millis=500", - "--spring.application.name=foo", "--spring.cloud.stream.instanceIndex=1", - "--spring.cloud.stream.bindings.streamMetrics.destination=foo", - "--spring.cloud.stream.metrics.key=foobarfoo"); - Emitter emitterSource = applicationContext.getBean(Emitter.class); - MessageCollector collector = applicationContext.getBean(MessageCollector.class); - Message message = collector.forChannel(emitterSource.metrics()).poll(1000, TimeUnit.MILLISECONDS); - Assert.assertNotNull(message); - ObjectMapper mapper = applicationContext.getBean(ObjectMapper.class); - ApplicationMetrics applicationMetrics = mapper.readValue((String) message.getPayload(), - ApplicationMetrics.class); - Assert.assertTrue(contains("integration.channel.errorChannel.errorRate.mean", applicationMetrics.getMetrics())); - Assert.assertFalse(contains("mem", applicationMetrics.getMetrics())); - Assert.assertEquals("foobarfoo", applicationMetrics.getName()); - Assert.assertEquals(1, applicationMetrics.getInstanceIndex()); - Assert.assertTrue(CollectionUtils.isEmpty(applicationMetrics.getProperties())); - applicationContext.close(); - } + @Test + public void overrideAppName() throws Exception { + ConfigurableApplicationContext applicationContext = SpringApplication.run( + BinderExporterApplication.class, "--server.port=0", + "--spring.jmx.enabled=false", "--spring.metrics.export.delay-millis=500", + "--spring.application.name=foo", "--spring.cloud.stream.instanceIndex=1", + "--spring.cloud.stream.bindings.streamMetrics.destination=foo", + "--spring.cloud.stream.metrics.key=foobarfoo"); + Emitter emitterSource = applicationContext.getBean(Emitter.class); + MessageCollector collector = applicationContext.getBean(MessageCollector.class); + Message message = collector.forChannel(emitterSource.metrics()).poll(1000, + TimeUnit.MILLISECONDS); + Assert.assertNotNull(message); + ObjectMapper mapper = applicationContext.getBean(ObjectMapper.class); + ApplicationMetrics applicationMetrics = mapper + .readValue((String) message.getPayload(), ApplicationMetrics.class); + Assert.assertTrue(contains("integration.channel.errorChannel.errorRate.mean", + applicationMetrics.getMetrics())); + Assert.assertFalse(contains("mem", applicationMetrics.getMetrics())); + Assert.assertEquals("foobarfoo", applicationMetrics.getName()); + Assert.assertEquals(1, applicationMetrics.getInstanceIndex()); + Assert.assertTrue(CollectionUtils.isEmpty(applicationMetrics.getProperties())); + applicationContext.close(); + } - private boolean contains(String metric, Collection metrics) { + private boolean contains(String metric, Collection> metrics) { boolean contains = false; - for (Metric entry : metrics) { + for (Metric entry : metrics) { contains = entry.getName().equals(metric); if (contains) { break;