diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/export/MetricExportProperties.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/export/MetricExportProperties.java index bd399c9cdf..53c390ebf3 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/export/MetricExportProperties.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/export/MetricExportProperties.java @@ -16,30 +16,31 @@ package org.springframework.boot.actuate.metrics.export; -import java.util.Arrays; import java.util.LinkedHashMap; import java.util.Map; import java.util.Map.Entry; import javax.annotation.PostConstruct; +import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.util.PatternMatchUtils; +import org.springframework.util.StringUtils; /** * @author Dave Syer */ @ConfigurationProperties("spring.metrics.export") -public class MetricExportProperties { +public class MetricExportProperties extends Trigger { /** * Flag to disable all metric exports (assuming any MetricWriters are available). */ private boolean enabled = true; - private Export export = new Export(); + private Map triggers = new LinkedHashMap(); - private Map writers = new LinkedHashMap(); + private Redis redis = new Redis(); /** * Default values for trigger configuration for all writers. Can also be set by @@ -47,8 +48,8 @@ public class MetricExportProperties { * * @return the default trigger configuration */ - public Export getDefault() { - return this.export; + public Trigger getDefault() { + return this; } /** @@ -58,30 +59,27 @@ public class MetricExportProperties { * * @return the writers */ - public Map getWriters() { - return this.writers; + public Map getTriggers() { + return this.triggers; + } + + public Redis getRedis() { + return redis; + } + + public void setRedis(Redis redis) { + this.redis = redis; } @PostConstruct public void setUpDefaults() { - Export defaults = null; - for (Entry entry : this.writers.entrySet()) { + Trigger defaults = this; + for (Entry entry : this.triggers.entrySet()) { String key = entry.getKey(); - Export value = entry.getValue(); + SpecificTrigger value = entry.getValue(); if (value.getNames() == null || value.getNames().length == 0) { value.setNames(new String[] { key }); } - if (Arrays.asList(value.getNames()).contains("*")) { - defaults = value; - } - } - if (defaults == null) { - this.export.setNames(new String[] { "*" }); - this.writers.put("*", this.export); - defaults = this.export; - } - if (defaults.isIgnoreTimestamps() == null) { - defaults.setIgnoreTimestamps(false); } if (defaults.isSendLatest() == null) { defaults.setSendLatest(true); @@ -89,10 +87,7 @@ public class MetricExportProperties { if (defaults.getDelayMillis() == null) { defaults.setDelayMillis(5000); } - for (Export value : this.writers.values()) { - if (value.isIgnoreTimestamps() == null) { - value.setIgnoreTimestamps(defaults.isIgnoreTimestamps()); - } + for (Trigger value : this.triggers.values()) { if (value.isSendLatest() == null) { value.setSendLatest(defaults.isSendLatest()); } @@ -110,37 +105,29 @@ public class MetricExportProperties { this.enabled = enabled; } - public static class Export { + /** + * Find a matching trigger configuration. + * @param name the bean name to match + * @return a matching configuration if there is one + */ + public Trigger findTrigger(String name) { + for (SpecificTrigger value : this.triggers.values()) { + if (PatternMatchUtils.simpleMatch(value.getNames(), name)) { + return value; + } + } + return this; + } + + /** + * Trigger for specific bean names. + */ + public static class SpecificTrigger extends Trigger { + /** * Names (or patterns) for bean names that this configuration applies to. */ private String[] names; - /** - * Delay in milliseconds between export ticks. Metrics are exported to external - * sources on a schedule with this delay. - */ - private Long delayMillis; - - /** - * Flag to enable metric export (assuming a MetricWriter is available). - */ - private boolean enabled = true; - - /** - * Flag to switch off any available optimizations based on not exporting unchanged - * metric values. - */ - private Boolean sendLatest; - - /** - * Flag to ignore timestamps completely. If true, send all metrics all the time, - * including ones that haven't changed since startup. - */ - private Boolean ignoreTimestamps; - - private String[] includes; - - private String[] excludes; public String[] getNames() { return this.names; @@ -150,66 +137,119 @@ public class MetricExportProperties { this.names = names; } - public String[] getIncludes() { - return this.includes; - } - - public void setIncludes(String[] includes) { - this.includes = includes; - } - - public void setExcludes(String[] excludes) { - this.excludes = excludes; - } - - public String[] getExcludes() { - return this.excludes; - } - - public boolean isEnabled() { - return this.enabled; - } - - public void setEnabled(boolean enabled) { - this.enabled = enabled; - } - - public Long getDelayMillis() { - return this.delayMillis; - } - - public void setDelayMillis(long delayMillis) { - this.delayMillis = delayMillis; - } - - public Boolean isIgnoreTimestamps() { - return this.ignoreTimestamps; - } - - public void setIgnoreTimestamps(boolean ignoreTimestamps) { - this.ignoreTimestamps = ignoreTimestamps; - } - - public Boolean isSendLatest() { - return this.sendLatest; - } - - public void setSendLatest(boolean sendLatest) { - this.sendLatest = sendLatest; - } } + public static class Redis { + + /** + * Prefix for redis repository if active. Should be unique for this JVM, but most + * useful if it also has the form "x.y.a.b" where "x.y" is globally unique across + * all processes sharing the same repository, "a" is unique to this physical + * process and "b" is unique to this logical process (this application). If you + * set spring.application.name elsewhere, then the default will be in the right + * form. + */ + @Value("spring.metrics.${random.value:0000}.${spring.application.name:application}") + private String prefix = "spring.metrics"; + + /** + * Key for redis repository export (if active). Should be globally unique for a + * system sharing a redis repository. + */ + private String key = "keys.spring.metrics"; + + 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 getAggregatePrefix() { + String[] tokens = StringUtils.delimitedListToStringArray(this.prefix, "."); + if (tokens.length > 1) { + if (StringUtils.hasText(tokens[1])) { + // If the prefix has 2 or more non-trivial parts, use the first 1 + return tokens[0]; + } + } + return this.prefix; + } + + } + +} + +class Trigger { + /** - * Find a matching trigger configuration. - * @param name the bean name to match - * @return a matching configuration if there is one + * Delay in milliseconds between export ticks. Metrics are exported to external + * sources on a schedule with this delay. */ - public Export findExport(String name) { - for (Export value : this.writers.values()) { - if (PatternMatchUtils.simpleMatch(value.getNames(), name)) { - return value; - } - } - return this.export; + private Long delayMillis; + + /** + * Flag to enable metric export (assuming a MetricWriter is available). + */ + private boolean enabled = true; + + /** + * Flag to switch off any available optimizations based on not exporting unchanged + * metric values. + */ + private Boolean sendLatest; + + private String[] includes; + + private String[] excludes; + + public String[] getIncludes() { + return this.includes; } + + public void setIncludes(String[] includes) { + this.includes = includes; + } + + public void setExcludes(String[] excludes) { + this.excludes = excludes; + } + + public String[] getExcludes() { + return this.excludes; + } + + public boolean isEnabled() { + return this.enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public Long getDelayMillis() { + return this.delayMillis; + } + + public void setDelayMillis(long delayMillis) { + this.delayMillis = delayMillis; + } + + public Boolean isSendLatest() { + return this.sendLatest; + } + + public void setSendLatest(boolean sendLatest) { + this.sendLatest = sendLatest; + } + } diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/export/MetricExporters.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/export/MetricExporters.java index bd2b36928e..395cd70edf 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/export/MetricExporters.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/export/MetricExporters.java @@ -20,7 +20,6 @@ import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; -import org.springframework.boot.actuate.metrics.export.MetricExportProperties.Export; import org.springframework.boot.actuate.metrics.reader.MetricReader; import org.springframework.boot.actuate.metrics.writer.MetricWriter; import org.springframework.scheduling.annotation.SchedulingConfigurer; @@ -56,7 +55,7 @@ public class MetricExporters implements SchedulingConfigurer { for (Entry entry : this.writers.entrySet()) { String name = entry.getKey(); - Export trigger = this.export.findExport(name); + Trigger trigger = this.export.findTrigger(name); if (trigger != null) { @@ -67,7 +66,6 @@ public class MetricExporters implements SchedulingConfigurer { exporter.setIncludes(trigger.getIncludes()); exporter.setExcludes(trigger.getExcludes()); } - exporter.setIgnoreTimestamps(trigger.isIgnoreTimestamps()); exporter.setSendLatest(trigger.isSendLatest()); this.exporters.put(name, exporter); diff --git a/spring-boot-docs/src/main/asciidoc/production-ready-features.adoc b/spring-boot-docs/src/main/asciidoc/production-ready-features.adoc index 228ba0423f..c7b1fdf0ee 100644 --- a/spring-boot-docs/src/main/asciidoc/production-ready-features.adoc +++ b/spring-boot-docs/src/main/asciidoc/production-ready-features.adoc @@ -910,7 +910,7 @@ used by default if you are on Java 8 or if you are using Dropwizard metrics. [[production-ready-metric-writers]] -=== Metric writers and aggregation +=== Metric writers, exporters and aggregation Spring Boot provides a couple of implementations of a marker interface called `Exporter` which can be used to copy metric readings from the in-memory buffers to a place where they @@ -919,11 +919,20 @@ can be analysed and displayed. Indeed, if you provide a `@Bean` that implements metric updates every 5 seconds (configured via `spring.metrics.export.delayMillis`) via a `@Scheduled` annotation in `MetricRepositoryAutoConfiguration`. -The default exporter is a `MetricCopyExporter` which tries to optimize itself by not -copying values that haven't changed since it was last called. The optimization can be -switched off using a flag (`spring.metrics.export.ignoreTimestamps`). Note also that the -`MetricRegistry` has no support for timestamps, so the optimization is not available if -you are using Dropwizard metrics (all metrics will be copied on every tick). +The default exporter is a `MetricCopyExporter` which tries to optimize +itself by not copying values that haven't changed since it was last +called (the optimization can be switched off using a flag +`spring.metrics.export.sendLatest`). Note also that the Dropwizard +`MetricRegistry` has no support for timestamps, so the optimization is +not available if you are using Dropwizard metrics (all metrics will be +copied on every tick). + +The default values for the export trigger (`delayMillis`, `includes`, +`excludes`, `ignoreTimestamps` and `sendLatest`) can be set as +`spring.metrics.export.*`. Individual values for specific +`MetricWriters` can be set as +`spring.metrics.export.triggers..*` where `` is a bean +name (or pattern for matching bean names). @@ -942,19 +951,20 @@ Example: [source,java,indent=0] ---- - -@Value("metrics.mysystem.${random.value:0000}.${spring.application.name:application}") -private String prefix = "metrics.mysystem"; - -@Value("${metrics.key:keys.mysystem}") -private String key = "METRICSKEY"; - @Bean -MetricWriter metricWriter() { - return new RedisMetricRepository(connectionFactory, prefix, key); +MetricWriter metricWriter(MetricExportProperties export) { + return new RedisMetricRepository(connectionFactory, + export.getRedis().getPrefix(), export.getRedis().getKey()); } ---- +.application.properties +[source,properties] +---- +spring.metrics.export.redis.prefix: metrics.mysystem.${random.value:0000}.${spring.application.name:application} +spring.metrics.export.redis.key: keys.mysystem +---- + The prefix is constructed with the application name at the end, so it can easily be used to identify a group of processes with the same logical name later. @@ -967,6 +977,11 @@ start with the master prefix (like `metrics.mysystem.*` in the example above). I efficient to read all the keys from a "master" repository like that, but inefficient to read a subset with a longer prefix (e.g. using one of the writing repositories). +NOTE: the example above uses `MetricExportProperties` to inject and +extract the key and prefix. This is provided to you as a convenience +by Spring Boot, and the defaults for that will be sensible, but there +is nothing to stop you using your own values as long as they follow +the recommendations. [[production-ready-metric-writers-export-to-open-tdsb]] @@ -1064,25 +1079,31 @@ results to the "/metrics" endpoint. Example: [source,java,indent=0] ---- - @Bean - public PublicMetrics metricsAggregate() { - return new MetricReaderPublicMetrics(aggregates()); - } + @Autowired + private MetricExportProperties export; - @Bean - protected MetricReader repository(RedisConnectionFactory connectionFactory) { - RedisMetricRepository repository = new RedisMetricRepository(connectionFactory, - "mysystem", "myorg.keys"); - return repository; - } + @Bean + public PublicMetrics metricsAggregate() { + return new MetricReaderPublicMetrics(aggregates()); + } - @Bean - protected MetricReader aggregates() { - AggregateMetricReader repository = new AggregateMetricReader(repository()); - return repository; - } + @Bean + protected MetricReader repository(RedisConnectionFactory connectionFactory) { + RedisMetricRepository repository = new RedisMetricRepository(connectionFactory, + this.export.getRedis().getAggregatePrefix(), this.export.getRedis().getKey()); + return repository; + } + + @Bean + protected MetricReader aggregates() { + AggregateMetricReader repository = new AggregateMetricReader(repository()); + return repository; + } ---- +NOTE: the example above uses `MetricExportProperties` to inject and +extract the key and prefix. This is provided to you as a convenience +by Spring Boot, and the defaults for that will be sensible. [[production-ready-dropwizard-metrics]] @@ -1134,33 +1155,33 @@ and obtain basic information about the last few requests: [source,json,indent=0] ---- - [{ - "timestamp": 1394343677415, - "info": { - "method": "GET", - "path": "/trace", - "headers": { - "request": { - "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", - "Connection": "keep-alive", - "Accept-Encoding": "gzip, deflate", - "User-Agent": "Mozilla/5.0 Gecko/Firefox", - "Accept-Language": "en-US,en;q=0.5", - "Cookie": "_ga=GA1.1.827067509.1390890128; ..." - "Authorization": "Basic ...", - "Host": "localhost:8080" - }, - "response": { - "Strict-Transport-Security": "max-age=31536000 ; includeSubDomains", - "X-Application-Context": "application:8080", - "Content-Type": "application/json;charset=UTF-8", - "status": "200" - } - } - } - },{ - "timestamp": 1394343684465, - ... + [{ + "timestamp": 1394343677415, + "info": { + "method": "GET", + "path": "/trace", + "headers": { + "request": { + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", + "Connection": "keep-alive", + "Accept-Encoding": "gzip, deflate", + "User-Agent": "Mozilla/5.0 Gecko/Firefox", + "Accept-Language": "en-US,en;q=0.5", + "Cookie": "_ga=GA1.1.827067509.1390890128; ..." + "Authorization": "Basic ...", + "Host": "localhost:8080" + }, + "response": { + "Strict-Transport-Security": "max-age=31536000 ; includeSubDomains", + "X-Application-Context": "application:8080", + "Content-Type": "application/json;charset=UTF-8", + "status": "200" + } + } + } + },{ + "timestamp": 1394343684465, + ... }] ---- @@ -1201,9 +1222,9 @@ In `META-INF/spring.factories` file you have to activate the listener(s): [indent=0] ---- - org.springframework.context.ApplicationListener=\ - org.springframework.boot.actuate.system.ApplicationPidFileWriter, - org.springframework.boot.actuate.system.EmbeddedServerPortFileWriter + org.springframework.context.ApplicationListener=\ + org.springframework.boot.actuate.system.ApplicationPidFileWriter, + org.springframework.boot.actuate.system.EmbeddedServerPortFileWriter ---- diff --git a/spring-boot-samples/spring-boot-sample-metrics-redis/src/main/java/sample/metrics/redis/AggregateMetricsConfiguration.java b/spring-boot-samples/spring-boot-sample-metrics-redis/src/main/java/sample/metrics/redis/AggregateMetricsConfiguration.java index a8b3fdddfa..0e3a41d1dc 100644 --- a/spring-boot-samples/spring-boot-sample-metrics-redis/src/main/java/sample/metrics/redis/AggregateMetricsConfiguration.java +++ b/spring-boot-samples/spring-boot-sample-metrics-redis/src/main/java/sample/metrics/redis/AggregateMetricsConfiguration.java @@ -20,6 +20,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.actuate.endpoint.MetricReaderPublicMetrics; import org.springframework.boot.actuate.endpoint.PublicMetrics; import org.springframework.boot.actuate.metrics.aggregate.AggregateMetricReader; +import org.springframework.boot.actuate.metrics.export.MetricExportProperties; import org.springframework.boot.actuate.metrics.reader.MetricReader; import org.springframework.boot.actuate.metrics.repository.redis.RedisMetricRepository; import org.springframework.context.annotation.Bean; @@ -33,7 +34,7 @@ import org.springframework.data.redis.connection.RedisConnectionFactory; public class AggregateMetricsConfiguration { @Autowired - private ExportProperties export; + private MetricExportProperties export; @Autowired private RedisConnectionFactory connectionFactory; @@ -45,7 +46,7 @@ public class AggregateMetricsConfiguration { private MetricReader globalMetricsForAggregation() { return new RedisMetricRepository(this.connectionFactory, - this.export.getAggregatePrefix(), this.export.getKey()); + this.export.getRedis().getAggregatePrefix(), this.export.getRedis().getKey()); } private MetricReader aggregatesMetricReader() { diff --git a/spring-boot-samples/spring-boot-sample-metrics-redis/src/main/java/sample/metrics/redis/ExportProperties.java b/spring-boot-samples/spring-boot-sample-metrics-redis/src/main/java/sample/metrics/redis/ExportProperties.java deleted file mode 100644 index 57c4685cb9..0000000000 --- a/spring-boot-samples/spring-boot-sample-metrics-redis/src/main/java/sample/metrics/redis/ExportProperties.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2012-2015 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 sample.metrics.redis; - -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.util.StringUtils; - -@ConfigurationProperties("redis.metrics.export") -class ExportProperties { - - private String prefix = "spring.metrics"; - private String key = "keys.spring.metrics"; - - public String getPrefix() { - return this.prefix; - } - - public void setPrefix(String prefix) { - this.prefix = prefix; - } - - public String getKey() { - return this.key; - } - - public void setKey(String key) { - this.key = key; - } - - public String getAggregatePrefix() { - String[] tokens = StringUtils.delimitedListToStringArray(this.prefix, "."); - if (tokens.length > 1) { - if (StringUtils.hasText(tokens[1])) { - // If the prefix has 2 or more non-trivial parts, use the first 1 - return tokens[0]; - } - } - return this.prefix; - } - -} \ No newline at end of file diff --git a/spring-boot-samples/spring-boot-sample-metrics-redis/src/main/java/sample/metrics/redis/SampleRedisExportApplication.java b/spring-boot-samples/spring-boot-sample-metrics-redis/src/main/java/sample/metrics/redis/SampleRedisExportApplication.java index e60e8e81f7..53e9206d1e 100644 --- a/spring-boot-samples/spring-boot-sample-metrics-redis/src/main/java/sample/metrics/redis/SampleRedisExportApplication.java +++ b/spring-boot-samples/spring-boot-sample-metrics-redis/src/main/java/sample/metrics/redis/SampleRedisExportApplication.java @@ -19,20 +19,19 @@ package sample.metrics.redis; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.SpringApplication; +import org.springframework.boot.actuate.metrics.export.MetricExportProperties; import org.springframework.boot.actuate.metrics.jmx.JmxMetricWriter; import org.springframework.boot.actuate.metrics.repository.redis.RedisMetricRepository; import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.jmx.export.MBeanExporter; @SpringBootApplication -@EnableConfigurationProperties(ExportProperties.class) public class SampleRedisExportApplication { @Autowired - private ExportProperties export; + private MetricExportProperties export; public static void main(String[] args) throws Exception { SpringApplication.run(SampleRedisExportApplication.class, args); @@ -41,8 +40,8 @@ public class SampleRedisExportApplication { @Bean public RedisMetricRepository redisMetricWriter( RedisConnectionFactory connectionFactory) { - return new RedisMetricRepository(connectionFactory, this.export.getPrefix(), - this.export.getKey()); + return new RedisMetricRepository(connectionFactory, this.export.getRedis().getPrefix(), + this.export.getRedis().getKey()); } @Bean diff --git a/spring-boot-samples/spring-boot-sample-metrics-redis/src/main/resources/application.properties b/spring-boot-samples/spring-boot-sample-metrics-redis/src/main/resources/application.properties index 61a8193c9f..55bd86821d 100644 --- a/spring-boot-samples/spring-boot-sample-metrics-redis/src/main/resources/application.properties +++ b/spring-boot-samples/spring-boot-sample-metrics-redis/src/main/resources/application.properties @@ -1,4 +1,4 @@ service.name: Phil -redis.metrics.export.prefix: metrics.sample.${random.value:0000}.${spring.application.name:application} -redis.metrics.export.key: keys.metrics.sample +spring.metrics.export.redis.prefix: metrics.sample.${random.value:0000}.${spring.application.name:application} +spring.metrics.export.redis.key: keys.metrics.sample spring.jmx.default-domain: org.springframework.boot