Support property based MeterFilters
Add per-meter property support for `enabled`, `percentiles-histogram`, `percentiles` and `sla`. Fixes gh-11800
This commit is contained in:
@@ -49,6 +49,7 @@ 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;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.integration.config.EnableIntegrationManagement;
|
||||
import org.springframework.integration.support.management.IntegrationManagementConfigurer;
|
||||
|
||||
@@ -96,6 +97,12 @@ public class MetricsAutoConfiguration {
|
||||
return Clock.SYSTEM;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Order(0)
|
||||
public PropertiesMeterFilter propertiesMeterFilter(MetricsProperties properties) {
|
||||
return new PropertiesMeterFilter(properties);
|
||||
}
|
||||
|
||||
/**
|
||||
* Binds metrics from Spring Integration.
|
||||
*/
|
||||
|
||||
@@ -16,7 +16,11 @@
|
||||
|
||||
package org.springframework.boot.actuate.autoconfigure.metrics;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* {@link ConfigurationProperties} for configuring Micrometer-based metrics.
|
||||
@@ -27,8 +31,6 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
@ConfigurationProperties("management.metrics")
|
||||
public class MetricsProperties {
|
||||
|
||||
private final Web web = new Web();
|
||||
|
||||
/**
|
||||
* Whether auto-configured MeterRegistry implementations should be bound to the global
|
||||
* static registry on Metrics. For testing, set this to 'false' to maximize test
|
||||
@@ -36,6 +38,16 @@ public class MetricsProperties {
|
||||
*/
|
||||
private boolean useGlobalRegistry = true;
|
||||
|
||||
/**
|
||||
* Whether meter IDs starting-with the specified name should be enabled. The longest
|
||||
* match wins, the key `all` can also be used to configure all meters.
|
||||
*/
|
||||
private Map<String, Boolean> enable = new LinkedHashMap<>();
|
||||
|
||||
private final Web web = new Web();
|
||||
|
||||
private final Distribution distribution = new Distribution();
|
||||
|
||||
public boolean isUseGlobalRegistry() {
|
||||
return this.useGlobalRegistry;
|
||||
}
|
||||
@@ -44,10 +56,23 @@ public class MetricsProperties {
|
||||
this.useGlobalRegistry = useGlobalRegistry;
|
||||
}
|
||||
|
||||
public Map<String, Boolean> getEnable() {
|
||||
return this.enable;
|
||||
}
|
||||
|
||||
public void setEnable(Map<String, Boolean> enable) {
|
||||
Assert.notNull(enable, "enable must not be null");
|
||||
this.enable = enable;
|
||||
}
|
||||
|
||||
public Web getWeb() {
|
||||
return this.web;
|
||||
}
|
||||
|
||||
public Distribution getDistribution() {
|
||||
return this.distribution;
|
||||
}
|
||||
|
||||
public static class Web {
|
||||
|
||||
private final Client client = new Client();
|
||||
@@ -118,13 +143,6 @@ public class MetricsProperties {
|
||||
*/
|
||||
private boolean autoTimeRequests = true;
|
||||
|
||||
/**
|
||||
* Whether or not instrumented requests record percentiles histogram buckets
|
||||
* by default. Can be overridden by adding '@Timed' to a request endpoint and
|
||||
* setting 'percentiles' to true.
|
||||
*/
|
||||
private boolean recordRequestPercentiles;
|
||||
|
||||
/**
|
||||
* Name of the metric for received requests.
|
||||
*/
|
||||
@@ -138,14 +156,6 @@ public class MetricsProperties {
|
||||
this.autoTimeRequests = autoTimeRequests;
|
||||
}
|
||||
|
||||
public boolean isRecordRequestPercentiles() {
|
||||
return this.recordRequestPercentiles;
|
||||
}
|
||||
|
||||
public void setRecordRequestPercentiles(boolean recordRequestPercentiles) {
|
||||
this.recordRequestPercentiles = recordRequestPercentiles;
|
||||
}
|
||||
|
||||
public String getRequestsMetricName() {
|
||||
return this.requestsMetricName;
|
||||
}
|
||||
@@ -158,4 +168,59 @@ public class MetricsProperties {
|
||||
|
||||
}
|
||||
|
||||
public static class Distribution {
|
||||
|
||||
/**
|
||||
* Whether meter IDs starting-with the specified name should be publish percentile
|
||||
* histograms. Monitoring systems that support aggregable percentile calculation
|
||||
* based on a histogram be set to true. For other systems, this has no effect. The
|
||||
* longest match wins, the key `all` can also be used to configure all meters.
|
||||
*/
|
||||
private Map<String, Boolean> percentilesHistogram = new LinkedHashMap<>();
|
||||
|
||||
/**
|
||||
* Specific computed non-aggregable percentiles to ship to the backend for meter
|
||||
* IDs starting-with the specified name. The longest match wins, the key `all` can
|
||||
* also be used to configure all meters.
|
||||
*/
|
||||
private Map<String, double[]> percentiles = new LinkedHashMap<>();
|
||||
|
||||
/**
|
||||
* Specific SLA boundaries for meter IDs starting-with the specified name. The
|
||||
* longest match wins, the key `all` can also be used to configure all meters.
|
||||
* Counters will be published for each sepecified boundary. Values can be
|
||||
* specified as a long or as a Duration value (for timer meters, defaulting to ms
|
||||
* if no unit specified).
|
||||
*/
|
||||
private Map<String, ServiceLevelAgreementBoundary[]> sla = new LinkedHashMap<>();
|
||||
|
||||
public Map<String, Boolean> getPercentilesHistogram() {
|
||||
return this.percentilesHistogram;
|
||||
}
|
||||
|
||||
public void setPercentilesHistogram(Map<String, Boolean> percentilesHistogram) {
|
||||
Assert.notNull(percentilesHistogram, "PercentilesHistogram must not be null");
|
||||
this.percentilesHistogram = percentilesHistogram;
|
||||
}
|
||||
|
||||
public Map<String, double[]> getPercentiles() {
|
||||
return this.percentiles;
|
||||
}
|
||||
|
||||
public void setPercentiles(Map<String, double[]> percentiles) {
|
||||
Assert.notNull(percentiles, "Percentiles must not be null");
|
||||
this.percentiles = percentiles;
|
||||
}
|
||||
|
||||
public Map<String, ServiceLevelAgreementBoundary[]> getSla() {
|
||||
return this.sla;
|
||||
}
|
||||
|
||||
public void setSla(Map<String, ServiceLevelAgreementBoundary[]> sla) {
|
||||
Assert.notNull(sla, "SLA must not be null");
|
||||
this.sla = sla;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* Copyright 2012-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.autoconfigure.metrics;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import io.micrometer.core.instrument.Meter;
|
||||
import io.micrometer.core.instrument.Meter.Id;
|
||||
import io.micrometer.core.instrument.config.MeterFilter;
|
||||
import io.micrometer.core.instrument.config.MeterFilterReply;
|
||||
import io.micrometer.core.instrument.histogram.HistogramConfig;
|
||||
|
||||
import org.springframework.boot.actuate.autoconfigure.metrics.MetricsProperties.Distribution;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* {@link MeterFilter} to apply settings from {@link MetricsProperties}.
|
||||
*
|
||||
* @author Jon Schneider
|
||||
* @author Phillip Webb
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public class PropertiesMeterFilter implements MeterFilter {
|
||||
|
||||
private static final ServiceLevelAgreementBoundary[] EMPTY_SLA = {};
|
||||
|
||||
private MetricsProperties properties;
|
||||
|
||||
public PropertiesMeterFilter(MetricsProperties properties) {
|
||||
Assert.notNull(properties, "Properties must not be null");
|
||||
this.properties = properties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MeterFilterReply accept(Meter.Id id) {
|
||||
boolean enabled = lookup(this.properties.getEnable(), id, true);
|
||||
return (enabled ? MeterFilterReply.NEUTRAL : MeterFilterReply.DENY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public HistogramConfig configure(Meter.Id id, HistogramConfig config) {
|
||||
HistogramConfig.Builder builder = HistogramConfig.builder();
|
||||
Distribution distribution = this.properties.getDistribution();
|
||||
builder.percentilesHistogram(lookup(distribution.getPercentilesHistogram(), id, null));
|
||||
builder.percentiles(lookup(distribution.getPercentiles(), id, null));
|
||||
builder.sla(convertSla(id.getType(), lookup(distribution.getSla(), id, null)));
|
||||
return builder.build().merge(config);
|
||||
}
|
||||
|
||||
private long[] convertSla(Meter.Type meterType, ServiceLevelAgreementBoundary[] sla) {
|
||||
long[] converted = Arrays.stream(sla == null ? EMPTY_SLA : sla)
|
||||
.map((candidate) -> candidate.getValue(meterType))
|
||||
.filter(Objects::nonNull).mapToLong(Long::longValue).toArray();
|
||||
return (converted.length == 0 ? null : converted);
|
||||
}
|
||||
|
||||
private <T> T lookup(Map<String, T> values, Id id, T defaultValue) {
|
||||
String name = id.getName();
|
||||
while (StringUtils.hasLength(name)) {
|
||||
T result = values.get(name);
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
int lastDot = name.lastIndexOf('.');
|
||||
name = lastDot == -1 ? "" : name.substring(0, lastDot);
|
||||
}
|
||||
return values.getOrDefault("all", defaultValue);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* Copyright 2012-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.autoconfigure.metrics;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import io.micrometer.core.instrument.Meter;
|
||||
import io.micrometer.core.instrument.Meter.Type;
|
||||
|
||||
import org.springframework.boot.context.properties.bind.convert.DurationConverter;
|
||||
|
||||
/**
|
||||
* A service level agreement boundary for use when configuring micrometer. Can be
|
||||
* specified as either a {@link Long} (applicable to timers and distribution summaries) or
|
||||
* a {@link Long} (applicable to only timers).
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public final class ServiceLevelAgreementBoundary {
|
||||
|
||||
private final Object value;
|
||||
|
||||
ServiceLevelAgreementBoundary(long value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
ServiceLevelAgreementBoundary(Duration value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the underlying value of the SLA in form suitable to apply to the given meter
|
||||
* type.
|
||||
* @param meterType the meter type
|
||||
* @return the value or {@code null} if the value cannot be applied
|
||||
*/
|
||||
public Long getValue(Meter.Type meterType) {
|
||||
if (meterType == Type.DISTRIBUTION_SUMMARY) {
|
||||
return getDistributionSummaryValue();
|
||||
}
|
||||
if (meterType == Type.TIMER) {
|
||||
return getTimerValue();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private Long getDistributionSummaryValue() {
|
||||
if (this.value instanceof Long) {
|
||||
return (Long) this.value;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private Long getTimerValue() {
|
||||
if (this.value instanceof Long) {
|
||||
return TimeUnit.MILLISECONDS.toNanos((long) this.value);
|
||||
}
|
||||
if (this.value instanceof Duration) {
|
||||
return ((Duration) this.value).toNanos();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static ServiceLevelAgreementBoundary valueOf(String value) {
|
||||
if (isNumber(value)) {
|
||||
return new ServiceLevelAgreementBoundary(Long.parseLong(value));
|
||||
}
|
||||
return new ServiceLevelAgreementBoundary(
|
||||
DurationConverter.toDuration(value, null));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a new {@link ServiceLevelAgreementBoundary} instance for the given long
|
||||
* value.
|
||||
* @param value the source value
|
||||
* @return a {@link ServiceLevelAgreementBoundary} instance
|
||||
*/
|
||||
public static ServiceLevelAgreementBoundary valueOf(long value) {
|
||||
return new ServiceLevelAgreementBoundary(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a new {@link ServiceLevelAgreementBoundary} instance for the given String
|
||||
* value. The value may contain a simple number, or a {@link DurationConverter
|
||||
* duration formatted value}
|
||||
* @param value the source value
|
||||
* @return a {@link ServiceLevelAgreementBoundary} instance
|
||||
*/
|
||||
private static boolean isNumber(String value) {
|
||||
return value.chars().allMatch(Character::isDigit);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -104,6 +104,16 @@ public class MetricsAutoConfigurationTests {
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void propertyBasedMeterFilter() {
|
||||
this.contextRunner.withPropertyValues("management.metrics.enable.my.org=false")
|
||||
.run(context -> {
|
||||
MeterRegistry registry = context.getBean(MeterRegistry.class);
|
||||
registry.timer("my.org.timer");
|
||||
assertThat(registry.find("my.org.timer").timer()).isNull();
|
||||
});
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class TwoDataSourcesConfiguration {
|
||||
|
||||
|
||||
@@ -0,0 +1,314 @@
|
||||
/*
|
||||
* Copyright 2012-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.autoconfigure.metrics;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import io.micrometer.core.instrument.Meter;
|
||||
import io.micrometer.core.instrument.Meter.Id;
|
||||
import io.micrometer.core.instrument.Meter.Type;
|
||||
import io.micrometer.core.instrument.config.MeterFilterReply;
|
||||
import io.micrometer.core.instrument.histogram.HistogramConfig;
|
||||
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import org.springframework.boot.context.properties.bind.Bindable;
|
||||
import org.springframework.boot.context.properties.bind.Binder;
|
||||
import org.springframework.boot.test.util.TestPropertyValues;
|
||||
import org.springframework.mock.env.MockEnvironment;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests fir {@link PropertiesMeterFilter}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Jon Schneider
|
||||
*/
|
||||
public class PropertiesMeterFilterTests {
|
||||
|
||||
@Rule
|
||||
public ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
@Mock
|
||||
private HistogramConfig config;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createWhenPropertiesIsNullShouldThrowException() {
|
||||
this.thrown.expect(IllegalArgumentException.class);
|
||||
this.thrown.expectMessage("Properties must not be null");
|
||||
new PropertiesMeterFilter(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void acceptWhenHasNoEnabledPropertiesShouldReturnNeutral() {
|
||||
PropertiesMeterFilter filter = new PropertiesMeterFilter(createProperties());
|
||||
assertThat(filter.accept(createMeterId("spring.boot")))
|
||||
.isEqualTo(MeterFilterReply.NEUTRAL);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void acceptWhenHasEnableFalseShouldReturnDeny() {
|
||||
PropertiesMeterFilter filter = new PropertiesMeterFilter(
|
||||
createProperties("enable.spring.boot=false"));
|
||||
assertThat(filter.accept(createMeterId("spring.boot")))
|
||||
.isEqualTo(MeterFilterReply.DENY);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void acceptWhenHasEnableTrueShouldReturnNeutral() {
|
||||
PropertiesMeterFilter filter = new PropertiesMeterFilter(
|
||||
createProperties("enable.spring.boot=true"));
|
||||
assertThat(filter.accept(createMeterId("spring.boot")))
|
||||
.isEqualTo(MeterFilterReply.NEUTRAL);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void acceptWhenHasHigherEnableFalseShouldReturnDeny() {
|
||||
PropertiesMeterFilter filter = new PropertiesMeterFilter(
|
||||
createProperties("enable.spring=false"));
|
||||
assertThat(filter.accept(createMeterId("spring.boot")))
|
||||
.isEqualTo(MeterFilterReply.DENY);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void acceptWhenHasHigherEnableTrueShouldReturnNeutral() {
|
||||
PropertiesMeterFilter filter = new PropertiesMeterFilter(
|
||||
createProperties("enable.spring=true"));
|
||||
assertThat(filter.accept(createMeterId("spring.boot")))
|
||||
.isEqualTo(MeterFilterReply.NEUTRAL);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void acceptWhenHasHigherEnableFalseExactEnableTrueShouldReturnNeutral() {
|
||||
PropertiesMeterFilter filter = new PropertiesMeterFilter(
|
||||
createProperties("enable.spring=false", "enable.spring.boot=true"));
|
||||
assertThat(filter.accept(createMeterId("spring.boot")))
|
||||
.isEqualTo(MeterFilterReply.NEUTRAL);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void acceptWhenHasHigherEnableTrueExactEnableFalseShouldReturnDeny() {
|
||||
PropertiesMeterFilter filter = new PropertiesMeterFilter(
|
||||
createProperties("enable.spring=true", "enable.spring.boot=false"));
|
||||
assertThat(filter.accept(createMeterId("spring.boot")))
|
||||
.isEqualTo(MeterFilterReply.DENY);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void acceptWhenHasAllEnableFalseShouldReturnDeny() {
|
||||
PropertiesMeterFilter filter = new PropertiesMeterFilter(
|
||||
createProperties("enable.all=false"));
|
||||
assertThat(filter.accept(createMeterId("spring.boot")))
|
||||
.isEqualTo(MeterFilterReply.DENY);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void acceptWhenHasAllEnableFalseButHigherEnableTrueShouldReturnNeutral() {
|
||||
PropertiesMeterFilter filter = new PropertiesMeterFilter(
|
||||
createProperties("enable.all=false", "enable.spring=true"));
|
||||
assertThat(filter.accept(createMeterId("spring.boot")))
|
||||
.isEqualTo(MeterFilterReply.NEUTRAL);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void configureWhenHasHistogramTrueShouldSetPercentilesHistogramToTrue() {
|
||||
PropertiesMeterFilter filter = new PropertiesMeterFilter(
|
||||
createProperties("distribution.percentiles-histogram.spring.boot=true"));
|
||||
assertThat(filter.configure(createMeterId("spring.boot"), HistogramConfig.DEFAULT)
|
||||
.isPercentileHistogram()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void configureWhenHasHistogramFalseShouldSetPercentilesHistogramToFalse() {
|
||||
PropertiesMeterFilter filter = new PropertiesMeterFilter(
|
||||
createProperties("distribution.percentiles-histogram.spring.boot=false"));
|
||||
assertThat(filter.configure(createMeterId("spring.boot"), HistogramConfig.DEFAULT)
|
||||
.isPercentileHistogram()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void configureWhenHasHigherHistogramTrueShouldSetPercentilesHistogramToTrue() {
|
||||
PropertiesMeterFilter filter = new PropertiesMeterFilter(
|
||||
createProperties("distribution.percentiles-histogram.spring=true"));
|
||||
assertThat(filter.configure(createMeterId("spring.boot"), HistogramConfig.DEFAULT)
|
||||
.isPercentileHistogram()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void configureWhenHasHigherHistogramFalseShouldSetPercentilesHistogramToFalse() {
|
||||
PropertiesMeterFilter filter = new PropertiesMeterFilter(
|
||||
createProperties("distribution.percentiles-histogram.spring=false"));
|
||||
assertThat(filter.configure(createMeterId("spring.boot"), HistogramConfig.DEFAULT)
|
||||
.isPercentileHistogram()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void configureWhenHasHigherHistogramTrueAndLowerFalseShouldSetPercentilesHistogramToFalse() {
|
||||
PropertiesMeterFilter filter = new PropertiesMeterFilter(
|
||||
createProperties("distribution.percentiles-histogram.spring=true",
|
||||
"distribution.percentiles-histogram.spring.boot=false"));
|
||||
assertThat(filter.configure(createMeterId("spring.boot"), HistogramConfig.DEFAULT)
|
||||
.isPercentileHistogram()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void configureWhenHasHigherHistogramFalseAndLowerTrueShouldSetPercentilesHistogramToFalse() {
|
||||
PropertiesMeterFilter filter = new PropertiesMeterFilter(
|
||||
createProperties("distribution.percentiles-histogram.spring=false",
|
||||
"distribution.percentiles-histogram.spring.boot=true"));
|
||||
assertThat(filter.configure(createMeterId("spring.boot"), HistogramConfig.DEFAULT)
|
||||
.isPercentileHistogram()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void configureWhenAllHistogramTrueSetPercentilesHistogramToTrue() {
|
||||
PropertiesMeterFilter filter = new PropertiesMeterFilter(
|
||||
createProperties("distribution.percentiles-histogram.all=true"));
|
||||
assertThat(filter.configure(createMeterId("spring.boot"), HistogramConfig.DEFAULT)
|
||||
.isPercentileHistogram()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void configureWhenHasPercentilesShouldSetPercentilesToValue() {
|
||||
PropertiesMeterFilter filter = new PropertiesMeterFilter(
|
||||
createProperties("distribution.percentiles.spring.boot=1,1.5,2"));
|
||||
assertThat(filter.configure(createMeterId("spring.boot"), HistogramConfig.DEFAULT)
|
||||
.getPercentiles()).containsExactly(1, 1.5, 2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void configureWhenHasHigherPercentilesShouldSetPercentilesToValue() {
|
||||
PropertiesMeterFilter filter = new PropertiesMeterFilter(
|
||||
createProperties("distribution.percentiles.spring=1,1.5,2"));
|
||||
assertThat(filter.configure(createMeterId("spring.boot"), HistogramConfig.DEFAULT)
|
||||
.getPercentiles()).containsExactly(1, 1.5, 2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void configureWhenHasHigherPercentilesAndLowerShouldSetPercentilesToHigher() {
|
||||
PropertiesMeterFilter filter = new PropertiesMeterFilter(
|
||||
createProperties("distribution.percentiles.spring=1,1.5,2",
|
||||
"distribution.percentiles.spring.boot=3,3.5,4"));
|
||||
assertThat(filter.configure(createMeterId("spring.boot"), HistogramConfig.DEFAULT)
|
||||
.getPercentiles()).containsExactly(3, 3.5, 4);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void configureWhenAllPercentilesSetShouldSetPercentilesToValue() {
|
||||
PropertiesMeterFilter filter = new PropertiesMeterFilter(
|
||||
createProperties("distribution.percentiles.all=1,1.5,2"));
|
||||
assertThat(filter.configure(createMeterId("spring.boot"), HistogramConfig.DEFAULT)
|
||||
.getPercentiles()).containsExactly(1, 1.5, 2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void configureWhenHasSlaShouldSetSlaToValue() {
|
||||
PropertiesMeterFilter filter = new PropertiesMeterFilter(
|
||||
createProperties("distribution.sla.spring.boot=1,2,3"));
|
||||
assertThat(filter.configure(createMeterId("spring.boot"), HistogramConfig.DEFAULT)
|
||||
.getSlaBoundaries()).containsExactly(1000000, 2000000, 3000000);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void configureWhenHasHigherSlaShouldSetPercentilesToValue() {
|
||||
PropertiesMeterFilter filter = new PropertiesMeterFilter(
|
||||
createProperties("distribution.sla.spring=1,2,3"));
|
||||
assertThat(filter.configure(createMeterId("spring.boot"), HistogramConfig.DEFAULT)
|
||||
.getSlaBoundaries()).containsExactly(1000000, 2000000, 3000000);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void configureWhenHasHigherSlaAndLowerShouldSetSlaToHigher() {
|
||||
PropertiesMeterFilter filter = new PropertiesMeterFilter(createProperties(
|
||||
"distribution.sla.spring=1,2,3", "distribution.sla.spring.boot=4,5,6"));
|
||||
assertThat(filter.configure(createMeterId("spring.boot"), HistogramConfig.DEFAULT)
|
||||
.getSlaBoundaries()).containsExactly(4000000, 5000000, 6000000);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void configureWhenAllSlaSetShouldSetSlaToValue() {
|
||||
PropertiesMeterFilter filter = new PropertiesMeterFilter(
|
||||
createProperties("distribution.sla.all=1,2,3"));
|
||||
assertThat(filter.configure(createMeterId("spring.boot"), HistogramConfig.DEFAULT)
|
||||
.getSlaBoundaries()).containsExactly(1000000, 2000000, 3000000);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void configureWhenSlaDurationShouldOnlyApplyToTimer() {
|
||||
PropertiesMeterFilter filter = new PropertiesMeterFilter(
|
||||
createProperties("distribution.sla.all=1ms,2ms,3ms"));
|
||||
Meter.Id timer = createMeterId("spring.boot", Meter.Type.TIMER);
|
||||
Meter.Id summary = createMeterId("spring.boot", Meter.Type.DISTRIBUTION_SUMMARY);
|
||||
Meter.Id counter = createMeterId("spring.boot", Meter.Type.COUNTER);
|
||||
assertThat(filter.configure(timer, HistogramConfig.DEFAULT).getSlaBoundaries())
|
||||
.containsExactly(1000000, 2000000, 3000000);
|
||||
assertThat(filter.configure(summary, HistogramConfig.DEFAULT).getSlaBoundaries())
|
||||
.isEmpty();
|
||||
assertThat(filter.configure(counter, HistogramConfig.DEFAULT).getSlaBoundaries())
|
||||
.isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void configureWhenSlaLongShouldOnlyApplyToTimerAndDistributionSummary() {
|
||||
PropertiesMeterFilter filter = new PropertiesMeterFilter(
|
||||
createProperties("distribution.sla.all=1,2,3"));
|
||||
Meter.Id timer = createMeterId("spring.boot", Meter.Type.TIMER);
|
||||
Meter.Id summary = createMeterId("spring.boot", Meter.Type.DISTRIBUTION_SUMMARY);
|
||||
Meter.Id counter = createMeterId("spring.boot", Meter.Type.COUNTER);
|
||||
assertThat(filter.configure(timer, HistogramConfig.DEFAULT).getSlaBoundaries())
|
||||
.containsExactly(1000000, 2000000, 3000000);
|
||||
assertThat(filter.configure(summary, HistogramConfig.DEFAULT).getSlaBoundaries())
|
||||
.containsExactly(1, 2, 3);
|
||||
assertThat(filter.configure(counter, HistogramConfig.DEFAULT).getSlaBoundaries())
|
||||
.isEmpty();
|
||||
}
|
||||
|
||||
private Id createMeterId(String name) {
|
||||
Meter.Type meterType = Type.TIMER;
|
||||
return createMeterId(name, meterType);
|
||||
}
|
||||
|
||||
private Id createMeterId(String name, Meter.Type meterType) {
|
||||
TestMeterRegistry registry = new TestMeterRegistry();
|
||||
return Meter.builder(name, meterType, Collections.emptyList()).register(registry)
|
||||
.getId();
|
||||
}
|
||||
|
||||
private MetricsProperties createProperties(String... properties) {
|
||||
MockEnvironment environment = new MockEnvironment();
|
||||
TestPropertyValues.of(properties).applyTo(environment);
|
||||
Binder binder = Binder.get(environment);
|
||||
return binder.bind("", Bindable.of(MetricsProperties.class))
|
||||
.orElseGet(MetricsProperties::new);
|
||||
}
|
||||
|
||||
private static class TestMeterRegistry extends SimpleMeterRegistry {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* Copyright 2012-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.autoconfigure.metrics;
|
||||
|
||||
import io.micrometer.core.instrument.Meter.Type;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.boot.context.properties.bind.Bindable;
|
||||
import org.springframework.boot.context.properties.bind.Binder;
|
||||
import org.springframework.boot.test.util.TestPropertyValues;
|
||||
import org.springframework.mock.env.MockEnvironment;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link ServiceLevelAgreementBoundary}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class ServiceLevelAgreementBoundaryTests {
|
||||
|
||||
@Test
|
||||
public void getValueForDistributionSummaryWhenFromLongShouldReturnLongValue() {
|
||||
ServiceLevelAgreementBoundary sla = ServiceLevelAgreementBoundary.valueOf(123L);
|
||||
assertThat(sla.getValue(Type.DISTRIBUTION_SUMMARY)).isEqualTo(123);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getValueForDistributionSummaryWhenFromNumberStringShouldReturnLongValue() {
|
||||
ServiceLevelAgreementBoundary sla = ServiceLevelAgreementBoundary.valueOf("123");
|
||||
assertThat(sla.getValue(Type.DISTRIBUTION_SUMMARY)).isEqualTo(123);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getValueForDistributionSummaryWhenFromDurationStringShouldReturnNull() {
|
||||
ServiceLevelAgreementBoundary sla = ServiceLevelAgreementBoundary
|
||||
.valueOf("123ms");
|
||||
assertThat(sla.getValue(Type.DISTRIBUTION_SUMMARY)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getValueForTimerWhenFromLongShouldReturnMsToNanosValue() {
|
||||
ServiceLevelAgreementBoundary sla = ServiceLevelAgreementBoundary.valueOf(123L);
|
||||
assertThat(sla.getValue(Type.TIMER)).isEqualTo(123000000);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getValueForTimerWhenFromNumberStringShouldMsToNanosValue() {
|
||||
ServiceLevelAgreementBoundary sla = ServiceLevelAgreementBoundary.valueOf("123");
|
||||
assertThat(sla.getValue(Type.TIMER)).isEqualTo(123000000);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getValueForTimerWhenFromDurationStringShouldReturnDrationNanos() {
|
||||
ServiceLevelAgreementBoundary sla = ServiceLevelAgreementBoundary
|
||||
.valueOf("123ms");
|
||||
assertThat(sla.getValue(Type.TIMER)).isEqualTo(123000000);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getValueForOthersShouldReturnNull() {
|
||||
ServiceLevelAgreementBoundary sla = ServiceLevelAgreementBoundary.valueOf("123");
|
||||
assertThat(sla.getValue(Type.COUNTER)).isNull();
|
||||
assertThat(sla.getValue(Type.GAUGE)).isNull();
|
||||
assertThat(sla.getValue(Type.LONG_TASK_TIMER)).isNull();
|
||||
assertThat(sla.getValue(Type.OTHER)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void valueOfShouldWorkInBinder() {
|
||||
MockEnvironment environment = new MockEnvironment();
|
||||
TestPropertyValues.of("duration=10ms", "long=20").applyTo(environment);
|
||||
assertThat(Binder.get(environment)
|
||||
.bind("duration", Bindable.of(ServiceLevelAgreementBoundary.class)).get()
|
||||
.getValue(Type.TIMER)).isEqualTo(10000000);
|
||||
assertThat(Binder.get(environment)
|
||||
.bind("long", Bindable.of(ServiceLevelAgreementBoundary.class)).get()
|
||||
.getValue(Type.TIMER)).isEqualTo(20000000);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1378,6 +1378,10 @@ content into your application. Rather, pick only the properties that you need.
|
||||
management.metrics.web.server.auto-time-requests=true # Whether requests handled by Spring MVC or WebFlux should be automatically timed.
|
||||
management.metrics.web.server.record-request-percentiles=false # Whether instrumented requests record percentiles histogram buckets by default.
|
||||
management.metrics.web.server.requests-metric-name=http.server.requests # Name of the metric for received requests.
|
||||
management.metrics.enabled.<name>= # Whether meter IDs starting-with the specified name should be enabled. The longest match wins, the key `all` can also be used to configure all meters.
|
||||
management.metrics.distribution.percentiles-histogram.<name>= # Whether meter IDs starting-with the specified name should be publish percentile histograms.
|
||||
management.metrics.distribution.percentiles.<name>= # Specific computed non-aggregable percentiles to ship to the backend for meter IDs starting-with the specified name.
|
||||
management.metrics.distribution.sla.<name>= Specific SLA boundaries for meter IDs starting-with the specified name. The longest match wins, the key `all` can also be used to configure all meters.
|
||||
|
||||
|
||||
# ----------------------------------------
|
||||
|
||||
@@ -65,6 +65,7 @@ Phillip Webb; Dave Syer; Josh Long; Stéphane Nicoll; Rob Winch; Andy Wilkinson;
|
||||
:gradle-user-guide: https://docs.gradle.org/4.2.1/userguide
|
||||
:hibernate-documentation: https://docs.jboss.org/hibernate/orm/5.2/userguide/html_single/Hibernate_User_Guide.html
|
||||
:jetty-documentation: https://www.eclipse.org/jetty/documentation/9.4.x
|
||||
:micrometer-concepts-documentation: https://micrometer.io/docs/concepts
|
||||
:tomcat-documentation: https://tomcat.apache.org/tomcat-8.5-doc
|
||||
// ======================================================================================
|
||||
|
||||
|
||||
@@ -1294,6 +1294,59 @@ Auto-configuration enables binding of a number of Spring Integration-related met
|
||||
|
||||
|
||||
|
||||
[[production-ready-metrics-per-meter-properties]]
|
||||
=== Customizing individual meters
|
||||
If you need to apply customizations to specific `Meter` instances you can use the
|
||||
`io.micrometer.core.instrument.config.MeterFilter` interface. By default, all
|
||||
`MeterFilter` beans will be automatically applied to the micrometer
|
||||
`MeterRegistry.Config`.
|
||||
|
||||
For example, if you want to rename the `mytag.region` tag to `mytag.area` for
|
||||
all meter IDs beginning with `com.example`, you can do the following:
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::{code-examples}/actuate/metrics/MetricsFilterBeanExample.java[tag=configuration]
|
||||
----
|
||||
|
||||
==== Per-meter properties
|
||||
In addition to `MeterFilter` beans, it's also possible to apply a limited set of
|
||||
customization on a per-meter basis using properties. Per-meter customizations apply to
|
||||
any all meter IDs that start with the given name. For example, the following will disable
|
||||
any meters that have an ID starting with `example.remote`
|
||||
|
||||
[source,properties,indent=0]
|
||||
----
|
||||
management.metrics.enable.example.remote=false
|
||||
----
|
||||
|
||||
The following properties allow per-meter customization:
|
||||
|
||||
.Per-meter customizations
|
||||
|===
|
||||
| Property | Description
|
||||
|
||||
| `management.metrics.enable`
|
||||
| Whether to deny meters from emitting any metrics.
|
||||
|
||||
| `management.metrics.distribution.percentiles-histogram`
|
||||
| Whether to publish a histogram suitable for computing aggregable (across dimension)
|
||||
percentile approximations.
|
||||
|
||||
| `management.metrics.distribution.percentiles`
|
||||
| Publish percentile values computed in your application
|
||||
|
||||
| `management.metrics.distribution.sla`
|
||||
| Publish a cumulative histogram with buckets defined by your SLAs.
|
||||
|
||||
|===
|
||||
|
||||
For more details on concepts behind `percentiles-histogram`, `percentiles` and `sla`
|
||||
refer to the {micrometer-concepts-documentation}#_histograms_and_percentiles["Histograms
|
||||
and percentiles" section] of the micrometer documentation.
|
||||
|
||||
|
||||
|
||||
[[production-ready-auditing]]
|
||||
== Auditing
|
||||
Once Spring Security is in play, Spring Boot Actuator has a flexible audit framework that
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright 2012-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.metrics;
|
||||
|
||||
import io.micrometer.core.instrument.config.MeterFilter;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* Example to show a {@link MeterFilter}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class MetricsFilterBeanExample {
|
||||
|
||||
@Configuration
|
||||
static class MetricsFilterExampleConfiguration {
|
||||
|
||||
// tag::configuration[]
|
||||
@Bean
|
||||
public MeterFilter renameRegionTagMeterFilter() {
|
||||
return MeterFilter.renameTag("com.example", "mytag.region", "mytag.area");
|
||||
}
|
||||
// end::configuration[]
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -37,8 +37,9 @@ import org.springframework.util.StringUtils;
|
||||
* {@link Duration#parse(CharSequence)} as well a more readable {@code 10s} form.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @since 2.0.0
|
||||
*/
|
||||
class DurationConverter implements GenericConverter {
|
||||
public class DurationConverter implements GenericConverter {
|
||||
|
||||
private static final Set<ConvertiblePair> TYPES;
|
||||
|
||||
@@ -78,11 +79,20 @@ class DurationConverter implements GenericConverter {
|
||||
if (source == null) {
|
||||
return null;
|
||||
}
|
||||
DefaultDurationUnit defaultUnit = targetType
|
||||
.getAnnotation(DefaultDurationUnit.class);
|
||||
return toDuration(source.toString(),
|
||||
targetType.getAnnotation(DefaultDurationUnit.class));
|
||||
(defaultUnit == null ? null : defaultUnit.value()));
|
||||
}
|
||||
|
||||
private Duration toDuration(String source, DefaultDurationUnit defaultUnit) {
|
||||
/**
|
||||
* Convert the specified source to a {@link Duration}.
|
||||
* @param source the source to convert
|
||||
* @param defaultUnit the default unit to use ({@code null} is treated as
|
||||
* milliseconds)
|
||||
* @return the duration
|
||||
*/
|
||||
public static Duration toDuration(String source, ChronoUnit defaultUnit) {
|
||||
try {
|
||||
if (!StringUtils.hasLength(source)) {
|
||||
return null;
|
||||
@@ -103,9 +113,9 @@ class DurationConverter implements GenericConverter {
|
||||
}
|
||||
}
|
||||
|
||||
private ChronoUnit getUnit(String value, DefaultDurationUnit defaultUnit) {
|
||||
private static ChronoUnit getUnit(String value, ChronoUnit defaultUnit) {
|
||||
if (StringUtils.isEmpty(value)) {
|
||||
return (defaultUnit != null ? defaultUnit.value() : ChronoUnit.MILLIS);
|
||||
return (defaultUnit != null ? defaultUnit : ChronoUnit.MILLIS);
|
||||
}
|
||||
ChronoUnit unit = UNITS.get(value.toLowerCase());
|
||||
Assert.state(unit != null, () -> "Unknown unit '" + value + "'");
|
||||
|
||||
Reference in New Issue
Block a user