diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/prometheus/PrometheusMetricsExportAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/prometheus/PrometheusMetricsExportAutoConfiguration.java index 784da3e5d8..8c8d365cc3 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/prometheus/PrometheusMetricsExportAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/prometheus/PrometheusMetricsExportAutoConfiguration.java @@ -25,11 +25,15 @@ import io.micrometer.core.instrument.Clock; import io.micrometer.prometheus.PrometheusConfig; import io.micrometer.prometheus.PrometheusMeterRegistry; import io.prometheus.client.CollectorRegistry; +import io.prometheus.client.exemplars.DefaultExemplarSampler; +import io.prometheus.client.exemplars.ExemplarSampler; +import io.prometheus.client.exemplars.tracer.common.SpanContextSupplier; import io.prometheus.client.exporter.BasicAuthHttpConnectionFactory; import io.prometheus.client.exporter.PushGateway; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnAvailableEndpoint; import org.springframework.boot.actuate.autoconfigure.metrics.CompositeMeterRegistryAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration; @@ -56,6 +60,7 @@ import org.springframework.util.StringUtils; * * @author Jon Schneider * @author David J. M. Karlsen + * @author Jonatan Ivanov * @since 2.0.0 */ @AutoConfiguration( @@ -76,8 +81,9 @@ public class PrometheusMetricsExportAutoConfiguration { @Bean @ConditionalOnMissingBean public PrometheusMeterRegistry prometheusMeterRegistry(PrometheusConfig prometheusConfig, - CollectorRegistry collectorRegistry, Clock clock) { - return new PrometheusMeterRegistry(prometheusConfig, collectorRegistry, clock); + CollectorRegistry collectorRegistry, Clock clock, ObjectProvider exemplarSamplerProvider) { + return new PrometheusMeterRegistry(prometheusConfig, collectorRegistry, clock, + exemplarSamplerProvider.getIfAvailable()); } @Bean @@ -86,6 +92,13 @@ public class PrometheusMetricsExportAutoConfiguration { return new CollectorRegistry(true); } + @Bean + @ConditionalOnMissingBean + @ConditionalOnBean(SpanContextSupplier.class) + public ExemplarSampler exemplarSampler(SpanContextSupplier spanContextSupplier) { + return new DefaultExemplarSampler(spanContextSupplier); + } + @Configuration(proxyBeanMethods = false) @ConditionalOnAvailableEndpoint(endpoint = PrometheusScrapeEndpoint.class) public static class PrometheusScrapeEndpointConfiguration { diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/prometheus/PrometheusMetricsExportAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/prometheus/PrometheusMetricsExportAutoConfigurationTests.java index b19f63c3bc..cafc333277 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/prometheus/PrometheusMetricsExportAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/prometheus/PrometheusMetricsExportAutoConfigurationTests.java @@ -22,6 +22,8 @@ import io.micrometer.core.instrument.Clock; import io.micrometer.prometheus.PrometheusConfig; import io.micrometer.prometheus.PrometheusMeterRegistry; import io.prometheus.client.CollectorRegistry; +import io.prometheus.client.exemplars.ExemplarSampler; +import io.prometheus.client.exemplars.tracer.common.SpanContextSupplier; import io.prometheus.client.exporter.BasicAuthHttpConnectionFactory; import io.prometheus.client.exporter.DefaultHttpConnectionFactory; import io.prometheus.client.exporter.HttpConnectionFactory; @@ -50,6 +52,7 @@ import static org.assertj.core.api.Assertions.assertThat; * * @author Andy Wilkinson * @author Stephane Nicoll + * @author Jonatan Ivanov */ @ExtendWith(OutputCaptureExtension.class) class PrometheusMetricsExportAutoConfigurationTests { @@ -109,6 +112,20 @@ class PrometheusMetricsExportAutoConfigurationTests { .hasSingleBean(PrometheusConfig.class)); } + @Test + void autoConfiguresExemplarSamplerIfSpanContextSupplierIsPresent() { + this.contextRunner.withUserConfiguration(ExemplarsConfiguration.class) + .run((context) -> assertThat(context).hasSingleBean(SpanContextSupplier.class) + .hasSingleBean(ExemplarSampler.class).hasSingleBean(PrometheusMeterRegistry.class)); + } + + @Test + void exemplarSamplerIsNotAutoConfiguredIfSpanContextSupplierIsMissing() { + this.contextRunner.withUserConfiguration(BaseConfiguration.class) + .run((context) -> assertThat(context).doesNotHaveBean(SpanContextSupplier.class) + .doesNotHaveBean(ExemplarSampler.class).hasSingleBean(PrometheusMeterRegistry.class)); + } + @Test void addsScrapeEndpointToManagementContext() { this.contextRunner.withConfiguration(AutoConfigurations.of(ManagementContextAutoConfiguration.class)) @@ -271,4 +288,25 @@ class PrometheusMetricsExportAutoConfigurationTests { } + @Configuration(proxyBeanMethods = false) + @Import(BaseConfiguration.class) + static class ExemplarsConfiguration { + + @Bean + SpanContextSupplier spanContextSupplier() { + return new SpanContextSupplier() { + @Override + public String getTraceId() { + return null; + } + + @Override + public String getSpanId() { + return null; + } + }; + } + + } + } diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/actuator/metrics.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/actuator/metrics.adoc index b147b5012a..601e788897 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/actuator/metrics.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/actuator/metrics.adoc @@ -455,6 +455,9 @@ The following example `scrape_config` adds to `prometheus.yml`: - targets: ["HOST:PORT"] ---- +https://prometheus.io/docs/prometheus/latest/feature_flags/#exemplars-storage[Prometheus Exemplars] are also supported. To enable this feature, a `SpanContextSupplier` bean should present. If you use https://spring.io/projects/spring-cloud-sleuth[Spring Cloud Sleuth], this will be auto-configured for you, but you can always create your own if you want. + +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. To enable Prometheus Pushgateway support, add the following dependency to your project: