Support property based MeterFilters

Add per-meter property support for `enabled`, `percentiles-histogram`,
`percentiles` and `sla`.

Fixes gh-11800
pull/11932/merge
Phillip Webb 7 years ago
parent 6b70c96e37
commit 6889ad59b8

@ -49,6 +49,7 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Import;
import org.springframework.core.annotation.Order;
import org.springframework.integration.config.EnableIntegrationManagement; import org.springframework.integration.config.EnableIntegrationManagement;
import org.springframework.integration.support.management.IntegrationManagementConfigurer; import org.springframework.integration.support.management.IntegrationManagementConfigurer;
@ -96,6 +97,12 @@ public class MetricsAutoConfiguration {
return Clock.SYSTEM; return Clock.SYSTEM;
} }
@Bean
@Order(0)
public PropertiesMeterFilter propertiesMeterFilter(MetricsProperties properties) {
return new PropertiesMeterFilter(properties);
}
/** /**
* Binds metrics from Spring Integration. * Binds metrics from Spring Integration.
*/ */

@ -16,7 +16,11 @@
package org.springframework.boot.actuate.autoconfigure.metrics; 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.boot.context.properties.ConfigurationProperties;
import org.springframework.util.Assert;
/** /**
* {@link ConfigurationProperties} for configuring Micrometer-based metrics. * {@link ConfigurationProperties} for configuring Micrometer-based metrics.
@ -27,8 +31,6 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties("management.metrics") @ConfigurationProperties("management.metrics")
public class MetricsProperties { public class MetricsProperties {
private final Web web = new Web();
/** /**
* Whether auto-configured MeterRegistry implementations should be bound to the global * Whether auto-configured MeterRegistry implementations should be bound to the global
* static registry on Metrics. For testing, set this to 'false' to maximize test * static registry on Metrics. For testing, set this to 'false' to maximize test
@ -36,6 +38,16 @@ public class MetricsProperties {
*/ */
private boolean useGlobalRegistry = true; 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() { public boolean isUseGlobalRegistry() {
return this.useGlobalRegistry; return this.useGlobalRegistry;
} }
@ -44,10 +56,23 @@ public class MetricsProperties {
this.useGlobalRegistry = useGlobalRegistry; 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() { public Web getWeb() {
return this.web; return this.web;
} }
public Distribution getDistribution() {
return this.distribution;
}
public static class Web { public static class Web {
private final Client client = new Client(); private final Client client = new Client();
@ -118,13 +143,6 @@ public class MetricsProperties {
*/ */
private boolean autoTimeRequests = true; 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. * Name of the metric for received requests.
*/ */
@ -138,14 +156,6 @@ public class MetricsProperties {
this.autoTimeRequests = autoTimeRequests; this.autoTimeRequests = autoTimeRequests;
} }
public boolean isRecordRequestPercentiles() {
return this.recordRequestPercentiles;
}
public void setRecordRequestPercentiles(boolean recordRequestPercentiles) {
this.recordRequestPercentiles = recordRequestPercentiles;
}
public String getRequestsMetricName() { public String getRequestsMetricName() {
return this.requestsMetricName; 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 @Configuration
static class TwoDataSourcesConfiguration { 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.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.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.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 :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 :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 :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 :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]] [[production-ready-auditing]]
== Auditing == Auditing
Once Spring Security is in play, Spring Boot Actuator has a flexible audit framework that 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. * {@link Duration#parse(CharSequence)} as well a more readable {@code 10s} form.
* *
* @author Phillip Webb * @author Phillip Webb
* @since 2.0.0
*/ */
class DurationConverter implements GenericConverter { public class DurationConverter implements GenericConverter {
private static final Set<ConvertiblePair> TYPES; private static final Set<ConvertiblePair> TYPES;
@ -78,11 +79,20 @@ class DurationConverter implements GenericConverter {
if (source == null) { if (source == null) {
return null; return null;
} }
DefaultDurationUnit defaultUnit = targetType
.getAnnotation(DefaultDurationUnit.class);
return toDuration(source.toString(), 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 { try {
if (!StringUtils.hasLength(source)) { if (!StringUtils.hasLength(source)) {
return null; 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)) { if (StringUtils.isEmpty(value)) {
return (defaultUnit != null ? defaultUnit.value() : ChronoUnit.MILLIS); return (defaultUnit != null ? defaultUnit : ChronoUnit.MILLIS);
} }
ChronoUnit unit = UNITS.get(value.toLowerCase()); ChronoUnit unit = UNITS.get(value.toLowerCase());
Assert.state(unit != null, () -> "Unknown unit '" + value + "'"); Assert.state(unit != null, () -> "Unknown unit '" + value + "'");

Loading…
Cancel
Save