Browse Source

Semantic conventions auto-configuration

Create beans for the conventions that will be picked up by other auto-configuration.
Adds a property to control which set of conventions are auto-configured, which makes this easier to configure consistently across applications.
pull/49241/head
Tommy Ludwig 1 month ago
parent
commit
36d7bde6d5
No known key found for this signature in database
GPG Key ID: 8606C9E50B2FCA7F
  1. 115
      module/spring-boot-micrometer-metrics/src/main/java/org/springframework/boot/micrometer/metrics/autoconfigure/SemanticConventionAutoConfiguration.java
  2. 158
      module/spring-boot-micrometer-metrics/src/test/java/org/springframework/boot/micrometer/metrics/autoconfigure/SemanticConventionAutoConfigurationTests.java
  3. 16
      module/spring-boot-micrometer-observation/src/main/java/org/springframework/boot/micrometer/observation/autoconfigure/ObservationProperties.java
  4. 27
      module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/autoconfigure/WebMvcObservationAutoConfiguration.java
  5. 15
      module/spring-boot-webmvc/src/test/java/org/springframework/boot/webmvc/autoconfigure/WebMvcObservationAutoConfigurationTests.java

115
module/spring-boot-micrometer-metrics/src/main/java/org/springframework/boot/micrometer/metrics/autoconfigure/SemanticConventionAutoConfiguration.java

