Align the exporter and the trigger name

Fix #903

Use 'application' as the trigger name consistently.

Since 'application' would be a too generic name for
the Exporter bean, use a BeanPostProcessor to install
the exporter in the `MetricExporters` bean.

Additional updates

- rename binding to `applicationMetrics` (we can decide later how
  the channel is named)
- remove the default setting for `spring.metrics.export`
- Improve documentation

Fix #904

Docs updates

Properly escape asciidoc control characters
This commit is contained in:
Marius Bogoevici
2017-04-07 02:18:25 -04:00
committed by Vinicius Carvalho
parent 161011fcde
commit d6fe841a63
6 changed files with 138 additions and 76 deletions

View File

@@ -1914,49 +1914,40 @@ Spring Cloud Stream provides a health indicator for binders.
It is registered under the name of `binders` and can be enabled or disabled by setting the `management.health.binders.enabled` property.
== Metrics Emitter
Spring Cloud Stream provides a module called `spring-cloud-stream-metrics` that can be used to emit any available metric from https://docs.spring.io/spring-boot/docs/current/reference/html/production-ready-metrics.html[Spring Boot metrics endpoint] to a named channel.
This module allow operators to collect metrics from stream applications without relying on polling their endpoints.
HTTP polling can be challenging on cloud environments since applications could be on a private network or behind a Load balancer that prevents per instance access.
The module is activated when you set the destination name for metrics binding, e.g. `spring.cloud.stream.bindings.applicationMetrics.destination=<DESTINATION_NAME>`.
`applicationMetrics` can be configured in a similar fashion to any other producer binding.
The default `contentType` setting of `applicationMetrics` is `application/json`.
The module is activated when you set the destination name for its channel, `spring.cloud.stream.bindings.applicationMetricsChannel.destination=<DESTINATION_NAME>`.
`applicationMetricsChannel` can be configured in a similar fashion to any other bound output channel.
The default `contentType` setting of `applicationMetricsChannel` is `application/json`.
The following properties can be used for customizing the emission of metrics:
By default the module is configured to only send Spring Integration message channel metrics.
Available properties for customization using the prefix `spring.cloud.stream.metrics`.
key::
spring.cloud.stream.metrics.key::
The name of the metric being emitted. Should be an unique value per application.
+
Default:: `${spring.application.name:${vcap.application.name:${spring.config.name:application}}}`
+
delay-millis::
The period in which metrics will be posted to the channel
+
Default: `5000`.
+
prefix::
Prefix string to be prepended to the metrics name
spring.cloud.stream.metrics.prefix::
Prefix string to be prepended to the metrics key.
+
Default: ``
+
includes::
An array of strings containing regex patterns of metrics that should be included.
+
Default: `integration**`
+
excludes::
An array of strings containing regex patterns of metrics that should be excluded.
+
Default: ``
+
properties::
spring.cloud.stream.metrics.properties::
Just like the `includes` option, it allows white listing application properties that will be added to the metrics payload
+
Default: null.
A detailed overview of the metrics export process can be found in the https://docs.spring.io/spring-boot/docs/current/reference/html/production-ready-metrics.html#production-ready-metric-writers[Spring Boot reference documentation].
Spring Cloud Stream provides a metric exporter named `application` that can be configured via regular https://github.com/spring-projects/spring-boot/blob/1.5.x/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/export/TriggerProperties.java[Spring Boot metrics configuration properties].
The exporter can be configured either by using the global Spring Boot configuration settings for exporters, or by using exporter-specific properties.
For using the global configuration settings, the properties should be prefixed by `spring.metric.export` (e.g. `spring.metric.export.includes=integration+++**+++`).
These configuration options will apply to all exporters (unless they have been configured differently).
Alternatively, if it is intended to use configuration settings that are different from the other exporters (e.g. for restricting the number of metrics published), the Spring Cloud Stream provided metrics exporter can be configured using the prefix `spring.metrics.export.triggers.application` (e.g. `spring.metrics.export.triggers.application.includes=integration+++**+++`).
[NOTE]
====
Due to Spring Boot's https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-external-config.html#boot-features-external-config-relaxed-binding[relaxed binding] the value of a property being included can be slightly different than the original value.

