Browse Source

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
pull/35890/head
Moritz Halbritter 3 years ago
parent
commit
c73315b4a3
  1. 11
      spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/ObservationAutoConfiguration.java
  2. 15
      spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/ObservationProperties.java
  3. 51
      spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/PropertiesObservationFilter.java
  4. 83
      spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/PropertiesObservationFilterPredicate.java
  5. 6
      spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json
  6. 18
      spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/ObservationAutoConfigurationTests.java
  7. 100
      spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/PropertiesObservationFilterPredicateTests.java
  8. 62
      spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/PropertiesObservationFilterTests.java
  9. 24
      spring-boot-project/spring-boot-docs/src/docs/asciidoc/actuator/observability.adoc
  10. 2
      spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/actuator/observability/preventingobservations/MyObservationPredicate.java

11
spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/ObservationAutoConfiguration.java

@ -40,7 +40,6 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; @@ -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 { @@ -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)

15
spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/ObservationProperties.java

@ -27,6 +27,7 @@ import org.springframework.core.Ordered; @@ -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 { @@ -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;
}

51
spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/PropertiesObservationFilter.java

@ -1,51 +0,0 @@ @@ -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);
}
}

83
spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/PropertiesObservationFilterPredicate.java

@ -0,0 +1,83 @@ @@ -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);
}
}

6
spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json

@ -2038,12 +2038,6 @@ @@ -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"

18
spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/ObservationAutoConfigurationTests.java

@ -191,7 +191,8 @@ class ObservationAutoConfigurationTests { @@ -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 { @@ -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)

100
spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/PropertiesObservationFilterPredicateTests.java

@ -0,0 +1,100 @@ @@ -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);
}
}

62
spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/observation/PropertiesObservationFilterTests.java

@ -1,62 +0,0 @@ @@ -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);
}
}

24
spring-boot-project/spring-boot-docs/src/docs/asciidoc/actuator/observability.adoc

@ -40,13 +40,29 @@ The preceding example adds `region` and `stack` key-values to all observations w @@ -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.

2
spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/actuator/observability/preventingobservations/MyObservationPredicate.java

@ -26,7 +26,7 @@ class MyObservationPredicate implements ObservationPredicate { @@ -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");
}
}

Loading…
Cancel
Save