parent
69d5b7a4e8
commit
ccc820f723
|
@ -82,13 +82,26 @@ public class MetricsAutoConfiguration {
|
||||||
@Bean
|
@Bean
|
||||||
@ConditionalOnMissingBean(MeterRegistry.class)
|
@ConditionalOnMissingBean(MeterRegistry.class)
|
||||||
public CompositeMeterRegistry compositeMeterRegistry(
|
public CompositeMeterRegistry compositeMeterRegistry(
|
||||||
|
MetricsProperties metricsProperties,
|
||||||
ObjectProvider<Collection<MetricsExporter>> exporters,
|
ObjectProvider<Collection<MetricsExporter>> exporters,
|
||||||
ObjectProvider<Collection<MeterRegistryConfigurer>> configurers) {
|
ObjectProvider<Collection<MeterRegistryConfigurer>> configurers) {
|
||||||
CompositeMeterRegistry composite = new CompositeMeterRegistry();
|
CompositeMeterRegistry composite = metricsProperties.isUseGlobalRegistry() ?
|
||||||
|
Metrics.globalRegistry : new CompositeMeterRegistry();
|
||||||
|
|
||||||
|
if (configurers.getIfAvailable() != null) {
|
||||||
configurers.getIfAvailable(Collections::emptyList)
|
configurers.getIfAvailable(Collections::emptyList)
|
||||||
.forEach((configurer) -> configurer.configureRegistry(composite));
|
.forEach((configurer) -> configurer.configureRegistry(composite));
|
||||||
exporters.getIfAvailable(Collections::emptyList).stream()
|
}
|
||||||
.map(MetricsExporter::registry).forEach(composite::add);
|
|
||||||
|
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;
|
return composite;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -129,7 +142,7 @@ public class MetricsAutoConfiguration {
|
||||||
ObjectProvider<Collection<MeterBinder>> binders) {
|
ObjectProvider<Collection<MeterBinder>> binders) {
|
||||||
binders.getIfAvailable(Collections::emptyList)
|
binders.getIfAvailable(Collections::emptyList)
|
||||||
.forEach((binder) -> binder.bindTo(registry));
|
.forEach((binder) -> binder.bindTo(registry));
|
||||||
if (config.isUseGlobalRegistry()) {
|
if (config.isUseGlobalRegistry() && registry != Metrics.globalRegistry) {
|
||||||
Metrics.addRegistry(registry);
|
Metrics.addRegistry(registry);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,6 +33,18 @@ public class DatadogProperties extends StepRegistryProperties {
|
||||||
*/
|
*/
|
||||||
private String apiKey;
|
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
|
* Tag that will be mapped to "host" when shipping metrics to Datadog. Can be
|
||||||
* omitted if host should be omitted on publishing.
|
* omitted if host should be omitted on publishing.
|
||||||
|
@ -53,6 +65,22 @@ public class DatadogProperties extends StepRegistryProperties {
|
||||||
this.apiKey = apiKey;
|
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() {
|
public String getHostTag() {
|
||||||
return this.hostTag;
|
return this.hostTag;
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,6 +39,11 @@ class DatadogPropertiesConfigAdapter
|
||||||
return get(DatadogProperties::getApiKey, DatadogConfig.super::apiKey);
|
return get(DatadogProperties::getApiKey, DatadogConfig.super::apiKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String applicationKey() {
|
||||||
|
return get(DatadogProperties::getApplicationKey, DatadogConfig.super::applicationKey);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String hostTag() {
|
public String hostTag() {
|
||||||
return get(DatadogProperties::getHostTag, DatadogConfig.super::hostTag);
|
return get(DatadogProperties::getHostTag, DatadogConfig.super::hostTag);
|
||||||
|
@ -49,4 +54,8 @@ class DatadogPropertiesConfigAdapter
|
||||||
return get(DatadogProperties::getUri, DatadogConfig.super::uri);
|
return get(DatadogProperties::getUri, DatadogConfig.super::uri);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean descriptions() {
|
||||||
|
return get(DatadogProperties::getDescriptions, DatadogConfig.super::descriptions);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,12 +18,14 @@ package org.springframework.boot.actuate.autoconfigure.metrics.export.jmx;
|
||||||
|
|
||||||
import io.micrometer.core.instrument.Clock;
|
import io.micrometer.core.instrument.Clock;
|
||||||
import io.micrometer.core.instrument.util.HierarchicalNameMapper;
|
import io.micrometer.core.instrument.util.HierarchicalNameMapper;
|
||||||
|
import io.micrometer.jmx.JmxConfig;
|
||||||
import io.micrometer.jmx.JmxMeterRegistry;
|
import io.micrometer.jmx.JmxMeterRegistry;
|
||||||
|
|
||||||
import org.springframework.boot.actuate.autoconfigure.metrics.export.MetricsExporter;
|
import org.springframework.boot.actuate.autoconfigure.metrics.export.MetricsExporter;
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
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.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
@ -35,12 +37,20 @@ import org.springframework.context.annotation.Configuration;
|
||||||
*/
|
*/
|
||||||
@Configuration
|
@Configuration
|
||||||
@ConditionalOnClass(JmxMeterRegistry.class)
|
@ConditionalOnClass(JmxMeterRegistry.class)
|
||||||
|
@EnableConfigurationProperties(JmxProperties.class)
|
||||||
public class JmxExportConfiguration {
|
public class JmxExportConfiguration {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@ConditionalOnMissingBean
|
||||||
|
public JmxConfig jmxConfig(JmxProperties jmxProperties) {
|
||||||
|
return new JmxPropertiesConfigAdapter(jmxProperties);
|
||||||
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@ConditionalOnProperty(value = "management.metrics.export.jmx.enabled", matchIfMissing = true)
|
@ConditionalOnProperty(value = "management.metrics.export.jmx.enabled", matchIfMissing = true)
|
||||||
public MetricsExporter jmxExporter(HierarchicalNameMapper nameMapper, Clock clock) {
|
public MetricsExporter jmxExporter(JmxConfig config,
|
||||||
return () -> new JmxMeterRegistry(nameMapper, clock);
|
HierarchicalNameMapper nameMapper, Clock clock) {
|
||||||
|
return () -> new JmxMeterRegistry(config, nameMapper, clock);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<JmxProperties>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -18,14 +18,12 @@ package org.springframework.boot.actuate.autoconfigure.metrics.web.client;
|
||||||
|
|
||||||
import io.micrometer.core.instrument.MeterRegistry;
|
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.autoconfigure.metrics.MetricsProperties;
|
||||||
import org.springframework.boot.actuate.metrics.web.client.DefaultRestTemplateExchangeTagsProvider;
|
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.MetricsRestTemplateCustomizer;
|
||||||
import org.springframework.boot.actuate.metrics.web.client.RestTemplateExchangeTagsProvider;
|
import org.springframework.boot.actuate.metrics.web.client.RestTemplateExchangeTagsProvider;
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||||
import org.springframework.context.ApplicationContext;
|
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.web.client.RestTemplate;
|
import org.springframework.web.client.RestTemplate;
|
||||||
|
@ -57,47 +55,4 @@ public class RestTemplateMetricsConfiguration {
|
||||||
properties.getWeb().getClient().isRecordRequestPercentiles());
|
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,6 +32,8 @@ import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
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.ImportAutoConfiguration;
|
||||||
import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration;
|
import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration;
|
||||||
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
|
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
|
||||||
|
@ -92,7 +94,6 @@ public class MetricsAutoConfigurationIntegrationTests {
|
||||||
"{\"message\": \"hello\"}", MediaType.APPLICATION_JSON));
|
"{\"message\": \"hello\"}", MediaType.APPLICATION_JSON));
|
||||||
assertThat(this.external.getForObject("/api/external", Map.class))
|
assertThat(this.external.getForObject("/api/external", Map.class))
|
||||||
.containsKey("message");
|
.containsKey("message");
|
||||||
MockClock.clock(this.registry).add(SimpleConfig.DEFAULT_STEP);
|
|
||||||
assertThat(this.registry.find("http.client.requests").value(Statistic.Count, 1.0)
|
assertThat(this.registry.find("http.client.requests").value(Statistic.Count, 1.0)
|
||||||
.timer()).isPresent();
|
.timer()).isPresent();
|
||||||
}
|
}
|
||||||
|
@ -100,7 +101,6 @@ public class MetricsAutoConfigurationIntegrationTests {
|
||||||
@Test
|
@Test
|
||||||
public void requestMappingIsInstrumented() {
|
public void requestMappingIsInstrumented() {
|
||||||
this.loopback.getForObject("/api/people", Set.class);
|
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)
|
assertThat(this.registry.find("http.server.requests").value(Statistic.Count, 1.0)
|
||||||
.timer()).isPresent();
|
.timer()).isPresent();
|
||||||
}
|
}
|
||||||
|
@ -126,8 +126,12 @@ public class MetricsAutoConfigurationIntegrationTests {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public RestTemplate restTemplate() {
|
public RestTemplate restTemplate(MeterRegistry registry) {
|
||||||
return new RestTemplate();
|
RestTemplate restTemplate = new RestTemplate();
|
||||||
|
MetricsRestTemplateCustomizer customizer = new MetricsRestTemplateCustomizer(
|
||||||
|
registry, new DefaultRestTemplateExchangeTagsProvider(), "http.client.requests", false);
|
||||||
|
customizer.customize(restTemplate);
|
||||||
|
return restTemplate;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,12 +27,10 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||||
*/
|
*/
|
||||||
public class DatadogPropertiesConfigAdapterTests {
|
public class DatadogPropertiesConfigAdapterTests {
|
||||||
|
|
||||||
@Test
|
@Test(expected = IllegalStateException.class)
|
||||||
public void apiKeyInferUri() {
|
public void apiKeyIsRequired() {
|
||||||
DatadogProperties properties = new DatadogProperties();
|
DatadogProperties properties = new DatadogProperties();
|
||||||
properties.setApiKey("my-key");
|
new DatadogPropertiesConfigAdapter(properties).apiKey();
|
||||||
assertThat(new DatadogPropertiesConfigAdapter(properties).uri())
|
|
||||||
.contains("?api_key=my-key");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
@ -46,7 +46,8 @@ public class SimpleExportConfigurationTests {
|
||||||
"management.metrics.export.influx.enabled=false",
|
"management.metrics.export.influx.enabled=false",
|
||||||
"management.metrics.export.jmx.enabled=false",
|
"management.metrics.export.jmx.enabled=false",
|
||||||
"management.metrics.export.prometheus.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))
|
.withConfiguration(AutoConfigurations.of(MetricsAutoConfiguration.class))
|
||||||
.run((context) -> {
|
.run((context) -> {
|
||||||
CompositeMeterRegistry meterRegistry = context
|
CompositeMeterRegistry meterRegistry = context
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -34,7 +34,7 @@ public class CaffeineCacheMeterBinderProvider
|
||||||
@Override
|
@Override
|
||||||
public MeterBinder getMeterBinder(CaffeineCache cache, String name,
|
public MeterBinder getMeterBinder(CaffeineCache cache, String name,
|
||||||
Iterable<Tag> tags) {
|
Iterable<Tag> tags) {
|
||||||
return new CaffeineCacheMetrics(cache.getNativeCache(), tags, name);
|
return new CaffeineCacheMetrics(cache.getNativeCache(), name, tags);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -93,7 +93,9 @@ public class WebMvcMetrics {
|
||||||
}
|
}
|
||||||
|
|
||||||
void preHandle(HttpServletRequest request, Object handler) {
|
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);
|
request.setAttribute(HANDLER_REQUEST_ATTRIBUTE, handler);
|
||||||
longTaskTimed(handler).forEach((config) -> {
|
longTaskTimed(handler).forEach((config) -> {
|
||||||
if (config.getName() == null) {
|
if (config.getName() == null) {
|
||||||
|
|
|
@ -29,6 +29,7 @@ import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.context.ApplicationContext;
|
import org.springframework.context.ApplicationContext;
|
||||||
import org.springframework.core.Ordered;
|
import org.springframework.core.Ordered;
|
||||||
import org.springframework.core.annotation.Order;
|
import org.springframework.core.annotation.Order;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.web.filter.OncePerRequestFilter;
|
import org.springframework.web.filter.OncePerRequestFilter;
|
||||||
import org.springframework.web.servlet.HandlerExecutionChain;
|
import org.springframework.web.servlet.HandlerExecutionChain;
|
||||||
import org.springframework.web.servlet.handler.HandlerMappingIntrospector;
|
import org.springframework.web.servlet.handler.HandlerMappingIntrospector;
|
||||||
|
@ -102,6 +103,7 @@ public class WebMvcMetricsFilter extends OncePerRequestFilter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (NestedServletException ex) {
|
catch (NestedServletException ex) {
|
||||||
|
response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
|
||||||
metrics.record(request, response, ex.getCause());
|
metrics.record(request, response, ex.getCause());
|
||||||
throw ex;
|
throw ex;
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,14 +64,12 @@ public class MetricsEndpointWebIntegrationTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void selectByName() {
|
public void selectByName() {
|
||||||
MockClock.clock(registry).add(SimpleConfig.DEFAULT_STEP);
|
|
||||||
client.get().uri("/actuator/metrics/jvm.memory.used").exchange().expectStatus()
|
client.get().uri("/actuator/metrics/jvm.memory.used").exchange().expectStatus()
|
||||||
.isOk().expectBody().jsonPath("$.name").isEqualTo("jvm.memory.used");
|
.isOk().expectBody().jsonPath("$.name").isEqualTo("jvm.memory.used");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void selectByTag() {
|
public void selectByTag() {
|
||||||
MockClock.clock(registry).add(SimpleConfig.DEFAULT_STEP);
|
|
||||||
client.get()
|
client.get()
|
||||||
.uri("/actuator/metrics/jvm.memory.used?tag=id:Compressed%20Class%20Space")
|
.uri("/actuator/metrics/jvm.memory.used?tag=id:Compressed%20Class%20Space")
|
||||||
.exchange().expectStatus().isOk().expectBody().jsonPath("$.name")
|
.exchange().expectStatus().isOk().expectBody().jsonPath("$.name")
|
||||||
|
|
|
@ -67,7 +67,6 @@ public class MetricsRestTemplateCustomizerTests {
|
||||||
.andRespond(MockRestResponseCreators.withSuccess("OK",
|
.andRespond(MockRestResponseCreators.withSuccess("OK",
|
||||||
MediaType.APPLICATION_JSON));
|
MediaType.APPLICATION_JSON));
|
||||||
String result = this.restTemplate.getForObject("/test/{id}", String.class, 123);
|
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")
|
assertThat(this.registry.find("http.client.requests")
|
||||||
.meters()).anySatisfy((m) -> assertThat(
|
.meters()).anySatisfy((m) -> assertThat(
|
||||||
StreamSupport.stream(m.getId().getTags().spliterator(), false)
|
StreamSupport.stream(m.getId().getTags().spliterator(), false)
|
||||||
|
|
|
@ -76,7 +76,6 @@ public class WebMvcMetricsFilterAutoTimedTests {
|
||||||
public void metricsCanBeAutoTimed() throws Exception {
|
public void metricsCanBeAutoTimed() throws Exception {
|
||||||
this.mvc.perform(get("/api/10")).andExpect(status().isOk());
|
this.mvc.perform(get("/api/10")).andExpect(status().isOk());
|
||||||
|
|
||||||
this.clock.add(SimpleConfig.DEFAULT_STEP);
|
|
||||||
assertThat(
|
assertThat(
|
||||||
this.registry.find("http.server.requests").tags("status", "200").timer())
|
this.registry.find("http.server.requests").tags("status", "200").timer())
|
||||||
.hasValueSatisfying((t) -> assertThat(t.count()).isEqualTo(1));
|
.hasValueSatisfying((t) -> assertThat(t.count()).isEqualTo(1));
|
||||||
|
|
|
@ -21,6 +21,7 @@ import java.lang.annotation.ElementType;
|
||||||
import java.lang.annotation.Retention;
|
import java.lang.annotation.Retention;
|
||||||
import java.lang.annotation.RetentionPolicy;
|
import java.lang.annotation.RetentionPolicy;
|
||||||
import java.lang.annotation.Target;
|
import java.lang.annotation.Target;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.concurrent.Callable;
|
import java.util.concurrent.Callable;
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
|
||||||
|
@ -30,10 +31,18 @@ import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
import io.micrometer.core.annotation.Timed;
|
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.MeterRegistry;
|
||||||
import io.micrometer.core.instrument.Statistic;
|
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.PrometheusConfig;
|
||||||
import io.micrometer.prometheus.PrometheusMeterRegistry;
|
import io.micrometer.prometheus.PrometheusMeterRegistry;
|
||||||
|
import io.prometheus.client.CollectorRegistry;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
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.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.context.annotation.Import;
|
import org.springframework.context.annotation.Import;
|
||||||
|
import org.springframework.context.annotation.Primary;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.test.context.junit4.SpringRunner;
|
import org.springframework.test.context.junit4.SpringRunner;
|
||||||
import org.springframework.test.context.web.WebAppConfiguration;
|
import org.springframework.test.context.web.WebAppConfiguration;
|
||||||
|
@ -75,9 +85,11 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
|
||||||
@RunWith(SpringRunner.class)
|
@RunWith(SpringRunner.class)
|
||||||
@WebAppConfiguration
|
@WebAppConfiguration
|
||||||
public class WebMvcMetricsFilterTests {
|
public class WebMvcMetricsFilterTests {
|
||||||
|
@Autowired
|
||||||
|
private SimpleMeterRegistry registry;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private PrometheusMeterRegistry registry;
|
private PrometheusMeterRegistry prometheusRegistry;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private WebApplicationContext context;
|
private WebApplicationContext context;
|
||||||
|
@ -196,15 +208,15 @@ public class WebMvcMetricsFilterTests {
|
||||||
@Test
|
@Test
|
||||||
public void recordQuantiles() throws Exception {
|
public void recordQuantiles() throws Exception {
|
||||||
this.mvc.perform(get("/api/c1/percentiles/10")).andExpect(status().isOk());
|
this.mvc.perform(get("/api/c1/percentiles/10")).andExpect(status().isOk());
|
||||||
assertThat(this.registry.scrape()).contains("quantile=\"0.5\"");
|
assertThat(this.prometheusRegistry.scrape()).contains("quantile=\"0.5\"");
|
||||||
assertThat(this.registry.scrape()).contains("quantile=\"0.95\"");
|
assertThat(this.prometheusRegistry.scrape()).contains("quantile=\"0.95\"");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void recordHistogram() throws Exception {
|
public void recordHistogram() throws Exception {
|
||||||
this.mvc.perform(get("/api/c1/histogram/10")).andExpect(status().isOk());
|
this.mvc.perform(get("/api/c1/histogram/10")).andExpect(status().isOk());
|
||||||
assertThat(this.registry.scrape()).contains("le=\"0.001\"");
|
assertThat(this.prometheusRegistry.scrape()).contains("le=\"0.001\"");
|
||||||
assertThat(this.registry.scrape()).contains("le=\"30.0\"");
|
assertThat(this.prometheusRegistry.scrape()).contains("le=\"30.0\"");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Target({ ElementType.METHOD })
|
@Target({ ElementType.METHOD })
|
||||||
|
@ -218,11 +230,34 @@ public class WebMvcMetricsFilterTests {
|
||||||
@EnableWebMvc
|
@EnableWebMvc
|
||||||
@Import({ Controller1.class, Controller2.class })
|
@Import({ Controller1.class, Controller2.class })
|
||||||
static class MetricsFilterApp {
|
static class MetricsFilterApp {
|
||||||
|
@Primary
|
||||||
|
@Bean
|
||||||
|
MeterRegistry meterRegistry(Collection<MeterRegistry> registries) {
|
||||||
|
CompositeMeterRegistry composite = new CompositeMeterRegistry();
|
||||||
|
registries.forEach(composite::add);
|
||||||
|
return composite;
|
||||||
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
MeterRegistry meterRegistry() {
|
SimpleMeterRegistry simple() {
|
||||||
// one of the few registries that support aggregable percentiles
|
return new SimpleMeterRegistry();
|
||||||
return new PrometheusMeterRegistry(PrometheusConfig.DEFAULT);
|
}
|
||||||
|
|
||||||
|
@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
|
@Bean
|
||||||
|
@ -333,7 +368,6 @@ public class WebMvcMetricsFilterTests {
|
||||||
public String successful(@PathVariable Long id) {
|
public String successful(@PathVariable Long id) {
|
||||||
return id.toString();
|
return id.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static class RedirectAndNotFoundFilter extends OncePerRequestFilter {
|
static class RedirectAndNotFoundFilter extends OncePerRequestFilter {
|
||||||
|
|
|
@ -82,9 +82,8 @@ public class WebMvcMetricsIntegrationTests {
|
||||||
@Test
|
@Test
|
||||||
public void handledExceptionIsRecordedInMetricTag() throws Exception {
|
public void handledExceptionIsRecordedInMetricTag() throws Exception {
|
||||||
this.mvc.perform(get("/api/handledError")).andExpect(status().is5xxServerError());
|
this.mvc.perform(get("/api/handledError")).andExpect(status().is5xxServerError());
|
||||||
this.clock.add(SimpleConfig.DEFAULT_STEP);
|
|
||||||
assertThat(this.registry.find("http.server.requests")
|
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();
|
.isPresent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -92,9 +91,8 @@ public class WebMvcMetricsIntegrationTests {
|
||||||
public void rethrownExceptionIsRecordedInMetricTag() {
|
public void rethrownExceptionIsRecordedInMetricTag() {
|
||||||
assertThatCode(() -> this.mvc.perform(get("/api/rethrownError"))
|
assertThatCode(() -> this.mvc.perform(get("/api/rethrownError"))
|
||||||
.andExpect(status().is5xxServerError()));
|
.andExpect(status().is5xxServerError()));
|
||||||
this.clock.add(SimpleConfig.DEFAULT_STEP);
|
|
||||||
assertThat(this.registry.find("http.server.requests")
|
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();
|
.isPresent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -113,7 +113,7 @@
|
||||||
<logback.version>1.2.3</logback.version>
|
<logback.version>1.2.3</logback.version>
|
||||||
<lombok.version>1.16.18</lombok.version>
|
<lombok.version>1.16.18</lombok.version>
|
||||||
<mariadb.version>2.2.1</mariadb.version>
|
<mariadb.version>2.2.1</mariadb.version>
|
||||||
<micrometer.version>1.0.0-rc.5</micrometer.version>
|
<micrometer.version>1.0.0-rc.6</micrometer.version>
|
||||||
<mssql-jdbc.version>6.2.2.jre8</mssql-jdbc.version>
|
<mssql-jdbc.version>6.2.2.jre8</mssql-jdbc.version>
|
||||||
<mockito.version>2.13.0</mockito.version>
|
<mockito.version>2.13.0</mockito.version>
|
||||||
<mongo-driver-reactivestreams.version>1.7.0</mongo-driver-reactivestreams.version>
|
<mongo-driver-reactivestreams.version>1.7.0</mongo-driver-reactivestreams.version>
|
||||||
|
@ -879,6 +879,11 @@
|
||||||
<artifactId>micrometer-registry-jmx</artifactId>
|
<artifactId>micrometer-registry-jmx</artifactId>
|
||||||
<version>${micrometer.version}</version>
|
<version>${micrometer.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.micrometer</groupId>
|
||||||
|
<artifactId>micrometer-registry-new-relic</artifactId>
|
||||||
|
<version>${micrometer.version}</version>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>io.micrometer</groupId>
|
<groupId>io.micrometer</groupId>
|
||||||
<artifactId>micrometer-registry-prometheus</artifactId>
|
<artifactId>micrometer-registry-prometheus</artifactId>
|
||||||
|
|
Loading…
Reference in New Issue