Add property for common key/values on observations
- Deprecates 'management.metrics.tags.*' Closes gh-33241
This commit is contained in:
		
							parent
							
								
									214f06083b
								
							
						
					
					
						commit
						5b06224af5
					
				|  | @ -79,6 +79,7 @@ public class MetricsProperties { | |||
| 		return this.enable; | ||||
| 	} | ||||
| 
 | ||||
| 	@DeprecatedConfigurationProperty(replacement = "management.observations.key-values") | ||||
| 	public Map<String, String> getTags() { | ||||
| 		return this.tags; | ||||
| 	} | ||||
|  |  | |||
|  | @ -43,6 +43,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClas | |||
| import org.springframework.boot.context.properties.EnableConfigurationProperties; | ||||
| import org.springframework.context.annotation.Bean; | ||||
| import org.springframework.context.annotation.Configuration; | ||||
| import org.springframework.core.annotation.Order; | ||||
| 
 | ||||
| /** | ||||
|  * {@link EnableAutoConfiguration Auto-configuration} for the Micrometer Observation API. | ||||
|  | @ -75,6 +76,12 @@ public class ObservationAutoConfiguration { | |||
| 		return ObservationRegistry.create(); | ||||
| 	} | ||||
| 
 | ||||
| 	@Bean | ||||
| 	@Order(0) | ||||
| 	PropertiesObservationFilter propertiesObservationFilter(ObservationProperties properties) { | ||||
| 		return new PropertiesObservationFilter(properties); | ||||
| 	} | ||||
| 
 | ||||
| 	@Configuration(proxyBeanMethods = false) | ||||
| 	@ConditionalOnClass(MeterRegistry.class) | ||||
| 	@ConditionalOnMissingClass("io.micrometer.tracing.Tracer") | ||||
|  |  | |||
|  | @ -16,6 +16,9 @@ | |||
| 
 | ||||
| package org.springframework.boot.actuate.autoconfigure.observation; | ||||
| 
 | ||||
| import java.util.LinkedHashMap; | ||||
| import java.util.Map; | ||||
| 
 | ||||
| import org.springframework.boot.context.properties.ConfigurationProperties; | ||||
| 
 | ||||
| /** | ||||
|  | @ -30,10 +33,23 @@ public class ObservationProperties { | |||
| 
 | ||||
| 	private final Http http = new Http(); | ||||
| 
 | ||||
| 	/** | ||||
| 	 * Common key-values that are applied to every observation. | ||||
| 	 */ | ||||
| 	private Map<String, String> keyValues = new LinkedHashMap<>(); | ||||
| 
 | ||||
| 	public Http getHttp() { | ||||
| 		return this.http; | ||||
| 	} | ||||
| 
 | ||||
| 	public Map<String, String> getKeyValues() { | ||||
| 		return this.keyValues; | ||||
| 	} | ||||
| 
 | ||||
| 	public void setKeyValues(Map<String, String> keyValues) { | ||||
| 		this.keyValues = keyValues; | ||||
| 	} | ||||
| 
 | ||||