View File

@@ -24,6 +24,7 @@ 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.cloud.stream.metrics.config.BinderMetricsAutoConfiguration;
import org.springframework.context.ApplicationListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.event.ContextRefreshedEvent;
@@ -49,9 +50,9 @@ public class ApplicationMetricsProperties
private String[] properties;
private MetricExportProperties export;
public TriggerProperties getTrigger() {
return export.findTrigger("application");
return export.findTrigger(BinderMetricsAutoConfiguration.APPLICATION_METRICS_EXPORTER_TRIGGER_NAME);
}
public ApplicationMetricsProperties(MetricExportProperties export) {

View File

@@ -16,7 +16,18 @@
package org.springframework.cloud.stream.metrics.config;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.boot.actuate.autoconfigure.MetricExportAutoConfiguration;
import org.springframework.boot.actuate.endpoint.MetricsEndpoint;
import org.springframework.boot.actuate.metrics.export.Exporter;
import org.springframework.boot.actuate.metrics.export.MetricExporters;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
@@ -26,27 +37,68 @@ 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.context.annotation.Lazy;
/**
* Autoconfiguration registering an {@link Exporter} that publishes application metrics over
* the {@link Emitter#applicationMetrics()} channel.
*
* @author Vinicius Carvalho
* @author Marius Bogoevici
*
*/
@Configuration
@ConditionalOnClass(Binder.class)
@EnableBinding(Emitter.class)
@EnableConfigurationProperties(ApplicationMetricsProperties.class)
@AutoConfigureAfter(MetricExportAutoConfiguration.class)
@ConditionalOnProperty("spring.cloud.stream.bindings." + Emitter.APPLICATION_METRICS
+ ".destination")
public class BinderMetricsAutoConfiguration {
@Bean
public ApplicationMetricsExporter aggregateMetricsExporter(MetricsEndpoint endpoint,
Emitter emitter, ApplicationMetricsProperties properties) {
return new ApplicationMetricsExporter(endpoint, emitter.applicationMetrics(), properties);
}
public static Log log = LogFactory.getLog(BinderMetricsAutoConfiguration.class);
public static final String APPLICATION_METRICS_EXPORTER_TRIGGER_NAME = "application";
@Bean
public MetricJsonSerializer metricJsonSerializer() {
return new MetricJsonSerializer();
}
/**
* Postprocessor for installing the {@link ApplicationMetricsExporter} as an
* exporter under the name {@code application}.
* @param endpoint the metrics endpoint (lazy reference to prevent early initialization)
* @param emitter the emitter bound interface
* @param properties application metrics properties
* @return
*/
@Bean
public static BeanPostProcessor metricExportersBeanPostProcessor(final @Lazy MetricsEndpoint endpoint,
final Emitter emitter, final ApplicationMetricsProperties properties) {
return new BeanPostProcessor() {
@Override
public Object postProcessBeforeInitialization(Object bean, String name) throws BeansException {
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String name) throws BeansException {
if (bean instanceof MetricExporters) {
Map<String, Exporter> exporters = ((MetricExporters) bean).getExporters();
if (!exporters.containsKey(APPLICATION_METRICS_EXPORTER_TRIGGER_NAME)) {
exporters.put(APPLICATION_METRICS_EXPORTER_TRIGGER_NAME,
new ApplicationMetricsExporter(endpoint, emitter.applicationMetrics(), properties));
}
else {
log.warn("Could not register ApplicationMetricExporter: "
+ exporters.get(APPLICATION_METRICS_EXPORTER_TRIGGER_NAME)
+ " was already registered as " + APPLICATION_METRICS_EXPORTER_TRIGGER_NAME);
}
}
return bean;
}
};
}
}

View File

@@ -33,7 +33,6 @@ public class BinderMetricsEnvironmentPostProcessor implements EnvironmentPostPro
Map<String, Object> propertiesToAdd = new HashMap<>();
propertiesToAdd.put("spring.cloud.stream.bindings." + Emitter.APPLICATION_METRICS + ".contentType",
"application/json");
propertiesToAdd.put("spring.metrics.export.includes", "integration**");
environment.getPropertySources()
.addLast(new MapPropertySource("binderMetricsDefaultProperties", propertiesToAdd));
}

View File

@@ -24,7 +24,7 @@ import org.springframework.messaging.MessageChannel;
*/
public interface Emitter {
String APPLICATION_METRICS = "applicationMetricsChannel";
String APPLICATION_METRICS = "applicationMetrics";
@Output(APPLICATION_METRICS)
MessageChannel applicationMetrics();

View File

@@ -73,20 +73,19 @@ public class ApplicationMetricsExporterTests {
ConfigurableApplicationContext applicationContext = SpringApplication.run(
BinderExporterApplication.class, "--server.port=0",
"--spring.jmx.enabled=false", "--spring.metrics.export.delay-millis=500",
"--spring.cloud.stream.bindings.applicationMetricsChannel.destination=foo");
"--spring.cloud.stream.bindings." + Emitter.APPLICATION_METRICS + ".destination=foo");
Emitter emitterSource = applicationContext.getBean(Emitter.class);
MessageCollector collector = applicationContext.getBean(MessageCollector.class);
Message<?> message = collector.forChannel(emitterSource.applicationMetrics()).poll(1000,
TimeUnit.MILLISECONDS);
Assert.assertNotNull(message);
ObjectMapper mapper = applicationContext.getBean(ObjectMapper.class);
ApplicationMetrics applicationMetricsChannel = mapper
ApplicationMetrics applicationMetrics = mapper
.readValue((String) message.getPayload(), ApplicationMetrics.class);
Assert.assertTrue(contains("integration.channel.errorChannel.errorRate.mean",
applicationMetricsChannel.getMetrics()));
Assert.assertFalse(contains("mem", applicationMetricsChannel.getMetrics()));
Assert.assertEquals("application", applicationMetricsChannel.getName());
Assert.assertTrue(CollectionUtils.isEmpty(applicationMetricsChannel.getProperties()));
applicationMetrics.getMetrics()));
Assert.assertEquals("application", applicationMetrics.getName());
Assert.assertTrue(CollectionUtils.isEmpty(applicationMetrics.getProperties()));
applicationContext.close();
}
@@ -95,21 +94,20 @@ public class ApplicationMetricsExporterTests {
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.bindings.applicationMetricsChannel.destination=foo");
"--spring.application.name=foo",
"--spring.cloud.stream.bindings." + Emitter.APPLICATION_METRICS + ".destination=foo");
Emitter emitterSource = applicationContext.getBean(Emitter.class);
MessageCollector collector = applicationContext.getBean(MessageCollector.class);
Message<?> message = collector.forChannel(emitterSource.applicationMetrics()).poll(1000,
TimeUnit.MILLISECONDS);
Assert.assertNotNull(message);
ObjectMapper mapper = applicationContext.getBean(ObjectMapper.class);
ApplicationMetrics applicationMetricsChannel = mapper
.readValue((String) message.getPayload(), ApplicationMetrics.class);
ApplicationMetrics applicationMetrics = mapper.readValue((String) message.getPayload(), ApplicationMetrics.class);
Assert.assertTrue(contains("integration.channel.errorChannel.errorRate.mean",
applicationMetricsChannel.getMetrics()));
Assert.assertFalse(contains("mem", applicationMetricsChannel.getMetrics()));
Assert.assertEquals("foo", applicationMetricsChannel.getName());
Assert.assertTrue(CollectionUtils.isEmpty(applicationMetricsChannel.getProperties()));
applicationMetrics.getMetrics()));
Assert.assertTrue(contains("mem", applicationMetrics.getMetrics()));
Assert.assertEquals("foo", applicationMetrics.getName());
Assert.assertTrue(CollectionUtils.isEmpty(applicationMetrics.getProperties()));
applicationContext.close();
}
@@ -119,29 +117,29 @@ public class ApplicationMetricsExporterTests {
BinderExporterApplication.class, "--server.port=0",
"--spring.jmx.enabled=false", "--spring.metrics.export.delay-millis=500",
"--spring.cloud.stream.metrics.prefix=foo",
"--spring.cloud.stream.bindings.applicationMetricsChannel.destination=foo");
"--spring.cloud.stream.bindings." + Emitter.APPLICATION_METRICS + ".destination=foo");
Emitter emitterSource = applicationContext.getBean(Emitter.class);
MessageCollector collector = applicationContext.getBean(MessageCollector.class);
Message<?> message = collector.forChannel(emitterSource.applicationMetrics()).poll(1000,
TimeUnit.MILLISECONDS);
Assert.assertNotNull(message);
ObjectMapper mapper = applicationContext.getBean(ObjectMapper.class);
ApplicationMetrics applicationMetricsChannel = mapper
ApplicationMetrics applicationMetrics = mapper
.readValue((String) message.getPayload(), ApplicationMetrics.class);
Assert.assertTrue(contains("integration.channel.errorChannel.errorRate.mean",
applicationMetricsChannel.getMetrics()));
Assert.assertFalse(contains("mem", applicationMetricsChannel.getMetrics()));
Assert.assertEquals("foo.application", applicationMetricsChannel.getName());
Assert.assertTrue(CollectionUtils.isEmpty(applicationMetricsChannel.getProperties()));
applicationMetrics.getMetrics()));
Assert.assertTrue(contains("mem", applicationMetrics.getMetrics()));
Assert.assertEquals("foo.application", applicationMetrics.getName());
Assert.assertTrue(CollectionUtils.isEmpty(applicationMetrics.getProperties()));
applicationContext.close();
}
@Test
public void includesExcludes() throws Exception {
public void includesExcludesDefaultConfig() throws Exception {
ConfigurableApplicationContext applicationContext = SpringApplication.run(
BinderExporterApplication.class, "--server.port=0",
"--spring.jmx.enabled=false", "--spring.metrics.export.delay-millis=500",
"--spring.cloud.stream.bindings.applicationMetricsChannel.destination=foo",
"--spring.cloud.stream.bindings." + Emitter.APPLICATION_METRICS + ".destination=foo",
"--spring.metrics.export.includes=mem**",
"--spring.metrics.export.excludes=integration**");
Emitter emitterSource = applicationContext.getBean(Emitter.class);
@@ -150,12 +148,35 @@ public class ApplicationMetricsExporterTests {
TimeUnit.MILLISECONDS);
Assert.assertNotNull(message);
ObjectMapper mapper = applicationContext.getBean(ObjectMapper.class);
ApplicationMetrics applicationMetricsChannel = mapper
ApplicationMetrics applicationMetrics = mapper
.readValue((String) message.getPayload(), ApplicationMetrics.class);
Assert.assertFalse(contains("integration.channel.errorChannel.errorRate.mean",
applicationMetricsChannel.getMetrics()));
Assert.assertTrue(contains("mem", applicationMetricsChannel.getMetrics()));
Assert.assertTrue(CollectionUtils.isEmpty(applicationMetricsChannel.getProperties()));
applicationMetrics.getMetrics()));
Assert.assertTrue(contains("mem", applicationMetrics.getMetrics()));
Assert.assertTrue(CollectionUtils.isEmpty(applicationMetrics.getProperties()));
applicationContext.close();
}
@Test
public void includesExcludesWithApplicationMetricsConfiguration() throws Exception {
ConfigurableApplicationContext applicationContext = SpringApplication.run(
BinderExporterApplication.class, "--server.port=0",
"--spring.jmx.enabled=false",
"--spring.metrics.export.triggers.application.delay-millis=500",
"--spring.cloud.stream.bindings." + Emitter.APPLICATION_METRICS + ".destination=foo",
"--spring.metrics.export.triggers.application.includes=mem**",
"--spring.metrics.export.triggers.application.excludes=integration**");
Emitter emitterSource = applicationContext.getBean(Emitter.class);
MessageCollector collector = applicationContext.getBean(MessageCollector.class);
Message<?> message = collector.forChannel(emitterSource.applicationMetrics()).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()));
Assert.assertTrue(contains("mem", applicationMetrics.getMetrics()));
Assert.assertTrue(CollectionUtils.isEmpty(applicationMetrics.getProperties()));
applicationContext.close();
}
@@ -164,7 +185,7 @@ public class ApplicationMetricsExporterTests {
ConfigurableApplicationContext applicationContext = SpringApplication.run(
BinderExporterApplication.class, "--server.port=0",
"--spring.jmx.enabled=false", "--spring.metrics.export.delay-millis=500",
"--spring.cloud.stream.bindings.applicationMetricsChannel.destination=foo",
"--spring.cloud.stream.bindings." + Emitter.APPLICATION_METRICS + ".destination=foo",
"--spring.metrics.export.includes=integration**",
"--spring.cloud.stream.metrics.properties=java**,spring.test.env**");
Emitter emitterSource = applicationContext.getBean(Emitter.class);
@@ -173,13 +194,12 @@ public class ApplicationMetricsExporterTests {
TimeUnit.MILLISECONDS);
Assert.assertNotNull(message);
ObjectMapper mapper = applicationContext.getBean(ObjectMapper.class);
ApplicationMetrics applicationMetricsChannel = mapper
.readValue((String) message.getPayload(), ApplicationMetrics.class);
Assert.assertFalse(contains("mem", applicationMetricsChannel.getMetrics()));
ApplicationMetrics applicationMetrics = mapper.readValue((String) message.getPayload(), ApplicationMetrics.class);
Assert.assertFalse(contains("mem", applicationMetrics.getMetrics()));
Assert.assertTrue(contains("integration.channel.errorChannel.errorRate.mean",
applicationMetricsChannel.getMetrics()));
Assert.assertFalse(CollectionUtils.isEmpty(applicationMetricsChannel.getProperties()));
Assert.assertTrue(applicationMetricsChannel.getProperties().get("spring.test.env.syntax")
applicationMetrics.getMetrics()));
Assert.assertFalse(CollectionUtils.isEmpty(applicationMetrics.getProperties()));
Assert.assertTrue(applicationMetrics.getProperties().get("spring.test.env.syntax")
.equals("testing"));
applicationContext.close();
}
@@ -190,7 +210,7 @@ public class ApplicationMetricsExporterTests {
BinderExporterApplication.class, "--server.port=0",
"--spring.jmx.enabled=false", "--spring.metrics.export.delay-millis=500",
"--spring.application.name=foo",
"--spring.cloud.stream.bindings.applicationMetricsChannel.destination=foo",
"--spring.cloud.stream.bindings." + Emitter.APPLICATION_METRICS + ".destination=foo",
"--spring.cloud.stream.metrics.key=foobarfoo");
Emitter emitterSource = applicationContext.getBean(Emitter.class);
MessageCollector collector = applicationContext.getBean(MessageCollector.class);
@@ -198,13 +218,12 @@ public class ApplicationMetricsExporterTests {
TimeUnit.MILLISECONDS);
Assert.assertNotNull(message);
ObjectMapper mapper = applicationContext.getBean(ObjectMapper.class);
ApplicationMetrics applicationMetricsChannel = mapper
.readValue((String) message.getPayload(), ApplicationMetrics.class);
ApplicationMetrics applicationMetrics = mapper.readValue((String) message.getPayload(), ApplicationMetrics.class);
Assert.assertTrue(contains("integration.channel.errorChannel.errorRate.mean",
applicationMetricsChannel.getMetrics()));
Assert.assertFalse(contains("mem", applicationMetricsChannel.getMetrics()));
Assert.assertEquals("foobarfoo", applicationMetricsChannel.getName());
Assert.assertTrue(CollectionUtils.isEmpty(applicationMetricsChannel.getProperties()));
applicationMetrics.getMetrics()));
Assert.assertTrue(contains("mem", applicationMetrics.getMetrics()));
Assert.assertEquals("foobarfoo", applicationMetrics.getName());
Assert.assertTrue(CollectionUtils.isEmpty(applicationMetrics.getProperties()));
applicationContext.close();
}