mirror of
https://github.com/spring-projects/spring-boot.git
synced 2026-09-17 12:09:16 +00:00
Merge branch '3.5.x'
Closes gh-47924
This commit is contained in:
+4
-6
@@ -17,7 +17,6 @@
|
||||
package org.springframework.boot.http.client.autoconfigure.metrics;
|
||||
|
||||
import io.micrometer.core.instrument.MeterRegistry;
|
||||
import io.micrometer.core.instrument.config.MeterFilter;
|
||||
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
@@ -51,13 +50,12 @@ public final class HttpClientMetricsAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
@Order(0)
|
||||
MeterFilter metricsHttpClientUriTagFilter(ObservationProperties observationProperties,
|
||||
OnlyOnceLoggingDenyMeterFilter metricsHttpClientUriTagFilter(ObservationProperties observationProperties,
|
||||
MetricsProperties metricsProperties) {
|
||||
Client clientProperties = metricsProperties.getWeb().getClient();
|
||||
String name = observationProperties.getHttp().getClient().getRequests().getName();
|
||||
MeterFilter denyFilter = new OnlyOnceLoggingDenyMeterFilter(
|
||||
() -> "Reached the maximum number of URI tags for '%s'. Are you using 'uriVariables'?".formatted(name));
|
||||
return MeterFilter.maximumAllowableTags(name, "uri", clientProperties.getMaxUriTags(), denyFilter);
|
||||
String meterNamePrefix = observationProperties.getHttp().getClient().getRequests().getName();
|
||||
int maxUriTags = clientProperties.getMaxUriTags();
|
||||
return new OnlyOnceLoggingDenyMeterFilter(meterNamePrefix, "uri", maxUriTags, "Are you using 'uriVariables'?");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+1
-1
@@ -52,7 +52,7 @@ class HttpClientMetricsAutoConfigurationTests {
|
||||
meterRegistry.timer("http.client.requests", "uri", "/test/" + i).record(Duration.ofSeconds(1));
|
||||
}
|
||||
assertThat(meterRegistry.find("http.client.requests").timers()).hasSize(2);
|
||||
assertThat(output).contains("Reached the maximum number of URI tags for 'http.client.requests'.")
|
||||
assertThat(output).contains("Reached the maximum number of 'uri' tags for 'http.client.requests'.")
|
||||
.contains("Are you using 'uriVariables'?");
|
||||
});
|
||||
}
|
||||
|
||||
+64
-5
@@ -16,6 +16,8 @@
|
||||
|
||||
package org.springframework.boot.micrometer.metrics;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
@@ -24,33 +26,90 @@ import io.micrometer.core.instrument.config.MeterFilter;
|
||||
import io.micrometer.core.instrument.config.MeterFilterReply;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* {@link MeterFilter} to log only once a warning message and deny a {@link Id Meter.Id}.
|
||||
* {@link MeterFilter} to log a single warning message and deny a {@link Id Meter.Id}
|
||||
* after a number of attempts for a given tag.
|
||||
*
|
||||
* @author Jon Schneider
|
||||
* @author Dmytro Nosan
|
||||
* @author Phillip Webb
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public final class OnlyOnceLoggingDenyMeterFilter implements MeterFilter {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(OnlyOnceLoggingDenyMeterFilter.class);
|
||||
private final Log logger;
|
||||
|
||||
private final AtomicBoolean alreadyWarned = new AtomicBoolean();
|
||||
|
||||
private final String meterNamePrefix;
|
||||
|
||||
private final int maximumTagValues;
|
||||
|
||||
private final String tagKey;
|
||||
|
||||
private final Supplier<String> message;
|
||||
|
||||
public OnlyOnceLoggingDenyMeterFilter(Supplier<String> message) {
|
||||
private final Set<String> observedTagValues = ConcurrentHashMap.newKeySet();
|
||||
|
||||
/**
|
||||
* Create a new {@link OnlyOnceLoggingDenyMeterFilter} with an upper bound on the
|
||||
* number of tags produced by matching metrics.
|
||||
* @param meterNamePrefix the prefix of the meter name to apply the filter to
|
||||
* @param tagKey the tag to place an upper bound on
|
||||
* @param maximumTagValues the total number of tag values that are allowable
|
||||
*/
|
||||
public OnlyOnceLoggingDenyMeterFilter(String meterNamePrefix, String tagKey, int maximumTagValues) {
|
||||
this(meterNamePrefix, tagKey, maximumTagValues, (String) null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link OnlyOnceLoggingDenyMeterFilter} with an upper bound on the
|
||||
* number of tags produced by matching metrics.
|
||||
* @param meterNamePrefix the prefix of the meter name to apply the filter to
|
||||
* @param tagKey the tag to place an upper bound on
|
||||
* @param maximumTagValues the total number of tag values that are allowable
|
||||
* @param hint an additional hint to add to the logged message or {@code null}
|
||||
*/
|
||||
public OnlyOnceLoggingDenyMeterFilter(String meterNamePrefix, String tagKey, int maximumTagValues,
|
||||
@Nullable String hint) {
|
||||
this(null, meterNamePrefix, tagKey, maximumTagValues,
|
||||
() -> String.format("Reached the maximum number of '%s' tags for '%s'.%s", tagKey, meterNamePrefix,
|
||||
(hint != null) ? " " + hint : ""));
|
||||
}
|
||||
|
||||
private OnlyOnceLoggingDenyMeterFilter(@Nullable Log logger, String meterNamePrefix, String tagKey,
|
||||
int maximumTagValues, Supplier<String> message) {
|
||||
Assert.notNull(message, "'message' must not be null");
|
||||
Assert.isTrue(maximumTagValues >= 0, "'maximumTagValues' must be positive");
|
||||
this.logger = (logger != null) ? logger : LogFactory.getLog(OnlyOnceLoggingDenyMeterFilter.class);
|
||||
this.meterNamePrefix = meterNamePrefix;
|
||||
this.maximumTagValues = maximumTagValues;
|
||||
this.tagKey = tagKey;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MeterFilterReply accept(Id id) {
|
||||
if (logger.isWarnEnabled() && this.alreadyWarned.compareAndSet(false, true)) {
|
||||
logger.warn(this.message.get());
|
||||
if (this.meterNamePrefix == null) {
|
||||
return logAndDeny();
|
||||
}
|
||||
String tagValue = id.getName().startsWith(this.meterNamePrefix) ? id.getTag(this.tagKey) : null;
|
||||
if (tagValue != null && !this.observedTagValues.contains(tagValue)) {
|
||||
if (this.observedTagValues.size() >= this.maximumTagValues) {
|
||||
return logAndDeny();
|
||||
}
|
||||
this.observedTagValues.add(tagValue);
|
||||
}
|
||||
return MeterFilterReply.NEUTRAL;
|
||||
}
|
||||
|
||||
private MeterFilterReply logAndDeny() {
|
||||
if (this.logger.isWarnEnabled() && this.alreadyWarned.compareAndSet(false, true)) {
|
||||
this.logger.warn(this.message.get());
|
||||
}
|
||||
return MeterFilterReply.DENY;
|
||||
}
|
||||
|
||||
+8
-3
@@ -19,6 +19,7 @@ package org.springframework.boot.micrometer.metrics.autoconfigure;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import io.micrometer.core.instrument.MeterRegistry;
|
||||
import io.micrometer.core.instrument.Metrics;
|
||||
@@ -30,6 +31,7 @@ import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.beans.factory.SmartInitializingSingleton;
|
||||
import org.springframework.beans.factory.config.BeanPostProcessor;
|
||||
import org.springframework.boot.micrometer.metrics.OnlyOnceLoggingDenyMeterFilter;
|
||||
import org.springframework.boot.util.LambdaSafe;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
|
||||
@@ -109,10 +111,13 @@ class MeterRegistryPostProcessor implements BeanPostProcessor, SmartInitializing
|
||||
}
|
||||
|
||||
private void applyFilters(MeterRegistry meterRegistry) {
|
||||
if (meterRegistry instanceof AutoConfiguredCompositeMeterRegistry) {
|
||||
return;
|
||||
if (this.filters != null) {
|
||||
Stream<MeterFilter> filters = this.filters.orderedStream();
|
||||
if (isAutoConfiguredComposite(meterRegistry)) {
|
||||
filters = filters.filter(OnlyOnceLoggingDenyMeterFilter.class::isInstance);
|
||||
}
|
||||
filters.forEach(meterRegistry.config()::meterFilter);
|
||||
}
|
||||
this.filters.orderedStream().forEach(meterRegistry.config()::meterFilter);
|
||||
}
|
||||
|
||||
private void addToGlobalRegistryIfNecessary(MeterRegistry meterRegistry) {
|
||||
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright 2012-present 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
|
||||
*
|
||||
* https://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.micrometer.metrics;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import io.micrometer.core.instrument.Meter;
|
||||
import io.micrometer.core.instrument.Meter.Type;
|
||||
import io.micrometer.core.instrument.MeterRegistry;
|
||||
import io.micrometer.core.instrument.config.MeterFilterReply;
|
||||
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
|
||||
import org.assertj.core.api.InstanceOfAssertFactories;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link OnlyOnceLoggingDenyMeterFilter}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class OnlyOnceLoggingDenyMeterFilterTests {
|
||||
|
||||
@Test
|
||||
void applyWhenNameDoesNotHavePrefixReturnsNeutral() {
|
||||
OnlyOnceLoggingDenyMeterFilter filter = new OnlyOnceLoggingDenyMeterFilter("test", "k", 1);
|
||||
assertThat(filter.accept(meterId("tset", "k", "v"))).isEqualTo(MeterFilterReply.NEUTRAL);
|
||||
assertThat(filter).extracting("observedTagValues").asInstanceOf(InstanceOfAssertFactories.COLLECTION).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void applyWhenNameHasPrefixButNoTagKeyReturnsNeutral() {
|
||||
OnlyOnceLoggingDenyMeterFilter filter = new OnlyOnceLoggingDenyMeterFilter("test", "k", 1);
|
||||
assertThat(filter.accept(meterId("test", "k", "v"))).isEqualTo(MeterFilterReply.NEUTRAL);
|
||||
assertThat(filter).extracting("observedTagValues")
|
||||
.asInstanceOf(InstanceOfAssertFactories.COLLECTION)
|
||||
.containsExactly("v");
|
||||
}
|
||||
|
||||
@Test
|
||||
void applyWhenNameHasPrefixAndTagKeyReturnsNeutralUntilLimit() {
|
||||
OnlyOnceLoggingDenyMeterFilter filter = new OnlyOnceLoggingDenyMeterFilter("test", "k", 1);
|
||||
assertThat(filter.accept(meterId("test", "k", "v1"))).isEqualTo(MeterFilterReply.NEUTRAL);
|
||||
assertThat(filter.accept(meterId("test", "k", "v2"))).isEqualTo(MeterFilterReply.DENY);
|
||||
assertThat(filter.accept(meterId("test", "k", "v3"))).isEqualTo(MeterFilterReply.DENY);
|
||||
assertThat(filter).extracting("observedTagValues")
|
||||
.asInstanceOf(InstanceOfAssertFactories.COLLECTION)
|
||||
.containsExactly("v1");
|
||||
}
|
||||
|
||||
private Meter.Id meterId(String name, String tagKey, String tagValue) {
|
||||
MeterRegistry registry = new SimpleMeterRegistry();
|
||||
Meter meter = Meter.builder(name, Type.COUNTER, Collections.emptyList())
|
||||
.tag(tagKey, tagValue)
|
||||
.register(registry);
|
||||
return meter.getId();
|
||||
}
|
||||
|
||||
}
|
||||
+27
-1
@@ -19,6 +19,7 @@ package org.springframework.boot.micrometer.metrics.autoconfigure;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import io.micrometer.core.instrument.Clock;
|
||||
import io.micrometer.core.instrument.MeterRegistry;
|
||||
@@ -27,6 +28,7 @@ import io.micrometer.core.instrument.Metrics;
|
||||
import io.micrometer.core.instrument.binder.MeterBinder;
|
||||
import io.micrometer.core.instrument.composite.CompositeMeterRegistry;
|
||||
import io.micrometer.core.instrument.config.MeterFilter;
|
||||
import org.assertj.core.api.InstanceOfAssertFactories;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InOrder;
|
||||
@@ -36,6 +38,7 @@ import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.boot.micrometer.metrics.OnlyOnceLoggingDenyMeterFilter;
|
||||
import org.springframework.boot.micrometer.metrics.autoconfigure.MeterRegistryPostProcessor.CompositeMeterRegistries;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
@@ -131,6 +134,22 @@ class MeterRegistryPostProcessorTests {
|
||||
then(this.mockConfig).should().meterFilter(this.mockFilter);
|
||||
}
|
||||
|
||||
@Test
|
||||
void postProcessAndInitializeOnlyAppliesLmiitedFiltersToAutoConfigured() {
|
||||
OnlyOnceLoggingDenyMeterFilter onlyOnceFilter = mock();
|
||||
this.filters.add(this.mockFilter);
|
||||
this.filters.add(onlyOnceFilter);
|
||||
MeterRegistryPostProcessor processor = new MeterRegistryPostProcessor(CompositeMeterRegistries.AUTO_CONFIGURED,
|
||||
createObjectProvider(this.properties), createObjectProvider(this.customizers),
|
||||
createObjectProvider(this.filters), createObjectProvider(this.binders));
|
||||
AutoConfiguredCompositeMeterRegistry composite = new AutoConfiguredCompositeMeterRegistry(Clock.SYSTEM,
|
||||
Collections.emptyList());
|
||||
postProcessAndInitialize(processor, composite);
|
||||
assertThat(composite).extracting("filters")
|
||||
.asInstanceOf(InstanceOfAssertFactories.ARRAY)
|
||||
.containsExactly(onlyOnceFilter);
|
||||
}
|
||||
|
||||
@Test
|
||||
void postProcessAndInitializeBindsTo() {
|
||||
given(this.mockRegistry.config()).willReturn(this.mockConfig);
|
||||
@@ -273,11 +292,18 @@ class MeterRegistryPostProcessorTests {
|
||||
}
|
||||
|
||||
private <T> ObjectProvider<T> createEmptyObjectProvider() {
|
||||
return new ObjectProvider<T>() {
|
||||
return new ObjectProvider<>() {
|
||||
|
||||
@Override
|
||||
public T getObject() throws BeansException {
|
||||
throw new NoSuchBeanDefinitionException("No bean");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<T> orderedStream() {
|
||||
return Stream.empty();
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
+4
-7
@@ -17,7 +17,6 @@
|
||||
package org.springframework.boot.webflux.autoconfigure;
|
||||
|
||||
import io.micrometer.core.instrument.MeterRegistry;
|
||||
import io.micrometer.core.instrument.config.MeterFilter;
|
||||
import io.micrometer.observation.Observation;
|
||||
import io.micrometer.observation.ObservationRegistry;
|
||||
|
||||
@@ -64,12 +63,10 @@ public final class WebFluxObservationAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
@Order(0)
|
||||
MeterFilter metricsHttpServerUriTagFilter(MetricsProperties metricsProperties) {
|
||||
String name = this.observationProperties.getHttp().getServer().getRequests().getName();
|
||||
MeterFilter filter = new OnlyOnceLoggingDenyMeterFilter(
|
||||
() -> "Reached the maximum number of URI tags for '%s'.".formatted(name));
|
||||
return MeterFilter.maximumAllowableTags(name, "uri", metricsProperties.getWeb().getServer().getMaxUriTags(),
|
||||
filter);
|
||||
OnlyOnceLoggingDenyMeterFilter metricsHttpServerUriTagFilter(MetricsProperties metricsProperties) {
|
||||
String meterNamePrefix = this.observationProperties.getHttp().getServer().getRequests().getName();
|
||||
int maxUriTags = metricsProperties.getWeb().getServer().getMaxUriTags();
|
||||
return new OnlyOnceLoggingDenyMeterFilter(meterNamePrefix, "uri", maxUriTags);
|
||||
}
|
||||
|
||||
@Bean
|
||||
|
||||
+4
-3
@@ -66,7 +66,7 @@ class WebFluxObservationAutoConfigurationTests {
|
||||
.run((context) -> {
|
||||
MeterRegistry registry = getInitializedMeterRegistry(context);
|
||||
assertThat(registry.get("http.server.requests").meters()).hasSizeLessThanOrEqualTo(2);
|
||||
assertThat(output).contains("Reached the maximum number of URI tags for 'http.server.requests'");
|
||||
assertThat(output).contains("Reached the maximum number of 'uri' tags for 'http.server.requests'");
|
||||
});
|
||||
}
|
||||
|
||||
@@ -80,7 +80,7 @@ class WebFluxObservationAutoConfigurationTests {
|
||||
.run((context) -> {
|
||||
MeterRegistry registry = getInitializedMeterRegistry(context, "my.http.server.requests");
|
||||
assertThat(registry.get("my.http.server.requests").meters()).hasSizeLessThanOrEqualTo(2);
|
||||
assertThat(output).contains("Reached the maximum number of URI tags for 'my.http.server.requests'");
|
||||
assertThat(output).contains("Reached the maximum number of 'uri' tags for 'my.http.server.requests'");
|
||||
});
|
||||
}
|
||||
|
||||
@@ -93,7 +93,8 @@ class WebFluxObservationAutoConfigurationTests {
|
||||
.run((context) -> {
|
||||
MeterRegistry registry = getInitializedMeterRegistry(context);
|
||||
assertThat(registry.get("http.server.requests").meters()).hasSize(3);
|
||||
assertThat(output).doesNotContain("Reached the maximum number of URI tags for 'http.server.requests'");
|
||||
assertThat(output)
|
||||
.doesNotContain("Reached the maximum number of 'uri' tags for 'http.server.requests'");
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
+4
-7
@@ -17,7 +17,6 @@
|
||||
package org.springframework.boot.webmvc.autoconfigure;
|
||||
|
||||
import io.micrometer.core.instrument.MeterRegistry;
|
||||
import io.micrometer.core.instrument.config.MeterFilter;
|
||||
import io.micrometer.observation.Observation;
|
||||
import io.micrometer.observation.ObservationRegistry;
|
||||
import jakarta.servlet.DispatcherType;
|
||||
@@ -86,13 +85,11 @@ public final class WebMvcObservationAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
@Order(0)
|
||||
MeterFilter metricsHttpServerUriTagFilter(ObservationProperties observationProperties,
|
||||
OnlyOnceLoggingDenyMeterFilter metricsHttpServerUriTagFilter(ObservationProperties observationProperties,
|
||||
MetricsProperties metricsProperties) {
|
||||
String name = observationProperties.getHttp().getServer().getRequests().getName();
|
||||
MeterFilter filter = new OnlyOnceLoggingDenyMeterFilter(
|
||||
() -> String.format("Reached the maximum number of URI tags for '%s'.", name));
|
||||
return MeterFilter.maximumAllowableTags(name, "uri", metricsProperties.getWeb().getServer().getMaxUriTags(),
|
||||
filter);
|
||||
String meterNamePrefix = observationProperties.getHttp().getServer().getRequests().getName();
|
||||
int maxUriTags = metricsProperties.getWeb().getServer().getMaxUriTags();
|
||||
return new OnlyOnceLoggingDenyMeterFilter(meterNamePrefix, "uri", maxUriTags);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+4
-3
@@ -139,7 +139,7 @@ class WebMvcObservationAutoConfigurationTests {
|
||||
.run((context) -> {
|
||||
MeterRegistry registry = getInitializedMeterRegistry(context);
|
||||
assertThat(registry.get("http.server.requests").meters()).hasSizeLessThanOrEqualTo(2);
|
||||
assertThat(output).contains("Reached the maximum number of URI tags for 'http.server.requests'");
|
||||
assertThat(output).contains("Reached the maximum number of 'uri' tags for 'http.server.requests'");
|
||||
});
|
||||
}
|
||||
|
||||
@@ -153,7 +153,7 @@ class WebMvcObservationAutoConfigurationTests {
|
||||
.run((context) -> {
|
||||
MeterRegistry registry = getInitializedMeterRegistry(context);
|
||||
assertThat(registry.get("my.http.server.requests").meters()).hasSizeLessThanOrEqualTo(2);
|
||||
assertThat(output).contains("Reached the maximum number of URI tags for 'my.http.server.requests'");
|
||||
assertThat(output).contains("Reached the maximum number of 'uri' tags for 'my.http.server.requests'");
|
||||
});
|
||||
}
|
||||
|
||||
@@ -166,7 +166,8 @@ class WebMvcObservationAutoConfigurationTests {
|
||||
.run((context) -> {
|
||||
MeterRegistry registry = getInitializedMeterRegistry(context);
|
||||
assertThat(registry.get("http.server.requests").meters()).hasSize(3);
|
||||
assertThat(output).doesNotContain("Reached the maximum number of URI tags for 'http.server.requests'");
|
||||
assertThat(output)
|
||||
.doesNotContain("Reached the maximum number of 'uri' tags for 'http.server.requests'");
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user