mirror of
https://github.com/spring-projects/spring-boot.git
synced 2024-09-03 04:26:12 +08:00
Upgrade to Micrometer 1.0.0-rc.3
Upgrade to Micrometer 1.0.0-rc.3 and refactor existing auto-configuration to align with updated APIs. Note that Spring MVC instrumentation has now changed from an interceptor to a Filter. See gh-10906
This commit is contained in:
parent
fa191d8c73
commit
65e6876025
@ -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,9 @@ class MeterBindersConfiguration {
|
||||
return new UptimeMetrics();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(ProcessorMetrics.class)
|
||||
public ProcessorMetrics processorMetrics() {
|
||||
return new ProcessorMetrics();
|
||||
}
|
||||
}
|
||||
|
@ -67,7 +67,7 @@ public abstract class StepRegistryProperties {
|
||||
this.step = step;
|
||||
}
|
||||
|
||||
public Boolean getEnabled() {
|
||||
public Boolean isEnabled() {
|
||||
return this.enabled;
|
||||
}
|
||||
|
||||
|
@ -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.
|
||||
@ -53,7 +53,7 @@ public abstract class StepRegistryPropertiesConfigAdapter<T extends StepRegistry
|
||||
|
||||
@Override
|
||||
public boolean enabled() {
|
||||
return get(T::getEnabled, C::enabled);
|
||||
return get(T::isEnabled, C::enabled);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -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 {
|
||||
|
||||
|
@ -49,7 +49,7 @@ class AtlasPropertiesConfigAdapter extends
|
||||
|
||||
@Override
|
||||
public boolean enabled() {
|
||||
return get(AtlasProperties::getEnabled, AtlasConfig::enabled);
|
||||
return get(AtlasProperties::isEnabled, AtlasConfig::enabled);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -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.
|
||||
* 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 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 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 String getUri() {
|
||||
return this.uri;
|
||||
}
|
||||
|
||||
public void setTimerPercentilesMax(Duration timerPercentilesMax) {
|
||||
this.timerPercentilesMax = timerPercentilesMax;
|
||||
}
|
||||
|
||||
public Duration getTimerPercentilesMin() {
|
||||
return this.timerPercentilesMin;
|
||||
}
|
||||
|
||||
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,7 @@ class DatadogPropertiesConfigAdapter
|
||||
}
|
||||
|
||||
@Override
|
||||
public Duration timerPercentilesMax() {
|
||||
return get(DatadogProperties::getTimerPercentilesMax,
|
||||
DatadogConfig::timerPercentilesMax);
|
||||
public String uri() {
|
||||
return get(DatadogProperties::getUri, DatadogConfig::uri);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Duration timerPercentilesMin() {
|
||||
return get(DatadogProperties::getTimerPercentilesMin,
|
||||
DatadogConfig::timerPercentilesMin);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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 {
|
||||
|
||||
|
@ -77,7 +77,7 @@ public class GangliaProperties {
|
||||
*/
|
||||
private Integer port;
|
||||
|
||||
public Boolean getEnabled() {
|
||||
public Boolean isEnabled() {
|
||||
return this.enabled;
|
||||
}
|
||||
|
||||
|
@ -47,7 +47,7 @@ class GangliaPropertiesConfigAdapter
|
||||
|
||||
@Override
|
||||
public boolean enabled() {
|
||||
return get(GangliaProperties::getEnabled, GangliaConfig::enabled);
|
||||
return get(GangliaProperties::isEnabled, GangliaConfig::enabled);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -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 {
|
||||
|
||||
|
@ -67,7 +67,7 @@ public class GraphiteProperties {
|
||||
*/
|
||||
private GraphiteProtocol protocol = GraphiteProtocol.Pickled;
|
||||
|
||||
public Boolean getEnabled() {
|
||||
public Boolean isEnabled() {
|
||||
return this.enabled;
|
||||
}
|
||||
|
||||
|
@ -47,7 +47,7 @@ class GraphitePropertiesConfigAdapter
|
||||
|
||||
@Override
|
||||
public boolean enabled() {
|
||||
return get(GraphiteProperties::getEnabled, GraphiteConfig::enabled);
|
||||
return get(GraphiteProperties::isEnabled, GraphiteConfig::enabled);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -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;
|
||||
@ -68,20 +66,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;
|
||||
}
|
||||
@ -137,20 +121,4 @@ public class InfluxProperties extends StepRegistryProperties {
|
||||
public void setCompressed(Boolean compressed) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -73,17 +73,4 @@ class InfluxPropertiesConfigAdapter
|
||||
public boolean compressed() {
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -41,20 +41,11 @@ public class PrometheusProperties {
|
||||
private Boolean descriptions = true;
|
||||
|
||||
/**
|
||||
* 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);
|
||||
private Duration step = Duration.ofMinutes(1);
|
||||
|
||||
/**
|
||||
* 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 Boolean getEnabled() {
|
||||
public Boolean isEnabled() {
|
||||
return this.enabled;
|
||||
}
|
||||
|
||||
@ -70,20 +61,11 @@ public class PrometheusProperties {
|
||||
this.descriptions = descriptions;
|
||||
}
|
||||
|
||||
public Duration getTimerPercentilesMax() {
|
||||
return this.timerPercentilesMax;
|
||||
public Duration getStep() {
|
||||
return this.step;
|
||||
}
|
||||
|
||||
public void setTimerPercentilesMax(Duration timerPercentilesMax) {
|
||||
this.timerPercentilesMax = timerPercentilesMax;
|
||||
public void setStep(Duration step) {
|
||||
this.step = step;
|
||||
}
|
||||
|
||||
public Duration getTimerPercentilesMin() {
|
||||
return this.timerPercentilesMin;
|
||||
}
|
||||
|
||||
public void setTimerPercentilesMin(Duration timerPercentilesMin) {
|
||||
this.timerPercentilesMin = timerPercentilesMin;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -49,15 +49,7 @@ class PrometheusPropertiesConfigAdapter
|
||||
}
|
||||
|
||||
@Override
|
||||
public Duration timerPercentilesMin() {
|
||||
return get(PrometheusProperties::getTimerPercentilesMin,
|
||||
PrometheusConfig::timerPercentilesMin);
|
||||
public Duration step() {
|
||||
return get(PrometheusProperties::getStep, PrometheusConfig::step);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Duration timerPercentilesMax() {
|
||||
return get(PrometheusProperties::getTimerPercentilesMax,
|
||||
PrometheusConfig::timerPercentilesMax);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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,9 @@ public class SimpleExportConfiguration {
|
||||
return Clock.SYSTEM;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(SimpleConfig.class)
|
||||
public SimpleConfig simpleConfig(SimpleProperties simpleProperties) {
|
||||
return new SimplePropertiesConfigAdapter(simpleProperties);
|
||||
}
|
||||
}
|
||||
|
@ -16,10 +16,12 @@
|
||||
|
||||
package org.springframework.boot.actuate.autoconfigure.metrics.export.simple;
|
||||
|
||||
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
|
||||
import java.time.Duration;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
|
||||
|
||||
/**
|
||||
* {@link ConfigurationProperties} for configuring metrics export to a
|
||||
* {@link SimpleMeterRegistry}.
|
||||
@ -30,8 +32,16 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
@ConfigurationProperties(prefix = "spring.metrics.export.simple")
|
||||
public class SimpleProperties {
|
||||
|
||||
/**
|
||||
* Enable publishing to the backend.
|
||||
*/
|
||||
private boolean enabled = true;
|
||||
|
||||
/**
|
||||
* The step size (reporting frequency) to use.
|
||||
*/
|
||||
private Duration step = Duration.ofSeconds(10);
|
||||
|
||||
public boolean isEnabled() {
|
||||
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,54 @@
|
||||
/*
|
||||
* 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::isEnabled, 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,8 @@ 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 +58,9 @@ public class StatsdExportConfiguration {
|
||||
return Clock.SYSTEM;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public HierarchicalNameMapper hierarchicalNameMapper() {
|
||||
return HierarchicalNameMapper.DEFAULT;
|
||||
}
|
||||
}
|
||||
|
@ -68,37 +68,7 @@ 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() {
|
||||
public Boolean isEnabled() {
|
||||
return this.enabled;
|
||||
}
|
||||
|
||||
|
@ -50,7 +50,7 @@ public class StatsdPropertiesConfigAdapter extends
|
||||
|
||||
@Override
|
||||
public boolean enabled() {
|
||||
return get(StatsdProperties::getEnabled, StatsdConfig::enabled);
|
||||
return get(StatsdProperties::isEnabled, StatsdConfig::enabled);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -20,7 +20,7 @@ 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.MetricsFilter;
|
||||
import org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetrics;
|
||||
import org.springframework.boot.actuate.metrics.web.servlet.WebMvcTagsProvider;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
@ -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,7 @@ public class WebMvcMetricsConfiguration {
|
||||
}
|
||||
|
||||
@Bean
|
||||
public MetricsHandlerInterceptor webMetricsInterceptor(
|
||||
WebMvcMetrics controllerMetrics) {
|
||||
return new MetricsHandlerInterceptor(controllerMetrics);
|
||||
public MetricsFilter webMetricsFilter(WebMvcMetrics controllerMetrics, HandlerMappingIntrospector introspector) {
|
||||
return new MetricsFilter(controllerMetrics, introspector);
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,50 @@
|
||||
/*
|
||||
* 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();
|
||||
}
|
||||
}
|
@ -21,18 +21,11 @@ import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import io.micrometer.core.instrument.Meter.Id;
|
||||
import io.micrometer.core.instrument.MeterRegistry;
|
||||
import io.micrometer.core.instrument.Tag;
|
||||
import io.micrometer.core.instrument.Tags;
|
||||
import io.micrometer.core.instrument.binder.MeterBinder;
|
||||
|
||||
import org.springframework.beans.factory.SmartInitializingSingleton;
|
||||
import org.springframework.integration.support.management.IntegrationManagementConfigurer;
|
||||
import org.springframework.integration.support.management.MessageChannelMetrics;
|
||||
import org.springframework.integration.support.management.MessageHandlerMetrics;
|
||||
import org.springframework.integration.support.management.MessageSourceMetrics;
|
||||
import org.springframework.integration.support.management.PollableChannelManagement;
|
||||
import org.springframework.integration.support.management.*;
|
||||
|
||||
import io.micrometer.core.instrument.*;
|
||||
import io.micrometer.core.instrument.binder.MeterBinder;
|
||||
|
||||
/**
|
||||
* A {@link MeterBinder} for Spring Integration metrics.
|
||||
@ -60,133 +53,104 @@ public class SpringIntegrationMetrics implements MeterBinder, SmartInitializingS
|
||||
|
||||
@Override
|
||||
public void bindTo(MeterRegistry registry) {
|
||||
bindChannelNames(registry);
|
||||
bindHandlerNames(registry);
|
||||
bindSourceNames(registry);
|
||||
Gauge.builder("spring.integration.channelNames", this.configurer,
|
||||
c -> c.getChannelNames().length).tags(tags)
|
||||
.description("The number of spring integration channels")
|
||||
.register(registry);
|
||||
|
||||
Gauge.builder("spring.integration.handlerNames", configurer,
|
||||
c -> c.getHandlerNames().length).tags(this.tags)
|
||||
.description("The number of spring integration handlers")
|
||||
.register(registry);
|
||||
|
||||
Gauge.builder("spring.integration.sourceNames", configurer,
|
||||
c -> c.getSourceNames().length).tags(this.tags)
|
||||
.description("The number of spring integration sources")
|
||||
.register(registry);
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
FunctionCounter
|
||||
.builder("spring.integration.source.messages", sourceMetrics,
|
||||
MessageSourceMetrics::getMessageCount)
|
||||
.tags(tagsWithSource)
|
||||
.description("The number of successful handler calls")
|
||||
.register(registry);
|
||||
}
|
||||
}
|
||||
|
||||
private void addHandlerMetrics(MeterRegistry registry) {
|
||||
for (String handler : this.configurer.getHandlerNames()) {
|
||||
addHandlerMetrics(registry, handler);
|
||||
MessageHandlerMetrics handlerMetrics = this.configurer
|
||||
.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);
|
||||
|
||||
TimeGauge
|
||||
.builder("spring.integration.handler.duration.max", handlerMetrics,
|
||||
TimeUnit.MILLISECONDS, MessageHandlerMetrics::getMaxDuration)
|
||||
.tags(tagsWithHandler).description("The maximum handler duration")
|
||||
.register(registry);
|
||||
|
||||
TimeGauge
|
||||
.builder("spring.integration.handler.duration.min", handlerMetrics,
|
||||
TimeUnit.MILLISECONDS, MessageHandlerMetrics::getMinDuration)
|
||||
.tags(tagsWithHandler).description("The minimum handler duration")
|
||||
.register(registry);
|
||||
|
||||
TimeGauge
|
||||
.builder("spring.integration.handler.duration.mean", handlerMetrics,
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
MessageChannelMetrics channelMetrics = this.configurer
|
||||
.getChannelMetrics(channel);
|
||||
Iterable<Tag> tagsWithChannel = Tags.concat(this.tags, "channel", channel);
|
||||
|
||||
FunctionCounter
|
||||
.builder("spring.integration.channel.sendErrors", channelMetrics,
|
||||
MessageChannelMetrics::getSendErrorCount)
|
||||
.tags(tagsWithChannel)
|
||||
.description(
|
||||
"The number of failed sends (either throwing an exception or rejected by the channel)")
|
||||
.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) {
|
||||
FunctionCounter
|
||||
.builder("spring.integration.receives",
|
||||
(PollableChannelManagement) channelMetrics,
|
||||
PollableChannelManagement::getReceiveCount)
|
||||
.tags(tagsWithChannel)
|
||||
.description("The number of messages received")
|
||||
.register(registry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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 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 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);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterSingletonsInstantiated() {
|
||||
this.registries.forEach((registry) -> {
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,96 @@
|
||||
/*
|
||||
* 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 execution time and results.
|
||||
*
|
||||
* @author Jon Schneider
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@Order(Ordered.HIGHEST_PRECEDENCE)
|
||||
public class MetricsFilter extends OncePerRequestFilter {
|
||||
private final WebMvcMetrics webMvcMetrics;
|
||||
private final HandlerMappingIntrospector mappingIntrospector;
|
||||
private final Logger logger = LoggerFactory.getLogger(MetricsFilter.class);
|
||||
|
||||
public MetricsFilter(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 handler;
|
||||
try {
|
||||
MatchableHandlerMapping matchableHandlerMapping = this.mappingIntrospector
|
||||
.getMatchableHandlerMapping(request);
|
||||
handler = matchableHandlerMapping.getHandler(request);
|
||||
}
|
||||
catch (Exception e) {
|
||||
this.logger.debug("Unable to time request", e);
|
||||
return;
|
||||
}
|
||||
|
||||
if (handler != null) {
|
||||
Object handlerObject = handler.getHandler();
|
||||
this.webMvcMetrics.preHandle(request, handlerObject);
|
||||
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 e) {
|
||||
this.webMvcMetrics.record(request, response, e.getCause());
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
else {
|
||||
filterChain.doFilter(request, 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,6 @@
|
||||
package org.springframework.boot.actuate.metrics.web.servlet;
|
||||
|
||||
import java.lang.reflect.AnnotatedElement;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Collections;
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.Map;
|
||||
@ -25,28 +24,21 @@ import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import io.micrometer.core.annotation.Timed;
|
||||
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.boot.actuate.metrics.TimedUtils;
|
||||
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 io.micrometer.core.annotation.Timed;
|
||||
import io.micrometer.core.instrument.*;
|
||||
|
||||
/**
|
||||
* Support class for Spring MVC metrics.
|
||||
*
|
||||
@ -154,25 +146,21 @@ 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.histogram);
|
||||
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"));
|
||||
Object handler) {
|
||||
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) {
|
||||
@ -210,22 +198,15 @@ public class WebMvcMetrics {
|
||||
}
|
||||
|
||||
private Set<TimerConfig> getNonLongTaskAnnotationConfig(AnnotatedElement element) {
|
||||
return findTimedAnnotations(element).filter((t) -> !t.longTask())
|
||||
return TimedUtils.findTimedAnnotations(element).filter((t) -> !t.longTask())
|
||||
.map(this::fromAnnotation).collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
private Set<TimerConfig> getLongTaskAnnotationConfig(AnnotatedElement element) {
|
||||
return findTimedAnnotations(element).filter(Timed::longTask)
|
||||
return TimedUtils.findTimedAnnotations(element).filter(Timed::longTask)
|
||||
.map(this::fromAnnotation).collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
private Stream<Timed> findTimedAnnotations(AnnotatedElement element) {
|
||||
if (element instanceof Class<?>) {
|
||||
return AnnotationUtils.findTimed((Class<?>) element);
|
||||
}
|
||||
return AnnotationUtils.findTimed((Method) element);
|
||||
}
|
||||
|
||||
private TimerConfig fromAnnotation(Timed timed) {
|
||||
return new TimerConfig(timed, this::getServerRequestName);
|
||||
}
|
||||
@ -240,22 +221,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 +252,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
|
||||
@ -301,5 +282,4 @@ public class WebMvcMetrics {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -21,11 +21,14 @@ 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;
|
||||
|
||||
import static io.micrometer.core.instrument.MockClock.clock;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
@ -36,7 +39,7 @@ 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,8 @@ 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);
|
||||
clock(registry).add(SimpleConfig.DEFAULT_STEP);
|
||||
|
||||
MetricsEndpoint.MetricResponse response = this.endpoint.metric("cache",
|
||||
Collections.emptyList());
|
||||
assertThat(response.getName()).isEqualTo("cache");
|
||||
@ -87,6 +92,8 @@ public class MetricsEndpointTests {
|
||||
@Test
|
||||
public void metricWithSpaceInTagValue() {
|
||||
this.registry.counter("counter", "key", "a space").increment(2);
|
||||
clock(registry).add(SimpleConfig.DEFAULT_STEP);
|
||||
|
||||
MetricsEndpoint.MetricResponse response = this.endpoint.metric("counter",
|
||||
Collections.singletonList("key:a space"));
|
||||
assertThat(response.getName()).isEqualTo("counter");
|
||||
|
@ -22,16 +22,20 @@ 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;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.actuate.endpoint.web.test.WebEndpointRunners;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||
|
||||
import static io.micrometer.core.instrument.MockClock.clock;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
@ -42,7 +46,7 @@ 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();
|
||||
@ -50,7 +54,7 @@ public class MetricsEndpointWebIntegrationTests {
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
public void listNames() throws IOException {
|
||||
String responseBody = MetricsEndpointWebIntegrationTests.client.get()
|
||||
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);
|
||||
@ -59,14 +63,16 @@ public class MetricsEndpointWebIntegrationTests {
|
||||
|
||||
@Test
|
||||
public void selectByName() throws IOException {
|
||||
MetricsEndpointWebIntegrationTests.client.get()
|
||||
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()
|
||||
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;
|
||||
|
||||
@ -31,6 +33,8 @@ import org.springframework.test.web.client.match.MockRestRequestMatchers;
|
||||
import org.springframework.test.web.client.response.MockRestResponseCreators;
|
||||
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;
|
||||
|
||||
/**
|
||||
@ -42,7 +46,7 @@ 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 +59,12 @@ public class MetricsRestTemplateCustomizerTests {
|
||||
.andRespond(MockRestResponseCreators.withSuccess("OK",
|
||||
MediaType.APPLICATION_JSON));
|
||||
String result = restTemplate.getForObject("/test/{id}", String.class, 123);
|
||||
clock(registry).add(SimpleConfig.DEFAULT_STEP);
|
||||
assertThat(registry.find("http.client.requests").meters())
|
||||
.anySatisfy(m -> assertThat(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,12 +16,14 @@
|
||||
|
||||
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;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
@ -36,87 +38,81 @@ 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 MetricsFilterAutoTimedTests {
|
||||
|
||||
@Autowired
|
||||
private MeterRegistry registry;
|
||||
@Autowired
|
||||
private MeterRegistry registry;
|
||||
|
||||
@Autowired
|
||||
private WebApplicationContext context;
|
||||
@Autowired
|
||||
private MockClock clock;
|
||||
|
||||
private MockMvc mvc;
|
||||
@Autowired
|
||||
private WebApplicationContext context;
|
||||
|
||||
@Before
|
||||
public void setupMockMvc() {
|
||||
this.mvc = MockMvcBuilders.webAppContextSetup(this.context).build();
|
||||
}
|
||||
private MockMvc mvc;
|
||||
|
||||
@Test
|
||||
public void metricsCanBeAutoTimed() throws Exception {
|
||||
this.mvc.perform(get("/api/10")).andExpect(status().isOk());
|
||||
assertThat(
|
||||
this.registry.find("http.server.requests").tags("status", "200").timer())
|
||||
.hasValueSatisfying((t) -> assertThat(t.count()).isEqualTo(1));
|
||||
}
|
||||
@Autowired
|
||||
private MetricsFilter filter;
|
||||
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
@Import(Controller.class)
|
||||
static class TestConfiguration {
|
||||
@Before
|
||||
public void setupMockMvc() {
|
||||
this.mvc = MockMvcBuilders.webAppContextSetup(this.context)
|
||||
.addFilters(filter)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void metricsCanBeAutoTimed() throws Exception {
|
||||
this.mvc.perform(get("/api/10")).andExpect(status().isOk());
|
||||
|
||||
clock.add(SimpleConfig.DEFAULT_STEP);
|
||||
assertThat(this.registry.find("http.server.requests").tags("status", "200").timer())
|
||||
.hasValueSatisfying((t) -> assertThat(t.count()).isEqualTo(1));
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
@Import({Controller.class})
|
||||
static class TestConfiguration {
|
||||
@Bean
|
||||
MockClock clock() {
|
||||
return new MockClock();
|
||||
}
|
||||
|
||||
@Bean
|
||||
MeterRegistry meterRegistry(Clock clock) {
|
||||
return new SimpleMeterRegistry(SimpleConfig.DEFAULT, clock);
|
||||
}
|
||||
|
||||
@Bean
|
||||
MeterRegistry meterRegistry() {
|
||||
return new SimpleMeterRegistry();
|
||||
public WebMvcMetrics controllerMetrics(MeterRegistry registry) {
|
||||
return new WebMvcMetrics(registry, new DefaultWebMvcTagsProvider(), "http.server.requests", true,
|
||||
false);
|
||||
}
|
||||
|
||||
@Bean
|
||||
WebMvcMetrics webMvcMetrics(MeterRegistry meterRegistry) {
|
||||
return new WebMvcMetrics(meterRegistry, new DefaultWebMvcTagsProvider(),
|
||||
"http.server.requests", true, true);
|
||||
public MetricsFilter webMetricsFilter(WebMvcMetrics controllerMetrics, HandlerMappingIntrospector introspector) {
|
||||
return new MetricsFilter(controllerMetrics, introspector);
|
||||
}
|
||||
}
|
||||
|
||||
@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));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api")
|
||||
static class Controller {
|
||||
|
||||
@GetMapping("/{id}")
|
||||
public String successful(@PathVariable Long id) {
|
||||
return id.toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api")
|
||||
static class Controller {
|
||||
@GetMapping("/{id}")
|
||||
public String successful(@PathVariable Long id) {
|
||||
return id.toString();
|
||||
}
|
||||
}
|
||||
}
|
@ -16,26 +16,39 @@
|
||||
|
||||
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;
|
||||
|
||||
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.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.test.context.TestPropertySource;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
import org.springframework.test.context.web.WebAppConfiguration;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
@ -48,10 +61,12 @@ 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,40 +76,57 @@ 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 MetricsFilter}
|
||||
*
|
||||
* @author Jon Schneider
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@WebAppConfiguration
|
||||
public class MetricsHandlerInterceptorTests {
|
||||
|
||||
private static final CountDownLatch longRequestCountDown = new CountDownLatch(1);
|
||||
|
||||
public class MetricsFilterTests {
|
||||
@Autowired
|
||||
private MeterRegistry registry;
|
||||
private PrometheusMeterRegistry registry;
|
||||
|
||||
@Autowired
|
||||
private WebApplicationContext context;
|
||||
|
||||
@Autowired
|
||||
private MetricsFilter 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(filter, new RedirectAndNotFoundFilter())
|
||||
.build();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void timedMethod() throws Exception {
|
||||
this.mvc.perform(get("/api/c1/10")).andExpect(status().isOk());
|
||||
|
||||
assertThat(this.registry.find("http.server.requests")
|
||||
.tags("status", "200", "uri", "/api/c1/{id}", "public", "true")
|
||||
.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());
|
||||
|
||||
assertThat(this.registry.find("http.server.requests")
|
||||
.tags("uri", "/api/c1/untimed/10").timer()).isEmpty();
|
||||
}
|
||||
@ -102,23 +134,48 @@ 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();
|
||||
@ -127,14 +184,19 @@ public class MetricsHandlerInterceptorTests {
|
||||
@Test
|
||||
public void longRunningRequest() throws Exception {
|
||||
MvcResult result = this.mvc.perform(get("/api/c1/long/10"))
|
||||
.andExpect(request().asyncStarted()).andReturn();
|
||||
.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();
|
||||
asyncLatch.countDown();
|
||||
|
||||
this.mvc.perform(asyncDispatch(result)).andExpect(status().isOk());
|
||||
|
||||
@ -145,6 +207,7 @@ public class MetricsHandlerInterceptorTests {
|
||||
@Test
|
||||
public void endpointThrowsError() throws Exception {
|
||||
this.mvc.perform(get("/api/c1/error/10")).andExpect(status().is4xxClientError());
|
||||
|
||||
assertThat(this.registry.find("http.server.requests").tags("status", "422")
|
||||
.value(Statistic.Count, 1.0).timer()).isPresent();
|
||||
}
|
||||
@ -152,6 +215,7 @@ public class MetricsHandlerInterceptorTests {
|
||||
@Test
|
||||
public void regexBasedRequestMapping() throws Exception {
|
||||
this.mvc.perform(get("/api/c1/regex/.abc")).andExpect(status().isOk());
|
||||
|
||||
assertThat(this.registry.find("http.server.requests")
|
||||
.tags("uri", "/api/c1/regex/{id:\\.[a-z]+}").value(Statistic.Count, 1.0)
|
||||
.timer()).isPresent();
|
||||
@ -159,80 +223,76 @@ public class MetricsHandlerInterceptorTests {
|
||||
|
||||
@Test
|
||||
public void recordQuantiles() throws Exception {
|
||||
this.mvc.perform(get("/api/c1/quantiles/10")).andExpect(status().isOk());
|
||||
this.mvc.perform(get("/api/c1/percentiles/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();
|
||||
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.find("http.server.requests").meters()
|
||||
.stream().flatMap((m) -> StreamSupport
|
||||
.stream(m.getId().getTags().spliterator(), false))
|
||||
.map(Tag::getKey)).contains("bucket");
|
||||
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 {
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
@Import({ Controller1.class, Controller2.class })
|
||||
static class TestConfiguration {
|
||||
|
||||
@Import({Controller1.class, Controller2.class})
|
||||
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 MetricsFilter webMetricsFilter(WebMvcMetrics controllerMetrics, HandlerMappingIntrospector introspector) {
|
||||
return new MetricsFilter(controllerMetrics, introspector);
|
||||
}
|
||||
}
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/c1")
|
||||
static class Controller1 {
|
||||
private final CountDownLatch asyncLatch;
|
||||
|
||||
@Timed(extraTags = { "public", "true" })
|
||||
public 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(value = "my.long.request", extraTags = { "region",
|
||||
"test" }, longTask = true) // in progress metric
|
||||
@Timed
|
||||
@Timed(value = "my.long.request", extraTags = {"region", "test"}, longTask = true)
|
||||
@GetMapping("/long/{id}")
|
||||
public Callable<String> takesLongTimeToSatisfy(@PathVariable Long id) {
|
||||
return () -> {
|
||||
try {
|
||||
longRequestCountDown.await();
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
asyncLatch.await();
|
||||
} catch (InterruptedException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return id.toString();
|
||||
@ -247,13 +307,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 +322,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;
|
||||
}
|
||||
|
||||
@ -279,19 +345,31 @@ public class MetricsHandlerInterceptorTests {
|
||||
ModelAndView defaultErrorHandler(HttpServletRequest request, Exception e) {
|
||||
return new ModelAndView("myerror");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@RestController
|
||||
@Timed
|
||||
@RequestMapping("/api/c2")
|
||||
static class Controller2 {
|
||||
|
||||
@GetMapping("/{id}")
|
||||
public String successful(@PathVariable Long id) {
|
||||
return id.toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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;
|
||||
@ -33,15 +36,10 @@ import org.springframework.test.context.junit4.SpringRunner;
|
||||
import org.springframework.test.context.web.WebAppConfiguration;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
|
||||
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.bind.annotation.*;
|
||||
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,42 +61,64 @@ public class WebMvcMetricsIntegrationTests {
|
||||
@Autowired
|
||||
private SimpleMeterRegistry registry;
|
||||
|
||||
@Autowired
|
||||
private MockClock clock;
|
||||
|
||||
private MockMvc mvc;
|
||||
|
||||
@Autowired
|
||||
private MetricsFilter filter;
|
||||
|
||||
@Before
|
||||
public void setupMockMvc() {
|
||||
this.mvc = MockMvcBuilders.webAppContextSetup(this.context).build();
|
||||
this.mvc = MockMvcBuilders.webAppContextSetup(this.context)
|
||||
.addFilters(filter)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void handledExceptionIsRecordedInMetricTag() throws Exception {
|
||||
this.mvc.perform(get("/api/handledError")).andExpect(status().is5xxServerError());
|
||||
|
||||
clock.add(SimpleConfig.DEFAULT_STEP);
|
||||
assertThat(this.registry.find("http.server.requests")
|
||||
.tags("exception", "Exception1").value(Statistic.Count, 1.0).timer())
|
||||
.isPresent();
|
||||
.isPresent();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rethrownExceptionIsRecordedInMetricTag() throws Exception {
|
||||
assertThatCode(() -> this.mvc.perform(get("/api/rethrownError"))
|
||||
.andExpect(status().is5xxServerError()));
|
||||
|
||||
clock.add(SimpleConfig.DEFAULT_STEP);
|
||||
assertThat(this.registry.find("http.server.requests")
|
||||
.tags("exception", "Exception2").value(Statistic.Count, 1.0).timer())
|
||||
.isPresent();
|
||||
.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 registry() {
|
||||
return new SimpleMeterRegistry();
|
||||
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
|
||||
public MetricsFilter webMetricsFilter(WebMvcMetrics controllerMetrics, HandlerMappingIntrospector introspector) {
|
||||
return new MetricsFilter(controllerMetrics, introspector);
|
||||
}
|
||||
|
||||
@RestController
|
||||
@ -122,33 +142,12 @@ 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 {
|
||||
|
||||
}
|
||||
|
||||
static class Exception2 extends RuntimeException {
|
||||
|
||||
}
|
||||
|
||||
@ControllerAdvice
|
||||
@ -168,7 +167,5 @@ public class WebMvcMetricsIntegrationTests {
|
||||
ResponseEntity<String> rethrowError(Exception2 ex) throws Throwable {
|
||||
throw ex;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -114,7 +114,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-SNAPSHOT</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>
|
||||
|
Loading…
Reference in New Issue
Block a user