| 	public static class Http { | ||||
| 
 | ||||
| 		private final Client client = new Client(); | ||||
|  |  | |||
|  | @ -0,0 +1,51 @@ | |||
| /* | ||||
|  * 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); | ||||
| 	} | ||||
| 
 | ||||
| } | ||||
|  | @ -189,6 +189,21 @@ class ObservationAutoConfigurationTests { | |||
| 		}); | ||||
| 	} | ||||
| 
 | ||||
| 	@Test | ||||
| 	void shouldSupplyPropertiesObservationFilterBean() { | ||||
| 		this.contextRunner.run((context) -> assertThat(context).hasSingleBean(PropertiesObservationFilter.class)); | ||||
| 	} | ||||
| 
 | ||||
| 	@Test | ||||
| 	void shouldApplyCommonKeyValuesToObservations() { | ||||
| 		this.contextRunner.withPropertyValues("management.observations.key-values.a=alpha").run((context) -> { | ||||
| 			ObservationRegistry observationRegistry = context.getBean(ObservationRegistry.class); | ||||
| 			Observation.start("keyvalues", observationRegistry).stop(); | ||||
| 			MeterRegistry meterRegistry = context.getBean(MeterRegistry.class); | ||||
| 			assertThat(meterRegistry.get("keyvalues").tag("a", "alpha").timer().count()).isOne(); | ||||
| 		}); | ||||
| 	} | ||||
| 
 | ||||
| 	@Test | ||||
| 	void autoConfiguresGlobalObservationConventions() { | ||||
| 		this.contextRunner.withUserConfiguration(CustomGlobalObservationConvention.class).run((context) -> { | ||||
|  |  | |||
|  | @ -0,0 +1,62 @@ | |||
| /* | ||||
|  * 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); | ||||
| 	} | ||||
| 
 | ||||
| } | ||||
|  | @ -1100,19 +1100,8 @@ These use the global registry that is not Spring-managed. | |||
| 
 | ||||
| [[actuator.metrics.customizing.common-tags]] | ||||
| ==== Common Tags | ||||
| Common tags are generally used for dimensional drill-down on the operating environment, such as host, instance, region, stack, and others. | ||||
| Commons tags are applied to all meters and can be configured, as the following example shows: | ||||
| 
 | ||||
| [source,yaml,indent=0,subs="verbatim",configprops,configblocks] | ||||
| ---- | ||||
| 	management: | ||||
| 	  metrics: | ||||
| 	    tags: | ||||
| 	      region: "us-east-1" | ||||
| 	      stack: "prod" | ||||
| ---- | ||||
| 
 | ||||
| The preceding example adds `region` and `stack` tags to all meters with a value of `us-east-1` and `prod`, respectively. | ||||
| You can configure common tags using the <<actuator#actuator.observability.common-key-values, configprop:management.observations.key-values[] property>>. | ||||
| 
 | ||||
| NOTE: The order of common tags is important if you use Graphite. | ||||
| As the order of common tags cannot be guaranteed by using this approach, Graphite users are advised to define a custom `MeterFilter` instead. | ||||
|  |  | |||
|  | @ -9,9 +9,9 @@ To create your own observations (which will lead to metrics and traces), you can | |||
| 
 | ||||
| include::code:MyCustomObservation[] | ||||
| 
 | ||||
| NOTE: Low cardinality tags will be added to metrics and traces, while high cardinality tags will only be added to traces. | ||||
| NOTE: Low cardinality key-values will be added to metrics and traces, while high cardinality key-values will only be added to traces. | ||||
| 
 | ||||
| Beans of type `ObservationPredicate`, `GlobalObservationConvention` and `ObservationHandler` will be automatically registered on the `ObservationRegistry`. | ||||
| Beans of type `ObservationPredicate`, `GlobalObservationConvention`, `ObservationFilter` and `ObservationHandler` will be automatically registered on the `ObservationRegistry`. | ||||
| You can additionally register any number of `ObservationRegistryCustomizer` beans to further configure the registry. | ||||
| 
 | ||||
| For more details please see the https://micrometer.io/docs/observation[Micrometer Observation documentation]. | ||||
|  | @ -21,4 +21,20 @@ For JDBC, the https://github.com/jdbc-observations/datasource-micrometer[Datasou | |||
| Read more about it https://jdbc-observations.github.io/datasource-micrometer/docs/current/docs/html/[in the reference documentation]. | ||||
| For R2DBC, the https://github.com/spring-projects-experimental/r2dbc-micrometer-spring-boot[Spring Boot Auto Configuration for R2DBC Observation] creates observations for R2DBC query invocations. | ||||
| 
 | ||||
| [[actuator.observability.common-key-values]] | ||||
| === Common Key-Values | ||||
| Common key-values are generally used for dimensional drill-down on the operating environment, such as host, instance, region, stack, and others. | ||||
| Commons key-values are applied to all observations as low cardinality key-values and can be configured, as the following example shows: | ||||
| 
 | ||||
| [source,yaml,indent=0,subs="verbatim",configprops,configblocks] | ||||
| ---- | ||||
| 	management: | ||||
| 	  observations: | ||||
| 	    key-values: | ||||
| 	      region: "us-east-1" | ||||
| 	      stack: "prod" | ||||
| ---- | ||||
| 
 | ||||
| The preceding example adds `region` and `stack` key-values to all observations with a value of `us-east-1` and `prod`, respectively. | ||||
| 
 | ||||
| The next sections will provide more details about logging, metrics and traces. | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue