Merge pull request #10906 from jkschneider/micrometer-rc3

* gh-10906:
  Polish Micrometer 1.0.0-rc.3 upgrade
  Upgrade to Micrometer 1.0.0-rc.3
pull/10921/merge
Phillip Webb 7 years ago
commit 144625022c

@ -19,6 +19,7 @@ package org.springframework.boot.actuate.autoconfigure.metrics;
import io.micrometer.core.instrument.binder.MeterBinder;
import io.micrometer.core.instrument.binder.jvm.JvmMemoryMetrics;
import io.micrometer.core.instrument.binder.logging.LogbackMetrics;
import io.micrometer.core.instrument.binder.system.ProcessorMetrics;
import io.micrometer.core.instrument.binder.system.UptimeMetrics;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
@ -53,4 +54,10 @@ class MeterBindersConfiguration {
return new UptimeMetrics();
}
@Bean
@ConditionalOnMissingBean(ProcessorMetrics.class)
public ProcessorMetrics processorMetrics() {
return new ProcessorMetrics();
}
}

@ -36,7 +36,7 @@ public abstract class StepRegistryProperties {
/**
* Enable publishing to the backend.
*/
private Boolean enabled = true;
private Boolean enabled;
/**
* The connection timeout for requests to the backend.

@ -18,7 +18,7 @@ package org.springframework.boot.actuate.autoconfigure.metrics.export;
import java.time.Duration;
import io.micrometer.core.instrument.spectator.step.StepRegistryConfig;
import io.micrometer.core.instrument.step.StepRegistryConfig;
/**
* Base class for {@link StepRegistryProperties} to {@link StepRegistryConfig} adapters.

@ -1,39 +0,0 @@
/*
* 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;
import java.time.Duration;
import org.springframework.boot.context.properties.ConfigurationPropertiesBinding;
import org.springframework.core.convert.converter.Converter;
/**
* A {@link Converter} to create a {@link Duration} from a {@link String}.
*
* @author Jon Schneider
* @author Andy Wilkinson
* @since 2.0.0
*/
@ConfigurationPropertiesBinding
public class StringToDurationConverter implements Converter<String, Duration> {
@Override
public Duration convert(String source) {
return Duration.parse(source);
}
}

@ -21,14 +21,12 @@ import io.micrometer.atlas.AtlasMeterRegistry;
import io.micrometer.core.instrument.Clock;
import org.springframework.boot.actuate.autoconfigure.metrics.export.MetricsExporter;
import org.springframework.boot.actuate.autoconfigure.metrics.export.StringToDurationConverter;
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;
import org.springframework.context.annotation.Import;
/**
* Configuration for exporting metrics to Atlas.
@ -39,7 +37,6 @@ import org.springframework.context.annotation.Import;
*/
@Configuration
@ConditionalOnClass(AtlasMeterRegistry.class)
@Import(StringToDurationConverter.class)
@EnableConfigurationProperties(AtlasProperties.class)
public class AtlasExportConfiguration {

@ -21,14 +21,12 @@ import io.micrometer.datadog.DatadogConfig;
import io.micrometer.datadog.DatadogMeterRegistry;
import org.springframework.boot.actuate.autoconfigure.metrics.export.MetricsExporter;
import org.springframework.boot.actuate.autoconfigure.metrics.export.StringToDurationConverter;
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;
import org.springframework.context.annotation.Import;
/**
* Configuration for exporting metrics to Datadog.
@ -39,7 +37,6 @@ import org.springframework.context.annotation.Import;
@Configuration
@ConditionalOnClass(DatadogMeterRegistry.class)
@ConditionalOnProperty("spring.metrics.datadog.api-key")
@Import(StringToDurationConverter.class)
@EnableConfigurationProperties(DatadogProperties.class)
public class DatadogExportConfiguration {

@ -16,8 +16,6 @@
package org.springframework.boot.actuate.autoconfigure.metrics.export.datadog;
import java.time.Duration;
import org.springframework.boot.actuate.autoconfigure.metrics.export.StepRegistryProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ -43,18 +41,10 @@ public class DatadogProperties extends StepRegistryProperties {
private String hostTag;
/**
* The bucket filter clamping the bucket domain of timer percentiles histograms to
* some max value. This is used to limit the number of buckets shipped to Prometheus
* to save on storage.
*/
private Duration timerPercentilesMax = Duration.ofMinutes(2);
/**
* The bucket filter clamping the bucket domain of timer percentiles histograms to
* some min value. This is used to limit the number of buckets shipped to Prometheus
* to save on storage.
* The URI to ship metrics to. If you need to publish metrics to an internal proxy
* en-route to datadoghq, you can define the location of the proxy with this.
*/
private Duration timerPercentilesMin = Duration.ofMillis(10);
private String uri;
public String getApiKey() {
return this.apiKey;
@ -72,19 +62,11 @@ public class DatadogProperties extends StepRegistryProperties {
this.hostTag = hostKey;
}
public Duration getTimerPercentilesMax() {
return this.timerPercentilesMax;
}
public void setTimerPercentilesMax(Duration timerPercentilesMax) {
this.timerPercentilesMax = timerPercentilesMax;
}
public Duration getTimerPercentilesMin() {
return this.timerPercentilesMin;
public String getUri() {
return this.uri;
}
public void setTimerPercentilesMin(Duration timerPercentilesMin) {
this.timerPercentilesMin = timerPercentilesMin;
public void setUri(String uri) {
this.uri = uri;
}
}

@ -16,8 +16,6 @@
package org.springframework.boot.actuate.autoconfigure.metrics.export.datadog;
import java.time.Duration;
import io.micrometer.datadog.DatadogConfig;
import org.springframework.boot.actuate.autoconfigure.metrics.export.StepRegistryPropertiesConfigAdapter;
@ -49,15 +47,8 @@ class DatadogPropertiesConfigAdapter
}
@Override
public Duration timerPercentilesMax() {
return get(DatadogProperties::getTimerPercentilesMax,
DatadogConfig::timerPercentilesMax);
}
@Override
public Duration timerPercentilesMin() {
return get(DatadogProperties::getTimerPercentilesMin,
DatadogConfig::timerPercentilesMin);
public String uri() {
return get(DatadogProperties::getUri, DatadogConfig::uri);
}
}

@ -22,14 +22,12 @@ import io.micrometer.ganglia.GangliaConfig;
import io.micrometer.ganglia.GangliaMeterRegistry;
import org.springframework.boot.actuate.autoconfigure.metrics.export.MetricsExporter;
import org.springframework.boot.actuate.autoconfigure.metrics.export.StringToDurationConverter;
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;
import org.springframework.context.annotation.Import;
/**
* Configuration for exporting metrics to Ganglia.
@ -39,7 +37,6 @@ import org.springframework.context.annotation.Import;
*/
@Configuration
@ConditionalOnClass(GangliaMeterRegistry.class)
@Import(StringToDurationConverter.class)
@EnableConfigurationProperties(GangliaProperties.class)
public class GangliaExportConfiguration {

@ -35,12 +35,12 @@ public class GangliaProperties {
/**
* Enable publishing to the backend.
*/
private Boolean enabled = true;
private Boolean enabled;
/**
* The step size (reporting frequency) to use.
*/
private Duration step = Duration.ofMinutes(1);
private Duration step;
/**
* The base time unit used to report rates.

@ -22,14 +22,12 @@ import io.micrometer.graphite.GraphiteConfig;
import io.micrometer.graphite.GraphiteMeterRegistry;
import org.springframework.boot.actuate.autoconfigure.metrics.export.MetricsExporter;
import org.springframework.boot.actuate.autoconfigure.metrics.export.StringToDurationConverter;
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;
import org.springframework.context.annotation.Import;
/**
* Configuration for exporting metrics to Graphite.
@ -39,7 +37,6 @@ import org.springframework.context.annotation.Import;
*/
@Configuration
@ConditionalOnClass(GraphiteMeterRegistry.class)
@Import(StringToDurationConverter.class)
@EnableConfigurationProperties(GraphiteProperties.class)
public class GraphiteExportConfiguration {

@ -35,12 +35,12 @@ public class GraphiteProperties {
/**
* Enable publishing to the backend.
*/
private Boolean enabled = true;
private Boolean enabled;
/**
* The step size (reporting frequency) to use.
*/
private Duration step = Duration.ofMinutes(1);
private Duration step;
/**
* The base time unit used to report rates.
@ -65,7 +65,7 @@ public class GraphiteProperties {
/**
* Protocol to use while shipping data to Graphite.
*/
private GraphiteProtocol protocol = GraphiteProtocol.Pickled;
private GraphiteProtocol protocol;
public Boolean getEnabled() {
return this.enabled;

@ -21,14 +21,12 @@ import io.micrometer.influx.InfluxConfig;
import io.micrometer.influx.InfluxMeterRegistry;
import org.springframework.boot.actuate.autoconfigure.metrics.export.MetricsExporter;
import org.springframework.boot.actuate.autoconfigure.metrics.export.StringToDurationConverter;
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;
import org.springframework.context.annotation.Import;
/**
* Configuration for exporting metrics to Influx.
@ -38,7 +36,6 @@ import org.springframework.context.annotation.Import;
*/
@Configuration
@ConditionalOnClass(InfluxMeterRegistry.class)
@Import(StringToDurationConverter.class)
@EnableConfigurationProperties(InfluxProperties.class)
public class InfluxExportConfiguration {

@ -16,8 +16,6 @@
package org.springframework.boot.actuate.autoconfigure.metrics.export.influx;
import java.time.Duration;
import io.micrometer.influx.InfluxConsistency;
import org.springframework.boot.actuate.autoconfigure.metrics.export.StepRegistryProperties;
@ -31,6 +29,7 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
*/
@ConfigurationProperties(prefix = "spring.metrics.export.influx")
public class InfluxProperties extends StepRegistryProperties {
/**
* The tag that will be mapped to "host" when shipping metrics to Influx, or
* {@code null} if host should be omitted on publishing.
@ -68,20 +67,6 @@ public class InfluxProperties extends StepRegistryProperties {
*/
private Boolean compressed;
/**
* The bucket filter clamping the bucket domain of timer percentiles histograms to
* some max value. This is used to limit the number of buckets shipped to Prometheus
* to save on storage.
*/
private Duration timerPercentilesMax = Duration.ofMinutes(2);
/**
* The bucket filter clamping the bucket domain of timer percentiles histograms to
* some min value. This is used to limit the number of buckets shipped to Prometheus
* to save on storage.
*/
private Duration timerPercentilesMin = Duration.ofMillis(10);
public String getDb() {
return this.db;
}
@ -138,19 +123,4 @@ public class InfluxProperties extends StepRegistryProperties {
this.compressed = compressed;
}
public Duration getTimerPercentilesMax() {
return this.timerPercentilesMax;
}
public void setTimerPercentilesMax(Duration timerPercentilesMax) {
this.timerPercentilesMax = timerPercentilesMax;
}
public Duration getTimerPercentilesMin() {
return this.timerPercentilesMin;
}
public void setTimerPercentilesMin(Duration timerPercentilesMin) {
this.timerPercentilesMin = timerPercentilesMin;
}
}

@ -16,8 +16,6 @@
package org.springframework.boot.actuate.autoconfigure.metrics.export.influx;
import java.time.Duration;
import io.micrometer.influx.InfluxConfig;
import io.micrometer.influx.InfluxConsistency;
@ -74,16 +72,4 @@ class InfluxPropertiesConfigAdapter
return get(InfluxProperties::getCompressed, InfluxConfig::compressed);
}
@Override
public Duration timerPercentilesMax() {
return get(InfluxProperties::getTimerPercentilesMax,
InfluxConfig::timerPercentilesMax);
}
@Override
public Duration timerPercentilesMin() {
return get(InfluxProperties::getTimerPercentilesMin,
InfluxConfig::timerPercentilesMin);
}
}

@ -32,27 +32,18 @@ public class PrometheusProperties {
/**
* Enable publishing to Prometheus.
*/
private Boolean enabled = true;
private Boolean enabled;
/**
* Enable publishing descriptions as part of the scrape payload to Prometheus. Turn
* this off to minimize the amount of data sent on each scrape.
*/
private Boolean descriptions = true;
private Boolean descriptions;
/**
* The bucket filter clamping the bucket domain of timer percentiles histograms to
* some max value. This is used to limit the number of buckets shipped to Prometheus
* to save on storage.
* The step size (reporting frequency) to use.
*/
private Duration timerPercentilesMax = Duration.ofMinutes(2);
/**
* The bucket filter clamping the bucket domain of timer percentiles histograms to
* some min value. This is used to limit the number of buckets shipped to Prometheus
* to save on storage.
*/
private Duration timerPercentilesMin = Duration.ofMillis(10);
private Duration step;
public Boolean getEnabled() {
return this.enabled;
@ -70,20 +61,12 @@ public class PrometheusProperties {
this.descriptions = descriptions;
}
public Duration getTimerPercentilesMax() {
return this.timerPercentilesMax;
}
public void setTimerPercentilesMax(Duration timerPercentilesMax) {
this.timerPercentilesMax = timerPercentilesMax;
}
public Duration getTimerPercentilesMin() {
return this.timerPercentilesMin;
public Duration getStep() {
return this.step;
}
public void setTimerPercentilesMin(Duration timerPercentilesMin) {
this.timerPercentilesMin = timerPercentilesMin;
public void setStep(Duration step) {
this.step = step;
}
}

@ -49,15 +49,8 @@ class PrometheusPropertiesConfigAdapter
}
@Override
public Duration timerPercentilesMin() {
return get(PrometheusProperties::getTimerPercentilesMin,
PrometheusConfig::timerPercentilesMin);
}
@Override
public Duration timerPercentilesMax() {
return get(PrometheusProperties::getTimerPercentilesMax,
PrometheusConfig::timerPercentilesMax);
public Duration step() {
return get(PrometheusProperties::getStep, PrometheusConfig::step);
}
}

@ -17,6 +17,7 @@
package org.springframework.boot.actuate.autoconfigure.metrics.export.simple;
import io.micrometer.core.instrument.Clock;
import io.micrometer.core.instrument.simple.SimpleConfig;
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
import org.springframework.boot.actuate.autoconfigure.metrics.export.MetricsExporter;
@ -39,8 +40,8 @@ public class SimpleExportConfiguration {
@Bean
@ConditionalOnProperty(value = "spring.metrics.export.simple.enabled", matchIfMissing = true)
@ConditionalOnMissingBean(MetricsExporter.class)
public MetricsExporter simpleExporter(Clock clock) {
return () -> new SimpleMeterRegistry(clock);
public MetricsExporter simpleExporter(SimpleConfig config, Clock clock) {
return () -> new SimpleMeterRegistry(config, clock);
}
@Bean
@ -49,4 +50,10 @@ public class SimpleExportConfiguration {
return Clock.SYSTEM;
}
@Bean
@ConditionalOnMissingBean(SimpleConfig.class)
public SimpleConfig simpleConfig(SimpleProperties simpleProperties) {
return new SimplePropertiesConfigAdapter(simpleProperties);
}
}

@ -16,6 +16,8 @@
package org.springframework.boot.actuate.autoconfigure.metrics.export.simple;
import java.time.Duration;
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ -30,9 +32,17 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(prefix = "spring.metrics.export.simple")
public class SimpleProperties {
private boolean enabled = true;
/**
* Enable publishing to the backend.
*/
private boolean enabled;
/**
* The step size (reporting frequency) to use.
*/
private Duration step = Duration.ofSeconds(10);
public boolean isEnabled() {
public boolean getEnabled() {
return this.enabled;
}
@ -40,4 +50,11 @@ public class SimpleProperties {
this.enabled = enabled;
}
public Duration getStep() {
return this.step;
}
public void setStep(Duration step) {
this.step = step;
}
}

@ -0,0 +1,53 @@
/*
* 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.simple;
import java.time.Duration;
import io.micrometer.core.instrument.simple.SimpleConfig;
import org.springframework.boot.actuate.autoconfigure.metrics.export.PropertiesConfigAdapter;
/**
* Adapter to convert {@link SimpleProperties} to a {@link SimpleConfig}.
*
* @author Jon Schneider
* @since 2.0.0
*/
public class SimplePropertiesConfigAdapter extends
PropertiesConfigAdapter<SimpleProperties, SimpleConfig> implements SimpleConfig {
private static final SimpleConfig DEFAULTS = (key) -> null;
public SimplePropertiesConfigAdapter(SimpleProperties properties) {
super(properties, DEFAULTS);
}
@Override
public String get(String k) {
return null;
}
@Override
public boolean enabled() {
return get(SimpleProperties::getEnabled, SimpleConfig::enabled);
}
@Override
public Duration step() {
return get(SimpleProperties::getStep, SimpleConfig::step);
}
}

@ -17,18 +17,17 @@
package org.springframework.boot.actuate.autoconfigure.metrics.export.statsd;
import io.micrometer.core.instrument.Clock;
import io.micrometer.core.instrument.util.HierarchicalNameMapper;
import io.micrometer.statsd.StatsdConfig;
import io.micrometer.statsd.StatsdMeterRegistry;
import org.springframework.boot.actuate.autoconfigure.metrics.export.MetricsExporter;
import org.springframework.boot.actuate.autoconfigure.metrics.export.StringToDurationConverter;
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;
import org.springframework.context.annotation.Import;
/**
* Configuration for exporting metrics to StatsD.
@ -38,7 +37,6 @@ import org.springframework.context.annotation.Import;
*/
@Configuration
@ConditionalOnClass(StatsdMeterRegistry.class)
@Import(StringToDurationConverter.class)
@EnableConfigurationProperties(StatsdProperties.class)
public class StatsdExportConfiguration {
@ -50,8 +48,9 @@ public class StatsdExportConfiguration {
@Bean
@ConditionalOnProperty(value = "spring.metrics.export.statsd.enabled", matchIfMissing = true)
public MetricsExporter statsdExporter(StatsdConfig statsdConfig, Clock clock) {
return () -> new StatsdMeterRegistry(statsdConfig, clock);
public MetricsExporter statsdExporter(StatsdConfig statsdConfig,
HierarchicalNameMapper hierarchicalNameMapper, Clock clock) {
return () -> new StatsdMeterRegistry(statsdConfig, hierarchicalNameMapper, clock);
}
@Bean
@ -60,4 +59,10 @@ public class StatsdExportConfiguration {
return Clock.SYSTEM;
}
@Bean
@ConditionalOnMissingBean
public HierarchicalNameMapper hierarchicalNameMapper() {
return HierarchicalNameMapper.DEFAULT;
}
}

@ -34,7 +34,7 @@ public class StatsdProperties {
/**
* Enable publishing to the backend.
*/
private Boolean enabled = true;
private Boolean enabled;
/**
* Variant of the StatsD line protocol to use.
@ -68,36 +68,6 @@ public class StatsdProperties {
*/
private Integer queueSize = Integer.MAX_VALUE;
/**
* Used to create a bucket filter clamping the bucket domain of timer percentiles
* histograms to some max value. This is used to limit the number of buckets shipped
* to StatsD to save on storage.
*/
private Duration timerPercentilesMax = Duration.ofMinutes(2);
/**
* Used to create a bucket filter clamping the bucket domain of timer percentiles
* histograms to some min value. This is used to limit the number of buckets shipped
* to StatsD to save on storage.
*/
private Duration timerPercentilesMin = Duration.ofMillis(10);
public Duration getTimerPercentilesMax() {
return this.timerPercentilesMax;
}
public void setTimerPercentilesMax(Duration timerPercentilesMax) {
this.timerPercentilesMax = timerPercentilesMax;
}
public Duration getTimerPercentilesMin() {
return this.timerPercentilesMin;
}
public void setTimerPercentilesMin(Duration timerPercentilesMin) {
this.timerPercentilesMin = timerPercentilesMin;
}
public Boolean getEnabled() {
return this.enabled;
}

@ -20,8 +20,8 @@ import io.micrometer.core.instrument.MeterRegistry;
import org.springframework.boot.actuate.autoconfigure.metrics.MetricsProperties;
import org.springframework.boot.actuate.metrics.web.servlet.DefaultWebMvcTagsProvider;
import org.springframework.boot.actuate.metrics.web.servlet.MetricsHandlerInterceptor;
import org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetrics;
import org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter;
import org.springframework.boot.actuate.metrics.web.servlet.WebMvcTagsProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
@ -30,8 +30,7 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.handler.HandlerMappingIntrospector;
/**
* Configures instrumentation of Spring Web MVC servlet-based request mappings.
@ -61,27 +60,9 @@ public class WebMvcMetricsConfiguration {
}
@Bean
public MetricsHandlerInterceptor webMetricsInterceptor(
WebMvcMetrics controllerMetrics) {
return new MetricsHandlerInterceptor(controllerMetrics);
}
@Configuration
public class MetricsServletRequestInterceptorConfiguration
implements WebMvcConfigurer {
private final MetricsHandlerInterceptor handlerInterceptor;
public MetricsServletRequestInterceptorConfiguration(
MetricsHandlerInterceptor handlerInterceptor) {
this.handlerInterceptor = handlerInterceptor;
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(this.handlerInterceptor);
}
public WebMvcMetricsFilter webMetricsFilter(WebMvcMetrics controllerMetrics,
HandlerMappingIntrospector introspector) {
return new WebMvcMetricsFilter(controllerMetrics, introspector);
}
}

@ -21,10 +21,12 @@ import java.util.Map;
import java.util.Set;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.MockClock;
import io.micrometer.core.instrument.Statistic;
import io.micrometer.core.instrument.binder.MeterBinder;
import io.micrometer.core.instrument.binder.jvm.JvmMemoryMetrics;
import io.micrometer.core.instrument.binder.logging.LogbackMetrics;
import io.micrometer.core.instrument.simple.SimpleConfig;
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
import org.junit.Test;
import org.junit.runner.RunWith;
@ -90,6 +92,7 @@ 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();
}
@ -97,6 +100,7 @@ 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();
}
@ -118,7 +122,7 @@ public class MetricsAutoConfigurationIntegrationTests {
@Bean
public MeterRegistry registry() {
return new SimpleMeterRegistry();
return new SimpleMeterRegistry(SimpleConfig.DEFAULT, new MockClock());
}
@Bean

@ -20,11 +20,14 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.concurrent.TimeUnit;
import java.util.function.ToDoubleFunction;
import io.micrometer.core.instrument.Meter.Id;
import io.micrometer.core.instrument.FunctionCounter;
import io.micrometer.core.instrument.Gauge;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Tag;
import io.micrometer.core.instrument.Tags;
import io.micrometer.core.instrument.TimeGauge;
import io.micrometer.core.instrument.binder.MeterBinder;
import org.springframework.beans.factory.SmartInitializingSingleton;
@ -60,131 +63,95 @@ public class SpringIntegrationMetrics implements MeterBinder, SmartInitializingS
@Override
public void bindTo(MeterRegistry registry) {
bindChannelNames(registry);
bindHandlerNames(registry);
bindSourceNames(registry);
registerGuage(registry, this.configurer, this.tags,
"spring.integration.channelNames",
"The number of spring integration channels",
(configurer) -> configurer.getChannelNames().length);
registerGuage(registry, this.configurer, this.tags,
"spring.integration.handlerNames",
"The number of spring integration handlers",
(configurer) -> configurer.getHandlerNames().length);
registerGuage(registry, this.configurer, this.tags,
"spring.integration.sourceNames",
"The number of spring integration sources",
(configurer) -> configurer.getSourceNames().length);
this.registries.add(registry);
}
private void bindChannelNames(MeterRegistry registry) {
Id id = registry.createId("spring.integration.channelNames", this.tags,
"The number of spring integration channels");
registry.gauge(id, this.configurer, (c) -> c.getChannelNames().length);
}
private void bindHandlerNames(MeterRegistry registry) {
Id id = registry.createId("spring.integration.handlerNames", this.tags,
"The number of spring integration handlers");
registry.gauge(id, this.configurer, (c) -> c.getHandlerNames().length);
}
private void bindSourceNames(MeterRegistry registry) {
Id id = registry.createId("spring.integration.sourceNames", this.tags,
"The number of spring integration sources");
registry.gauge(id, this.configurer, (c) -> c.getSourceNames().length);
}
private void addSourceMetrics(MeterRegistry registry) {
for (String sourceName : this.configurer.getSourceNames()) {
addSourceMetrics(registry, sourceName);
for (String source : this.configurer.getSourceNames()) {
MessageSourceMetrics sourceMetrics = this.configurer.getSourceMetrics(source);
Iterable<Tag> tagsWithSource = Tags.concat(this.tags, "source", source);
registerFunctionCounter(registry, sourceMetrics, tagsWithSource,
"spring.integration.source.messages",
"The number of successful handler calls",
MessageSourceMetrics::getMessageCount);
}
}
private void addSourceMetrics(MeterRegistry registry, String sourceName) {
MessageSourceMetrics sourceMetrics = this.configurer.getSourceMetrics(sourceName);
Iterable<Tag> tags = Tags.concat(this.tags, "source", sourceName);
Id id = registry.createId("spring.integration.source.messages", tags,
"The number of successful handler calls");
registry.more().counter(id, sourceMetrics, MessageSourceMetrics::getMessageCount);
}
private void addHandlerMetrics(MeterRegistry registry) {
for (String handler : this.configurer.getHandlerNames()) {
addHandlerMetrics(registry, handler);
MessageHandlerMetrics handlerMetrics = this.configurer
.getHandlerMetrics(handler);
Iterable<Tag> tagsWithHandler = Tags.concat(this.tags, "handler", handler);
registerTimedGauge(registry, handlerMetrics, tagsWithHandler,
"spring.integration.handler.duration.max",
"The maximum handler duration",
MessageHandlerMetrics::getMaxDuration);
registerTimedGauge(registry, handlerMetrics, tagsWithHandler,
"spring.integration.handler.duration.min",
"The minimum handler duration",
MessageHandlerMetrics::getMinDuration);
registerTimedGauge(registry, handlerMetrics, tagsWithHandler,
"spring.integration.handler.duration.mean",
"The mean handler duration", MessageHandlerMetrics::getMeanDuration);
registerGuage(registry, handlerMetrics, tagsWithHandler,
"spring.integration.handler.activeCount",
"The number of active handlers",
MessageHandlerMetrics::getActiveCount);
}
}
private void addHandlerMetrics(MeterRegistry registry, String handler) {
MessageHandlerMetrics handlerMetrics = this.configurer.getHandlerMetrics(handler);
// FIXME could use improvement to dynamically compute the handler name with its
// ID, which can change after
// creation as shown in the SpringIntegrationApplication sample.
Iterable<Tag> tags = Tags.concat(this.tags, "handler", handler);
addDurationMax(registry, handlerMetrics, tags);
addDurationMin(registry, handlerMetrics, tags);
addDurationMean(registry, handlerMetrics, tags);
addActiveCount(registry, handlerMetrics, tags);
}
private void addDurationMax(MeterRegistry registry,
MessageHandlerMetrics handlerMetrics, Iterable<Tag> tags) {
Id id = registry.createId("spring.integration.handler.duration.max", tags,
"The maximum handler duration");
registry.more().timeGauge(id, handlerMetrics, TimeUnit.MILLISECONDS,
MessageHandlerMetrics::getMaxDuration);
}
private void addDurationMin(MeterRegistry registry,
MessageHandlerMetrics handlerMetrics, Iterable<Tag> tags) {
Id id = registry.createId("spring.integration.handler.duration.min", tags,
"The minimum handler duration");
registry.more().timeGauge(id, handlerMetrics, TimeUnit.MILLISECONDS,
MessageHandlerMetrics::getMinDuration);
}
private void addDurationMean(MeterRegistry registry,
MessageHandlerMetrics handlerMetrics, Iterable<Tag> tags) {
Id id = registry.createId("spring.integration.handler.duration.mean", tags,
"The mean handler duration");
registry.more().timeGauge(id, handlerMetrics, TimeUnit.MILLISECONDS,
MessageHandlerMetrics::getMeanDuration);
}
private void addActiveCount(MeterRegistry registry,
MessageHandlerMetrics handlerMetrics, Iterable<Tag> tags) {
Id id = registry.createId("spring.integration.handler.activeCount", tags,
"The number of active handlers");
registry.gauge(id, handlerMetrics, MessageHandlerMetrics::getActiveCount);
}
private void addChannelMetrics(MeterRegistry registry) {
for (String channel : this.configurer.getChannelNames()) {
addChannelMetrics(registry, channel);
}
}
private void addChannelMetrics(MeterRegistry registry, String channel) {
Iterable<Tag> tags = Tags.concat(this.tags, "channel", channel);
MessageChannelMetrics channelMetrics = this.configurer.getChannelMetrics(channel);
addSendErrors(registry, tags, channelMetrics);
addChannelSends(registry, tags, channelMetrics);
if (channelMetrics instanceof PollableChannelManagement) {
addReceives(registry, tags, channelMetrics);
MessageChannelMetrics channelMetrics = this.configurer
.getChannelMetrics(channel);
Iterable<Tag> tagsWithChannel = Tags.concat(this.tags, "channel", channel);
registerFunctionCounter(registry, channelMetrics, tagsWithChannel,
"spring.integration.channel.sendErrors",
"The number of failed sends (either throwing an exception or rejected by the channel)",
MessageChannelMetrics::getSendErrorCount);
registerFunctionCounter(registry, channelMetrics, tagsWithChannel,
"spring.integration.channel.sends", "The number of successful sends",
MessageChannelMetrics::getSendCount);
if (channelMetrics instanceof PollableChannelManagement) {
registerFunctionCounter(registry,
(PollableChannelManagement) channelMetrics, tagsWithChannel,
"spring.integration.receives", "The number of messages received",
PollableChannelManagement::getReceiveCount);
}
}
}
private void addSendErrors(MeterRegistry registry, Iterable<Tag> tags,
MessageChannelMetrics channelMetrics) {
Id id = registry.createId("spring.integration.channel.sendErrors", tags,
"The number of failed sends (either throwing an exception or "
+ "rejected by the channel)");
registry.more().counter(id, channelMetrics,
MessageChannelMetrics::getSendErrorCount);
private <T> void registerGuage(MeterRegistry registry, T object, Iterable<Tag> tags,
String name, String description, ToDoubleFunction<T> value) {
Gauge.Builder<?> builder = Gauge.builder(name, object, value);
builder.tags(this.tags).description(description).register(registry);
}
private void addReceives(MeterRegistry registry, Iterable<Tag> tags,
MessageChannelMetrics channelMetrics) {
Id id = registry.createId("spring.integration.channel.receives", tags,
"The number of messages received");
registry.more().counter(id, (PollableChannelManagement) channelMetrics,
PollableChannelManagement::getReceiveCount);
private <T> void registerTimedGauge(MeterRegistry registry, T object,
Iterable<Tag> tags, String name, String description,
ToDoubleFunction<T> value) {
TimeGauge.Builder<?> builder = TimeGauge.builder(name, object,
TimeUnit.MILLISECONDS, value);
builder.tags(tags).description(description).register(registry);
}
private void addChannelSends(MeterRegistry registry, Iterable<Tag> tags,
MessageChannelMetrics channelMetrics) {
Id id = registry.createId("spring.integration.channel.sends", tags,
"The number of successful sends");
registry.more().counter(id, channelMetrics, MessageChannelMetrics::getSendCount);
private <T> void registerFunctionCounter(MeterRegistry registry, T object,
Iterable<Tag> tags, String name, String description,
ToDoubleFunction<T> value) {
FunctionCounter.builder(name, object, value).tags(tags)
.description(description).register(registry);
}
@Override

@ -23,7 +23,6 @@ import java.util.concurrent.TimeUnit;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Timer;
import io.micrometer.core.instrument.stats.hist.Histogram;
import org.springframework.core.NamedThreadLocal;
import org.springframework.http.HttpRequest;
@ -98,13 +97,10 @@ class MetricsClientHttpRequestInterceptor implements ClientHttpRequestIntercepto
private Timer.Builder getTimeBuilder(HttpRequest request,
ClientHttpResponse response) {
Timer.Builder builder = Timer.builder(this.metricName)
return Timer.builder(this.metricName)
.tags(this.tagProvider.getTags(urlTemplate.get(), request, response))
.description("Timer of RestTemplate operation");
if (this.recordPercentiles) {
builder = builder.histogram(Histogram.percentilesTime());
}
return builder;
.description("Timer of RestTemplate operation")
.publishPercentileHistogram(this.recordPercentiles);
}
}

@ -39,7 +39,7 @@ public class DefaultWebMvcTagsProvider implements WebMvcTagsProvider {
*/
@Override
public Iterable<Tag> httpLongRequestTags(HttpServletRequest request, Object handler) {
return Arrays.asList(WebMvcTags.method(request), WebMvcTags.uri(request));
return Arrays.asList(WebMvcTags.method(request), WebMvcTags.uri(request, null));
}
/**
@ -52,8 +52,9 @@ public class DefaultWebMvcTagsProvider implements WebMvcTagsProvider {
@Override
public Iterable<Tag> httpRequestTags(HttpServletRequest request,
HttpServletResponse response, Throwable ex) {
return Arrays.asList(WebMvcTags.method(request), WebMvcTags.uri(request),
WebMvcTags.exception(ex), WebMvcTags.status(response));
return Arrays.asList(WebMvcTags.method(request),
WebMvcTags.uri(request, response), WebMvcTags.exception(ex),
WebMvcTags.status(response));
}
}

@ -1,52 +0,0 @@
/*
* 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.metrics.web.servlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
/**
* Intercepts incoming HTTP requests and records metrics about execution time and results.
*
* @author Jon Schneider
* @since 2.0.0
*/
public class MetricsHandlerInterceptor extends HandlerInterceptorAdapter {
private final WebMvcMetrics webMvcMetrics;
public MetricsHandlerInterceptor(WebMvcMetrics webMvcMetrics) {
this.webMvcMetrics = webMvcMetrics;
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
Object handler) throws Exception {
this.webMvcMetrics.preHandle(request, handler);
return super.preHandle(request, response, handler);
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
Object handler, Exception ex) throws Exception {
this.webMvcMetrics.record(request, response, ex);
super.afterCompletion(request, response, handler, ex);
}
}

@ -17,7 +17,7 @@
package org.springframework.boot.actuate.metrics.web.servlet;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.Map;
@ -31,21 +31,21 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import io.micrometer.core.annotation.Timed;
import io.micrometer.core.annotation.TimedSet;
import io.micrometer.core.instrument.LongTaskTimer;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Tag;
import io.micrometer.core.instrument.Tags;
import io.micrometer.core.instrument.Timer;
import io.micrometer.core.instrument.stats.hist.Histogram;
import io.micrometer.core.instrument.stats.quantile.WindowSketchQuantiles;
import io.micrometer.core.instrument.util.AnnotationUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.resource.ResourceHttpRequestHandler;
/**
* Support class for Spring MVC metrics.
@ -154,25 +154,20 @@ public class WebMvcMetrics {
HttpServletResponse response, Throwable thrown, TimerConfig config) {
Timer.Builder builder = Timer.builder(config.getName())
.tags(this.tagsProvider.httpRequestTags(request, response, thrown))
.tags(config.getExtraTags()).description("Timer of servlet request");
if (config.getQuantiles().length > 0) {
WindowSketchQuantiles quantiles = WindowSketchQuantiles
.quantiles(config.getQuantiles()).create();
builder = builder.quantiles(quantiles);
}
if (config.isPercentiles()) {
builder = builder.histogram(Histogram.percentilesTime());
.tags(config.getExtraTags()).description("Timer of servlet request")
.publishPercentileHistogram(config.isHistogram());
if (config.getPercentiles().length > 0) {
builder = builder.publishPercentiles(config.getPercentiles());
}
return builder;
}
private LongTaskTimer longTaskTimer(TimerConfig config, HttpServletRequest request,
Object handler) {
Iterable<Tag> tags = Tags.concat(
this.tagsProvider.httpLongRequestTags(request, handler),
config.getExtraTags());
return this.registry.more().longTaskTimer(this.registry.createId(config.getName(),
tags, "Timer of long servlet request"));
return LongTaskTimer.builder(config.getName())
.tags(this.tagsProvider.httpLongRequestTags(request, handler))
.tags(config.getExtraTags()).description("Timer of long servlet request")
.register(this.registry);
}
private Set<TimerConfig> longTaskTimed(Object handler) {
@ -194,6 +189,11 @@ public class WebMvcMetrics {
if (handler instanceof HandlerMethod) {
return timed((HandlerMethod) handler);
}
if ((handler == null || handler instanceof ResourceHttpRequestHandler)
&& this.autoTimeRequests) {
return Collections.singleton(
new TimerConfig(getServerRequestName(), this.recordAsPercentiles));
}
return Collections.emptySet();
}
@ -220,10 +220,15 @@ public class WebMvcMetrics {
}
private Stream<Timed> findTimedAnnotations(AnnotatedElement element) {
if (element instanceof Class<?>) {
return AnnotationUtils.findTimed((Class<?>) element);
Timed timed = AnnotationUtils.findAnnotation(element, Timed.class);
if (timed != null) {
return Stream.of(timed);
}
TimedSet ts = AnnotationUtils.findAnnotation(element, TimedSet.class);
if (ts != null) {
return Arrays.stream(ts.value());
}
return AnnotationUtils.findTimed((Method) element);
return Stream.empty();
}
private TimerConfig fromAnnotation(Timed timed) {
@ -240,22 +245,22 @@ public class WebMvcMetrics {
private final Iterable<Tag> extraTags;
private final double[] quantiles;
private final double[] percentiles;
private final boolean percentiles;
private final boolean histogram;
TimerConfig(String name, boolean percentiles) {
TimerConfig(String name, boolean histogram) {
this.name = name;
this.extraTags = Collections.emptyList();
this.quantiles = new double[0];
this.percentiles = percentiles;
this.percentiles = new double[0];
this.histogram = histogram;
}
TimerConfig(Timed timed, Supplier<String> name) {
this.name = buildName(timed, name);
this.extraTags = Tags.zip(timed.extraTags());
this.quantiles = timed.quantiles();
this.percentiles = timed.percentiles();
this.histogram = timed.histogram();
}
private String buildName(Timed timed, Supplier<String> name) {
@ -271,16 +276,16 @@ public class WebMvcMetrics {
return this.name;
}
public Iterable<Tag> getExtraTags() {
Iterable<Tag> getExtraTags() {
return this.extraTags;
}
public double[] getQuantiles() {
return this.quantiles;
double[] getPercentiles() {
return this.percentiles;
}
public boolean isPercentiles() {
return this.percentiles;
boolean isHistogram() {
return this.histogram;
}
@Override

@ -0,0 +1,108 @@
/*
* 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.metrics.web.servlet;
import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.servlet.HandlerExecutionChain;
import org.springframework.web.servlet.handler.HandlerMappingIntrospector;
import org.springframework.web.servlet.handler.MatchableHandlerMapping;
import org.springframework.web.util.NestedServletException;
/**
* Intercepts incoming HTTP requests and records metrics about Spring MVC execution time
* and results.
*
* @author Jon Schneider
* @since 2.0.0
*/
@Order(Ordered.HIGHEST_PRECEDENCE)
public class WebMvcMetricsFilter extends OncePerRequestFilter {
private static final Logger logger = LoggerFactory
.getLogger(WebMvcMetricsFilter.class);
private final WebMvcMetrics webMvcMetrics;
private final HandlerMappingIntrospector mappingIntrospector;
public WebMvcMetricsFilter(WebMvcMetrics webMvcMetrics,
HandlerMappingIntrospector mappingIntrospector) {
this.webMvcMetrics = webMvcMetrics;
this.mappingIntrospector = mappingIntrospector;
}
@Override
protected boolean shouldNotFilterAsyncDispatch() {
return false;
}
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
HandlerExecutionChain handlerExecutionChain = getHandlerExecutionChain(request);
Object handler = (handlerExecutionChain == null ? null
: handlerExecutionChain.getHandler());
filterWithMetrics(request, response, filterChain, handler);
}
private HandlerExecutionChain getHandlerExecutionChain(HttpServletRequest request) {
try {
MatchableHandlerMapping matchableHandlerMapping = this.mappingIntrospector
.getMatchableHandlerMapping(request);
return (matchableHandlerMapping == null ? null
: matchableHandlerMapping.getHandler(request));
}
catch (Exception ex) {
if (logger.isDebugEnabled()) {
logger.debug("Unable to time request", ex);
}
return null;
}
}
private void filterWithMetrics(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain, Object handler)
throws IOException, ServletException, NestedServletException {
this.webMvcMetrics.preHandle(request, handler);
try {
filterChain.doFilter(request, response);
// When an async operation is complete, the whole filter gets called again
// with isAsyncStarted = false
if (!request.isAsyncStarted()) {
this.webMvcMetrics.record(request, response, null);
}
}
catch (NestedServletException ex) {
this.webMvcMetrics.record(request, response, ex.getCause());
throw ex;
}
}
}

@ -21,6 +21,7 @@ import javax.servlet.http.HttpServletResponse;
import io.micrometer.core.instrument.Tag;
import org.springframework.http.HttpStatus;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.HandlerMapping;
@ -62,9 +63,19 @@ public final class WebMvcTags {
* available, falling back to the request's {@link HttpServletRequest#getPathInfo()
* path info} if necessary.
* @param request the request
* @param response the response
* @return the uri tag derived from the request
*/
public static Tag uri(HttpServletRequest request) {
public static Tag uri(HttpServletRequest request, HttpServletResponse response) {
if (response != null) {
HttpStatus status = HttpStatus.valueOf(response.getStatus());
if (status.is3xxRedirection()) {
return Tag.of("uri", "REDIRECTION");
}
if (status.equals(HttpStatus.NOT_FOUND)) {
return Tag.of("uri", "NOT_FOUND");
}
}
String uri = (String) request
.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE);
if (uri == null) {

@ -21,8 +21,10 @@ import java.util.Optional;
import java.util.stream.Stream;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.MockClock;
import io.micrometer.core.instrument.Statistic;
import io.micrometer.core.instrument.composite.CompositeMeterRegistry;
import io.micrometer.core.instrument.simple.SimpleConfig;
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
import org.junit.Test;
@ -36,7 +38,8 @@ import static org.assertj.core.api.Assertions.assertThat;
*/
public class MetricsEndpointTests {
private final MeterRegistry registry = new SimpleMeterRegistry();
private final MeterRegistry registry = new SimpleMeterRegistry(SimpleConfig.DEFAULT,
new MockClock());
private final MetricsEndpoint endpoint = new MetricsEndpoint(this.registry);
@ -74,6 +77,7 @@ public class MetricsEndpointTests {
this.registry.counter("cache", "result", "hit", "host", "1").increment(2);
this.registry.counter("cache", "result", "miss", "host", "1").increment(2);
this.registry.counter("cache", "result", "hit", "host", "2").increment(2);
MockClock.clock(this.registry).add(SimpleConfig.DEFAULT_STEP);
MetricsEndpoint.MetricResponse response = this.endpoint.metric("cache",
Collections.emptyList());
assertThat(response.getName()).isEqualTo("cache");
@ -87,6 +91,7 @@ public class MetricsEndpointTests {
@Test
public void metricWithSpaceInTagValue() {
this.registry.counter("counter", "key", "a space").increment(2);
MockClock.clock(this.registry).add(SimpleConfig.DEFAULT_STEP);
MetricsEndpoint.MetricResponse response = this.endpoint.metric("counter",
Collections.singletonList("key:a space"));
assertThat(response.getName()).isEqualTo("counter");

@ -22,7 +22,9 @@ import java.util.Map;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.MockClock;
import io.micrometer.core.instrument.binder.jvm.JvmMemoryMetrics;
import io.micrometer.core.instrument.simple.SimpleConfig;
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
import org.junit.Test;
import org.junit.runner.RunWith;
@ -43,30 +45,34 @@ import static org.assertj.core.api.Assertions.assertThat;
@RunWith(WebEndpointRunners.class)
public class MetricsEndpointWebIntegrationTests {
private static MeterRegistry registry = new SimpleMeterRegistry(SimpleConfig.DEFAULT,
new MockClock());
private static WebTestClient client;
private final ObjectMapper mapper = new ObjectMapper();
@SuppressWarnings("unchecked")
@Test
@SuppressWarnings("unchecked")
public void listNames() throws IOException {
String responseBody = MetricsEndpointWebIntegrationTests.client.get()
.uri("/application/metrics").exchange().expectStatus().isOk()
.expectBody(String.class).returnResult().getResponseBody();
String responseBody = client.get().uri("/application/metrics").exchange()
.expectStatus().isOk().expectBody(String.class).returnResult()
.getResponseBody();
Map<String, List<String>> names = this.mapper.readValue(responseBody, Map.class);
assertThat(names.get("names")).containsOnlyOnce("jvm.memory.used");
}
@Test
public void selectByName() throws IOException {
MetricsEndpointWebIntegrationTests.client.get()
.uri("/application/metrics/jvm.memory.used").exchange().expectStatus()
MockClock.clock(registry).add(SimpleConfig.DEFAULT_STEP);
client.get().uri("/application/metrics/jvm.memory.used").exchange().expectStatus()
.isOk().expectBody().jsonPath("$.name").isEqualTo("jvm.memory.used");
}
@Test
public void selectByTag() {
MetricsEndpointWebIntegrationTests.client.get()
MockClock.clock(registry).add(SimpleConfig.DEFAULT_STEP);
client.get()
.uri("/application/metrics/jvm.memory.used?tag=id:Compressed%20Class%20Space")
.exchange().expectStatus().isOk().expectBody().jsonPath("$.name")
.isEqualTo("jvm.memory.used");
@ -77,7 +83,7 @@ public class MetricsEndpointWebIntegrationTests {
@Bean
public MeterRegistry registry() {
return new SimpleMeterRegistry();
return registry;
}
@Bean

@ -19,8 +19,10 @@ package org.springframework.boot.actuate.metrics.web.client;
import java.util.stream.StreamSupport;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.MockClock;
import io.micrometer.core.instrument.Statistic;
import io.micrometer.core.instrument.Tag;
import io.micrometer.core.instrument.simple.SimpleConfig;
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
import org.junit.Test;
@ -42,7 +44,8 @@ public class MetricsRestTemplateCustomizerTests {
@Test
public void interceptRestTemplate() {
MeterRegistry registry = new SimpleMeterRegistry();
MeterRegistry registry = new SimpleMeterRegistry(SimpleConfig.DEFAULT,
new MockClock());
RestTemplate restTemplate = new RestTemplate();
MetricsRestTemplateCustomizer customizer = new MetricsRestTemplateCustomizer(
registry, new DefaultRestTemplateExchangeTagsProvider(),
@ -55,13 +58,14 @@ public class MetricsRestTemplateCustomizerTests {
.andRespond(MockRestResponseCreators.withSuccess("OK",
MediaType.APPLICATION_JSON));
String result = restTemplate.getForObject("/test/{id}", String.class, 123);
MockClock.clock(registry).add(SimpleConfig.DEFAULT_STEP);
assertThat(registry.find("http.client.requests")
.meters()).anySatisfy(m -> assertThat(
StreamSupport.stream(m.getId().getTags().spliterator(), false)
.map(Tag::getKey)).doesNotContain("bucket"));
assertThat(registry.find("http.client.requests")
.tags("method", "GET", "uri", "/test/{id}", "status", "200")
.value(Statistic.Count, 1.0).timer()).isPresent();
assertThat(registry.find("http.client.requests").meters()
.stream().flatMap((m) -> StreamSupport
.stream(m.getId().getTags().spliterator(), false))
.map(Tag::getKey)).contains("bucket");
assertThat(result).isEqualTo("OK");
mockServer.verify();
}

@ -16,7 +16,10 @@
package org.springframework.boot.actuate.metrics.web.servlet;
import io.micrometer.core.instrument.Clock;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.MockClock;
import io.micrometer.core.instrument.simple.SimpleConfig;
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
import org.junit.Before;
import org.junit.Test;
@ -36,38 +39,44 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.handler.HandlerMappingIntrospector;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
* Tests for {@link MetricsHandlerInterceptor} with auto-timed server requests.
*
* @author Jon Schneider
*/
@RunWith(SpringRunner.class)
@WebAppConfiguration
public class MetricsHandlerInterceptorAutoTimedTests {
public class WebMvcMetricsFilterAutoTimedTests {
@Autowired
private MeterRegistry registry;
@Autowired
private MockClock clock;
@Autowired
private WebApplicationContext context;
private MockMvc mvc;
@Autowired
private WebMvcMetricsFilter filter;
@Before
public void setupMockMvc() {
this.mvc = MockMvcBuilders.webAppContextSetup(this.context).build();
this.mvc = MockMvcBuilders.webAppContextSetup(this.context)
.addFilters(this.filter).build();
}
@Test
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));
@ -75,35 +84,29 @@ public class MetricsHandlerInterceptorAutoTimedTests {
@Configuration
@EnableWebMvc
@Import(Controller.class)
@Import({ Controller.class })
static class TestConfiguration {
@Bean
MeterRegistry meterRegistry() {
return new SimpleMeterRegistry();
MockClock clock() {
return new MockClock();
}
@Bean
WebMvcMetrics webMvcMetrics(MeterRegistry meterRegistry) {
return new WebMvcMetrics(meterRegistry, new DefaultWebMvcTagsProvider(),
"http.server.requests", true, true);
MeterRegistry meterRegistry(Clock clock) {
return new SimpleMeterRegistry(SimpleConfig.DEFAULT, clock);
}
@Configuration
static class HandlerInterceptorConfiguration implements WebMvcConfigurer {
private final WebMvcMetrics webMvcMetrics;
HandlerInterceptorConfiguration(WebMvcMetrics webMvcMetrics) {
this.webMvcMetrics = webMvcMetrics;
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(
new MetricsHandlerInterceptor(this.webMvcMetrics));
}
@Bean
public WebMvcMetrics controllerMetrics(MeterRegistry registry) {
return new WebMvcMetrics(registry, new DefaultWebMvcTagsProvider(),
"http.server.requests", true, false);
}
@Bean
public WebMvcMetricsFilter webMetricsFilter(WebMvcMetrics controllerMetrics,
HandlerMappingIntrospector introspector) {
return new WebMvcMetricsFilter(controllerMetrics, introspector);
}
}

@ -16,17 +16,24 @@
package org.springframework.boot.actuate.metrics.web.servlet;
import java.io.IOException;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import java.util.stream.StreamSupport;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import io.micrometer.core.annotation.Timed;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Statistic;
import io.micrometer.core.instrument.Tag;
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
import io.micrometer.prometheus.PrometheusConfig;
import io.micrometer.prometheus.PrometheusMeterRegistry;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@ -48,10 +55,10 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.handler.HandlerMappingIntrospector;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatCode;
@ -61,27 +68,32 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
* Tests for {@link MetricsHandlerInterceptor}.
* Tests for {@link WebMvcMetricsFilter}
*
* @author Jon Schneider
*/
@RunWith(SpringRunner.class)
@WebAppConfiguration
public class MetricsHandlerInterceptorTests {
private static final CountDownLatch longRequestCountDown = new CountDownLatch(1);
public class WebMvcMetricsFilterTests {
@Autowired
private MeterRegistry registry;
private PrometheusMeterRegistry registry;
@Autowired
private WebApplicationContext context;
@Autowired
private WebMvcMetricsFilter filter;
private MockMvc mvc;
@Autowired
private CountDownLatch asyncLatch;
@Before
public void setupMockMvc() {
this.mvc = MockMvcBuilders.webAppContextSetup(this.context).build();
this.mvc = MockMvcBuilders.webAppContextSetup(this.context)
.addFilters(this.filter, new RedirectAndNotFoundFilter()).build();
}
@Test
@ -92,6 +104,14 @@ public class MetricsHandlerInterceptorTests {
.value(Statistic.Count, 1.0).timer()).isPresent();
}
@Test
public void subclassedTimedMethod() throws Exception {
this.mvc.perform(get("/api/c1/metaTimed/10")).andExpect(status().isOk());
assertThat(this.registry.find("http.server.requests")
.tags("status", "200", "uri", "/api/c1/metaTimed/{id}")
.value(Statistic.Count, 1.0).timer()).isPresent();
}
@Test
public void untimedMethod() throws Exception {
this.mvc.perform(get("/api/c1/untimed/10")).andExpect(status().isOk());
@ -102,23 +122,40 @@ public class MetricsHandlerInterceptorTests {
@Test
public void timedControllerClass() throws Exception {
this.mvc.perform(get("/api/c2/10")).andExpect(status().isOk());
assertThat(
this.registry.find("http.server.requests").tags("status", "200").timer())
.hasValueSatisfying((t) -> assertThat(t.count()).isEqualTo(1));
assertThat(this.registry.find("http.server.requests").tags("status", "200")
.value(Statistic.Count, 1.0).timer()).isPresent();
}
@Test
public void badClientRequest() throws Exception {
this.mvc.perform(get("/api/c1/oops")).andExpect(status().is4xxClientError());
assertThat(
this.registry.find("http.server.requests").tags("status", "400").timer())
.hasValueSatisfying((t) -> assertThat(t.count()).isEqualTo(1));
assertThat(this.registry.find("http.server.requests").tags("status", "400")
.value(Statistic.Count, 1.0).timer()).isPresent();
}
@Test
public void redirectRequest() throws Exception {
this.mvc.perform(get("/api/redirect")
.header(RedirectAndNotFoundFilter.TEST_MISBEHAVE_HEADER, "302"))
.andExpect(status().is3xxRedirection());
assertThat(this.registry.find("http.server.requests").tags("uri", "REDIRECTION")
.tags("status", "302").timer()).isPresent();
}
@Test
public void notFoundRequest() throws Exception {
this.mvc.perform(get("/api/not/found")
.header(RedirectAndNotFoundFilter.TEST_MISBEHAVE_HEADER, "404"))
.andExpect(status().is4xxClientError());
assertThat(this.registry.find("http.server.requests").tags("uri", "NOT_FOUND")
.tags("status", "404").timer()).isPresent();
}
@Test
public void unhandledError() throws Exception {
assertThatCode(() -> this.mvc.perform(get("/api/c1/unhandledError/10"))
.andExpect(status().isOk())).hasCauseInstanceOf(RuntimeException.class);
.andExpect(status().isOk()))
.hasRootCauseInstanceOf(RuntimeException.class);
assertThat(this.registry.find("http.server.requests")
.tags("exception", "RuntimeException").value(Statistic.Count, 1.0)
.timer()).isPresent();
@ -128,16 +165,15 @@ public class MetricsHandlerInterceptorTests {
public void longRunningRequest() throws Exception {
MvcResult result = this.mvc.perform(get("/api/c1/long/10"))
.andExpect(request().asyncStarted()).andReturn();
// the request is not prematurely recorded as complete
assertThat(this.registry.find("http.server.requests").tags("uri", "/api/c1/async")
.timer()).isNotPresent();
// while the mapping is running, it contributes to the activeTasks count
assertThat(this.registry.find("my.long.request").tags("region", "test")
.value(Statistic.Count, 1.0).longTaskTimer()).isPresent();
// once the mapping completes, we can gather information about status, etc.
longRequestCountDown.countDown();
this.asyncLatch.countDown();
this.mvc.perform(asyncDispatch(result)).andExpect(status().isOk());
assertThat(this.registry.find("http.server.requests").tags("status", "200")
.value(Statistic.Count, 1.0).timer()).isPresent();
}
@ -159,55 +195,51 @@ public class MetricsHandlerInterceptorTests {
@Test
public void recordQuantiles() throws Exception {
this.mvc.perform(get("/api/c1/quantiles/10")).andExpect(status().isOk());
assertThat(this.registry.find("http.server.requests").tags("quantile", "0.5")
.gauge()).isNotEmpty();
assertThat(this.registry.find("http.server.requests").tags("quantile", "0.95")
.gauge()).isNotEmpty();
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\"");
}
@Test
public void recordPercentiles() throws Exception {
this.mvc.perform(get("/api/c1/percentiles/10")).andExpect(status().isOk());
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\"");
}
@Target({ ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Timed(percentiles = 0.95)
public @interface Timed95 {
assertThat(this.registry.find("http.server.requests").meters()
.stream().flatMap((m) -> StreamSupport
.stream(m.getId().getTags().spliterator(), false))
.map(Tag::getKey)).contains("bucket");
}
@Configuration
@EnableWebMvc
@Import({ Controller1.class, Controller2.class })
static class TestConfiguration {
static class MetricsFilterApp {
@Bean
MeterRegistry meterRegistry() {
return new SimpleMeterRegistry();
// one of the few registries that support aggregable percentiles
return new PrometheusMeterRegistry(PrometheusConfig.DEFAULT);
}
@Bean
WebMvcMetrics webMvcMetrics(MeterRegistry meterRegistry) {
return new WebMvcMetrics(meterRegistry, new DefaultWebMvcTagsProvider(),
"http.server.requests", true, true);
CountDownLatch asyncLatch() {
return new CountDownLatch(1);
}
@Configuration
static class HandlerInterceptorConfiguration implements WebMvcConfigurer {
private final WebMvcMetrics webMvcMetrics;
HandlerInterceptorConfiguration(WebMvcMetrics webMvcMetrics) {
this.webMvcMetrics = webMvcMetrics;
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(
new MetricsHandlerInterceptor(this.webMvcMetrics));
}
@Bean
public WebMvcMetrics controllerMetrics(MeterRegistry registry) {
return new WebMvcMetrics(registry, new DefaultWebMvcTagsProvider(),
"http.server.requests", true, false);
}
@Bean
public WebMvcMetricsFilter webMetricsFilter(WebMvcMetrics controllerMetrics,
HandlerMappingIntrospector introspector) {
return new WebMvcMetricsFilter(controllerMetrics, introspector);
}
}
@ -216,21 +248,26 @@ public class MetricsHandlerInterceptorTests {
@RequestMapping("/api/c1")
static class Controller1 {
private final CountDownLatch asyncLatch;
Controller1(CountDownLatch asyncLatch) {
this.asyncLatch = asyncLatch;
}
@Timed(extraTags = { "public", "true" })
@GetMapping("/{id}")
public String successfulWithExtraTags(@PathVariable Long id) {
return id.toString();
}
@Timed // contains dimensions for status, etc. that can't be known until after the
// response is sent
@Timed
@Timed(value = "my.long.request", extraTags = { "region",
"test" }, longTask = true) // in progress metric
"test" }, longTask = true)
@GetMapping("/long/{id}")
public Callable<String> takesLongTimeToSatisfy(@PathVariable Long id) {
return () -> {
try {
longRequestCountDown.await();
this.asyncLatch.await();
}
catch (InterruptedException e) {
throw new RuntimeException(e);
@ -247,13 +284,13 @@ public class MetricsHandlerInterceptorTests {
@Timed
@GetMapping("/error/{id}")
public String alwaysThrowsException(@PathVariable Long id) {
throw new IllegalStateException("Boom on $id!");
throw new IllegalStateException("Boom on " + id + "!");
}
@Timed
@GetMapping("/unhandledError/{id}")
public String alwaysThrowsUnhandledException(@PathVariable Long id) {
throw new RuntimeException("Boom on $id!");
throw new RuntimeException("Boom on " + id + "!");
}
@Timed
@ -262,15 +299,21 @@ public class MetricsHandlerInterceptorTests {
return id;
}
@Timed(quantiles = { 0.5, 0.95 })
@GetMapping("/quantiles/{id}")
public String quantiles(@PathVariable String id) {
@Timed(percentiles = { 0.50, 0.95 })
@GetMapping("/percentiles/{id}")
public String percentiles(@PathVariable String id) {
return id;
}
@Timed(percentiles = true)
@GetMapping("/percentiles/{id}")
public String percentiles(@PathVariable String id) {
@Timed(histogram = true)
@GetMapping("/histogram/{id}")
public String histogram(@PathVariable String id) {
return id;
}
@Timed95
@GetMapping("/metaTimed/{id}")
public String meta(@PathVariable String id) {
return id;
}
@ -294,4 +337,23 @@ public class MetricsHandlerInterceptorTests {
}
static class RedirectAndNotFoundFilter extends OncePerRequestFilter {
static final String TEST_MISBEHAVE_HEADER = "x-test-misbehave-status";
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String misbehave = request.getHeader(TEST_MISBEHAVE_HEADER);
if (misbehave != null) {
response.setStatus(Integer.parseInt(misbehave));
}
else {
filterChain.doFilter(request, response);
}
}
}
}

@ -17,8 +17,11 @@
package org.springframework.boot.actuate.metrics.web.servlet;
import io.micrometer.core.annotation.Timed;
import io.micrometer.core.instrument.Clock;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.MockClock;
import io.micrometer.core.instrument.Statistic;
import io.micrometer.core.instrument.simple.SimpleConfig;
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
import org.junit.Before;
import org.junit.Test;
@ -40,8 +43,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.handler.HandlerMappingIntrospector;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatCode;
@ -63,16 +65,24 @@ public class WebMvcMetricsIntegrationTests {
@Autowired
private SimpleMeterRegistry registry;
@Autowired
private MockClock clock;
private MockMvc mvc;
@Autowired
private WebMvcMetricsFilter filter;
@Before
public void setupMockMvc() {
this.mvc = MockMvcBuilders.webAppContextSetup(this.context).build();
this.mvc = MockMvcBuilders.webAppContextSetup(this.context)
.addFilters(this.filter).build();
}
@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())
.isPresent();
@ -82,23 +92,36 @@ public class WebMvcMetricsIntegrationTests {
public void rethrownExceptionIsRecordedInMetricTag() throws Exception {
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())
.isPresent();
}
@Configuration
@EnableWebMvc
static class TestConfiguration {
@Bean
WebMvcMetrics webMvcMetrics(MeterRegistry meterRegistry) {
return new WebMvcMetrics(meterRegistry, new DefaultWebMvcTagsProvider(),
"http.server.requests", true, true);
MockClock clock() {
return new MockClock();
}
@Bean
MeterRegistry meterRegistry(Clock clock) {
return new SimpleMeterRegistry(SimpleConfig.DEFAULT, clock);
}
@Bean
public WebMvcMetrics controllerMetrics(MeterRegistry registry) {
return new WebMvcMetrics(registry, new DefaultWebMvcTagsProvider(),
"http.server.requests", true, false);
}
@Bean
MeterRegistry registry() {
return new SimpleMeterRegistry();
public WebMvcMetricsFilter webMetricsFilter(WebMvcMetrics controllerMetrics,
HandlerMappingIntrospector introspector) {
return new WebMvcMetricsFilter(controllerMetrics, introspector);
}
@RestController
@ -123,24 +146,6 @@ public class WebMvcMetricsIntegrationTests {
}
@Configuration
@EnableWebMvc
static class HandlerInterceptorConfiguration implements WebMvcConfigurer {
private final WebMvcMetrics webMvcMetrics;
HandlerInterceptorConfiguration(WebMvcMetrics webMvcMetrics) {
this.webMvcMetrics = webMvcMetrics;
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(
new MetricsHandlerInterceptor(this.webMvcMetrics));
}
}
}
static class Exception1 extends RuntimeException {

@ -60,6 +60,7 @@
<gson.version>2.8.2</gson.version>
<h2.version>1.4.196</h2.version>
<hamcrest.version>1.3</hamcrest.version>
<hdrhistogram.version>2.1.10</hdrhistogram.version>
<hazelcast.version>3.9</hazelcast.version>
<hazelcast-hibernate5.version>1.2.2</hazelcast-hibernate5.version>
<hibernate.version>5.2.12.Final</hibernate.version>
@ -114,7 +115,7 @@
<logback.version>1.2.3</logback.version>
<lombok.version>1.16.18</lombok.version>
<mariadb.version>2.1.2</mariadb.version>
<micrometer.version>1.0.0-rc.2</micrometer.version>
<micrometer.version>1.0.0-rc.3</micrometer.version>
<mssql-jdbc.version>6.2.2.jre8</mssql-jdbc.version>
<mockito.version>2.11.0</mockito.version>
<mongo-driver-reactivestreams.version>1.6.0</mongo-driver-reactivestreams.version>
@ -2056,6 +2057,11 @@
<artifactId>hamcrest-library</artifactId>
<version>${hamcrest.version}</version>
</dependency>
<dependency>
<groupId>org.hdrhistogram</groupId>
<artifactId>HdrHistogram</artifactId>
<version>${hdrhistogram.version}</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-c3p0</artifactId>

Loading…
Cancel
Save