Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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());
}

}

}
Original file line number Diff line number Diff line change
@@ -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");
}
};
}

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand All @@ -65,6 +67,20 @@ public void setKeyValues(Map<String, String> keyValues) {
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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,13 @@
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;
Expand All @@ -39,6 +40,7 @@
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;
Expand All @@ -64,13 +66,26 @@
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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
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;
Expand Down Expand Up @@ -93,6 +94,20 @@ void definesFilterWhenRegistryIsPresent() {
});
}

@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)
Expand Down
Loading