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
Expand Up @@ -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.



Expand Down Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
@@ -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 {

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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<Tracer> tracerProvider,
TracingProperties properties) {
return new LazyTracingExemplarContextProvider(tracerProvider, properties.getExemplars().getFilter());
return new LazyTracingExemplarContextProvider(tracerProvider, properties.getExemplars().getInclude());
}

/**
Expand All @@ -70,11 +73,11 @@ static class LazyTracingExemplarContextProvider implements ExemplarContextProvid

private final SingletonSupplier<Tracer> tracer;

private final Filter filter;
private final Include include;

LazyTracingExemplarContextProvider(ObjectProvider<Tracer> tracerProvider, Filter filter) {
LazyTracingExemplarContextProvider(ObjectProvider<Tracer> tracerProvider, Include include) {
this.tracer = SingletonSupplier.of(tracerProvider::getObject);
this.filter = filter;
this.include = include;
}

@Override
Expand All @@ -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);
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/**
Expand All @@ -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<Tracer> tracerProvider, TracingProperties properties) {
return new LazyTracingSpanContext(tracerProvider, properties.getExemplars().getFilter());
return new LazyTracingSpanContext(tracerProvider, properties.getExemplars().getInclude());
}

/**
Expand All @@ -66,15 +69,15 @@ static class LazyTracingSpanContext implements SpanContext {

private final SingletonSupplier<Tracer> tracer;

private final Filter filter;
private final Include include;

LazyTracingSpanContext(ObjectProvider<Tracer> 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<Tracer> 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
Expand All @@ -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);
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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);
Expand All @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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);
Expand Down