diff --git a/documentation/spring-boot-docs/src/docs/antora/modules/reference/pages/actuator/metrics.adoc b/documentation/spring-boot-docs/src/docs/antora/modules/reference/pages/actuator/metrics.adoc index a7172e80c781..0cb77aea18c9 100644 --- a/documentation/spring-boot-docs/src/docs/antora/modules/reference/pages/actuator/metrics.adoc +++ b/documentation/spring-boot-docs/src/docs/antora/modules/reference/pages/actuator/metrics.adoc @@ -531,7 +531,7 @@ https://opentelemetry.io/docs/specs/otel/metrics/data-model/#exemplars[OTLP Exem To enable this feature, an javadoc:io.micrometer.registry.otlp.ExemplarContextProvider[] bean should be present. If you use xref:actuator/tracing.adoc[Micrometer Tracing], this will be auto-configured for you. By default, only sampled traces are selected as exemplars. -You can control this behavior using the configprop:management.tracing.exemplars.filter[] property. +You can control this behavior using the configprop:management.tracing.exemplars.include[] property. @@ -559,8 +559,8 @@ To enable this feature, a javadoc:io.prometheus.metrics.tracer.common.SpanContex If you're using the deprecated Prometheus simpleclient support and want to enable that feature, a javadoc:io.prometheus.client.exemplars.tracer.common.SpanContextSupplier[] bean should be present. If you use {url-micrometer-tracing-docs}[Micrometer Tracing], this will be auto-configured for you, but you can always create your own if you want. By default, only sampled traces are selected as exemplars. -You can control this behavior using the configprop:management.tracing.exemplars.filter[] property. -The value `always-on` is not supported with Prometheus. +You can control this behavior using the configprop:management.tracing.exemplars.include[] property. +The value `all` is not supported with Prometheus. Please check the https://prometheus.io/docs/prometheus/latest/feature_flags/#exemplars-storage[Prometheus Docs], since this feature needs to be explicitly enabled on Prometheus' side, and it is only supported using the https://github.com/OpenObservability/OpenMetrics/blob/v1.0.0/specification/OpenMetrics.md#exemplars[OpenMetrics] format. For ephemeral or batch jobs that may not exist long enough to be scraped, you can use https://github.com/prometheus/pushgateway[Prometheus Pushgateway] support to expose the metrics to Prometheus. diff --git a/module/spring-boot-micrometer-tracing-brave/src/test/java/org/springframework/boot/micrometer/tracing/brave/autoconfigure/OtlpExemplarsAutoConfigurationTests.java b/module/spring-boot-micrometer-tracing-brave/src/test/java/org/springframework/boot/micrometer/tracing/brave/autoconfigure/OtlpExemplarsAutoConfigurationTests.java index a963d0c2edeb..ef8c3dd5a973 100644 --- a/module/spring-boot-micrometer-tracing-brave/src/test/java/org/springframework/boot/micrometer/tracing/brave/autoconfigure/OtlpExemplarsAutoConfigurationTests.java +++ b/module/spring-boot-micrometer-tracing-brave/src/test/java/org/springframework/boot/micrometer/tracing/brave/autoconfigure/OtlpExemplarsAutoConfigurationTests.java @@ -95,10 +95,10 @@ void otlpOutputShouldContainExemplars() { } @Test - void otlpOutputShouldContainExemplarsWhenFilterIsAlwaysOnAndSpanIsNotSampled() { + void otlpOutputShouldContainExemplarsWhenIncludeIsAllAndSpanIsNotSampled() { this.contextRunner.withUserConfiguration(TracingConfiguration.class) .withPropertyValues("management.tracing.sampling.probability=0.0", - "management.tracing.exemplars.filter=always-on") + "management.tracing.exemplars.include=all") .run((context) -> { assertThat(context).hasSingleBean(ExemplarContextProvider.class); ObservationRegistry observationRegistry = context.getBean(ObservationRegistry.class); @@ -114,11 +114,11 @@ void otlpOutputShouldContainExemplarsWhenFilterIsAlwaysOnAndSpanIsNotSampled() { } @Test - void otlpOutputShouldNotContainExemplarsWhenFilterIsAlwaysOff() { + void otlpOutputShouldNotContainExemplarsWhenIncludeIsNone() { this.contextRunner.withUserConfiguration(TracingConfiguration.class) - .withPropertyValues("management.tracing.exemplars.filter=always-off") + .withPropertyValues("management.tracing.exemplars.include=none") .run((context) -> { - assertThat(context).hasSingleBean(ExemplarContextProvider.class); + assertThat(context).doesNotHaveBean(ExemplarContextProvider.class); ObservationRegistry observationRegistry = context.getBean(ObservationRegistry.class); Observation.start("test.observation", observationRegistry).stop(); OtlpMeterRegistry otlpMeterRegistry = context.getBean(OtlpMeterRegistry.class); diff --git a/module/spring-boot-micrometer-tracing-brave/src/test/java/org/springframework/boot/micrometer/tracing/brave/autoconfigure/PrometheusExemplarsAutoConfigurationTests.java b/module/spring-boot-micrometer-tracing-brave/src/test/java/org/springframework/boot/micrometer/tracing/brave/autoconfigure/PrometheusExemplarsAutoConfigurationTests.java index 652ec872e598..dd2df91e3b34 100644 --- a/module/spring-boot-micrometer-tracing-brave/src/test/java/org/springframework/boot/micrometer/tracing/brave/autoconfigure/PrometheusExemplarsAutoConfigurationTests.java +++ b/module/spring-boot-micrometer-tracing-brave/src/test/java/org/springframework/boot/micrometer/tracing/brave/autoconfigure/PrometheusExemplarsAutoConfigurationTests.java @@ -150,23 +150,23 @@ void prometheusOpenMetricsOutputShouldContainExemplars() { } @Test - void shouldFailWhenFilterIsAlwaysOn() { + void shouldFailWhenIncludeIsAll() { this.contextRunner.withUserConfiguration(TracingConfiguration.class) - .withPropertyValues("management.tracing.exemplars.filter=always-on") + .withPropertyValues("management.tracing.exemplars.include=all") .run((context) -> assertThat(context).hasFailed() .getFailure() .rootCause() .isInstanceOf(InvalidConfigurationPropertyValueException.class) .hasMessageContaining( - "Property management.tracing.exemplars.filter with value 'always-on' is invalid: Prometheus doesn't support the 'always-on' exemplar filter.")); + "Property management.tracing.exemplars.include with value 'all' is invalid: Prometheus doesn't support including exemplars for all traces.")); } @Test - void prometheusOpenMetricsOutputShouldNotContainExemplarsWhenFilterIsAlwaysOff() { + void prometheusOpenMetricsOutputShouldNotContainExemplarsWhenIncludeIsNone() { this.contextRunner.withUserConfiguration(TracingConfiguration.class) - .withPropertyValues("management.tracing.exemplars.filter=always-off") + .withPropertyValues("management.tracing.exemplars.include=none") .run((context) -> { - assertThat(context).hasSingleBean(SpanContext.class); + assertThat(context).doesNotHaveBean(SpanContext.class); ObservationRegistry observationRegistry = context.getBean(ObservationRegistry.class); Observation.start("test.observation", observationRegistry).stop(); PrometheusMeterRegistry prometheusMeterRegistry = context.getBean(PrometheusMeterRegistry.class); diff --git a/module/spring-boot-micrometer-tracing/src/main/java/org/springframework/boot/micrometer/tracing/autoconfigure/OnExemplarsIncludedCondition.java b/module/spring-boot-micrometer-tracing/src/main/java/org/springframework/boot/micrometer/tracing/autoconfigure/OnExemplarsIncludedCondition.java new file mode 100644 index 000000000000..c41ec496b5c0 --- /dev/null +++ b/module/spring-boot-micrometer-tracing/src/main/java/org/springframework/boot/micrometer/tracing/autoconfigure/OnExemplarsIncludedCondition.java @@ -0,0 +1,44 @@ +/* + * 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.tracing.autoconfigure; + +import org.springframework.boot.autoconfigure.condition.AnyNestedCondition; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; + +/** + * Condition that matches when exemplar support should be enabled. + * + * @author MJY + */ +public final class OnExemplarsIncludedCondition extends AnyNestedCondition { + + OnExemplarsIncludedCondition() { + super(ConfigurationPhase.REGISTER_BEAN); + } + + @ConditionalOnProperty(prefix = "management.tracing.exemplars", name = "include", havingValue = "all") + static class All { + + } + + @ConditionalOnProperty(prefix = "management.tracing.exemplars", name = "include", havingValue = "sampled-traces", + matchIfMissing = true) + static class SampledTraces { + + } + +} diff --git a/module/spring-boot-micrometer-tracing/src/main/java/org/springframework/boot/micrometer/tracing/autoconfigure/TracingProperties.java b/module/spring-boot-micrometer-tracing/src/main/java/org/springframework/boot/micrometer/tracing/autoconfigure/TracingProperties.java index 0bda60865f49..2f75350b2d01 100644 --- a/module/spring-boot-micrometer-tracing/src/main/java/org/springframework/boot/micrometer/tracing/autoconfigure/TracingProperties.java +++ b/module/spring-boot-micrometer-tracing/src/main/java/org/springframework/boot/micrometer/tracing/autoconfigure/TracingProperties.java @@ -264,33 +264,32 @@ public enum PropagationType { public static class Exemplars { /** - * Filter which exemplars are selected. ALWAYS_ON is not supported when using - * Prometheus. + * Which exemplars are included. ALL is not supported when using Prometheus. */ - private Filter filter = Filter.SAMPLED_TRACES; + private Include include = Include.SAMPLED_TRACES; - public Filter getFilter() { - return this.filter; + public Include getInclude() { + return this.include; } - public void setFilter(Filter filter) { - this.filter = filter; + public void setInclude(Include include) { + this.include = include; } - public enum Filter { + public enum Include { /** - * Always select exemplars, regardless of whether the span is sampled. + * Include exemplars for all traces. */ - ALWAYS_ON, + ALL, /** - * Never select exemplars. + * Do not include exemplars. */ - ALWAYS_OFF, + NONE, /** - * Only select exemplars from sampled traces. + * Only include exemplars from sampled traces. */ SAMPLED_TRACES diff --git a/module/spring-boot-micrometer-tracing/src/main/java/org/springframework/boot/micrometer/tracing/autoconfigure/otlp/OtlpExemplarsAutoConfiguration.java b/module/spring-boot-micrometer-tracing/src/main/java/org/springframework/boot/micrometer/tracing/autoconfigure/otlp/OtlpExemplarsAutoConfiguration.java index bc646102d2b0..2add2ee81b00 100644 --- a/module/spring-boot-micrometer-tracing/src/main/java/org/springframework/boot/micrometer/tracing/autoconfigure/otlp/OtlpExemplarsAutoConfiguration.java +++ b/module/spring-boot-micrometer-tracing/src/main/java/org/springframework/boot/micrometer/tracing/autoconfigure/otlp/OtlpExemplarsAutoConfiguration.java @@ -31,8 +31,10 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.micrometer.tracing.autoconfigure.MicrometerTracingAutoConfiguration; +import org.springframework.boot.micrometer.tracing.autoconfigure.OnExemplarsIncludedCondition; import org.springframework.boot.micrometer.tracing.autoconfigure.TracingProperties; -import org.springframework.boot.micrometer.tracing.autoconfigure.TracingProperties.Exemplars.Filter; +import org.springframework.boot.micrometer.tracing.autoconfigure.TracingProperties.Exemplars.Include; +import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Bean; import org.springframework.lang.Contract; import org.springframework.util.function.SingletonSupplier; @@ -51,13 +53,14 @@ @ConditionalOnBean(Tracer.class) @ConditionalOnClass({ Tracer.class, ExemplarContextProvider.class }) @EnableConfigurationProperties(TracingProperties.class) +@Conditional(OnExemplarsIncludedCondition.class) public final class OtlpExemplarsAutoConfiguration { @Bean @ConditionalOnMissingBean ExemplarContextProvider exemplarContextProvider(ObjectProvider tracerProvider, TracingProperties properties) { - return new LazyTracingExemplarContextProvider(tracerProvider, properties.getExemplars().getFilter()); + return new LazyTracingExemplarContextProvider(tracerProvider, properties.getExemplars().getInclude()); } /** @@ -70,11 +73,11 @@ static class LazyTracingExemplarContextProvider implements ExemplarContextProvid private final SingletonSupplier tracer; - private final Filter filter; + private final Include include; - LazyTracingExemplarContextProvider(ObjectProvider tracerProvider, Filter filter) { + LazyTracingExemplarContextProvider(ObjectProvider tracerProvider, Include include) { this.tracer = SingletonSupplier.of(tracerProvider::getObject); - this.filter = filter; + this.include = include; } @Override @@ -92,9 +95,9 @@ private boolean isExemplar(@Nullable Span span) { if (span == null) { return false; } - return switch (this.filter) { - case ALWAYS_ON -> true; - case ALWAYS_OFF -> false; + return switch (this.include) { + case ALL -> true; + case NONE -> false; case SAMPLED_TRACES -> isSampled(span); }; } diff --git a/module/spring-boot-micrometer-tracing/src/main/java/org/springframework/boot/micrometer/tracing/autoconfigure/prometheus/PrometheusExemplarsAutoConfiguration.java b/module/spring-boot-micrometer-tracing/src/main/java/org/springframework/boot/micrometer/tracing/autoconfigure/prometheus/PrometheusExemplarsAutoConfiguration.java index 8b2164d78809..237ce4491da2 100644 --- a/module/spring-boot-micrometer-tracing/src/main/java/org/springframework/boot/micrometer/tracing/autoconfigure/prometheus/PrometheusExemplarsAutoConfiguration.java +++ b/module/spring-boot-micrometer-tracing/src/main/java/org/springframework/boot/micrometer/tracing/autoconfigure/prometheus/PrometheusExemplarsAutoConfiguration.java @@ -30,9 +30,11 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.properties.source.InvalidConfigurationPropertyValueException; import org.springframework.boot.micrometer.tracing.autoconfigure.MicrometerTracingAutoConfiguration; +import org.springframework.boot.micrometer.tracing.autoconfigure.OnExemplarsIncludedCondition; import org.springframework.boot.micrometer.tracing.autoconfigure.TracingProperties; -import org.springframework.boot.micrometer.tracing.autoconfigure.TracingProperties.Exemplars.Filter; +import org.springframework.boot.micrometer.tracing.autoconfigure.TracingProperties.Exemplars.Include; import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Conditional; import org.springframework.util.function.SingletonSupplier; /** @@ -49,12 +51,13 @@ @ConditionalOnBean(Tracer.class) @ConditionalOnClass({ Tracer.class, SpanContext.class }) @EnableConfigurationProperties(TracingProperties.class) +@Conditional(OnExemplarsIncludedCondition.class) public final class PrometheusExemplarsAutoConfiguration { @Bean @ConditionalOnMissingBean SpanContext spanContext(ObjectProvider tracerProvider, TracingProperties properties) { - return new LazyTracingSpanContext(tracerProvider, properties.getExemplars().getFilter()); + return new LazyTracingSpanContext(tracerProvider, properties.getExemplars().getInclude()); } /** @@ -66,15 +69,15 @@ static class LazyTracingSpanContext implements SpanContext { private final SingletonSupplier tracer; - private final Filter filter; + private final Include include; - LazyTracingSpanContext(ObjectProvider tracerProvider, Filter filter) { - if (filter == Filter.ALWAYS_ON) { - throw new InvalidConfigurationPropertyValueException("management.tracing.exemplars.filter", "always-on", - "Prometheus doesn't support the 'always-on' exemplar filter."); + LazyTracingSpanContext(ObjectProvider tracerProvider, Include include) { + if (include == Include.ALL) { + throw new InvalidConfigurationPropertyValueException("management.tracing.exemplars.include", "all", + "Prometheus doesn't support including exemplars for all traces."); } this.tracer = SingletonSupplier.of(tracerProvider::getObject); - this.filter = filter; + this.include = include; } @Override @@ -95,9 +98,9 @@ public boolean isCurrentSpanSampled() { if (currentSpan == null) { return false; } - return switch (this.filter) { - case ALWAYS_ON -> throw new UnsupportedOperationException("ALWAYS_ON filter is not supported"); - case ALWAYS_OFF -> false; + return switch (this.include) { + case ALL -> throw new UnsupportedOperationException("ALL include is not supported"); + case NONE -> false; case SAMPLED_TRACES -> isSampled(currentSpan); }; } diff --git a/module/spring-boot-micrometer-tracing/src/test/java/org/springframework/boot/micrometer/tracing/autoconfigure/otlp/LazyTracingExemplarContextProviderTests.java b/module/spring-boot-micrometer-tracing/src/test/java/org/springframework/boot/micrometer/tracing/autoconfigure/otlp/LazyTracingExemplarContextProviderTests.java index fbeba665762d..84ddfd32ee04 100644 --- a/module/spring-boot-micrometer-tracing/src/test/java/org/springframework/boot/micrometer/tracing/autoconfigure/otlp/LazyTracingExemplarContextProviderTests.java +++ b/module/spring-boot-micrometer-tracing/src/test/java/org/springframework/boot/micrometer/tracing/autoconfigure/otlp/LazyTracingExemplarContextProviderTests.java @@ -24,7 +24,7 @@ import org.springframework.beans.BeansException; import org.springframework.beans.factory.ObjectProvider; -import org.springframework.boot.micrometer.tracing.autoconfigure.TracingProperties.Exemplars.Filter; +import org.springframework.boot.micrometer.tracing.autoconfigure.TracingProperties.Exemplars.Include; import org.springframework.boot.micrometer.tracing.autoconfigure.otlp.OtlpExemplarsAutoConfiguration.LazyTracingExemplarContextProvider; import static org.assertj.core.api.Assertions.assertThat; @@ -64,7 +64,7 @@ public Tracer getIfUnique() throws BeansException { }; private LazyTracingExemplarContextProvider contextProvider = new LazyTracingExemplarContextProvider( - this.objectProvider, Filter.SAMPLED_TRACES); + this.objectProvider, Include.SAMPLED_TRACES); @Test void whenCurrentSpanIsNullThenExemplarContextIsNull() { @@ -148,8 +148,8 @@ void whenCurrentSpanHasDeferredSamplingThenExemplarContextIsNull() { } @Test - void whenFilterIsAlwaysOnAndSpanIsNotSampledThenExemplarContextIsNotNull() { - this.contextProvider = new LazyTracingExemplarContextProvider(this.objectProvider, Filter.ALWAYS_ON); + void whenIncludeIsAllAndSpanIsNotSampledThenExemplarContextIsNotNull() { + this.contextProvider = new LazyTracingExemplarContextProvider(this.objectProvider, Include.ALL); Span span = mock(Span.class); given(this.tracer.currentSpan()).willReturn(span); TraceContext traceContext = mock(TraceContext.class); @@ -159,14 +159,14 @@ void whenFilterIsAlwaysOnAndSpanIsNotSampledThenExemplarContextIsNotNull() { } @Test - void whenFilterIsAlwaysOnAndCurrentSpanIsNullThenExemplarContextIsNull() { - this.contextProvider = new LazyTracingExemplarContextProvider(this.objectProvider, Filter.ALWAYS_ON); + void whenIncludeIsAllAndCurrentSpanIsNullThenExemplarContextIsNull() { + this.contextProvider = new LazyTracingExemplarContextProvider(this.objectProvider, Include.ALL); assertThat(this.contextProvider.getExemplarContext()).isNull(); } @Test - void whenFilterIsAlwaysOffAndSpanIsSampledThenExemplarContextIsNull() { - this.contextProvider = new LazyTracingExemplarContextProvider(this.objectProvider, Filter.ALWAYS_OFF); + void whenIncludeIsNoneAndSpanIsSampledThenExemplarContextIsNull() { + this.contextProvider = new LazyTracingExemplarContextProvider(this.objectProvider, Include.NONE); Span span = mock(Span.class); given(this.tracer.currentSpan()).willReturn(span); TraceContext traceContext = mock(TraceContext.class); diff --git a/module/spring-boot-micrometer-tracing/src/test/java/org/springframework/boot/micrometer/tracing/autoconfigure/prometheus/LazyTracingSpanContextTests.java b/module/spring-boot-micrometer-tracing/src/test/java/org/springframework/boot/micrometer/tracing/autoconfigure/prometheus/LazyTracingSpanContextTests.java index 870b8c460313..c1c9318edae1 100644 --- a/module/spring-boot-micrometer-tracing/src/test/java/org/springframework/boot/micrometer/tracing/autoconfigure/prometheus/LazyTracingSpanContextTests.java +++ b/module/spring-boot-micrometer-tracing/src/test/java/org/springframework/boot/micrometer/tracing/autoconfigure/prometheus/LazyTracingSpanContextTests.java @@ -25,7 +25,7 @@ import org.springframework.beans.BeansException; import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.context.properties.source.InvalidConfigurationPropertyValueException; -import org.springframework.boot.micrometer.tracing.autoconfigure.TracingProperties.Exemplars.Filter; +import org.springframework.boot.micrometer.tracing.autoconfigure.TracingProperties.Exemplars.Include; import org.springframework.boot.micrometer.tracing.autoconfigure.prometheus.PrometheusExemplarsAutoConfiguration.LazyTracingSpanContext; import static org.assertj.core.api.Assertions.assertThat; @@ -66,7 +66,7 @@ public Tracer getIfUnique() throws BeansException { }; - private LazyTracingSpanContext spanContext = new LazyTracingSpanContext(this.objectProvider, Filter.SAMPLED_TRACES); + private LazyTracingSpanContext spanContext = new LazyTracingSpanContext(this.objectProvider, Include.SAMPLED_TRACES); @Test void whenCurrentSpanIsNullThenSpanIdIsNull() { @@ -152,14 +152,14 @@ void whenCurrentSpanHasDeferredSamplingThenSampledIsFalse() { } @Test - void whenFilterIsAlwaysOnThenConstructorThrows() { + void whenIncludeIsAllThenConstructorThrows() { assertThatExceptionOfType(InvalidConfigurationPropertyValueException.class) - .isThrownBy(() -> new LazyTracingSpanContext(this.objectProvider, Filter.ALWAYS_ON)); + .isThrownBy(() -> new LazyTracingSpanContext(this.objectProvider, Include.ALL)); } @Test - void whenFilterIsAlwaysOffAndSpanIsSampledThenSampledIsFalse() { - this.spanContext = new LazyTracingSpanContext(this.objectProvider, Filter.ALWAYS_OFF); + void whenIncludeIsNoneAndSpanIsSampledThenSampledIsFalse() { + this.spanContext = new LazyTracingSpanContext(this.objectProvider, Include.NONE); Span span = mock(Span.class); given(this.tracer.currentSpan()).willReturn(span); TraceContext traceContext = mock(TraceContext.class);