Skip to content

Commit 36d7bde

Browse files
committed
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.
1 parent 0ff99b0 commit 36d7bde

File tree

5 files changed

+325
-6
lines changed

5 files changed

+325
-6
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
/*
2+
* Copyright 2012-present the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.micrometer.metrics.autoconfigure;
18+
19+
import io.micrometer.core.instrument.Tags;
20+
import io.micrometer.core.instrument.binder.jvm.convention.JvmClassLoadingMeterConventions;
21+
import io.micrometer.core.instrument.binder.jvm.convention.JvmCpuMeterConventions;
22+
import io.micrometer.core.instrument.binder.jvm.convention.JvmMemoryMeterConventions;
23+
import io.micrometer.core.instrument.binder.jvm.convention.JvmThreadMeterConventions;
24+
import io.micrometer.core.instrument.binder.jvm.convention.micrometer.MicrometerJvmClassLoadingMeterConventions;
25+
import io.micrometer.core.instrument.binder.jvm.convention.micrometer.MicrometerJvmCpuMeterConventions;
26+
import io.micrometer.core.instrument.binder.jvm.convention.micrometer.MicrometerJvmMemoryMeterConventions;
27+
import io.micrometer.core.instrument.binder.jvm.convention.micrometer.MicrometerJvmThreadMeterConventions;
28+
import io.micrometer.core.instrument.binder.jvm.convention.otel.OpenTelemetryJvmClassLoadingMeterConventions;
29+
import io.micrometer.core.instrument.binder.jvm.convention.otel.OpenTelemetryJvmCpuMeterConventions;
30+
import io.micrometer.core.instrument.binder.jvm.convention.otel.OpenTelemetryJvmMemoryMeterConventions;
31+
import io.micrometer.core.instrument.binder.jvm.convention.otel.OpenTelemetryJvmThreadMeterConventions;
32+
33+
import org.springframework.boot.autoconfigure.AutoConfiguration;
34+
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
35+
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
36+
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
37+
import org.springframework.boot.context.properties.EnableConfigurationProperties;
38+
import org.springframework.boot.micrometer.metrics.autoconfigure.jvm.JvmMetricsAutoConfiguration;
39+
import org.springframework.boot.micrometer.metrics.autoconfigure.system.SystemMetricsAutoConfiguration;
40+
import org.springframework.boot.micrometer.observation.autoconfigure.ObservationProperties;
41+
import org.springframework.context.annotation.Bean;
42+
import org.springframework.context.annotation.Configuration;
43+
44+
/**
45+
* {@link EnableAutoConfiguration Auto-configuration} for semantic conventions for metrics
46+
* and observations.
47+
*
48+
* @since 4.1.0
49+
*/
50+
@AutoConfiguration(before = { JvmMetricsAutoConfiguration.class, SystemMetricsAutoConfiguration.class })
51+
@EnableConfigurationProperties(ObservationProperties.class)
52+
public final class SemanticConventionAutoConfiguration {
53+
54+
@Configuration(proxyBeanMethods = false)
55+
@ConditionalOnProperty(prefix = "management.observations", name = "conventions", havingValue = "micrometer",
56+
matchIfMissing = true)
57+
static class MicrometerSemanticConventionConfiguration {
58+
59+
@Bean
60+
@ConditionalOnMissingBean(JvmMemoryMeterConventions.class)
61+
MicrometerJvmMemoryMeterConventions micrometerJvmMemoryMeterConventions() {
62+
return new MicrometerJvmMemoryMeterConventions();
63+
}
64+
65+
@Bean
66+
@ConditionalOnMissingBean(JvmClassLoadingMeterConventions.class)
67+
MicrometerJvmClassLoadingMeterConventions micrometerJvmClassLoadingMeterConventions() {
68+
return new MicrometerJvmClassLoadingMeterConventions();
69+
}
70+
71+
@Bean
72+
@ConditionalOnMissingBean(JvmCpuMeterConventions.class)
73+
MicrometerJvmCpuMeterConventions micrometerJvmCpuMeterConventions() {
74+
return new MicrometerJvmCpuMeterConventions(Tags.empty());
75+
}
76+
77+
@Bean
78+
@ConditionalOnMissingBean(JvmThreadMeterConventions.class)
79+
MicrometerJvmThreadMeterConventions micrometerJvmThreadMeterConventions() {
80+
return new MicrometerJvmThreadMeterConventions(Tags.empty());
81+
}
82+
83+
}
84+
85+
@Configuration(proxyBeanMethods = false)
86+
@ConditionalOnProperty(prefix = "management.observations", name = "conventions", havingValue = "opentelemetry")
87+
static class OpenTelemetrySemanticConventionConfiguration {
88+
89+
@Bean
90+
@ConditionalOnMissingBean(JvmMemoryMeterConventions.class)
91+
OpenTelemetryJvmMemoryMeterConventions openTelemetryJvmMemoryMeterConventions() {
92+
return new OpenTelemetryJvmMemoryMeterConventions(Tags.empty());
93+
}
94+
95+
@Bean
96+
@ConditionalOnMissingBean(JvmClassLoadingMeterConventions.class)
97+
OpenTelemetryJvmClassLoadingMeterConventions openTelemetryJvmClassLoadingMeterConventions() {
98+
return new OpenTelemetryJvmClassLoadingMeterConventions();
99+
}
100+
101+
@Bean
102+
@ConditionalOnMissingBean(JvmCpuMeterConventions.class)
103+
OpenTelemetryJvmCpuMeterConventions openTelemetryJvmCpuMeterConventions() {
104+
return new OpenTelemetryJvmCpuMeterConventions(Tags.empty());
105+
}
106+
107+
@Bean
108+
@ConditionalOnMissingBean(JvmThreadMeterConventions.class)
109+
OpenTelemetryJvmThreadMeterConventions openTelemetryJvmThreadMeterConventions() {
110+
return new OpenTelemetryJvmThreadMeterConventions(Tags.empty());
111+
}
112+
113+
}
114+
115+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
/*
2+
* Copyright 2012-present the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.micrometer.metrics.autoconfigure;
18+
19+
import java.lang.management.MemoryPoolMXBean;
20+
21+
import io.micrometer.core.instrument.binder.MeterConvention;
22+
import io.micrometer.core.instrument.binder.SimpleMeterConvention;
23+
import io.micrometer.core.instrument.binder.jvm.convention.JvmClassLoadingMeterConventions;
24+
import io.micrometer.core.instrument.binder.jvm.convention.JvmCpuMeterConventions;
25+
import io.micrometer.core.instrument.binder.jvm.convention.JvmMemoryMeterConventions;
26+
import io.micrometer.core.instrument.binder.jvm.convention.JvmThreadMeterConventions;
27+
import io.micrometer.core.instrument.binder.jvm.convention.micrometer.MicrometerJvmClassLoadingMeterConventions;
28+
import io.micrometer.core.instrument.binder.jvm.convention.micrometer.MicrometerJvmCpuMeterConventions;
29+
import io.micrometer.core.instrument.binder.jvm.convention.micrometer.MicrometerJvmMemoryMeterConventions;
30+
import io.micrometer.core.instrument.binder.jvm.convention.micrometer.MicrometerJvmThreadMeterConventions;
31+
import io.micrometer.core.instrument.binder.jvm.convention.otel.OpenTelemetryJvmClassLoadingMeterConventions;
32+
import io.micrometer.core.instrument.binder.jvm.convention.otel.OpenTelemetryJvmCpuMeterConventions;
33+
import io.micrometer.core.instrument.binder.jvm.convention.otel.OpenTelemetryJvmMemoryMeterConventions;
34+
import io.micrometer.core.instrument.binder.jvm.convention.otel.OpenTelemetryJvmThreadMeterConventions;
35+
import org.junit.jupiter.api.Test;
36+
37+
import org.springframework.boot.autoconfigure.AutoConfigurations;
38+
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
39+
import org.springframework.context.annotation.Bean;
40+
import org.springframework.context.annotation.Configuration;
41+
42+
import static org.assertj.core.api.Assertions.assertThat;
43+
44+
/**
45+
* Tests for {@link SemanticConventionAutoConfiguration}.
46+
*/
47+
class SemanticConventionAutoConfigurationTests {
48+
49+
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
50+
.withConfiguration(AutoConfigurations.of(SemanticConventionAutoConfiguration.class));
51+
52+
@Test
53+
void registersMicrometerConventionsByDefault() {
54+
this.contextRunner.run((context) -> {
55+
assertThat(context).hasSingleBean(MicrometerJvmMemoryMeterConventions.class);
56+
assertThat(context).hasSingleBean(MicrometerJvmClassLoadingMeterConventions.class);
57+
assertThat(context).hasSingleBean(MicrometerJvmCpuMeterConventions.class);
58+
assertThat(context).hasSingleBean(MicrometerJvmThreadMeterConventions.class);
59+
assertThat(context).doesNotHaveBean(OpenTelemetryJvmMemoryMeterConventions.class);
60+
assertThat(context).doesNotHaveBean(OpenTelemetryJvmClassLoadingMeterConventions.class);
61+
assertThat(context).doesNotHaveBean(OpenTelemetryJvmCpuMeterConventions.class);
62+
assertThat(context).doesNotHaveBean(OpenTelemetryJvmThreadMeterConventions.class);
63+
});
64+
}
65+
66+
@Test
67+
void registersOpenTelemetryConventionsWhenConventionsSetToOpenTelemetry() {
68+
this.contextRunner.withPropertyValues("management.observations.conventions=opentelemetry").run((context) -> {
69+
assertThat(context).hasSingleBean(JvmMemoryMeterConventions.class)
70+
.hasSingleBean(OpenTelemetryJvmMemoryMeterConventions.class);
71+
assertThat(context).hasSingleBean(JvmClassLoadingMeterConventions.class)
72+
.hasSingleBean(OpenTelemetryJvmClassLoadingMeterConventions.class);
73+
assertThat(context).hasSingleBean(JvmCpuMeterConventions.class)
74+
.hasSingleBean(OpenTelemetryJvmCpuMeterConventions.class);
75+
assertThat(context).hasSingleBean(JvmThreadMeterConventions.class)
76+
.hasSingleBean(OpenTelemetryJvmThreadMeterConventions.class);
77+
});
78+
}
79+
80+
@Test
81+
void allowsCustomMicrometerConventionsToBeUsed() {
82+
this.contextRunner.withPropertyValues("management.observations.conventions=micrometer")
83+
.withUserConfiguration(CustomJvmMemoryMeterConventionsConfiguration.class)
84+
.run((context) -> {
85+
assertThat(context).hasSingleBean(JvmMemoryMeterConventions.class)
86+
.hasBean("customJvmMemoryMeterConventions");
87+
assertThat(context).doesNotHaveBean(MicrometerJvmMemoryMeterConventions.class);
88+
assertThat(context).hasSingleBean(MicrometerJvmClassLoadingMeterConventions.class);
89+
assertThat(context).hasSingleBean(MicrometerJvmCpuMeterConventions.class);
90+
assertThat(context).hasSingleBean(MicrometerJvmThreadMeterConventions.class);
91+
});
92+
}
93+
94+
@Test
95+
void allowsCustomOpenTelemetryConventionsToBeUsed() {
96+
this.contextRunner.withPropertyValues("management.observations.conventions=opentelemetry")
97+
.withUserConfiguration(CustomJvmClassLoadingMeterConventionsConfiguration.class)
98+
.run((context) -> {
99+
assertThat(context).hasSingleBean(JvmClassLoadingMeterConventions.class)
100+
.hasBean("customJvmClassLoadingMeterConventions");
101+
assertThat(context).doesNotHaveBean(MicrometerJvmClassLoadingMeterConventions.class);
102+
assertThat(context).hasSingleBean(OpenTelemetryJvmMemoryMeterConventions.class);
103+
assertThat(context).hasSingleBean(OpenTelemetryJvmCpuMeterConventions.class);
104+
assertThat(context).hasSingleBean(OpenTelemetryJvmThreadMeterConventions.class);
105+
});
106+
}
107+
108+
@Configuration(proxyBeanMethods = false)
109+
static class CustomJvmMemoryMeterConventionsConfiguration {
110+
111+
@Bean
112+
JvmMemoryMeterConventions customJvmMemoryMeterConventions() {
113+
return new JvmMemoryMeterConventions() {
114+
@Override
115+
public MeterConvention<MemoryPoolMXBean> getMemoryUsedConvention() {
116+
return new SimpleMeterConvention<>("my.memory.used");
117+
}
118+
119+
@Override
120+
public MeterConvention<MemoryPoolMXBean> getMemoryCommittedConvention() {
121+
return new SimpleMeterConvention<>("my.memory.committed");
122+
}
123+
124+
@Override
125+
public MeterConvention<MemoryPoolMXBean> getMemoryMaxConvention() {
126+
return new SimpleMeterConvention<>("my.memory.max");
127+
}
128+
};
129+
}
130+
131+
}
132+
133+
@Configuration(proxyBeanMethods = false)
134+
static class CustomJvmClassLoadingMeterConventionsConfiguration {
135+
136+
@Bean
137+
JvmClassLoadingMeterConventions customJvmClassLoadingMeterConventions() {
138+
return new JvmClassLoadingMeterConventions() {
139+
@Override
140+
public MeterConvention<Object> loadedConvention() {
141+
return new SimpleMeterConvention<>("my.classes.loaded");
142+
}
143+
144+
@Override
145+
public MeterConvention<Object> unloadedConvention() {
146+
return new SimpleMeterConvention<>("my.classes.unloaded");
147+
}
148+
149+
@Override
150+
public MeterConvention<Object> currentClassCountConvention() {
151+
return new SimpleMeterConvention<>("my.classes.current");
152+
}
153+
};
154+
}
155+
156+
}
157+
158+
}

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

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ public class ObservationProperties {
4545
*/
4646
private Map<String, Boolean> enable = new LinkedHashMap<>();
4747

48+
private ConventionsVariant conventions = ConventionsVariant.MICROMETER;
49+
4850
public Map<String, Boolean> getEnable() {
4951
return this.enable;
5052
}
@@ -65,6 +67,20 @@ public void setKeyValues(Map<String, String> keyValues) {
6567
this.keyValues = keyValues;
6668
}
6769

70+
public ConventionsVariant getConventions() {
71+
return this.conventions;
72+
}
73+
74+
public void setConventions(ConventionsVariant conventions) {
75+
this.conventions = conventions;
76+
}
77+
78+
public enum ConventionsVariant {
79+
80+
OPENTELEMETRY, MICROMETER,
81+
82+
}
83+
6884
public static class Http {
6985

7086
private final Client client = new Client();

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

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,13 @@
2121
import io.micrometer.observation.ObservationRegistry;
2222
import jakarta.servlet.DispatcherType;
2323

24-
import org.springframework.beans.factory.ObjectProvider;
2524
import org.springframework.boot.autoconfigure.AutoConfiguration;
2625
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
2726
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
2827
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
28+
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
2929
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingFilterBean;
30+
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
3031
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
3132
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
3233
import org.springframework.boot.context.properties.EnableConfigurationProperties;
@@ -39,6 +40,7 @@
3940
import org.springframework.core.Ordered;
4041
import org.springframework.core.annotation.Order;
4142
import org.springframework.http.server.observation.DefaultServerRequestObservationConvention;
43+
import org.springframework.http.server.observation.OpenTelemetryServerRequestObservationConvention;
4244
import org.springframework.http.server.observation.ServerRequestObservationConvention;
4345
import org.springframework.web.filter.ServerHttpObservationFilter;
4446
import org.springframework.web.servlet.DispatcherServlet;
@@ -64,13 +66,26 @@
6466
public final class WebMvcObservationAutoConfiguration {
6567

6668
@Bean
67-
@ConditionalOnMissingFilterBean
68-
FilterRegistrationBean<ServerHttpObservationFilter> webMvcObservationFilter(ObservationRegistry registry,
69-
ObjectProvider<ServerRequestObservationConvention> customConvention,
69+
@ConditionalOnMissingBean(ServerRequestObservationConvention.class)
70+
@ConditionalOnProperty(name = "management.observations.conventions", havingValue = "micrometer",
71+
matchIfMissing = true)
72+
DefaultServerRequestObservationConvention micrometerServerRequestObservationConvention(
7073
ObservationProperties observationProperties) {
7174
String name = observationProperties.getHttp().getServer().getRequests().getName();
72-
ServerRequestObservationConvention convention = customConvention
73-
.getIfAvailable(() -> new DefaultServerRequestObservationConvention(name));
75+
return new DefaultServerRequestObservationConvention(name);
76+
}
77+
78+
@Bean
79+
@ConditionalOnMissingBean(ServerRequestObservationConvention.class)
80+
@ConditionalOnProperty(name = "management.observations.conventions", havingValue = "opentelemetry")
81+
OpenTelemetryServerRequestObservationConvention openTelemetryServerRequestObservationConvention() {
82+
return new OpenTelemetryServerRequestObservationConvention();
83+
}
84+
85+
@Bean
86+
@ConditionalOnMissingFilterBean
87+
FilterRegistrationBean<ServerHttpObservationFilter> webMvcObservationFilter(ObservationRegistry registry,
88+
ServerRequestObservationConvention convention) {
7489
ServerHttpObservationFilter filter = new ServerHttpObservationFilter(registry, convention);
7590
FilterRegistrationBean<ServerHttpObservationFilter> registration = new FilterRegistrationBean<>(filter);
7691
registration.setOrder(Ordered.HIGHEST_PRECEDENCE + 1);

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
import org.springframework.context.annotation.Configuration;
4747
import org.springframework.core.Ordered;
4848
import org.springframework.http.server.observation.DefaultServerRequestObservationConvention;
49+
import org.springframework.http.server.observation.OpenTelemetryServerRequestObservationConvention;
4950
import org.springframework.test.web.servlet.assertj.MockMvcTester;
5051
import org.springframework.web.bind.annotation.GetMapping;
5152
import org.springframework.web.bind.annotation.RestController;
@@ -93,6 +94,20 @@ void definesFilterWhenRegistryIsPresent() {
9394
});
9495
}
9596

97+
@Test
98+
void defaultMicrometerConvention() {
99+
this.contextRunner.run((context) -> {
100+
assertThat(context).hasSingleBean(DefaultServerRequestObservationConvention.class);
101+
});
102+
}
103+
104+
@Test
105+
void openTelemetryConventionConfiguredViaProperties() {
106+
this.contextRunner.withPropertyValues("management.observations.conventions=opentelemetry").run((context) -> {
107+
assertThat(context).hasSingleBean(OpenTelemetryServerRequestObservationConvention.class);
108+
});
109+
}
110+
96111
@Test
97112
void customConventionWhenPresent() {
98113
this.contextRunner.withUserConfiguration(CustomConventionConfiguration.class)

0 commit comments

Comments
 (0)