Commit ccc820f7 authored by Jon Schneider's avatar Jon Schneider Committed by Stephane Nicoll

Upgrade to Micrometer 1.0.0-rc.6

See gh-11598
parent 69d5b7a4
...@@ -82,13 +82,26 @@ public class MetricsAutoConfiguration { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 ...@@ -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 ...@@ -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; ...@@ -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; ...@@ -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
......
/*
* 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;
}
}
/*
* 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; ...@@ -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 { ...@@ -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; ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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; ...@@ -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 { ...@@ -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
......
/*
* 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 ...@@ -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 { ...@@ -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; ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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; ...@@ -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; ...@@ -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; ...@@ -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. ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 @@ ...@@ -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 @@ ...@@ -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>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment