Polish Micrometer 1.0.0-rc.3 upgrade

Polish Micrometer 1.0.0-rc.3 upgrade changes.

Closes gh-10906
pull/10921/merge
Phillip Webb 7 years ago
parent 65e6876025
commit cf17e5708b

@ -59,4 +59,5 @@ class MeterBindersConfiguration {
public ProcessorMetrics processorMetrics() { public ProcessorMetrics processorMetrics() {
return new ProcessorMetrics(); return new ProcessorMetrics();
} }
} }

@ -36,7 +36,7 @@ public abstract class StepRegistryProperties {
/** /**
* Enable publishing to the backend. * Enable publishing to the backend.
*/ */
private Boolean enabled = true; private Boolean enabled;
/** /**
* The connection timeout for requests to the backend. * The connection timeout for requests to the backend.
@ -67,7 +67,7 @@ public abstract class StepRegistryProperties {
this.step = step; this.step = step;
} }
public Boolean isEnabled() { public Boolean getEnabled() {
return this.enabled; return this.enabled;
} }

@ -53,7 +53,7 @@ public abstract class StepRegistryPropertiesConfigAdapter<T extends StepRegistry
@Override @Override
public boolean enabled() { public boolean enabled() {
return get(T::isEnabled, C::enabled); return get(T::getEnabled, C::enabled);
} }
@Override @Override

@ -49,7 +49,7 @@ class AtlasPropertiesConfigAdapter extends
@Override @Override
public boolean enabled() { public boolean enabled() {
return get(AtlasProperties::isEnabled, AtlasConfig::enabled); return get(AtlasProperties::getEnabled, AtlasConfig::enabled);
} }
@Override @Override

@ -41,8 +41,8 @@ public class DatadogProperties extends StepRegistryProperties {
private String hostTag; private String hostTag;
/** /**
* The URI to ship metrics to. If you need to publish metrics to an internal proxy en route to * The URI to ship metrics to. If you need to publish metrics to an internal proxy
* datadoghq, you can define the location of the proxy with this. * en-route to datadoghq, you can define the location of the proxy with this.
*/ */
private String uri; private String uri;

@ -50,4 +50,5 @@ class DatadogPropertiesConfigAdapter
public String uri() { public String uri() {
return get(DatadogProperties::getUri, DatadogConfig::uri); return get(DatadogProperties::getUri, DatadogConfig::uri);
} }
} }

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

@ -47,7 +47,7 @@ class GangliaPropertiesConfigAdapter
@Override @Override
public boolean enabled() { public boolean enabled() {
return get(GangliaProperties::isEnabled, GangliaConfig::enabled); return get(GangliaProperties::getEnabled, GangliaConfig::enabled);
} }
@Override @Override

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

@ -47,7 +47,7 @@ class GraphitePropertiesConfigAdapter
@Override @Override
public boolean enabled() { public boolean enabled() {
return get(GraphiteProperties::isEnabled, GraphiteConfig::enabled); return get(GraphiteProperties::getEnabled, GraphiteConfig::enabled);
} }
@Override @Override

@ -29,6 +29,7 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
*/ */
@ConfigurationProperties(prefix = "spring.metrics.export.influx") @ConfigurationProperties(prefix = "spring.metrics.export.influx")
public class InfluxProperties extends StepRegistryProperties { public class InfluxProperties extends StepRegistryProperties {
/** /**
* The tag that will be mapped to "host" when shipping metrics to Influx, or * The tag that will be mapped to "host" when shipping metrics to Influx, or
* {@code null} if host should be omitted on publishing. * {@code null} if host should be omitted on publishing.
@ -121,4 +122,5 @@ public class InfluxProperties extends StepRegistryProperties {
public void setCompressed(Boolean compressed) { public void setCompressed(Boolean compressed) {
this.compressed = compressed; this.compressed = compressed;
} }
} }

@ -16,8 +16,6 @@
package org.springframework.boot.actuate.autoconfigure.metrics.export.influx; package org.springframework.boot.actuate.autoconfigure.metrics.export.influx;
import java.time.Duration;
import io.micrometer.influx.InfluxConfig; import io.micrometer.influx.InfluxConfig;
import io.micrometer.influx.InfluxConsistency; import io.micrometer.influx.InfluxConsistency;
@ -73,4 +71,5 @@ class InfluxPropertiesConfigAdapter
public boolean compressed() { public boolean compressed() {
return get(InfluxProperties::getCompressed, InfluxConfig::compressed); return get(InfluxProperties::getCompressed, InfluxConfig::compressed);
} }
} }

@ -32,20 +32,20 @@ public class PrometheusProperties {
/** /**
* Enable publishing to Prometheus. * Enable publishing to Prometheus.
*/ */
private Boolean enabled = true; private Boolean enabled;
/** /**
* Enable publishing descriptions as part of the scrape payload to Prometheus. Turn * Enable publishing descriptions as part of the scrape payload to Prometheus. Turn
* this off to minimize the amount of data sent on each scrape. * this off to minimize the amount of data sent on each scrape.
*/ */
private Boolean descriptions = true; private Boolean descriptions;
/** /**
* The step size (reporting frequency) to use. * The step size (reporting frequency) to use.
*/ */
private Duration step = Duration.ofMinutes(1); private Duration step;
public Boolean isEnabled() { public Boolean getEnabled() {
return this.enabled; return this.enabled;
} }
@ -68,4 +68,5 @@ public class PrometheusProperties {
public void setStep(Duration step) { public void setStep(Duration step) {
this.step = step; this.step = step;
} }
} }

@ -52,4 +52,5 @@ class PrometheusPropertiesConfigAdapter
public Duration step() { public Duration step() {
return get(PrometheusProperties::getStep, PrometheusConfig::step); return get(PrometheusProperties::getStep, PrometheusConfig::step);
} }
} }

@ -55,4 +55,5 @@ public class SimpleExportConfiguration {
public SimpleConfig simpleConfig(SimpleProperties simpleProperties) { public SimpleConfig simpleConfig(SimpleProperties simpleProperties) {
return new SimplePropertiesConfigAdapter(simpleProperties); return new SimplePropertiesConfigAdapter(simpleProperties);
} }
} }

@ -18,10 +18,10 @@ package org.springframework.boot.actuate.autoconfigure.metrics.export.simple;
import java.time.Duration; import java.time.Duration;
import org.springframework.boot.context.properties.ConfigurationProperties;
import io.micrometer.core.instrument.simple.SimpleMeterRegistry; import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
import org.springframework.boot.context.properties.ConfigurationProperties;
/** /**
* {@link ConfigurationProperties} for configuring metrics export to a * {@link ConfigurationProperties} for configuring metrics export to a
* {@link SimpleMeterRegistry}. * {@link SimpleMeterRegistry}.
@ -35,14 +35,14 @@ public class SimpleProperties {
/** /**
* Enable publishing to the backend. * Enable publishing to the backend.
*/ */
private boolean enabled = true; private boolean enabled;
/** /**
* The step size (reporting frequency) to use. * The step size (reporting frequency) to use.
*/ */
private Duration step = Duration.ofSeconds(10); private Duration step = Duration.ofSeconds(10);
public boolean isEnabled() { public boolean getEnabled() {
return this.enabled; return this.enabled;
} }

