Add more fine-grained support for selecting metric exemplars

Closes gh-49572
This commit is contained in:
Moritz Halbritter
2026-03-12 11:58:05 +01:00
parent bf1b0f6e32
commit 5ca8e0956a
8 changed files with 219 additions and 13 deletions
@@ -94,6 +94,43 @@ class OtlpExemplarsAutoConfigurationTests {
});
}
@Test
void otlpOutputShouldContainExemplarsWhenFilterIsAlwaysOnAndSpanIsNotSampled() {
this.contextRunner.withUserConfiguration(TracingConfiguration.class)
.withPropertyValues("management.tracing.sampling.probability=0.0",
"management.tracing.exemplars.filter=always-on")
.run((context) -> {
assertThat(context).hasSingleBean(ExemplarContextProvider.class);
ObservationRegistry observationRegistry = context.getBean(ObservationRegistry.class);
Observation.start("test.observation", observationRegistry).stop();
OtlpMeterRegistry otlpMeterRegistry = context.getBean(OtlpMeterRegistry.class);
TestOtlpMetricsSender metricsSender = context.getBean(TestOtlpMetricsSender.class);
otlpMeterRegistry.close();
assertThat(metricsSender.getOtlpRequest()).containsOnlyOnce("name: \"test.observation\"")
.containsOnlyOnce("exemplars")
.containsOnlyOnce("span_id")
.containsOnlyOnce("trace_id");
});
}
@Test
void otlpOutputShouldNotContainExemplarsWhenFilterIsAlwaysOff() {
this.contextRunner.withUserConfiguration(TracingConfiguration.class)
.withPropertyValues("management.tracing.exemplars.filter=always-off")
.run((context) -> {
assertThat(context).hasSingleBean(ExemplarContextProvider.class);
ObservationRegistry observationRegistry = context.getBean(ObservationRegistry.class);
Observation.start("test.observation", observationRegistry).stop();
OtlpMeterRegistry otlpMeterRegistry = context.getBean(OtlpMeterRegistry.class);
TestOtlpMetricsSender metricsSender = context.getBean(TestOtlpMetricsSender.class);
otlpMeterRegistry.close();
assertThat(metricsSender.getOtlpRequest()).containsOnlyOnce("name: \"test.observation\"")
.doesNotContain("exemplars")
.doesNotContain("span_id")
.doesNotContain("trace_id");
});
}
@Configuration(proxyBeanMethods = false)
private static final class CustomConfiguration {
@@ -32,6 +32,7 @@ import io.prometheus.metrics.tracer.common.SpanContext;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.context.properties.source.InvalidConfigurationPropertyValueException;
import org.springframework.boot.micrometer.metrics.autoconfigure.MetricsAutoConfiguration;
import org.springframework.boot.micrometer.metrics.autoconfigure.export.prometheus.PrometheusMetricsExportAutoConfiguration;
import org.springframework.boot.micrometer.observation.autoconfigure.ObservationAutoConfiguration;
@@ -148,6 +149,33 @@ class PrometheusExemplarsAutoConfigurationTests {
});
}
@Test
void shouldFailWhenFilterIsAlwaysOn() {
this.contextRunner.withUserConfiguration(TracingConfiguration.class)
.withPropertyValues("management.tracing.exemplars.filter=always-on")
.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."));
}
@Test
void prometheusOpenMetricsOutputShouldNotContainExemplarsWhenFilterIsAlwaysOff() {
this.contextRunner.withUserConfiguration(TracingConfiguration.class)
.withPropertyValues("management.tracing.exemplars.filter=always-off")
.run((context) -> {
assertThat(context).hasSingleBean(SpanContext.class);
ObservationRegistry observationRegistry = context.getBean(ObservationRegistry.class);
Observation.start("test.observation", observationRegistry).stop();
PrometheusMeterRegistry prometheusMeterRegistry = context.getBean(PrometheusMeterRegistry.class);
String openMetricsOutput = prometheusMeterRegistry.scrape(OpenMetricsTextFormatWriter.CONTENT_TYPE);
assertThat(openMetricsOutput).contains("test_observation_seconds_bucket");
assertThat(openMetricsOutput).doesNotContain("span_id").doesNotContain("trace_id");
});
}
@Configuration(proxyBeanMethods = false)
private static final class CustomConfiguration {