Add property to prevent observations starting with a prefix
For example, setting management.observations.enable.denied.prefix=false will prevent all observations starting with 'denied.prefix' Closes gh-34802
This commit is contained in:
@@ -40,7 +40,6 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
@@ -79,14 +78,8 @@ public class ObservationAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
@Order(0)
|
||||
PropertiesObservationFilter propertiesObservationFilter(ObservationProperties properties) {
|
||||
return new PropertiesObservationFilter(properties);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnProperty(name = "management.observations.spring-security.enabled", havingValue = "false")
|
||||
ObservationPredicate springSecurityObservationsDisabler() {
|
||||
return (name, context) -> !name.startsWith("spring.security.");
|
||||
PropertiesObservationFilterPredicate propertiesObservationFilter(ObservationProperties properties) {
|
||||
return new PropertiesObservationFilterPredicate(properties);
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
|
||||
@@ -27,6 +27,7 @@ import org.springframework.core.Ordered;
|
||||
* observations.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
* @author Moritz Halbritter
|
||||
* @since 3.0.0
|
||||
*/
|
||||
@ConfigurationProperties("management.observations")
|
||||
@@ -39,6 +40,20 @@ public class ObservationProperties {
|
||||
*/
|
||||
private Map<String, String> keyValues = new LinkedHashMap<>();
|
||||
|
||||
/**
|
||||
* Whether observations starting with the specified name should be enabled. The
|
||||
* longest match wins, the key 'all' can also be used to configure all observations.
|
||||
*/
|
||||
private Map<String, Boolean> enable = new LinkedHashMap<>();
|
||||
|
||||
public Map<String, Boolean> getEnable() {
|
||||
return this.enable;
|
||||
}
|
||||
|
||||
public void setEnable(Map<String, Boolean> enable) {
|
||||
this.enable = enable;
|
||||
}
|
||||
|
||||
public Http getHttp() {
|
||||
return this.http;
|
||||
}
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-2023 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.actuate.autoconfigure.observation;
|
||||
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import io.micrometer.common.KeyValues;
|
||||
import io.micrometer.observation.Observation.Context;
|
||||
import io.micrometer.observation.ObservationFilter;
|
||||
|
||||
/**
|
||||
* {@link ObservationFilter} to apply settings from {@link ObservationProperties}.
|
||||
*
|
||||
* @author Moritz Halbritter
|
||||
*/
|
||||
class PropertiesObservationFilter implements ObservationFilter {
|
||||
|
||||
private final ObservationFilter delegate;
|
||||
|
||||
PropertiesObservationFilter(ObservationProperties properties) {
|
||||
this.delegate = createDelegate(properties);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Context map(Context context) {
|
||||
return this.delegate.map(context);
|
||||
}
|
||||
|
||||
private static ObservationFilter createDelegate(ObservationProperties properties) {
|
||||
if (properties.getKeyValues().isEmpty()) {
|
||||
return (context) -> context;
|
||||
}
|
||||
KeyValues keyValues = KeyValues.of(properties.getKeyValues().entrySet(), Entry::getKey, Entry::getValue);
|
||||
return (context) -> context.addLowCardinalityKeyValues(keyValues);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Copyright 2012-2023 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.actuate.autoconfigure.observation;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import io.micrometer.common.KeyValues;
|
||||
import io.micrometer.observation.Observation.Context;
|
||||
import io.micrometer.observation.ObservationFilter;
|
||||
import io.micrometer.observation.ObservationPredicate;
|
||||
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* {@link ObservationFilter} to apply settings from {@link ObservationProperties}.
|
||||
*
|
||||
* @author Moritz Halbritter
|
||||
*/
|
||||
class PropertiesObservationFilterPredicate implements ObservationFilter, ObservationPredicate {
|
||||
|
||||
private final ObservationFilter commonKeyValuesFilter;
|
||||
|
||||
private final ObservationProperties properties;
|
||||
|
||||
PropertiesObservationFilterPredicate(ObservationProperties properties) {
|
||||
this.properties = properties;
|
||||
this.commonKeyValuesFilter = createCommonKeyValuesFilter(properties);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Context map(Context context) {
|
||||
return this.commonKeyValuesFilter.map(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean test(String name, Context context) {
|
||||
return lookupWithFallbackToAll(this.properties.getEnable(), name, true);
|
||||
}
|
||||
|
||||
private static <T> T lookupWithFallbackToAll(Map<String, T> values, String name, T defaultValue) {
|
||||
if (values.isEmpty()) {
|
||||
return defaultValue;
|
||||
}
|
||||
return doLookup(values, name, () -> values.getOrDefault("all", defaultValue));
|
||||
}
|
||||
|
||||
private static <T> T doLookup(Map<String, T> values, String name, Supplier<T> defaultValue) {
|
||||
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 defaultValue.get();
|
||||
}
|
||||
|
||||
private static ObservationFilter createCommonKeyValuesFilter(ObservationProperties properties) {
|
||||
if (properties.getKeyValues().isEmpty()) {
|
||||
return (context) -> context;
|
||||
}
|
||||
KeyValues keyValues = KeyValues.of(properties.getKeyValues().entrySet(), Entry::getKey, Entry::getValue);
|
||||
return (context) -> context.addLowCardinalityKeyValues(keyValues);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2038,12 +2038,6 @@
|
||||
"level": "error"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "management.observations.spring-security.enabled",
|
||||
"description": "Whether to enable observations for Spring Security",
|
||||
"type": "java.lang.Boolean",
|
||||
"defaultValue": true
|
||||
},
|
||||
{
|
||||
"name": "management.otlp.tracing.compression",
|
||||
"defaultValue": "none"
|
||||
|
||||
@@ -191,7 +191,8 @@ class ObservationAutoConfigurationTests {
|
||||
|
||||
@Test
|
||||
void shouldSupplyPropertiesObservationFilterBean() {
|
||||
this.contextRunner.run((context) -> assertThat(context).hasSingleBean(PropertiesObservationFilter.class));
|
||||
this.contextRunner
|
||||
.run((context) -> assertThat(context).hasSingleBean(PropertiesObservationFilterPredicate.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -303,14 +304,13 @@ class ObservationAutoConfigurationTests {
|
||||
|
||||
@Test
|
||||
void shouldDisableSpringSecurityObservationsIfPropertyIsSet() {
|
||||
this.contextRunner.withPropertyValues("management.observations.spring-security.enabled=false")
|
||||
.run((context) -> {
|
||||
ObservationRegistry observationRegistry = context.getBean(ObservationRegistry.class);
|
||||
Observation.start("spring.security.filterchains", observationRegistry).stop();
|
||||
MeterRegistry meterRegistry = context.getBean(MeterRegistry.class);
|
||||
assertThatThrownBy(() -> meterRegistry.get("spring.security.filterchains").timer())
|
||||
.isInstanceOf(MeterNotFoundException.class);
|
||||
});
|
||||
this.contextRunner.withPropertyValues("management.observations.enable.spring.security=false").run((context) -> {
|
||||
ObservationRegistry observationRegistry = context.getBean(ObservationRegistry.class);
|
||||
Observation.start("spring.security.filterchains", observationRegistry).stop();
|
||||
MeterRegistry meterRegistry = context.getBean(MeterRegistry.class);
|
||||
assertThatThrownBy(() -> meterRegistry.get("spring.security.filterchains").timer())
|
||||
.isInstanceOf(MeterNotFoundException.class);
|
||||
});
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* Copyright 2012-2023 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.actuate.autoconfigure.observation;
|
||||
|
||||
import io.micrometer.common.KeyValue;
|
||||
import io.micrometer.common.KeyValues;
|
||||
import io.micrometer.observation.Observation.Context;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link PropertiesObservationFilterPredicate}.
|
||||
*
|
||||
* @author Moritz Halbritter
|
||||
*/
|
||||
class PropertiesObservationFilterPredicateTests {
|
||||
|
||||
@Test
|
||||
void shouldDoNothingIfKeyValuesAreEmpty() {
|
||||
PropertiesObservationFilterPredicate filter = createFilter();
|
||||
Context mapped = mapContext(filter, "a", "alpha");
|
||||
assertThat(mapped.getLowCardinalityKeyValues()).containsExactly(KeyValue.of("a", "alpha"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldAddKeyValues() {
|
||||
PropertiesObservationFilterPredicate filter = createFilter("b", "beta");
|
||||
Context mapped = mapContext(filter, "a", "alpha");
|
||||
assertThat(mapped.getLowCardinalityKeyValues()).containsExactly(KeyValue.of("a", "alpha"),
|
||||
KeyValue.of("b", "beta"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFilter() {
|
||||
PropertiesObservationFilterPredicate predicate = createPredicate("spring.security");
|
||||
Context context = new Context();
|
||||
assertThat(predicate.test("spring.security.filterchains", context)).isFalse();
|
||||
assertThat(predicate.test("spring.security", context)).isFalse();
|
||||
assertThat(predicate.test("spring.data", context)).isTrue();
|
||||
assertThat(predicate.test("spring", context)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void filterShouldFallbackToAll() {
|
||||
PropertiesObservationFilterPredicate predicate = createPredicate("all");
|
||||
Context context = new Context();
|
||||
assertThat(predicate.test("spring.security.filterchains", context)).isFalse();
|
||||
assertThat(predicate.test("spring.security", context)).isFalse();
|
||||
assertThat(predicate.test("spring.data", context)).isFalse();
|
||||
assertThat(predicate.test("spring", context)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotFilterIfDisabledNamesIsEmpty() {
|
||||
PropertiesObservationFilterPredicate predicate = createPredicate();
|
||||
Context context = new Context();
|
||||
assertThat(predicate.test("spring.security.filterchains", context)).isTrue();
|
||||
assertThat(predicate.test("spring.security", context)).isTrue();
|
||||
assertThat(predicate.test("spring.data", context)).isTrue();
|
||||
assertThat(predicate.test("spring", context)).isTrue();
|
||||
}
|
||||
|
||||
private static Context mapContext(PropertiesObservationFilterPredicate filter, String... initialKeyValues) {
|
||||
Context context = new Context();
|
||||
context.addLowCardinalityKeyValues(KeyValues.of(initialKeyValues));
|
||||
return filter.map(context);
|
||||
}
|
||||
|
||||
private static PropertiesObservationFilterPredicate createFilter(String... keyValues) {
|
||||
ObservationProperties properties = new ObservationProperties();
|
||||
for (int i = 0; i < keyValues.length; i += 2) {
|
||||
properties.getKeyValues().put(keyValues[i], keyValues[i + 1]);
|
||||
}
|
||||
return new PropertiesObservationFilterPredicate(properties);
|
||||
}
|
||||
|
||||
private static PropertiesObservationFilterPredicate createPredicate(String... disabledNames) {
|
||||
ObservationProperties properties = new ObservationProperties();
|
||||
for (String name : disabledNames) {
|
||||
properties.getEnable().put(name, false);
|
||||
}
|
||||
return new PropertiesObservationFilterPredicate(properties);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-2023 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.actuate.autoconfigure.observation;
|
||||
|
||||
import io.micrometer.common.KeyValue;
|
||||
import io.micrometer.common.KeyValues;
|
||||
import io.micrometer.observation.Observation.Context;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link PropertiesObservationFilter}.
|
||||
*
|
||||
* @author Moritz Halbritter
|
||||
*/
|
||||
class PropertiesObservationFilterTests {
|
||||
|
||||
@Test
|
||||
void shouldDoNothingIfKeyValuesAreEmpty() {
|
||||
PropertiesObservationFilter filter = createFilter();
|
||||
Context mapped = mapContext(filter, "a", "alpha");
|
||||
assertThat(mapped.getLowCardinalityKeyValues()).containsExactly(KeyValue.of("a", "alpha"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldAddKeyValues() {
|
||||
PropertiesObservationFilter filter = createFilter("b", "beta");
|
||||
Context mapped = mapContext(filter, "a", "alpha");
|
||||
assertThat(mapped.getLowCardinalityKeyValues()).containsExactly(KeyValue.of("a", "alpha"),
|
||||
KeyValue.of("b", "beta"));
|
||||
}
|
||||
|
||||
private static Context mapContext(PropertiesObservationFilter filter, String... initialKeyValues) {
|
||||
Context context = new Context();
|
||||
context.addLowCardinalityKeyValues(KeyValues.of(initialKeyValues));
|
||||
return filter.map(context);
|
||||
}
|
||||
|
||||
private static PropertiesObservationFilter createFilter(String... keyValues) {
|
||||
ObservationProperties properties = new ObservationProperties();
|
||||
for (int i = 0; i < keyValues.length; i += 2) {
|
||||
properties.getKeyValues().put(keyValues[i], keyValues[i + 1]);
|
||||
}
|
||||
return new PropertiesObservationFilter(properties);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -40,13 +40,29 @@ The preceding example adds `region` and `stack` key-values to all observations w
|
||||
[[actuator.observability.preventing-observations]]
|
||||
=== Preventing Observations
|
||||
|
||||
If you'd like to prevent some observations from being reported, you can register beans of type `ObservationPredicate`.
|
||||
If you'd like to prevent some observations from being reported, you can use the configprop:management.observations.enable[] properties:
|
||||
|
||||
[source,yaml,indent=0,subs="verbatim",configprops,configblocks]
|
||||
----
|
||||
management:
|
||||
observations:
|
||||
enable:
|
||||
denied:
|
||||
prefix: false
|
||||
another:
|
||||
denied:
|
||||
prefix: false
|
||||
----
|
||||
|
||||
The preceding example will prevent all observations with a name starting with `denied.prefix` or `another.denied.prefix`.
|
||||
|
||||
TIP: If you want to prevent Spring Security from reporting observations, set the property configprop:management.observations.enable.spring.security[] to `false`.
|
||||
|
||||
If you need greater control over the prevention of observations, you can register beans of type `ObservationPredicate`.
|
||||
Observations are only reported if all the `ObservationPredicate` beans return `true` for that observation.
|
||||
|
||||
include::code:MyObservationPredicate[]
|
||||
|
||||
The preceding example will prevent all observations with a name starting with "denied.prefix.".
|
||||
|
||||
TIP: If you want to prevent Spring Security from reporting observations, set the property configprop:management.observations.spring-security.enabled[] to `false`.
|
||||
The preceding example will prevent all observations whose name contains "denied".
|
||||
|
||||
The next sections will provide more details about logging, metrics and traces.
|
||||
|
||||
@@ -26,7 +26,7 @@ class MyObservationPredicate implements ObservationPredicate {
|
||||
|
||||
@Override
|
||||
public boolean test(String name, Context context) {
|
||||
return !name.startsWith("denied.prefix.");
|
||||
return !name.contains("denied");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user