@ -22,7 +22,6 @@ import io.micrometer.core.instrument.simple.SimpleConfig;
import org.springframework.boot.actuate.autoconfigure.metrics.export.PropertiesConfigAdapter; import org.springframework.boot.actuate.autoconfigure.metrics.export.PropertiesConfigAdapter;
/** /**
* Adapter to convert {@link SimpleProperties} to a {@link SimpleConfig}. * Adapter to convert {@link SimpleProperties} to a {@link SimpleConfig}.
* *
@ -44,7 +43,7 @@ public class SimplePropertiesConfigAdapter extends
@Override @Override
public boolean enabled() { public boolean enabled() {
return get(SimpleProperties::isEnabled, SimpleConfig::enabled); return get(SimpleProperties::getEnabled, SimpleConfig::enabled);
} }
@Override @Override

@ -48,7 +48,8 @@ public class StatsdExportConfiguration {
@Bean @Bean
@ConditionalOnProperty(value = "spring.metrics.export.statsd.enabled", matchIfMissing = true) @ConditionalOnProperty(value = "spring.metrics.export.statsd.enabled", matchIfMissing = true)
public MetricsExporter statsdExporter(StatsdConfig statsdConfig, HierarchicalNameMapper hierarchicalNameMapper, Clock clock) { public MetricsExporter statsdExporter(StatsdConfig statsdConfig,
HierarchicalNameMapper hierarchicalNameMapper, Clock clock) {
return () -> new StatsdMeterRegistry(statsdConfig, hierarchicalNameMapper, clock); return () -> new StatsdMeterRegistry(statsdConfig, hierarchicalNameMapper, clock);
} }
@ -63,4 +64,5 @@ public class StatsdExportConfiguration {
public HierarchicalNameMapper hierarchicalNameMapper() { public HierarchicalNameMapper hierarchicalNameMapper() {
return HierarchicalNameMapper.DEFAULT; return HierarchicalNameMapper.DEFAULT;
} }
} }

@ -34,7 +34,7 @@ public class StatsdProperties {
/** /**
* Enable publishing to the backend. * Enable publishing to the backend.
*/ */
private Boolean enabled = true; private Boolean enabled;
/** /**
* Variant of the StatsD line protocol to use. * Variant of the StatsD line protocol to use.
@ -68,7 +68,7 @@ public class StatsdProperties {
*/ */
private Integer queueSize = Integer.MAX_VALUE; private Integer queueSize = Integer.MAX_VALUE;
public Boolean isEnabled() { public Boolean getEnabled() {
return this.enabled; return this.enabled;
} }

@ -50,7 +50,7 @@ public class StatsdPropertiesConfigAdapter extends
@Override @Override
public boolean enabled() { public boolean enabled() {
return get(StatsdProperties::isEnabled, StatsdConfig::enabled); return get(StatsdProperties::getEnabled, StatsdConfig::enabled);
} }
@Override @Override

@ -20,8 +20,8 @@ import io.micrometer.core.instrument.MeterRegistry;
import org.springframework.boot.actuate.autoconfigure.metrics.MetricsProperties; 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.DefaultWebMvcTagsProvider;
import org.springframework.boot.actuate.metrics.web.servlet.MetricsFilter;
import org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetrics; 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.actuate.metrics.web.servlet.WebMvcTagsProvider;
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;
@ -60,7 +60,9 @@ public class WebMvcMetricsConfiguration {
} }
@Bean @Bean
public MetricsFilter webMetricsFilter(WebMvcMetrics controllerMetrics, HandlerMappingIntrospector introspector) { public WebMvcMetricsFilter webMetricsFilter(WebMvcMetrics controllerMetrics,
return new MetricsFilter(controllerMetrics, introspector); HandlerMappingIntrospector introspector) {
return new WebMvcMetricsFilter(controllerMetrics, introspector);
} }
} }

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

@ -1,50 +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;
import java.lang.reflect.AnnotatedElement;
import java.util.Arrays;
import java.util.stream.Stream;
import io.micrometer.core.annotation.Timed;
import io.micrometer.core.annotation.TimedSet;
import org.springframework.core.annotation.AnnotationUtils;
/**
* A utility class for finding {@link Timed} annotations.
*
* @author Jon Schneider
* @since 2.0.0
*/
public final class TimedUtils {
private TimedUtils() {
}
public static Stream<Timed> findTimedAnnotations(AnnotatedElement element) {
Timed t = AnnotationUtils.findAnnotation(element, Timed.class);
if (t != null)
return Stream.of(t);
TimedSet ts = AnnotationUtils.findAnnotation(element, TimedSet.class);
if (ts != null) {
return Arrays.stream(ts.value());
}
return Stream.empty();
}
}

