From c5b2bc3dc74552e6cbc61d66c0d146799d183b9c Mon Sep 17 00:00:00 2001 From: Jonatan Ivanov Date: Fri, 16 Sep 2022 18:43:50 -0700 Subject: [PATCH 1/2] Add auto-configuration for Exemplars See gh-32415 --- .../exemplars/ExemplarsAutoConfiguration.java | 93 ++++++++++++++++ .../tracing/exemplars/package-info.java | 20 ++++ ...ot.autoconfigure.AutoConfiguration.imports | 1 + .../ExemplarsAutoConfigurationTests.java | 105 ++++++++++++++++++ 4 files changed, 219 insertions(+) create mode 100644 spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/exemplars/ExemplarsAutoConfiguration.java create mode 100644 spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/exemplars/package-info.java create mode 100644 spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/exemplars/ExemplarsAutoConfigurationTests.java diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/exemplars/ExemplarsAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/exemplars/ExemplarsAutoConfiguration.java new file mode 100644 index 0000000000..8eb0940f66 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/exemplars/ExemplarsAutoConfiguration.java @@ -0,0 +1,93 @@ +/* + * Copyright 2012-2022 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.tracing.exemplars; + +import io.micrometer.tracing.Span; +import io.micrometer.tracing.Tracer; +import io.prometheus.client.exemplars.tracer.common.SpanContextSupplier; + +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.beans.factory.SmartInitializingSingleton; +import org.springframework.boot.actuate.autoconfigure.metrics.export.prometheus.PrometheusMetricsExportAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.tracing.ConditionalOnEnabledTracing; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; + +/** + * {@link EnableAutoConfiguration Auto-configuration} for Prometheus Exemplars with + * Micrometer Tracing. + * + * @author Jonatan Ivanov + * @since 3.0.0 + */ +@AutoConfiguration(before = PrometheusMetricsExportAutoConfiguration.class) +@ConditionalOnClass({ Tracer.class, SpanContextSupplier.class }) +@ConditionalOnEnabledTracing +public class ExemplarsAutoConfiguration { + + @Bean + @ConditionalOnMissingBean + SpanContextSupplier spanContextSupplier(ObjectProvider tracerProvider) { + return new LazyTracingSpanContextSupplier(tracerProvider); + } + + /** + * Since the MeterRegistry can depend on the {@link Tracer} (Exemplars) and the + * {@link Tracer} can depend on the MeterRegistry (recording metrics), this + * {@link SpanContextSupplier} breaks the circle by lazily loading the {@link Tracer}. + */ + static class LazyTracingSpanContextSupplier implements SpanContextSupplier, SmartInitializingSingleton { + + private final ObjectProvider tracerProvider; + + private Tracer tracer; + + LazyTracingSpanContextSupplier(ObjectProvider tracerProvider) { + this.tracerProvider = tracerProvider; + } + + @Override + public String getTraceId() { + return this.tracer.currentSpan().context().traceId(); + } + + @Override + public String getSpanId() { + return this.tracer.currentSpan().context().spanId(); + } + + @Override + public boolean isSampled() { + return this.tracer != null && isSampled(this.tracer); + } + + private boolean isSampled(Tracer tracer) { + Span currentSpan = tracer.currentSpan(); + return currentSpan != null && currentSpan.context().sampled(); + } + + @Override + public void afterSingletonsInstantiated() { + this.tracer = this.tracerProvider.getIfAvailable(); + } + + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/exemplars/package-info.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/exemplars/package-info.java new file mode 100644 index 0000000000..4fde3ff674 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/exemplars/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2012-2022 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. + */ + +/** + * Auto-configuration for Prometheus Exemplars with Micrometer Tracing. + */ +package org.springframework.boot.actuate.autoconfigure.tracing.exemplars; diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports index 1ba70ac93f..2bdd69ff79 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -101,6 +101,7 @@ org.springframework.boot.actuate.autoconfigure.trace.http.HttpTraceEndpointAutoC org.springframework.boot.actuate.autoconfigure.tracing.BraveAutoConfiguration org.springframework.boot.actuate.autoconfigure.tracing.MicrometerTracingAutoConfiguration org.springframework.boot.actuate.autoconfigure.tracing.OpenTelemetryAutoConfiguration +org.springframework.boot.actuate.autoconfigure.tracing.exemplars.ExemplarsAutoConfiguration org.springframework.boot.actuate.autoconfigure.tracing.wavefront.WavefrontTracingAutoConfiguration org.springframework.boot.actuate.autoconfigure.tracing.zipkin.ZipkinAutoConfiguration org.springframework.boot.actuate.autoconfigure.web.mappings.MappingsEndpointAutoConfiguration diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/exemplars/ExemplarsAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/exemplars/ExemplarsAutoConfigurationTests.java new file mode 100644 index 0000000000..5eb67e3f6b --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/exemplars/ExemplarsAutoConfigurationTests.java @@ -0,0 +1,105 @@ +/* + * Copyright 2012-2022 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.tracing.exemplars; + +import io.micrometer.observation.Observation; +import io.micrometer.observation.ObservationRegistry; +import io.micrometer.prometheus.PrometheusMeterRegistry; +import io.prometheus.client.exemplars.tracer.common.SpanContextSupplier; +import io.prometheus.client.exporter.common.TextFormat; +import org.junit.jupiter.api.Test; + +import org.springframework.boot.actuate.autoconfigure.metrics.export.prometheus.PrometheusMetricsExportAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.metrics.test.MetricsRun; +import org.springframework.boot.actuate.autoconfigure.observation.ObservationAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.tracing.BraveAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.tracing.MicrometerTracingAutoConfiguration; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.FilteredClassLoader; +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; +import static org.mockito.Mockito.mock; + +/** + * Tests for {@link ExemplarsAutoConfiguration}. + * + * * @author Jonatan Ivanov + */ +class ExemplarsAutoConfigurationTests { + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withPropertyValues("management.tracing.sampling.probability=1.0", + "management.metrics.distribution.percentiles-histogram.all=true") + .with(MetricsRun.limitedTo(PrometheusMetricsExportAutoConfiguration.class)).withConfiguration( + AutoConfigurations.of(ExemplarsAutoConfiguration.class, ObservationAutoConfiguration.class, + BraveAutoConfiguration.class, MicrometerTracingAutoConfiguration.class)); + + @Test + void shouldNotSupplyBeansIfTracingIsDisabled() { + this.contextRunner.withPropertyValues("management.tracing.enabled=false") + .run((context) -> assertThat(context).doesNotHaveBean(SpanContextSupplier.class)); + } + + @Test + void shouldNotSupplyBeansIfPrometheusSupportIsMissing() { + this.contextRunner.withClassLoader(new FilteredClassLoader("io.prometheus.client.exemplars")) + .run((context) -> assertThat(context).doesNotHaveBean(SpanContextSupplier.class)); + } + + @Test + void shouldNotSupplyBeansIfMicrometerTracingIsMissing() { + this.contextRunner.withClassLoader(new FilteredClassLoader("io.micrometer.tracing")) + .run((context) -> assertThat(context).doesNotHaveBean(SpanContextSupplier.class)); + } + + @Test + void shouldSupplyCustomBeans() { + this.contextRunner.withUserConfiguration(CustomConfiguration.class) + .run((context) -> assertThat(context).hasSingleBean(SpanContextSupplier.class) + .getBean(SpanContextSupplier.class).isSameAs(CustomConfiguration.SUPPLIER)); + } + + @Test + void prometheusOpenMetricsOutputShouldContainExemplars() { + this.contextRunner.run((context) -> { + assertThat(context).hasSingleBean(SpanContextSupplier.class); + ObservationRegistry observationRegistry = context.getBean(ObservationRegistry.class); + Observation.start("test.observation", observationRegistry).stop(); + + PrometheusMeterRegistry prometheusMeterRegistry = context.getBean(PrometheusMeterRegistry.class); + String openMetricsOutput = prometheusMeterRegistry.scrape(TextFormat.CONTENT_TYPE_OPENMETRICS_100); + assertThat(openMetricsOutput).contains("test_observation_seconds_bucket").containsOnlyOnce("trace_id=") + .containsOnlyOnce("span_id="); + }); + } + + @Configuration(proxyBeanMethods = false) + private static class CustomConfiguration { + + static final SpanContextSupplier SUPPLIER = mock(SpanContextSupplier.class); + + @Bean + SpanContextSupplier customSpanContextSupplier() { + return SUPPLIER; + } + + } + +} From d593d194de5aeee812a283178fe100212f89fbd4 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Thu, 13 Oct 2022 12:40:08 +0100 Subject: [PATCH 2/2] Polish "Add auto-configuration for Exemplars" See gh-32415 --- .../exemplars/ExemplarsAutoConfiguration.java | 31 +++++++++---------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/exemplars/ExemplarsAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/exemplars/ExemplarsAutoConfiguration.java index 8eb0940f66..d5b0f9b0c3 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/exemplars/ExemplarsAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/exemplars/ExemplarsAutoConfiguration.java @@ -21,14 +21,16 @@ import io.micrometer.tracing.Tracer; import io.prometheus.client.exemplars.tracer.common.SpanContextSupplier; import org.springframework.beans.factory.ObjectProvider; -import org.springframework.beans.factory.SmartInitializingSingleton; import org.springframework.boot.actuate.autoconfigure.metrics.export.prometheus.PrometheusMetricsExportAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.tracing.ConditionalOnEnabledTracing; +import org.springframework.boot.actuate.autoconfigure.tracing.MicrometerTracingAutoConfiguration; 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.context.annotation.Bean; +import org.springframework.util.function.SingletonSupplier; /** * {@link EnableAutoConfiguration Auto-configuration} for Prometheus Exemplars with @@ -37,7 +39,9 @@ import org.springframework.context.annotation.Bean; * @author Jonatan Ivanov * @since 3.0.0 */ -@AutoConfiguration(before = PrometheusMetricsExportAutoConfiguration.class) +@AutoConfiguration(before = PrometheusMetricsExportAutoConfiguration.class, + after = MicrometerTracingAutoConfiguration.class) +@ConditionalOnBean(Tracer.class) @ConditionalOnClass({ Tracer.class, SpanContextSupplier.class }) @ConditionalOnEnabledTracing public class ExemplarsAutoConfiguration { @@ -53,39 +57,32 @@ public class ExemplarsAutoConfiguration { * {@link Tracer} can depend on the MeterRegistry (recording metrics), this * {@link SpanContextSupplier} breaks the circle by lazily loading the {@link Tracer}. */ - static class LazyTracingSpanContextSupplier implements SpanContextSupplier, SmartInitializingSingleton { + static class LazyTracingSpanContextSupplier implements SpanContextSupplier { - private final ObjectProvider tracerProvider; - - private Tracer tracer; + private final SingletonSupplier tracer; LazyTracingSpanContextSupplier(ObjectProvider tracerProvider) { - this.tracerProvider = tracerProvider; + this.tracer = SingletonSupplier.of(tracerProvider::getObject); } @Override public String getTraceId() { - return this.tracer.currentSpan().context().traceId(); + return currentSpan().context().traceId(); } @Override public String getSpanId() { - return this.tracer.currentSpan().context().spanId(); + return currentSpan().context().spanId(); } @Override public boolean isSampled() { - return this.tracer != null && isSampled(this.tracer); - } - - private boolean isSampled(Tracer tracer) { - Span currentSpan = tracer.currentSpan(); + Span currentSpan = currentSpan(); return currentSpan != null && currentSpan.context().sampled(); } - @Override - public void afterSingletonsInstantiated() { - this.tracer = this.tracerProvider.getIfAvailable(); + private Span currentSpan() { + return this.tracer.obtain().currentSpan(); } }