@ -0,0 +1,115 @@ @@ -0,0 +1,115 @@
/*
* 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.autoconfigure;
import io.micrometer.core.instrument.Tags;
import io.micrometer.core.instrument.binder.jvm.convention.JvmClassLoadingMeterConventions;
import io.micrometer.core.instrument.binder.jvm.convention.JvmCpuMeterConventions;
import io.micrometer.core.instrument.binder.jvm.convention.JvmMemoryMeterConventions;
import io.micrometer.core.instrument.binder.jvm.convention.JvmThreadMeterConventions;
import io.micrometer.core.instrument.binder.jvm.convention.micrometer.MicrometerJvmClassLoadingMeterConventions;
import io.micrometer.core.instrument.binder.jvm.convention.micrometer.MicrometerJvmCpuMeterConventions;
import io.micrometer.core.instrument.binder.jvm.convention.micrometer.MicrometerJvmMemoryMeterConventions;
import io.micrometer.core.instrument.binder.jvm.convention.micrometer.MicrometerJvmThreadMeterConventions;
import io.micrometer.core.instrument.binder.jvm.convention.otel.OpenTelemetryJvmClassLoadingMeterConventions;
import io.micrometer.core.instrument.binder.jvm.convention.otel.OpenTelemetryJvmCpuMeterConventions;
import io.micrometer.core.instrument.binder.jvm.convention.otel.OpenTelemetryJvmMemoryMeterConventions;
import io.micrometer.core.instrument.binder.jvm.convention.otel.OpenTelemetryJvmThreadMeterConventions;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.micrometer.metrics.autoconfigure.jvm.JvmMetricsAutoConfiguration;
import org.springframework.boot.micrometer.metrics.autoconfigure.system.SystemMetricsAutoConfiguration;
import org.springframework.boot.micrometer.observation.autoconfigure.ObservationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* {@link EnableAutoConfiguration Auto-configuration} for semantic conventions for metrics
* and observations.
*
* @since 4.1.0
*/
@AutoConfiguration(before = { JvmMetricsAutoConfiguration.class, SystemMetricsAutoConfiguration.class })
@EnableConfigurationProperties(ObservationProperties.class)
public final class SemanticConventionAutoConfiguration {
@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(prefix = "management.observations", name = "conventions", havingValue = "micrometer",
matchIfMissing = true)
static class MicrometerSemanticConventionConfiguration {
@Bean
@ConditionalOnMissingBean(JvmMemoryMeterConventions.class)
MicrometerJvmMemoryMeterConventions micrometerJvmMemoryMeterConventions() {
return new MicrometerJvmMemoryMeterConventions();
}
@Bean
@ConditionalOnMissingBean(JvmClassLoadingMeterConventions.class)
MicrometerJvmClassLoadingMeterConventions micrometerJvmClassLoadingMeterConventions() {
return new MicrometerJvmClassLoadingMeterConventions();
}
@Bean
@ConditionalOnMissingBean(JvmCpuMeterConventions.class)
MicrometerJvmCpuMeterConventions micrometerJvmCpuMeterConventions() {
return new MicrometerJvmCpuMeterConventions(Tags.empty());
}
@Bean
@ConditionalOnMissingBean(JvmThreadMeterConventions.class)
MicrometerJvmThreadMeterConventions micrometerJvmThreadMeterConventions() {
return new MicrometerJvmThreadMeterConventions(Tags.empty());
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(prefix = "management.observations", name = "conventions", havingValue = "opentelemetry")
static class OpenTelemetrySemanticConventionConfiguration {
@Bean
@ConditionalOnMissingBean(JvmMemoryMeterConventions.class)
OpenTelemetryJvmMemoryMeterConventions openTelemetryJvmMemoryMeterConventions() {
return new OpenTelemetryJvmMemoryMeterConventions(Tags.empty());
}
@Bean
@ConditionalOnMissingBean(JvmClassLoadingMeterConventions.class)
OpenTelemetryJvmClassLoadingMeterConventions openTelemetryJvmClassLoadingMeterConventions() {
return new OpenTelemetryJvmClassLoadingMeterConventions();
}
@Bean
@ConditionalOnMissingBean(JvmCpuMeterConventions.class)
OpenTelemetryJvmCpuMeterConventions openTelemetryJvmCpuMeterConventions() {
return new OpenTelemetryJvmCpuMeterConventions(Tags.empty());
}
@Bean
@ConditionalOnMissingBean(JvmThreadMeterConventions.class)
OpenTelemetryJvmThreadMeterConventions openTelemetryJvmThreadMeterConventions() {
return new OpenTelemetryJvmThreadMeterConventions(Tags.empty());
}
}
}

158
module/spring-boot-micrometer-metrics/src/test/java/org/springframework/boot/micrometer/metrics/autoconfigure/SemanticConventionAutoConfigurationTests.java

@ -0,0 +1,158 @@ @@ -0,0 +1,158 @@
/*
* 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.autoconfigure;
import java.lang.management.MemoryPoolMXBean;
import io.micrometer.core.instrument.binder.MeterConvention;
import io.micrometer.core.instrument.binder.SimpleMeterConvention;
import io.micrometer.core.instrument.binder.jvm.convention.JvmClassLoadingMeterConventions;
import io.micrometer.core.instrument.binder.jvm.convention.JvmCpuMeterConventions;
import io.micrometer.core.instrument.binder.jvm.convention.JvmMemoryMeterConventions;
import io.micrometer.core.instrument.binder.jvm.convention.JvmThreadMeterConventions;
import io.micrometer.core.instrument.binder.jvm.convention.micrometer.MicrometerJvmClassLoadingMeterConventions;
import io.micrometer.core.instrument.binder.jvm.convention.micrometer.MicrometerJvmCpuMeterConventions;
import io.micrometer.core.instrument.binder.jvm.convention.micrometer.MicrometerJvmMemoryMeterConventions;
import io.micrometer.core.instrument.binder.jvm.convention.micrometer.MicrometerJvmThreadMeterConventions;
import io.micrometer.core.instrument.binder.jvm.convention.otel.OpenTelemetryJvmClassLoadingMeterConventions;
import io.micrometer.core.instrument.binder.jvm.convention.otel.OpenTelemetryJvmCpuMeterConventions;
import io.micrometer.core.instrument.binder.jvm.convention.otel.OpenTelemetryJvmMemoryMeterConventions;
import io.micrometer.core.instrument.binder.jvm.convention.otel.OpenTelemetryJvmThreadMeterConventions;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link SemanticConventionAutoConfiguration}.
*/
class SemanticConventionAutoConfigurationTests {
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(SemanticConventionAutoConfiguration.class));
@Test
void registersMicrometerConventionsByDefault() {
this.contextRunner.run((context) -> {
assertThat(context).hasSingleBean(MicrometerJvmMemoryMeterConventions.class);
assertThat(context).hasSingleBean(MicrometerJvmClassLoadingMeterConventions.class);
assertThat(context).hasSingleBean(MicrometerJvmCpuMeterConventions.class);
assertThat(context).hasSingleBean(MicrometerJvmThreadMeterConventions.class);
assertThat(context).doesNotHaveBean(OpenTelemetryJvmMemoryMeterConventions.class);
assertThat(context).doesNotHaveBean(OpenTelemetryJvmClassLoadingMeterConventions.class);
assertThat(context).doesNotHaveBean(OpenTelemetryJvmCpuMeterConventions.class);
assertThat(context).doesNotHaveBean(OpenTelemetryJvmThreadMeterConventions.class);
});
}
@Test
void registersOpenTelemetryConventionsWhenConventionsSetToOpenTelemetry() {
this.contextRunner.withPropertyValues("management.observations.conventions=opentelemetry").run((context) -> {
assertThat(context).hasSingleBean(JvmMemoryMeterConventions.class)
.hasSingleBean(OpenTelemetryJvmMemoryMeterConventions.class);
assertThat(context).hasSingleBean(JvmClassLoadingMeterConventions.class)
.hasSingleBean(OpenTelemetryJvmClassLoadingMeterConventions.class);
assertThat(context).hasSingleBean(JvmCpuMeterConventions.class)
.hasSingleBean(OpenTelemetryJvmCpuMeterConventions.class);
assertThat(context).hasSingleBean(JvmThreadMeterConventions.class)
.hasSingleBean(OpenTelemetryJvmThreadMeterConventions.class);
});
}
@Test
void allowsCustomMicrometerConventionsToBeUsed() {
this.contextRunner.withPropertyValues("management.observations.conventions=micrometer")
.withUserConfiguration(CustomJvmMemoryMeterConventionsConfiguration.class)
.run((context) -> {
assertThat(context).hasSingleBean(JvmMemoryMeterConventions.class)
.hasBean("customJvmMemoryMeterConventions");
assertThat(context).doesNotHaveBean(MicrometerJvmMemoryMeterConventions.class);
assertThat(context).hasSingleBean(MicrometerJvmClassLoadingMeterConventions.class);
assertThat(context).hasSingleBean(MicrometerJvmCpuMeterConventions.class);
assertThat(context).hasSingleBean(MicrometerJvmThreadMeterConventions.class);
});
}
@Test
void allowsCustomOpenTelemetryConventionsToBeUsed() {
this.contextRunner.withPropertyValues("management.observations.conventions=opentelemetry")
.withUserConfiguration(CustomJvmClassLoadingMeterConventionsConfiguration.class)
.run((context) -> {
assertThat(context).hasSingleBean(JvmClassLoadingMeterConventions.class)
.hasBean("customJvmClassLoadingMeterConventions");
assertThat(context).doesNotHaveBean(MicrometerJvmClassLoadingMeterConventions.class);
assertThat(context).hasSingleBean(OpenTelemetryJvmMemoryMeterConventions.class);
assertThat(context).hasSingleBean(OpenTelemetryJvmCpuMeterConventions.class);
assertThat(context).hasSingleBean(OpenTelemetryJvmThreadMeterConventions.class);
});
}
@Configuration(proxyBeanMethods = false)
static class CustomJvmMemoryMeterConventionsConfiguration {
@Bean
JvmMemoryMeterConventions customJvmMemoryMeterConventions() {
return new JvmMemoryMeterConventions() {
@Override
public MeterConvention<MemoryPoolMXBean> getMemoryUsedConvention() {
return new SimpleMeterConvention<>("my.memory.used");
}
@Override
public MeterConvention<MemoryPoolMXBean> getMemoryCommittedConvention() {
return new SimpleMeterConvention<>("my.memory.committed");
}
@Override
public MeterConvention<MemoryPoolMXBean> getMemoryMaxConvention() {
return new SimpleMeterConvention<>("my.memory.max");
}
};
}
}
@Configuration(proxyBeanMethods = false)
static class CustomJvmClassLoadingMeterConventionsConfiguration {
@Bean
JvmClassLoadingMeterConventions customJvmClassLoadingMeterConventions() {
return new JvmClassLoadingMeterConventions() {
@Override
public MeterConvention<Object> loadedConvention() {
return new SimpleMeterConvention<>("my.classes.loaded");
}
@Override
public MeterConvention<Object> unloadedConvention() {
return new SimpleMeterConvention<>("my.classes.unloaded");
}
@Override
public MeterConvention<Object> currentClassCountConvention() {
return new SimpleMeterConvention<>("my.classes.current");
}
};
}
}
}

16
module/spring-boot-micrometer-observation/src/main/java/org/springframework/boot/micrometer/observation/autoconfigure/ObservationProperties.java

@ -45,6 +45,8 @@ public class ObservationProperties { @@ -45,6 +45,8 @@ public class ObservationProperties {
*/
private Map<String, Boolean> enable = new LinkedHashMap<>();
private ConventionsVariant conventions = ConventionsVariant.MICROMETER;
public Map<String, Boolean> getEnable() {
return this.enable;
}
@ -65,6 +67,20 @@ public class ObservationProperties { @@ -65,6 +67,20 @@ public class ObservationProperties {
this.keyValues = keyValues;
}
public ConventionsVariant getConventions() {
return this.conventions;
}
public void setConventions(ConventionsVariant conventions) {
this.conventions = conventions;
}
public enum ConventionsVariant {
OPENTELEMETRY, MICROMETER,
}
public static class Http {
private final Client client = new Client();

27
module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/autoconfigure/WebMvcObservationAutoConfiguration.java

@ -21,12 +21,13 @@ import io.micrometer.observation.Observation; @@ -21,12 +21,13 @@ import io.micrometer.observation.Observation;
import io.micrometer.observation.ObservationRegistry;
import jakarta.servlet.DispatcherType;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
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.ConditionalOnMissingFilterBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
@ -39,6 +40,7 @@ import org.springframework.context.annotation.Configuration; @@ -39,6 +40,7 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.server.observation.DefaultServerRequestObservationConvention;
import org.springframework.http.server.observation.OpenTelemetryServerRequestObservationConvention;
import org.springframework.http.server.observation.ServerRequestObservationConvention;
import org.springframework.web.filter.ServerHttpObservationFilter;
import org.springframework.web.servlet.DispatcherServlet;
@ -64,13 +66,26 @@ import org.springframework.web.servlet.DispatcherServlet; @@ -64,13 +66,26 @@ import org.springframework.web.servlet.DispatcherServlet;
public final class WebMvcObservationAutoConfiguration {
@Bean
@ConditionalOnMissingFilterBean
FilterRegistrationBean<ServerHttpObservationFilter> webMvcObservationFilter(ObservationRegistry registry,
ObjectProvider<ServerRequestObservationConvention> customConvention,
@ConditionalOnMissingBean(ServerRequestObservationConvention.class)
@ConditionalOnProperty(name = "management.observations.conventions", havingValue = "micrometer",
matchIfMissing = true)
DefaultServerRequestObservationConvention micrometerServerRequestObservationConvention(
ObservationProperties observationProperties) {
String name = observationProperties.getHttp().getServer().getRequests().getName();
ServerRequestObservationConvention convention = customConvention
.getIfAvailable(() -> new DefaultServerRequestObservationConvention(name));
return new DefaultServerRequestObservationConvention(name);
}
@Bean
@ConditionalOnMissingBean(ServerRequestObservationConvention.class)
@ConditionalOnProperty(name = "management.observations.conventions", havingValue = "opentelemetry")
OpenTelemetryServerRequestObservationConvention openTelemetryServerRequestObservationConvention() {
return new OpenTelemetryServerRequestObservationConvention();
}
@Bean
@ConditionalOnMissingFilterBean
FilterRegistrationBean<ServerHttpObservationFilter> webMvcObservationFilter(ObservationRegistry registry,
ServerRequestObservationConvention convention) {
ServerHttpObservationFilter filter = new ServerHttpObservationFilter(registry, convention);
FilterRegistrationBean<ServerHttpObservationFilter> registration = new FilterRegistrationBean<>(filter);
registration.setOrder(Ordered.HIGHEST_PRECEDENCE + 1);

15
module/spring-boot-webmvc/src/test/java/org/springframework/boot/webmvc/autoconfigure/WebMvcObservationAutoConfigurationTests.java

@ -46,6 +46,7 @@ import org.springframework.context.annotation.Bean; @@ -46,6 +46,7 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.http.server.observation.DefaultServerRequestObservationConvention;
import org.springframework.http.server.observation.OpenTelemetryServerRequestObservationConvention;
import org.springframework.test.web.servlet.assertj.MockMvcTester;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@ -93,6 +94,20 @@ class WebMvcObservationAutoConfigurationTests { @@ -93,6 +94,20 @@ class WebMvcObservationAutoConfigurationTests {
});
}
@Test
void defaultMicrometerConvention() {
this.contextRunner.run((context) -> {
assertThat(context).hasSingleBean(DefaultServerRequestObservationConvention.class);
});
}
@Test
void openTelemetryConventionConfiguredViaProperties() {
this.contextRunner.withPropertyValues("management.observations.conventions=opentelemetry").run((context) -> {
assertThat(context).hasSingleBean(OpenTelemetryServerRequestObservationConvention.class);
});
}
@Test
void customConventionWhenPresent() {
this.contextRunner.withUserConfiguration(CustomConventionConfiguration.class)

Loading…
Cancel
Save