@ -20,12 +20,22 @@ import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.function.ToDoubleFunction;
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; import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.integration.support.management.*; import org.springframework.integration.support.management.IntegrationManagementConfigurer;
import org.springframework.integration.support.management.MessageChannelMetrics;
import io.micrometer.core.instrument.*; import org.springframework.integration.support.management.MessageHandlerMetrics;
import io.micrometer.core.instrument.binder.MeterBinder; import org.springframework.integration.support.management.MessageSourceMetrics;
import org.springframework.integration.support.management.PollableChannelManagement;
/** /**
* A {@link MeterBinder} for Spring Integration metrics. * A {@link MeterBinder} for Spring Integration metrics.
@ -53,21 +63,18 @@ public class SpringIntegrationMetrics implements MeterBinder, SmartInitializingS
@Override @Override
public void bindTo(MeterRegistry registry) { public void bindTo(MeterRegistry registry) {
Gauge.builder("spring.integration.channelNames", this.configurer, registerGuage(registry, this.configurer, this.tags,
c -> c.getChannelNames().length).tags(tags) "spring.integration.channelNames",
.description("The number of spring integration channels") "The number of spring integration channels",
.register(registry); (configurer) -> configurer.getChannelNames().length);
registerGuage(registry, this.configurer, this.tags,
Gauge.builder("spring.integration.handlerNames", configurer, "spring.integration.handlerNames",
c -> c.getHandlerNames().length).tags(this.tags) "The number of spring integration handlers",
.description("The number of spring integration handlers") (configurer) -> configurer.getHandlerNames().length);
.register(registry); registerGuage(registry, this.configurer, this.tags,
"spring.integration.sourceNames",
Gauge.builder("spring.integration.sourceNames", configurer, "The number of spring integration sources",
c -> c.getSourceNames().length).tags(this.tags) (configurer) -> configurer.getSourceNames().length);
.description("The number of spring integration sources")
.register(registry);
this.registries.add(registry); this.registries.add(registry);
} }
@ -75,13 +82,10 @@ public class SpringIntegrationMetrics implements MeterBinder, SmartInitializingS
for (String source : this.configurer.getSourceNames()) { for (String source : this.configurer.getSourceNames()) {
MessageSourceMetrics sourceMetrics = this.configurer.getSourceMetrics(source); MessageSourceMetrics sourceMetrics = this.configurer.getSourceMetrics(source);
Iterable<Tag> tagsWithSource = Tags.concat(this.tags, "source", source); Iterable<Tag> tagsWithSource = Tags.concat(this.tags, "source", source);
registerFunctionCounter(registry, sourceMetrics, tagsWithSource,
FunctionCounter "spring.integration.source.messages",
.builder("spring.integration.source.messages", sourceMetrics, "The number of successful handler calls",
MessageSourceMetrics::getMessageCount) MessageSourceMetrics::getMessageCount);
.tags(tagsWithSource)
.description("The number of successful handler calls")
.register(registry);
} }
} }
@ -89,33 +93,22 @@ public class SpringIntegrationMetrics implements MeterBinder, SmartInitializingS
for (String handler : this.configurer.getHandlerNames()) { for (String handler : this.configurer.getHandlerNames()) {
MessageHandlerMetrics handlerMetrics = this.configurer MessageHandlerMetrics handlerMetrics = this.configurer
.getHandlerMetrics(handler); .getHandlerMetrics(handler);
// TODO could use improvement to dynamically commute the handler name with its
// ID, which can change after creation as shown in the
// SpringIntegrationApplication sample.
Iterable<Tag> tagsWithHandler = Tags.concat(this.tags, "handler", handler); Iterable<Tag> tagsWithHandler = Tags.concat(this.tags, "handler", handler);
registerTimedGauge(registry, handlerMetrics, tagsWithHandler,
TimeGauge "spring.integration.handler.duration.max",
.builder("spring.integration.handler.duration.max", handlerMetrics, "The maximum handler duration",
TimeUnit.MILLISECONDS, MessageHandlerMetrics::getMaxDuration) MessageHandlerMetrics::getMaxDuration);
.tags(tagsWithHandler).description("The maximum handler duration") registerTimedGauge(registry, handlerMetrics, tagsWithHandler,
.register(registry); "spring.integration.handler.duration.min",
"The minimum handler duration",
TimeGauge MessageHandlerMetrics::getMinDuration);
.builder("spring.integration.handler.duration.min", handlerMetrics, registerTimedGauge(registry, handlerMetrics, tagsWithHandler,
TimeUnit.MILLISECONDS, MessageHandlerMetrics::getMinDuration) "spring.integration.handler.duration.mean",
.tags(tagsWithHandler).description("The minimum handler duration") "The mean handler duration", MessageHandlerMetrics::getMeanDuration);
.register(registry); registerGuage(registry, handlerMetrics, tagsWithHandler,
"spring.integration.handler.activeCount",
TimeGauge "The number of active handlers",
.builder("spring.integration.handler.duration.mean", handlerMetrics, MessageHandlerMetrics::getActiveCount);
TimeUnit.MILLISECONDS, MessageHandlerMetrics::getMeanDuration)
.tags(tagsWithHandler).description("The mean handler duration")
.register(registry);
Gauge.builder("spring.integration.handler.activeCount", handlerMetrics,
MessageHandlerMetrics::getActiveCount).tags(tagsWithHandler)
.description("The number of active handlers").register(registry);
} }
} }
@ -124,33 +117,43 @@ public class SpringIntegrationMetrics implements MeterBinder, SmartInitializingS
MessageChannelMetrics channelMetrics = this.configurer MessageChannelMetrics channelMetrics = this.configurer
.getChannelMetrics(channel); .getChannelMetrics(channel);
Iterable<Tag> tagsWithChannel = Tags.concat(this.tags, "channel", channel); Iterable<Tag> tagsWithChannel = Tags.concat(this.tags, "channel", channel);
registerFunctionCounter(registry, channelMetrics, tagsWithChannel,
FunctionCounter "spring.integration.channel.sendErrors",
.builder("spring.integration.channel.sendErrors", channelMetrics, "The number of failed sends (either throwing an exception or rejected by the channel)",
MessageChannelMetrics::getSendErrorCount) MessageChannelMetrics::getSendErrorCount);
.tags(tagsWithChannel) registerFunctionCounter(registry, channelMetrics, tagsWithChannel,
.description( "spring.integration.channel.sends", "The number of successful sends",
"The number of failed sends (either throwing an exception or rejected by the channel)") MessageChannelMetrics::getSendCount);
.register(registry);
FunctionCounter
.builder("spring.integration.channel.sends", channelMetrics,
MessageChannelMetrics::getSendCount)
.tags(tagsWithChannel).description("The number of successful sends")
.register(registry);
if (channelMetrics instanceof PollableChannelManagement) { if (channelMetrics instanceof PollableChannelManagement) {
FunctionCounter registerFunctionCounter(registry,
.builder("spring.integration.receives", (PollableChannelManagement) channelMetrics, tagsWithChannel,
(PollableChannelManagement) channelMetrics, "spring.integration.receives", "The number of messages received",
PollableChannelManagement::getReceiveCount) PollableChannelManagement::getReceiveCount);
.tags(tagsWithChannel)
.description("The number of messages received")
.register(registry);
} }
} }
} }
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 <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 <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 @Override
public void afterSingletonsInstantiated() { public void afterSingletonsInstantiated() {
this.registries.forEach((registry) -> { this.registries.forEach((registry) -> {

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

@ -17,6 +17,7 @@
package org.springframework.boot.actuate.metrics.web.servlet; package org.springframework.boot.actuate.metrics.web.servlet;
import java.lang.reflect.AnnotatedElement; import java.lang.reflect.AnnotatedElement;
import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.IdentityHashMap; import java.util.IdentityHashMap;
import java.util.Map; import java.util.Map;
@ -24,20 +25,27 @@ import java.util.Set;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.function.Supplier; import java.util.function.Supplier;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.servlet.http.HttpServletRequest; 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.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 org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.springframework.boot.actuate.metrics.TimedUtils;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.ObjectUtils; import org.springframework.util.ObjectUtils;
import org.springframework.web.context.request.RequestAttributes; import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.method.HandlerMethod; import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.resource.ResourceHttpRequestHandler;
import io.micrometer.core.annotation.Timed;
import io.micrometer.core.instrument.*;
/** /**
* Support class for Spring MVC metrics. * Support class for Spring MVC metrics.
@ -147,7 +155,7 @@ public class WebMvcMetrics {
Timer.Builder builder = Timer.builder(config.getName()) Timer.Builder builder = Timer.builder(config.getName())
.tags(this.tagsProvider.httpRequestTags(request, response, thrown)) .tags(this.tagsProvider.httpRequestTags(request, response, thrown))
.tags(config.getExtraTags()).description("Timer of servlet request") .tags(config.getExtraTags()).description("Timer of servlet request")
.publishPercentileHistogram(config.histogram); .publishPercentileHistogram(config.isHistogram());
if (config.getPercentiles().length > 0) { if (config.getPercentiles().length > 0) {
builder = builder.publishPercentiles(config.getPercentiles()); builder = builder.publishPercentiles(config.getPercentiles());
} }
@ -155,11 +163,10 @@ public class WebMvcMetrics {
} }
private LongTaskTimer longTaskTimer(TimerConfig config, HttpServletRequest request, private LongTaskTimer longTaskTimer(TimerConfig config, HttpServletRequest request,
Object handler) { Object handler) {
return LongTaskTimer.builder(config.getName()) return LongTaskTimer.builder(config.getName())
.tags(this.tagsProvider.httpLongRequestTags(request, handler)) .tags(this.tagsProvider.httpLongRequestTags(request, handler))
.tags(config.getExtraTags()) .tags(config.getExtraTags()).description("Timer of long servlet request")
.description("Timer of long servlet request")
.register(this.registry); .register(this.registry);
} }
@ -182,6 +189,11 @@ public class WebMvcMetrics {
if (handler instanceof HandlerMethod) { if (handler instanceof HandlerMethod) {
return timed((HandlerMethod) handler); return timed((HandlerMethod) handler);
} }
if ((handler == null || handler instanceof ResourceHttpRequestHandler)
&& this.autoTimeRequests) {
return Collections.singleton(
new TimerConfig(getServerRequestName(), this.recordAsPercentiles));
}
return Collections.emptySet(); return Collections.emptySet();
} }
@ -198,15 +210,27 @@ public class WebMvcMetrics {
} }
private Set<TimerConfig> getNonLongTaskAnnotationConfig(AnnotatedElement element) { private Set<TimerConfig> getNonLongTaskAnnotationConfig(AnnotatedElement element) {
return TimedUtils.findTimedAnnotations(element).filter((t) -> !t.longTask()) return findTimedAnnotations(element).filter((t) -> !t.longTask())
.map(this::fromAnnotation).collect(Collectors.toSet()); .map(this::fromAnnotation).collect(Collectors.toSet());
} }
private Set<TimerConfig> getLongTaskAnnotationConfig(AnnotatedElement element) { private Set<TimerConfig> getLongTaskAnnotationConfig(AnnotatedElement element) {
return TimedUtils.findTimedAnnotations(element).filter(Timed::longTask) return findTimedAnnotations(element).filter(Timed::longTask)
.map(this::fromAnnotation).collect(Collectors.toSet()); .map(this::fromAnnotation).collect(Collectors.toSet());
} }
private Stream<Timed> findTimedAnnotations(AnnotatedElement 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 Stream.empty();
}
private TimerConfig fromAnnotation(Timed timed) { private TimerConfig fromAnnotation(Timed timed) {
return new TimerConfig(timed, this::getServerRequestName); return new TimerConfig(timed, this::getServerRequestName);
} }
@ -282,4 +306,5 @@ public class WebMvcMetrics {
} }
} }
} }

@ -35,18 +35,23 @@ import org.springframework.web.servlet.handler.MatchableHandlerMapping;
import org.springframework.web.util.NestedServletException; import org.springframework.web.util.NestedServletException;
/** /**
* Intercepts incoming HTTP requests and records metrics about execution time and results. * Intercepts incoming HTTP requests and records metrics about Spring MVC execution time
* and results.
* *
* @author Jon Schneider * @author Jon Schneider
* @since 2.0.0 * @since 2.0.0
*/ */
@Order(Ordered.HIGHEST_PRECEDENCE) @Order(Ordered.HIGHEST_PRECEDENCE)
public class MetricsFilter extends OncePerRequestFilter { public class WebMvcMetricsFilter extends OncePerRequestFilter {
private static final Logger logger = LoggerFactory
.getLogger(WebMvcMetricsFilter.class);
private final WebMvcMetrics webMvcMetrics; private final WebMvcMetrics webMvcMetrics;
private final HandlerMappingIntrospector mappingIntrospector; private final HandlerMappingIntrospector mappingIntrospector;
private final Logger logger = LoggerFactory.getLogger(MetricsFilter.class);
public MetricsFilter(WebMvcMetrics webMvcMetrics, public WebMvcMetricsFilter(WebMvcMetrics webMvcMetrics,
HandlerMappingIntrospector mappingIntrospector) { HandlerMappingIntrospector mappingIntrospector) {
this.webMvcMetrics = webMvcMetrics; this.webMvcMetrics = webMvcMetrics;
this.mappingIntrospector = mappingIntrospector; this.mappingIntrospector = mappingIntrospector;
@ -60,37 +65,44 @@ public class MetricsFilter extends OncePerRequestFilter {
@Override @Override
protected void doFilterInternal(HttpServletRequest request, protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain) HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException { throws ServletException, IOException {
HandlerExecutionChain handler; HandlerExecutionChain handlerExecutionChain = getHandlerExecutionChain(request);
Object handler = (handlerExecutionChain == null ? null
: handlerExecutionChain.getHandler());
filterWithMetrics(request, response, filterChain, handler);
}
private HandlerExecutionChain getHandlerExecutionChain(HttpServletRequest request) {
try { try {
MatchableHandlerMapping matchableHandlerMapping = this.mappingIntrospector MatchableHandlerMapping matchableHandlerMapping = this.mappingIntrospector
.getMatchableHandlerMapping(request); .getMatchableHandlerMapping(request);
handler = matchableHandlerMapping.getHandler(request); return (matchableHandlerMapping == null ? null
: matchableHandlerMapping.getHandler(request));
} }
catch (Exception e) { catch (Exception ex) {
this.logger.debug("Unable to time request", e); if (logger.isDebugEnabled()) {
return; logger.debug("Unable to time request", ex);
}
return null;
} }
}
if (handler != null) { private void filterWithMetrics(HttpServletRequest request,
Object handlerObject = handler.getHandler(); HttpServletResponse response, FilterChain filterChain, Object handler)
this.webMvcMetrics.preHandle(request, handlerObject); throws IOException, ServletException, NestedServletException {
try { this.webMvcMetrics.preHandle(request, handler);
filterChain.doFilter(request, response); try {
// 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 e) {
this.webMvcMetrics.record(request, response, e.getCause());
throw e;
}
}
else {
filterChain.doFilter(request, response); 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 io.micrometer.core.instrument.Tag;
import org.springframework.http.HttpStatus;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import org.springframework.web.servlet.HandlerMapping; import org.springframework.web.servlet.HandlerMapping;
@ -62,9 +63,19 @@ public final class WebMvcTags {
* available, falling back to the request's {@link HttpServletRequest#getPathInfo() * available, falling back to the request's {@link HttpServletRequest#getPathInfo()
* path info} if necessary. * path info} if necessary.
* @param request the request * @param request the request
* @param response the response
* @return the uri tag derived from the request * @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 String uri = (String) request
.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE); .getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE);
if (uri == null) { if (uri == null) {

@ -28,7 +28,6 @@ import io.micrometer.core.instrument.simple.SimpleConfig;
import io.micrometer.core.instrument.simple.SimpleMeterRegistry; import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
import org.junit.Test; import org.junit.Test;
import static io.micrometer.core.instrument.MockClock.clock;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
/** /**
@ -39,7 +38,8 @@ import static org.assertj.core.api.Assertions.assertThat;
*/ */
public class MetricsEndpointTests { public class MetricsEndpointTests {
private final MeterRegistry registry = new SimpleMeterRegistry(SimpleConfig.DEFAULT, new MockClock()); private final MeterRegistry registry = new SimpleMeterRegistry(SimpleConfig.DEFAULT,
new MockClock());
private final MetricsEndpoint endpoint = new MetricsEndpoint(this.registry); private final MetricsEndpoint endpoint = new MetricsEndpoint(this.registry);
@ -77,8 +77,7 @@ public class MetricsEndpointTests {
this.registry.counter("cache", "result", "hit", "host", "1").increment(2); 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", "miss", "host", "1").increment(2);
this.registry.counter("cache", "result", "hit", "host", "2").increment(2); this.registry.counter("cache", "result", "hit", "host", "2").increment(2);
clock(registry).add(SimpleConfig.DEFAULT_STEP); MockClock.clock(this.registry).add(SimpleConfig.DEFAULT_STEP);
MetricsEndpoint.MetricResponse response = this.endpoint.metric("cache", MetricsEndpoint.MetricResponse response = this.endpoint.metric("cache",
Collections.emptyList()); Collections.emptyList());
assertThat(response.getName()).isEqualTo("cache"); assertThat(response.getName()).isEqualTo("cache");
@ -92,8 +91,7 @@ public class MetricsEndpointTests {
@Test @Test
public void metricWithSpaceInTagValue() { public void metricWithSpaceInTagValue() {
this.registry.counter("counter", "key", "a space").increment(2); this.registry.counter("counter", "key", "a space").increment(2);
clock(registry).add(SimpleConfig.DEFAULT_STEP); MockClock.clock(this.registry).add(SimpleConfig.DEFAULT_STEP);
MetricsEndpoint.MetricResponse response = this.endpoint.metric("counter", MetricsEndpoint.MetricResponse response = this.endpoint.metric("counter",
Collections.singletonList("key:a space")); Collections.singletonList("key:a space"));
assertThat(response.getName()).isEqualTo("counter"); assertThat(response.getName()).isEqualTo("counter");

@ -29,13 +29,11 @@ import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.endpoint.web.test.WebEndpointRunners; import org.springframework.boot.actuate.endpoint.web.test.WebEndpointRunners;
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.test.web.reactive.server.WebTestClient; import org.springframework.test.web.reactive.server.WebTestClient;
import static io.micrometer.core.instrument.MockClock.clock;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
/** /**
@ -46,32 +44,34 @@ import static org.assertj.core.api.Assertions.assertThat;
*/ */
@RunWith(WebEndpointRunners.class) @RunWith(WebEndpointRunners.class)
public class MetricsEndpointWebIntegrationTests { public class MetricsEndpointWebIntegrationTests {
private static MeterRegistry registry = new SimpleMeterRegistry(SimpleConfig.DEFAULT, new MockClock());
private static MeterRegistry registry = new SimpleMeterRegistry(SimpleConfig.DEFAULT,
new MockClock());
private static WebTestClient client; private static WebTestClient client;
private final ObjectMapper mapper = new ObjectMapper(); private final ObjectMapper mapper = new ObjectMapper();
@SuppressWarnings("unchecked")
@Test @Test
@SuppressWarnings("unchecked")
public void listNames() throws IOException { public void listNames() throws IOException {
String responseBody = client.get() String responseBody = client.get().uri("/application/metrics").exchange()
.uri("/application/metrics").exchange().expectStatus().isOk() .expectStatus().isOk().expectBody(String.class).returnResult()
.expectBody(String.class).returnResult().getResponseBody(); .getResponseBody();
Map<String, List<String>> names = this.mapper.readValue(responseBody, Map.class); Map<String, List<String>> names = this.mapper.readValue(responseBody, Map.class);
assertThat(names.get("names")).containsOnlyOnce("jvm.memory.used"); assertThat(names.get("names")).containsOnlyOnce("jvm.memory.used");
} }
@Test @Test
public void selectByName() throws IOException { public void selectByName() throws IOException {
clock(registry).add(SimpleConfig.DEFAULT_STEP); MockClock.clock(registry).add(SimpleConfig.DEFAULT_STEP);
client.get() client.get().uri("/application/metrics/jvm.memory.used").exchange().expectStatus()
.uri("/application/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() {
clock(registry).add(SimpleConfig.DEFAULT_STEP); MockClock.clock(registry).add(SimpleConfig.DEFAULT_STEP);
client.get() client.get()
.uri("/application/metrics/jvm.memory.used?tag=id:Compressed%20Class%20Space") .uri("/application/metrics/jvm.memory.used?tag=id:Compressed%20Class%20Space")
.exchange().expectStatus().isOk().expectBody().jsonPath("$.name") .exchange().expectStatus().isOk().expectBody().jsonPath("$.name")

@ -33,8 +33,6 @@ import org.springframework.test.web.client.match.MockRestRequestMatchers;
import org.springframework.test.web.client.response.MockRestResponseCreators; import org.springframework.test.web.client.response.MockRestResponseCreators;
import org.springframework.web.client.RestTemplate; import org.springframework.web.client.RestTemplate;
import static io.micrometer.core.instrument.MockClock.clock;
import static java.util.stream.StreamSupport.stream;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
/** /**
@ -46,7 +44,8 @@ public class MetricsRestTemplateCustomizerTests {
@Test @Test
public void interceptRestTemplate() { public void interceptRestTemplate() {
MeterRegistry registry = new SimpleMeterRegistry(SimpleConfig.DEFAULT, new MockClock()); MeterRegistry registry = new SimpleMeterRegistry(SimpleConfig.DEFAULT,
new MockClock());
RestTemplate restTemplate = new RestTemplate(); RestTemplate restTemplate = new RestTemplate();
MetricsRestTemplateCustomizer customizer = new MetricsRestTemplateCustomizer( MetricsRestTemplateCustomizer customizer = new MetricsRestTemplateCustomizer(
registry, new DefaultRestTemplateExchangeTagsProvider(), registry, new DefaultRestTemplateExchangeTagsProvider(),
@ -59,9 +58,11 @@ public class MetricsRestTemplateCustomizerTests {
.andRespond(MockRestResponseCreators.withSuccess("OK", .andRespond(MockRestResponseCreators.withSuccess("OK",
MediaType.APPLICATION_JSON)); MediaType.APPLICATION_JSON));
String result = restTemplate.getForObject("/test/{id}", String.class, 123); String result = restTemplate.getForObject("/test/{id}", String.class, 123);
clock(registry).add(SimpleConfig.DEFAULT_STEP); MockClock.clock(registry).add(SimpleConfig.DEFAULT_STEP);
assertThat(registry.find("http.client.requests").meters()) assertThat(registry.find("http.client.requests")
.anySatisfy(m -> assertThat(stream(m.getId().getTags().spliterator(), false).map(Tag::getKey)).doesNotContain("bucket")); .meters()).anySatisfy(m -> assertThat(
StreamSupport.stream(m.getId().getTags().spliterator(), false)
.map(Tag::getKey)).doesNotContain("bucket"));
assertThat(registry.find("http.client.requests") assertThat(registry.find("http.client.requests")
.tags("method", "GET", "uri", "/test/{id}", "status", "200") .tags("method", "GET", "uri", "/test/{id}", "status", "200")
.value(Statistic.Count, 1.0).timer()).isPresent(); .value(Statistic.Count, 1.0).timer()).isPresent();

@ -24,6 +24,7 @@ import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
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;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
@ -49,70 +50,76 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
*/ */
@RunWith(SpringRunner.class) @RunWith(SpringRunner.class)
@WebAppConfiguration @WebAppConfiguration
public class MetricsFilterAutoTimedTests { public class WebMvcMetricsFilterAutoTimedTests {
@Autowired
private MeterRegistry registry;
@Autowired @Autowired
private MeterRegistry registry; private MockClock clock;
@Autowired @Autowired
private MockClock clock; private WebApplicationContext context;
@Autowired private MockMvc mvc;
private WebApplicationContext context;
private MockMvc mvc; @Autowired
private WebMvcMetricsFilter filter;
@Autowired @Before
private MetricsFilter filter; public void setupMockMvc() {
this.mvc = MockMvcBuilders.webAppContextSetup(this.context)
.addFilters(this.filter).build();
}
@Before @Test
public void setupMockMvc() { public void metricsCanBeAutoTimed() throws Exception {
this.mvc = MockMvcBuilders.webAppContextSetup(this.context) this.mvc.perform(get("/api/10")).andExpect(status().isOk());
.addFilters(filter)
.build();
}
@Test this.clock.add(SimpleConfig.DEFAULT_STEP);
public void metricsCanBeAutoTimed() throws Exception { assertThat(
this.mvc.perform(get("/api/10")).andExpect(status().isOk()); this.registry.find("http.server.requests").tags("status", "200").timer())
.hasValueSatisfying((t) -> assertThat(t.count()).isEqualTo(1));
}
clock.add(SimpleConfig.DEFAULT_STEP); @Configuration
assertThat(this.registry.find("http.server.requests").tags("status", "200").timer()) @EnableWebMvc
.hasValueSatisfying((t) -> assertThat(t.count()).isEqualTo(1)); @Import({ Controller.class })
} static class TestConfiguration {
@Configuration @Bean
@EnableWebMvc MockClock clock() {
@Import({Controller.class}) return new MockClock();
static class TestConfiguration { }
@Bean
MockClock clock() {
return new MockClock();
}
@Bean @Bean
MeterRegistry meterRegistry(Clock clock) { MeterRegistry meterRegistry(Clock clock) {
return new SimpleMeterRegistry(SimpleConfig.DEFAULT, clock); return new SimpleMeterRegistry(SimpleConfig.DEFAULT, clock);
} }
@Bean @Bean
public WebMvcMetrics controllerMetrics(MeterRegistry registry) { public WebMvcMetrics controllerMetrics(MeterRegistry registry) {
return new WebMvcMetrics(registry, new DefaultWebMvcTagsProvider(), "http.server.requests", true, return new WebMvcMetrics(registry, new DefaultWebMvcTagsProvider(),
false); "http.server.requests", true, false);
} }
@Bean @Bean
public MetricsFilter webMetricsFilter(WebMvcMetrics controllerMetrics, HandlerMappingIntrospector introspector) { public WebMvcMetricsFilter webMetricsFilter(WebMvcMetrics controllerMetrics,
return new MetricsFilter(controllerMetrics, introspector); HandlerMappingIntrospector introspector) {
return new WebMvcMetricsFilter(controllerMetrics, introspector);
} }
}
}
@RestController
@RequestMapping("/api") @RestController
static class Controller { @RequestMapping("/api")
@GetMapping("/{id}") static class Controller {
public String successful(@PathVariable Long id) {
return id.toString(); @GetMapping("/{id}")
} public String successful(@PathVariable Long id) {
} return id.toString();
}
}
} }

@ -23,7 +23,6 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import java.lang.annotation.Target;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.stream.StreamSupport;
import javax.servlet.FilterChain; import javax.servlet.FilterChain;
import javax.servlet.ServletException; import javax.servlet.ServletException;
@ -33,8 +32,6 @@ import javax.servlet.http.HttpServletResponse;
import io.micrometer.core.annotation.Timed; import io.micrometer.core.annotation.Timed;
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.simple.SimpleMeterRegistry;
import io.micrometer.prometheus.PrometheusConfig; import io.micrometer.prometheus.PrometheusConfig;
import io.micrometer.prometheus.PrometheusMeterRegistry; import io.micrometer.prometheus.PrometheusMeterRegistry;
import org.junit.Before; import org.junit.Before;
@ -42,13 +39,10 @@ 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.autoconfigure.SpringBootApplication;
import org.springframework.boot.test.context.SpringBootTest;
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.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.test.context.TestPropertySource;
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;
import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MockMvc;
@ -64,8 +58,6 @@ import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.filter.OncePerRequestFilter; import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.config.annotation.EnableWebMvc; 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 org.springframework.web.servlet.handler.HandlerMappingIntrospector;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
@ -76,13 +68,14 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/** /**
* Tests for {@link MetricsFilter} * Tests for {@link WebMvcMetricsFilter}
* *
* @author Jon Schneider * @author Jon Schneider
*/ */
@RunWith(SpringRunner.class) @RunWith(SpringRunner.class)
@WebAppConfiguration @WebAppConfiguration
public class MetricsFilterTests { public class WebMvcMetricsFilterTests {
@Autowired @Autowired
private PrometheusMeterRegistry registry; private PrometheusMeterRegistry registry;
@ -90,7 +83,7 @@ public class MetricsFilterTests {
private WebApplicationContext context; private WebApplicationContext context;
@Autowired @Autowired
private MetricsFilter filter; private WebMvcMetricsFilter filter;
private MockMvc mvc; private MockMvc mvc;
@ -99,16 +92,13 @@ public class MetricsFilterTests {
@Before @Before
public void setupMockMvc() { public void setupMockMvc() {
this.mvc = MockMvcBuilders this.mvc = MockMvcBuilders.webAppContextSetup(this.context)
.webAppContextSetup(this.context) .addFilters(this.filter, new RedirectAndNotFoundFilter()).build();
.addFilters(filter, new RedirectAndNotFoundFilter())
.build();
} }
@Test @Test
public void timedMethod() throws Exception { public void timedMethod() throws Exception {
this.mvc.perform(get("/api/c1/10")).andExpect(status().isOk()); this.mvc.perform(get("/api/c1/10")).andExpect(status().isOk());
assertThat(this.registry.find("http.server.requests") assertThat(this.registry.find("http.server.requests")
.tags("status", "200", "uri", "/api/c1/{id}", "public", "true") .tags("status", "200", "uri", "/api/c1/{id}", "public", "true")
.value(Statistic.Count, 1.0).timer()).isPresent(); .value(Statistic.Count, 1.0).timer()).isPresent();
@ -117,7 +107,6 @@ public class MetricsFilterTests {
@Test @Test
public void subclassedTimedMethod() throws Exception { public void subclassedTimedMethod() throws Exception {
this.mvc.perform(get("/api/c1/metaTimed/10")).andExpect(status().isOk()); this.mvc.perform(get("/api/c1/metaTimed/10")).andExpect(status().isOk());
assertThat(this.registry.find("http.server.requests") assertThat(this.registry.find("http.server.requests")
.tags("status", "200", "uri", "/api/c1/metaTimed/{id}") .tags("status", "200", "uri", "/api/c1/metaTimed/{id}")
.value(Statistic.Count, 1.0).timer()).isPresent(); .value(Statistic.Count, 1.0).timer()).isPresent();
@ -126,7 +115,6 @@ public class MetricsFilterTests {
@Test @Test
public void untimedMethod() throws Exception { public void untimedMethod() throws Exception {
this.mvc.perform(get("/api/c1/untimed/10")).andExpect(status().isOk()); this.mvc.perform(get("/api/c1/untimed/10")).andExpect(status().isOk());
assertThat(this.registry.find("http.server.requests") assertThat(this.registry.find("http.server.requests")
.tags("uri", "/api/c1/untimed/10").timer()).isEmpty(); .tags("uri", "/api/c1/untimed/10").timer()).isEmpty();
} }
@ -134,39 +122,32 @@ public class MetricsFilterTests {
@Test @Test
public void timedControllerClass() throws Exception { public void timedControllerClass() throws Exception {
this.mvc.perform(get("/api/c2/10")).andExpect(status().isOk()); this.mvc.perform(get("/api/c2/10")).andExpect(status().isOk());
assertThat(this.registry.find("http.server.requests").tags("status", "200") assertThat(this.registry.find("http.server.requests").tags("status", "200")
.value(Statistic.Count, 1.0) .value(Statistic.Count, 1.0).timer()).isPresent();
.timer()).isPresent();
} }
@Test @Test
public void badClientRequest() throws Exception { public void badClientRequest() throws Exception {
this.mvc.perform(get("/api/c1/oops")).andExpect(status().is4xxClientError()); this.mvc.perform(get("/api/c1/oops")).andExpect(status().is4xxClientError());
assertThat(this.registry.find("http.server.requests").tags("status", "400") assertThat(this.registry.find("http.server.requests").tags("status", "400")
.value(Statistic.Count, 1.0) .value(Statistic.Count, 1.0).timer()).isPresent();
.timer()).isPresent();
} }
@Test @Test
public void redirectRequest() throws Exception { public void redirectRequest() throws Exception {
this.mvc.perform(get("/api/redirect") this.mvc.perform(get("/api/redirect")
.header(RedirectAndNotFoundFilter.TEST_MISBEHAVE_HEADER, "302")).andExpect(status().is3xxRedirection()); .header(RedirectAndNotFoundFilter.TEST_MISBEHAVE_HEADER, "302"))
.andExpect(status().is3xxRedirection());
assertThat(this.registry.find("http.server.requests") assertThat(this.registry.find("http.server.requests").tags("uri", "REDIRECTION")
.tags("uri", "REDIRECTION")
.tags("status", "302").timer()).isPresent(); .tags("status", "302").timer()).isPresent();
} }
@Test @Test
public void notFoundRequest() throws Exception { public void notFoundRequest() throws Exception {
this.mvc.perform(get("/api/not/found") this.mvc.perform(get("/api/not/found")
.header(RedirectAndNotFoundFilter.TEST_MISBEHAVE_HEADER, "404")).andExpect(status().is4xxClientError()); .header(RedirectAndNotFoundFilter.TEST_MISBEHAVE_HEADER, "404"))
.andExpect(status().is4xxClientError());
assertThat(this.registry.find("http.server.requests") assertThat(this.registry.find("http.server.requests").tags("uri", "NOT_FOUND")
.tags("uri", "NOT_FOUND")
.tags("status", "404").timer()).isPresent(); .tags("status", "404").timer()).isPresent();
} }
@ -174,8 +155,7 @@ public class MetricsFilterTests {
public void unhandledError() throws Exception { public void unhandledError() throws Exception {
assertThatCode(() -> this.mvc.perform(get("/api/c1/unhandledError/10")) assertThatCode(() -> this.mvc.perform(get("/api/c1/unhandledError/10"))
.andExpect(status().isOk())) .andExpect(status().isOk()))
.hasRootCauseInstanceOf(RuntimeException.class); .hasRootCauseInstanceOf(RuntimeException.class);
assertThat(this.registry.find("http.server.requests") assertThat(this.registry.find("http.server.requests")
.tags("exception", "RuntimeException").value(Statistic.Count, 1.0) .tags("exception", "RuntimeException").value(Statistic.Count, 1.0)
.timer()).isPresent(); .timer()).isPresent();
@ -184,22 +164,16 @@ public class MetricsFilterTests {
@Test @Test
public void longRunningRequest() throws Exception { public void longRunningRequest() throws Exception {
MvcResult result = this.mvc.perform(get("/api/c1/long/10")) MvcResult result = this.mvc.perform(get("/api/c1/long/10"))
.andExpect(request().asyncStarted()) .andExpect(request().asyncStarted()).andReturn();
.andReturn();
// the request is not prematurely recorded as complete // the request is not prematurely recorded as complete
assertThat(this.registry.find("http.server.requests") assertThat(this.registry.find("http.server.requests").tags("uri", "/api/c1/async")
.tags("uri", "/api/c1/async").timer()).isNotPresent(); .timer()).isNotPresent();
// while the mapping is running, it contributes to the activeTasks count // while the mapping is running, it contributes to the activeTasks count
assertThat(this.registry.find("my.long.request").tags("region", "test") assertThat(this.registry.find("my.long.request").tags("region", "test")
.value(Statistic.Count, 1.0).longTaskTimer()).isPresent(); .value(Statistic.Count, 1.0).longTaskTimer()).isPresent();
// once the mapping completes, we can gather information about status, etc. // once the mapping completes, we can gather information about status, etc.
asyncLatch.countDown(); this.asyncLatch.countDown();
this.mvc.perform(asyncDispatch(result)).andExpect(status().isOk()); this.mvc.perform(asyncDispatch(result)).andExpect(status().isOk());
assertThat(this.registry.find("http.server.requests").tags("status", "200") assertThat(this.registry.find("http.server.requests").tags("status", "200")
.value(Statistic.Count, 1.0).timer()).isPresent(); .value(Statistic.Count, 1.0).timer()).isPresent();
} }
@ -207,7 +181,6 @@ public class MetricsFilterTests {
@Test @Test
public void endpointThrowsError() throws Exception { public void endpointThrowsError() throws Exception {
this.mvc.perform(get("/api/c1/error/10")).andExpect(status().is4xxClientError()); this.mvc.perform(get("/api/c1/error/10")).andExpect(status().is4xxClientError());
assertThat(this.registry.find("http.server.requests").tags("status", "422") assertThat(this.registry.find("http.server.requests").tags("status", "422")
.value(Statistic.Count, 1.0).timer()).isPresent(); .value(Statistic.Count, 1.0).timer()).isPresent();
} }
@ -215,7 +188,6 @@ public class MetricsFilterTests {
@Test @Test
public void regexBasedRequestMapping() throws Exception { public void regexBasedRequestMapping() throws Exception {
this.mvc.perform(get("/api/c1/regex/.abc")).andExpect(status().isOk()); this.mvc.perform(get("/api/c1/regex/.abc")).andExpect(status().isOk());
assertThat(this.registry.find("http.server.requests") assertThat(this.registry.find("http.server.requests")
.tags("uri", "/api/c1/regex/{id:\\.[a-z]+}").value(Statistic.Count, 1.0) .tags("uri", "/api/c1/regex/{id:\\.[a-z]+}").value(Statistic.Count, 1.0)
.timer()).isPresent(); .timer()).isPresent();
@ -224,7 +196,6 @@ public class MetricsFilterTests {
@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.registry.scrape()).contains("quantile=\"0.5\"");
assertThat(this.registry.scrape()).contains("quantile=\"0.95\""); assertThat(this.registry.scrape()).contains("quantile=\"0.95\"");
} }
@ -232,21 +203,22 @@ public class MetricsFilterTests {
@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.registry.scrape()).contains("le=\"0.001\"");
assertThat(this.registry.scrape()).contains("le=\"30.0\""); assertThat(this.registry.scrape()).contains("le=\"30.0\"");
} }
@Target({ElementType.METHOD}) @Target({ ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Timed(percentiles = 0.95) @Timed(percentiles = 0.95)
public @interface Timed95 { public @interface Timed95 {
} }
@Configuration @Configuration
@EnableWebMvc @EnableWebMvc
@Import({Controller1.class, Controller2.class}) @Import({ Controller1.class, Controller2.class })
static class MetricsFilterApp { static class MetricsFilterApp {
@Bean @Bean
MeterRegistry meterRegistry() { MeterRegistry meterRegistry() {
// one of the few registries that support aggregable percentiles // one of the few registries that support aggregable percentiles
@ -260,39 +232,44 @@ public class MetricsFilterTests {
@Bean @Bean
public WebMvcMetrics controllerMetrics(MeterRegistry registry) { public WebMvcMetrics controllerMetrics(MeterRegistry registry) {
return new WebMvcMetrics(registry, new DefaultWebMvcTagsProvider(), "http.server.requests", true, return new WebMvcMetrics(registry, new DefaultWebMvcTagsProvider(),
false); "http.server.requests", true, false);
} }
@Bean @Bean
public MetricsFilter webMetricsFilter(WebMvcMetrics controllerMetrics, HandlerMappingIntrospector introspector) { public WebMvcMetricsFilter webMetricsFilter(WebMvcMetrics controllerMetrics,
return new MetricsFilter(controllerMetrics, introspector); HandlerMappingIntrospector introspector) {
return new WebMvcMetricsFilter(controllerMetrics, introspector);
} }
} }
@RestController @RestController
@RequestMapping("/api/c1") @RequestMapping("/api/c1")
static class Controller1 { static class Controller1 {
private final CountDownLatch asyncLatch; private final CountDownLatch asyncLatch;
public Controller1(CountDownLatch asyncLatch) { Controller1(CountDownLatch asyncLatch) {
this.asyncLatch = asyncLatch; this.asyncLatch = asyncLatch;
} }
@Timed(extraTags = {"public", "true"}) @Timed(extraTags = { "public", "true" })
@GetMapping("/{id}") @GetMapping("/{id}")
public String successfulWithExtraTags(@PathVariable Long id) { public String successfulWithExtraTags(@PathVariable Long id) {
return id.toString(); return id.toString();
} }
@Timed @Timed
@Timed(value = "my.long.request", extraTags = {"region", "test"}, longTask = true) @Timed(value = "my.long.request", extraTags = { "region",
"test" }, longTask = true)
@GetMapping("/long/{id}") @GetMapping("/long/{id}")
public Callable<String> takesLongTimeToSatisfy(@PathVariable Long id) { public Callable<String> takesLongTimeToSatisfy(@PathVariable Long id) {
return () -> { return () -> {
try { try {
asyncLatch.await(); this.asyncLatch.await();
} catch (InterruptedException e) { }
catch (InterruptedException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
return id.toString(); return id.toString();
@ -322,7 +299,7 @@ public class MetricsFilterTests {
return id; return id;
} }
@Timed(percentiles = {0.50, 0.95}) @Timed(percentiles = { 0.50, 0.95 })
@GetMapping("/percentiles/{id}") @GetMapping("/percentiles/{id}")
public String percentiles(@PathVariable String id) { public String percentiles(@PathVariable String id) {
return id; return id;
@ -345,16 +322,19 @@ public class MetricsFilterTests {
ModelAndView defaultErrorHandler(HttpServletRequest request, Exception e) { ModelAndView defaultErrorHandler(HttpServletRequest request, Exception e) {
return new ModelAndView("myerror"); return new ModelAndView("myerror");
} }
} }
@RestController @RestController
@Timed @Timed
@RequestMapping("/api/c2") @RequestMapping("/api/c2")
static class Controller2 { static class Controller2 {
@GetMapping("/{id}") @GetMapping("/{id}")
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 {
@ -362,14 +342,18 @@ public class MetricsFilterTests {
static final String TEST_MISBEHAVE_HEADER = "x-test-misbehave-status"; static final String TEST_MISBEHAVE_HEADER = "x-test-misbehave-status";
@Override @Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String misbehave = request.getHeader(TEST_MISBEHAVE_HEADER); String misbehave = request.getHeader(TEST_MISBEHAVE_HEADER);
if (misbehave != null) { if (misbehave != null) {
response.setStatus(Integer.parseInt(misbehave)); response.setStatus(Integer.parseInt(misbehave));
} else { }
else {
filterChain.doFilter(request, response); filterChain.doFilter(request, response);
} }
} }
} }
}
}

@ -36,7 +36,11 @@ import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.context.web.WebAppConfiguration; import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.handler.HandlerMappingIntrospector; import org.springframework.web.servlet.handler.HandlerMappingIntrospector;
@ -67,39 +71,37 @@ public class WebMvcMetricsIntegrationTests {
private MockMvc mvc; private MockMvc mvc;
@Autowired @Autowired
private MetricsFilter filter; private WebMvcMetricsFilter filter;
@Before @Before
public void setupMockMvc() { public void setupMockMvc() {
this.mvc = MockMvcBuilders.webAppContextSetup(this.context) this.mvc = MockMvcBuilders.webAppContextSetup(this.context)
.addFilters(filter) .addFilters(this.filter).build();
.build();
} }
@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);
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").value(Statistic.Count, 1.0).timer())
.isPresent(); .isPresent();
} }
@Test @Test
public void rethrownExceptionIsRecordedInMetricTag() throws Exception { public void rethrownExceptionIsRecordedInMetricTag() throws Exception {
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);
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").value(Statistic.Count, 1.0).timer())
.isPresent(); .isPresent();
} }
@Configuration @Configuration
@EnableWebMvc @EnableWebMvc
static class TestConfiguration { static class TestConfiguration {
@Bean @Bean
MockClock clock() { MockClock clock() {
return new MockClock(); return new MockClock();
@ -112,13 +114,14 @@ public class WebMvcMetricsIntegrationTests {
@Bean @Bean
public WebMvcMetrics controllerMetrics(MeterRegistry registry) { public WebMvcMetrics controllerMetrics(MeterRegistry registry) {
return new WebMvcMetrics(registry, new DefaultWebMvcTagsProvider(), "http.server.requests", true, return new WebMvcMetrics(registry, new DefaultWebMvcTagsProvider(),
false); "http.server.requests", true, false);
} }
@Bean @Bean
public MetricsFilter webMetricsFilter(WebMvcMetrics controllerMetrics, HandlerMappingIntrospector introspector) { public WebMvcMetricsFilter webMetricsFilter(WebMvcMetrics controllerMetrics,
return new MetricsFilter(controllerMetrics, introspector); HandlerMappingIntrospector introspector) {
return new WebMvcMetricsFilter(controllerMetrics, introspector);
} }
@RestController @RestController
@ -142,12 +145,15 @@ public class WebMvcMetricsIntegrationTests {
} }
} }
} }
static class Exception1 extends RuntimeException { static class Exception1 extends RuntimeException {
} }
static class Exception2 extends RuntimeException { static class Exception2 extends RuntimeException {
} }
@ControllerAdvice @ControllerAdvice
@ -167,5 +173,7 @@ public class WebMvcMetricsIntegrationTests {
ResponseEntity<String> rethrowError(Exception2 ex) throws Throwable { ResponseEntity<String> rethrowError(Exception2 ex) throws Throwable {
throw ex; throw ex;
} }
} }
}
}

@ -60,6 +60,7 @@
<gson.version>2.8.2</gson.version> <gson.version>2.8.2</gson.version>
<h2.version>1.4.196</h2.version> <h2.version>1.4.196</h2.version>
<hamcrest.version>1.3</hamcrest.version> <hamcrest.version>1.3</hamcrest.version>
<hdrhistogram.version>2.1.10</hdrhistogram.version>
<hazelcast.version>3.9</hazelcast.version> <hazelcast.version>3.9</hazelcast.version>
<hazelcast-hibernate5.version>1.2.2</hazelcast-hibernate5.version> <hazelcast-hibernate5.version>1.2.2</hazelcast-hibernate5.version>
<hibernate.version>5.2.12.Final</hibernate.version> <hibernate.version>5.2.12.Final</hibernate.version>
@ -114,7 +115,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.1.2</mariadb.version> <mariadb.version>2.1.2</mariadb.version>
<micrometer.version>1.0.0-SNAPSHOT</micrometer.version> <micrometer.version>1.0.0-rc.3</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.11.0</mockito.version> <mockito.version>2.11.0</mockito.version>
<mongo-driver-reactivestreams.version>1.6.0</mongo-driver-reactivestreams.version> <mongo-driver-reactivestreams.version>1.6.0</mongo-driver-reactivestreams.version>
@ -2056,6 +2057,11 @@
<artifactId>hamcrest-library</artifactId> <artifactId>hamcrest-library</artifactId>
<version>${hamcrest.version}</version> <version>${hamcrest.version}</version>
</dependency> </dependency>
<dependency>
<groupId>org.hdrhistogram</groupId>
<artifactId>HdrHistogram</artifactId>
<version>${hdrhistogram.version}</version>
</dependency>
<dependency> <dependency>
<groupId>org.hibernate</groupId> <groupId>org.hibernate</groupId>
<artifactId>hibernate-c3p0</artifactId> <artifactId>hibernate-c3p0</artifactId>

Loading…
Cancel
Save