diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/MetricsAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/MetricsAutoConfiguration.java index 1e686b165a9..41ae3859087 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/MetricsAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/MetricsAutoConfiguration.java @@ -82,13 +82,26 @@ public class MetricsAutoConfiguration { @Bean @ConditionalOnMissingBean(MeterRegistry.class) public CompositeMeterRegistry compositeMeterRegistry( + MetricsProperties metricsProperties, ObjectProvider> exporters, ObjectProvider> configurers) { - CompositeMeterRegistry composite = new CompositeMeterRegistry(); - configurers.getIfAvailable(Collections::emptyList) - .forEach((configurer) -> configurer.configureRegistry(composite)); - exporters.getIfAvailable(Collections::emptyList).stream() - .map(MetricsExporter::registry).forEach(composite::add); + CompositeMeterRegistry composite = metricsProperties.isUseGlobalRegistry() ? + Metrics.globalRegistry : new CompositeMeterRegistry(); + + if (configurers.getIfAvailable() != null) { + configurers.getIfAvailable(Collections::emptyList) + .forEach((configurer) -> configurer.configureRegistry(composite)); + } + + if (exporters.getIfAvailable() != null) { + exporters.getIfAvailable().forEach(exporter -> { + final MeterRegistry childRegistry = exporter.registry(); + if (composite == childRegistry) { + throw new IllegalStateException("cannot add a CompositeMeterRegistry to itself"); + } + composite.add(childRegistry); + }); + } return composite; } @@ -129,7 +142,7 @@ public class MetricsAutoConfiguration { ObjectProvider> binders) { binders.getIfAvailable(Collections::emptyList) .forEach((binder) -> binder.bindTo(registry)); - if (config.isUseGlobalRegistry()) { + if (config.isUseGlobalRegistry() && registry != Metrics.globalRegistry) { Metrics.addRegistry(registry); } } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/datadog/DatadogProperties.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/datadog/DatadogProperties.java index d4c80b068c4..a258db9799d 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/datadog/DatadogProperties.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/datadog/DatadogProperties.java @@ -33,6 +33,18 @@ public class DatadogProperties extends StepRegistryProperties { */ private String apiKey; + /** + * Datadog application key. Not strictly required, but improves the Datadog + * experience by sending meter descriptions, types, and base units to Datadog. + */ + private String applicationKey; + + /** + * Enable publishing descriptions metadata to Datadog. Turn + * this off to minimize the amount of metadata sent. + */ + private Boolean descriptions; + /** * Tag that will be mapped to "host" when shipping metrics to Datadog. Can be * omitted if host should be omitted on publishing. @@ -53,6 +65,22 @@ public class DatadogProperties extends StepRegistryProperties { this.apiKey = apiKey; } + public String getApplicationKey() { + return this.applicationKey; + } + + public void setApplicationKey(String applicationKey) { + this.applicationKey = applicationKey; + } + + public Boolean getDescriptions() { + return this.descriptions; + } + + public void setDescriptions(Boolean descriptions) { + this.descriptions = descriptions; + } + public String getHostTag() { return this.hostTag; } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/datadog/DatadogPropertiesConfigAdapter.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/datadog/DatadogPropertiesConfigAdapter.java index 725ed3580ba..4a4655038a6 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/datadog/DatadogPropertiesConfigAdapter.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/datadog/DatadogPropertiesConfigAdapter.java @@ -39,6 +39,11 @@ class DatadogPropertiesConfigAdapter return get(DatadogProperties::getApiKey, DatadogConfig.super::apiKey); } + @Override + public String applicationKey() { + return get(DatadogProperties::getApplicationKey, DatadogConfig.super::applicationKey); + } + @Override public String hostTag() { return get(DatadogProperties::getHostTag, DatadogConfig.super::hostTag); @@ -49,4 +54,8 @@ class DatadogPropertiesConfigAdapter return get(DatadogProperties::getUri, DatadogConfig.super::uri); } + @Override + public boolean descriptions() { + return get(DatadogProperties::getDescriptions, DatadogConfig.super::descriptions); + } } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/jmx/JmxExportConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/jmx/JmxExportConfiguration.java index 14a10c7b15a..a6f54b729d6 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/jmx/JmxExportConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/jmx/JmxExportConfiguration.java @@ -18,12 +18,14 @@ package org.springframework.boot.actuate.autoconfigure.metrics.export.jmx; import io.micrometer.core.instrument.Clock; import io.micrometer.core.instrument.util.HierarchicalNameMapper; +import io.micrometer.jmx.JmxConfig; import io.micrometer.jmx.JmxMeterRegistry; import org.springframework.boot.actuate.autoconfigure.metrics.export.MetricsExporter; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -35,12 +37,20 @@ import org.springframework.context.annotation.Configuration; */ @Configuration @ConditionalOnClass(JmxMeterRegistry.class) +@EnableConfigurationProperties(JmxProperties.class) public class JmxExportConfiguration { + @Bean + @ConditionalOnMissingBean + public JmxConfig jmxConfig(JmxProperties jmxProperties) { + return new JmxPropertiesConfigAdapter(jmxProperties); + } + @Bean @ConditionalOnProperty(value = "management.metrics.export.jmx.enabled", matchIfMissing = true) - public MetricsExporter jmxExporter(HierarchicalNameMapper nameMapper, Clock clock) { - return () -> new JmxMeterRegistry(nameMapper, clock); + public MetricsExporter jmxExporter(JmxConfig config, + HierarchicalNameMapper nameMapper, Clock clock) { + return () -> new JmxMeterRegistry(config, nameMapper, clock); } @Bean diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/jmx/JmxProperties.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/jmx/JmxProperties.java new file mode 100644 index 00000000000..ee52ed602ec --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/jmx/JmxProperties.java @@ -0,0 +1,43 @@ +/* + * Copyright 2012-2018 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.boot.actuate.autoconfigure.metrics.export.jmx; + +import java.time.Duration; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * {@link ConfigurationProperties} for configuring JMX metrics export. + * + * @author Jon Schneider + * @since 2.0.0 + */ +@ConfigurationProperties(prefix = "management.metrics.export.jmx") +public class JmxProperties { + /** + * Step size (i.e. reporting frequency) to use. + */ + private Duration step; + + public Duration getStep() { + return this.step; + } + + public void setStep(Duration step) { + this.step = step; + } +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/jmx/JmxPropertiesConfigAdapter.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/jmx/JmxPropertiesConfigAdapter.java new file mode 100644 index 00000000000..3211c980347 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/jmx/JmxPropertiesConfigAdapter.java @@ -0,0 +1,46 @@ +/* + * Copyright 2012-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.boot.actuate.autoconfigure.metrics.export.jmx; + +import java.time.Duration; + +import io.micrometer.jmx.JmxConfig; + +import org.springframework.boot.actuate.autoconfigure.metrics.export.PropertiesConfigAdapter; + +/** + * Adapter to convert {@link JmxProperties} to a {@link JmxConfig}. + * + * @author Jon Schneider + */ +class JmxPropertiesConfigAdapter extends PropertiesConfigAdapter + implements JmxConfig { + + JmxPropertiesConfigAdapter(JmxProperties properties) { + super(properties); + } + + @Override + public String get(String k) { + return null; + } + + @Override + public Duration step() { + return get(JmxProperties::getStep, JmxConfig.super::step); + } +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/web/client/RestTemplateMetricsConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/web/client/RestTemplateMetricsConfiguration.java index 14a7f59a1ab..c16632370d0 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/web/client/RestTemplateMetricsConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/web/client/RestTemplateMetricsConfiguration.java @@ -18,14 +18,12 @@ package org.springframework.boot.actuate.autoconfigure.metrics.web.client; import io.micrometer.core.instrument.MeterRegistry; -import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.boot.actuate.autoconfigure.metrics.MetricsProperties; import org.springframework.boot.actuate.metrics.web.client.DefaultRestTemplateExchangeTagsProvider; import org.springframework.boot.actuate.metrics.web.client.MetricsRestTemplateCustomizer; import org.springframework.boot.actuate.metrics.web.client.RestTemplateExchangeTagsProvider; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.client.RestTemplate; @@ -57,47 +55,4 @@ public class RestTemplateMetricsConfiguration { properties.getWeb().getClient().isRecordRequestPercentiles()); } - @Bean - public static BeanPostProcessor restTemplateInterceptorPostProcessor( - ApplicationContext applicationContext) { - return new MetricsInterceptorPostProcessor(applicationContext); - } - - /** - * {@link BeanPostProcessor} to apply {@link MetricsRestTemplateCustomizer} to any - * directly registered {@link RestTemplate} beans. - */ - private static class MetricsInterceptorPostProcessor implements BeanPostProcessor { - - private final ApplicationContext applicationContext; - - private MetricsRestTemplateCustomizer customizer; - - MetricsInterceptorPostProcessor(ApplicationContext applicationContext) { - this.applicationContext = applicationContext; - } - - @Override - public Object postProcessBeforeInitialization(Object bean, String beanName) { - return bean; - } - - @Override - public Object postProcessAfterInitialization(Object bean, String beanName) { - if (bean instanceof RestTemplate) { - getCustomizer().customize((RestTemplate) bean); - } - return bean; - } - - private MetricsRestTemplateCustomizer getCustomizer() { - if (this.customizer == null) { - this.customizer = this.applicationContext - .getBean(MetricsRestTemplateCustomizer.class); - } - return this.customizer; - } - - } - } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/MetricsAutoConfigurationIntegrationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/MetricsAutoConfigurationIntegrationTests.java index 7633b504bbb..5fb91f9d6c2 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/MetricsAutoConfigurationIntegrationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/MetricsAutoConfigurationIntegrationTests.java @@ -32,6 +32,8 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.actuate.metrics.web.client.DefaultRestTemplateExchangeTagsProvider; +import org.springframework.boot.actuate.metrics.web.client.MetricsRestTemplateCustomizer; import org.springframework.boot.autoconfigure.ImportAutoConfiguration; import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration; import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; @@ -92,7 +94,6 @@ public class MetricsAutoConfigurationIntegrationTests { "{\"message\": \"hello\"}", MediaType.APPLICATION_JSON)); assertThat(this.external.getForObject("/api/external", Map.class)) .containsKey("message"); - MockClock.clock(this.registry).add(SimpleConfig.DEFAULT_STEP); assertThat(this.registry.find("http.client.requests").value(Statistic.Count, 1.0) .timer()).isPresent(); } @@ -100,7 +101,6 @@ public class MetricsAutoConfigurationIntegrationTests { @Test public void requestMappingIsInstrumented() { this.loopback.getForObject("/api/people", Set.class); - MockClock.clock(this.registry).add(SimpleConfig.DEFAULT_STEP); assertThat(this.registry.find("http.server.requests").value(Statistic.Count, 1.0) .timer()).isPresent(); } @@ -126,8 +126,12 @@ public class MetricsAutoConfigurationIntegrationTests { } @Bean - public RestTemplate restTemplate() { - return new RestTemplate(); + public RestTemplate restTemplate(MeterRegistry registry) { + RestTemplate restTemplate = new RestTemplate(); + MetricsRestTemplateCustomizer customizer = new MetricsRestTemplateCustomizer( + registry, new DefaultRestTemplateExchangeTagsProvider(), "http.client.requests", false); + customizer.customize(restTemplate); + return restTemplate; } } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/datadog/DatadogPropertiesConfigAdapterTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/datadog/DatadogPropertiesConfigAdapterTests.java index 7ca2affe1b6..4b59738437c 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/datadog/DatadogPropertiesConfigAdapterTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/datadog/DatadogPropertiesConfigAdapterTests.java @@ -27,12 +27,10 @@ import static org.assertj.core.api.Assertions.assertThat; */ public class DatadogPropertiesConfigAdapterTests { - @Test - public void apiKeyInferUri() { + @Test(expected = IllegalStateException.class) + public void apiKeyIsRequired() { DatadogProperties properties = new DatadogProperties(); - properties.setApiKey("my-key"); - assertThat(new DatadogPropertiesConfigAdapter(properties).uri()) - .contains("?api_key=my-key"); + new DatadogPropertiesConfigAdapter(properties).apiKey(); } @Test diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/simple/SimpleExportConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/simple/SimpleExportConfigurationTests.java index 01256cfe434..2b1b3b51546 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/simple/SimpleExportConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/simple/SimpleExportConfigurationTests.java @@ -46,7 +46,8 @@ public class SimpleExportConfigurationTests { "management.metrics.export.influx.enabled=false", "management.metrics.export.jmx.enabled=false", "management.metrics.export.prometheus.enabled=false", - "management.metrics.export.statsd.enabled=false") + "management.metrics.export.statsd.enabled=false", + "management.metrics.use-global-registry=false") .withConfiguration(AutoConfigurations.of(MetricsAutoConfiguration.class)) .run((context) -> { CompositeMeterRegistry meterRegistry = context diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/web/client/RestTemplateMetricsConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/web/client/RestTemplateMetricsConfigurationTests.java new file mode 100644 index 00000000000..9a76e863371 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/web/client/RestTemplateMetricsConfigurationTests.java @@ -0,0 +1,94 @@ +/* + * Copyright 2012-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.boot.actuate.autoconfigure.metrics.web.client; + +import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.instrument.simple.SimpleMeterRegistry; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration; +import org.springframework.boot.autoconfigure.data.cassandra.CassandraDataAutoConfiguration; +import org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchAutoConfiguration; +import org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchDataAutoConfiguration; +import org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration; +import org.springframework.boot.autoconfigure.data.neo4j.Neo4jDataAutoConfiguration; +import org.springframework.boot.autoconfigure.data.neo4j.Neo4jRepositoriesAutoConfiguration; +import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration; +import org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration; +import org.springframework.boot.autoconfigure.data.rest.RepositoryRestMvcAutoConfiguration; +import org.springframework.boot.autoconfigure.data.solr.SolrRepositoriesAutoConfiguration; +import org.springframework.boot.autoconfigure.elasticsearch.jest.JestAutoConfiguration; +import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration; +import org.springframework.boot.autoconfigure.hazelcast.HazelcastAutoConfiguration; +import org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration; +import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration; +import org.springframework.boot.autoconfigure.solr.SolrAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.context.annotation.Bean; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.web.client.RestTemplate; + +import static org.assertj.core.api.Assertions.assertThat; + +@RunWith(SpringRunner.class) +@SpringBootTest(classes = RestTemplateMetricsConfigurationTests.ClientApp.class, webEnvironment = SpringBootTest.WebEnvironment.NONE) +public class RestTemplateMetricsConfigurationTests { + @Autowired + private RestTemplate client; + + @Autowired + private MeterRegistry registry; + + @Test + public void restTemplatesCreatedWithBuilderAreInstrumented() { + try { + this.client.getForObject("http://google.com", String.class); + } + catch (Throwable ignored) { + // doesn't matter whether the request succeeded or not + } + assertThat(this.registry.find("http.client.requests").meter()).isPresent(); + } + + @SpringBootApplication(exclude = { + FlywayAutoConfiguration.class, LiquibaseAutoConfiguration.class, + CassandraAutoConfiguration.class, CassandraDataAutoConfiguration.class, + Neo4jDataAutoConfiguration.class, Neo4jRepositoriesAutoConfiguration.class, + MongoAutoConfiguration.class, RepositoryRestMvcAutoConfiguration.class, + HazelcastAutoConfiguration.class, MongoDataAutoConfiguration.class, + ElasticsearchAutoConfiguration.class, ElasticsearchDataAutoConfiguration.class, + JestAutoConfiguration.class, SolrRepositoriesAutoConfiguration.class, + SolrAutoConfiguration.class, RedisAutoConfiguration.class, + RedisRepositoriesAutoConfiguration.class + }) + static class ClientApp { + @Bean + public MeterRegistry registry() { + return new SimpleMeterRegistry(); + } + + @Bean + public RestTemplate restTemplate(RestTemplateBuilder builder) { + return builder.build(); + } + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/cache/CaffeineCacheMeterBinderProvider.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/cache/CaffeineCacheMeterBinderProvider.java index 3450059c628..11a3968fd3a 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/cache/CaffeineCacheMeterBinderProvider.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/cache/CaffeineCacheMeterBinderProvider.java @@ -34,7 +34,7 @@ public class CaffeineCacheMeterBinderProvider @Override public MeterBinder getMeterBinder(CaffeineCache cache, String name, Iterable tags) { - return new CaffeineCacheMetrics(cache.getNativeCache(), tags, name); + return new CaffeineCacheMetrics(cache.getNativeCache(), name, tags); } } diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/servlet/WebMvcMetrics.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/servlet/WebMvcMetrics.java index b4723ddd269..39a1163cdf9 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/servlet/WebMvcMetrics.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/servlet/WebMvcMetrics.java @@ -93,7 +93,9 @@ public class WebMvcMetrics { } void preHandle(HttpServletRequest request, Object handler) { - request.setAttribute(TIMING_REQUEST_ATTRIBUTE, System.nanoTime()); + if (request.getAttribute(TIMING_REQUEST_ATTRIBUTE) == null) { + request.setAttribute(TIMING_REQUEST_ATTRIBUTE, this.registry.config().clock().monotonicTime()); + } request.setAttribute(HANDLER_REQUEST_ATTRIBUTE, handler); longTaskTimed(handler).forEach((config) -> { if (config.getName() == null) { diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/servlet/WebMvcMetricsFilter.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/servlet/WebMvcMetricsFilter.java index c67a8d4a508..60598cf25d4 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/servlet/WebMvcMetricsFilter.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/servlet/WebMvcMetricsFilter.java @@ -29,6 +29,7 @@ import org.slf4j.LoggerFactory; import org.springframework.context.ApplicationContext; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; +import org.springframework.http.HttpStatus; import org.springframework.web.filter.OncePerRequestFilter; import org.springframework.web.servlet.HandlerExecutionChain; import org.springframework.web.servlet.handler.HandlerMappingIntrospector; @@ -102,6 +103,7 @@ public class WebMvcMetricsFilter extends OncePerRequestFilter { } } catch (NestedServletException ex) { + response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value()); metrics.record(request, response, ex.getCause()); throw ex; } diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/MetricsEndpointWebIntegrationTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/MetricsEndpointWebIntegrationTests.java index 5fe8c4fea75..5faa3f36afa 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/MetricsEndpointWebIntegrationTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/MetricsEndpointWebIntegrationTests.java @@ -64,14 +64,12 @@ public class MetricsEndpointWebIntegrationTests { @Test public void selectByName() { - MockClock.clock(registry).add(SimpleConfig.DEFAULT_STEP); client.get().uri("/actuator/metrics/jvm.memory.used").exchange().expectStatus() .isOk().expectBody().jsonPath("$.name").isEqualTo("jvm.memory.used"); } @Test public void selectByTag() { - MockClock.clock(registry).add(SimpleConfig.DEFAULT_STEP); client.get() .uri("/actuator/metrics/jvm.memory.used?tag=id:Compressed%20Class%20Space") .exchange().expectStatus().isOk().expectBody().jsonPath("$.name") diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/client/MetricsRestTemplateCustomizerTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/client/MetricsRestTemplateCustomizerTests.java index 2f395a60128..c7f5aab8fa3 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/client/MetricsRestTemplateCustomizerTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/client/MetricsRestTemplateCustomizerTests.java @@ -67,7 +67,6 @@ public class MetricsRestTemplateCustomizerTests { .andRespond(MockRestResponseCreators.withSuccess("OK", MediaType.APPLICATION_JSON)); String result = this.restTemplate.getForObject("/test/{id}", String.class, 123); - MockClock.clock(this.registry).add(SimpleConfig.DEFAULT_STEP); assertThat(this.registry.find("http.client.requests") .meters()).anySatisfy((m) -> assertThat( StreamSupport.stream(m.getId().getTags().spliterator(), false) diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/servlet/WebMvcMetricsFilterAutoTimedTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/servlet/WebMvcMetricsFilterAutoTimedTests.java index 107d775cb37..e372c73d45e 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/servlet/WebMvcMetricsFilterAutoTimedTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/servlet/WebMvcMetricsFilterAutoTimedTests.java @@ -76,7 +76,6 @@ public class WebMvcMetricsFilterAutoTimedTests { public void metricsCanBeAutoTimed() throws Exception { this.mvc.perform(get("/api/10")).andExpect(status().isOk()); - this.clock.add(SimpleConfig.DEFAULT_STEP); assertThat( this.registry.find("http.server.requests").tags("status", "200").timer()) .hasValueSatisfying((t) -> assertThat(t.count()).isEqualTo(1)); diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/servlet/WebMvcMetricsFilterTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/servlet/WebMvcMetricsFilterTests.java index 146f090c64f..0613c915c00 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/servlet/WebMvcMetricsFilterTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/servlet/WebMvcMetricsFilterTests.java @@ -21,6 +21,7 @@ import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import java.util.Collection; import java.util.concurrent.Callable; import java.util.concurrent.CountDownLatch; @@ -30,10 +31,18 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import io.micrometer.core.annotation.Timed; +import io.micrometer.core.instrument.Clock; +import io.micrometer.core.instrument.Meter; import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.Statistic; +import io.micrometer.core.instrument.Tag; +import io.micrometer.core.instrument.composite.CompositeMeterRegistry; +import io.micrometer.core.instrument.config.MeterFilter; +import io.micrometer.core.instrument.config.MeterFilterReply; +import io.micrometer.core.instrument.simple.SimpleMeterRegistry; import io.micrometer.prometheus.PrometheusConfig; import io.micrometer.prometheus.PrometheusMeterRegistry; +import io.prometheus.client.CollectorRegistry; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -43,6 +52,7 @@ import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; +import org.springframework.context.annotation.Primary; import org.springframework.http.HttpStatus; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.context.web.WebAppConfiguration; @@ -75,9 +85,11 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. @RunWith(SpringRunner.class) @WebAppConfiguration public class WebMvcMetricsFilterTests { + @Autowired + private SimpleMeterRegistry registry; @Autowired - private PrometheusMeterRegistry registry; + private PrometheusMeterRegistry prometheusRegistry; @Autowired private WebApplicationContext context; @@ -155,7 +167,7 @@ public class WebMvcMetricsFilterTests { public void unhandledError() { assertThatCode(() -> this.mvc.perform(get("/api/c1/unhandledError/10")) .andExpect(status().isOk())) - .hasRootCauseInstanceOf(RuntimeException.class); + .hasRootCauseInstanceOf(RuntimeException.class); assertThat(this.registry.find("http.server.requests") .tags("exception", "RuntimeException").value(Statistic.Count, 1.0) .timer()).isPresent(); @@ -196,15 +208,15 @@ public class WebMvcMetricsFilterTests { @Test public void recordQuantiles() throws Exception { this.mvc.perform(get("/api/c1/percentiles/10")).andExpect(status().isOk()); - assertThat(this.registry.scrape()).contains("quantile=\"0.5\""); - assertThat(this.registry.scrape()).contains("quantile=\"0.95\""); + assertThat(this.prometheusRegistry.scrape()).contains("quantile=\"0.5\""); + assertThat(this.prometheusRegistry.scrape()).contains("quantile=\"0.95\""); } @Test public void recordHistogram() throws Exception { this.mvc.perform(get("/api/c1/histogram/10")).andExpect(status().isOk()); - assertThat(this.registry.scrape()).contains("le=\"0.001\""); - assertThat(this.registry.scrape()).contains("le=\"30.0\""); + assertThat(this.prometheusRegistry.scrape()).contains("le=\"0.001\""); + assertThat(this.prometheusRegistry.scrape()).contains("le=\"30.0\""); } @Target({ ElementType.METHOD }) @@ -218,11 +230,34 @@ public class WebMvcMetricsFilterTests { @EnableWebMvc @Import({ Controller1.class, Controller2.class }) static class MetricsFilterApp { + @Primary + @Bean + MeterRegistry meterRegistry(Collection registries) { + CompositeMeterRegistry composite = new CompositeMeterRegistry(); + registries.forEach(composite::add); + return composite; + } @Bean - MeterRegistry meterRegistry() { - // one of the few registries that support aggregable percentiles - return new PrometheusMeterRegistry(PrometheusConfig.DEFAULT); + SimpleMeterRegistry simple() { + return new SimpleMeterRegistry(); + } + + @Bean + PrometheusMeterRegistry prometheus() { + PrometheusMeterRegistry r = new PrometheusMeterRegistry(PrometheusConfig.DEFAULT, new CollectorRegistry(), Clock.SYSTEM); + r.config().meterFilter(new MeterFilter() { + @Override + public MeterFilterReply accept(Meter.Id id) { + for (Tag tag : id.getTags()) { + if (tag.getKey().equals("uri") && (tag.getValue().contains("histogram") || tag.getValue().contains("percentiles"))) { + return MeterFilterReply.ACCEPT; + } + } + return MeterFilterReply.DENY; + } + }); + return r; } @Bean @@ -333,7 +368,6 @@ public class WebMvcMetricsFilterTests { public String successful(@PathVariable Long id) { return id.toString(); } - } static class RedirectAndNotFoundFilter extends OncePerRequestFilter { @@ -343,7 +377,7 @@ public class WebMvcMetricsFilterTests { @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) - throws ServletException, IOException { + throws ServletException, IOException { String misbehave = request.getHeader(TEST_MISBEHAVE_HEADER); if (misbehave != null) { response.setStatus(Integer.parseInt(misbehave)); diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/servlet/WebMvcMetricsIntegrationTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/servlet/WebMvcMetricsIntegrationTests.java index 69be0df1c57..47a7c5d0f4c 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/servlet/WebMvcMetricsIntegrationTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/servlet/WebMvcMetricsIntegrationTests.java @@ -82,9 +82,8 @@ public class WebMvcMetricsIntegrationTests { @Test public void handledExceptionIsRecordedInMetricTag() throws Exception { this.mvc.perform(get("/api/handledError")).andExpect(status().is5xxServerError()); - this.clock.add(SimpleConfig.DEFAULT_STEP); assertThat(this.registry.find("http.server.requests") - .tags("exception", "Exception1").value(Statistic.Count, 1.0).timer()) + .tags("exception", "Exception1", "status", "500").value(Statistic.Count, 1.0).timer()) .isPresent(); } @@ -92,9 +91,8 @@ public class WebMvcMetricsIntegrationTests { public void rethrownExceptionIsRecordedInMetricTag() { assertThatCode(() -> this.mvc.perform(get("/api/rethrownError")) .andExpect(status().is5xxServerError())); - this.clock.add(SimpleConfig.DEFAULT_STEP); assertThat(this.registry.find("http.server.requests") - .tags("exception", "Exception2").value(Statistic.Count, 1.0).timer()) + .tags("exception", "Exception2", "status", "500").value(Statistic.Count, 1.0).timer()) .isPresent(); } diff --git a/spring-boot-project/spring-boot-dependencies/pom.xml b/spring-boot-project/spring-boot-dependencies/pom.xml index 3fabecd175e..635026e6505 100644 --- a/spring-boot-project/spring-boot-dependencies/pom.xml +++ b/spring-boot-project/spring-boot-dependencies/pom.xml @@ -113,7 +113,7 @@ 1.2.3 1.16.18 2.2.1 - 1.0.0-rc.5 + 1.0.0-rc.6 6.2.2.jre8 2.13.0 1.7.0 @@ -879,6 +879,11 @@ micrometer-registry-jmx ${micrometer.version} + + io.micrometer + micrometer-registry-new-relic + ${micrometer.version} + io.micrometer micrometer-registry-prometheus