diff --git a/docs/src/main/asciidoc/integrations.adoc b/docs/src/main/asciidoc/integrations.adoc
index a01ccc32b..3cc5a4d1f 100644
--- a/docs/src/main/asciidoc/integrations.adoc
+++ b/docs/src/main/asciidoc/integrations.adoc
@@ -801,3 +801,11 @@ In order to disable this instrumentation set `spring.sleuth.session.enabled` to
This feature is available for all tracer implementations.
We're adding Kotlin Coroutines that allow you to retrieve the current span via the `Tracer` bean. You can either pass the bean to the Kotlin Coroutine context via `Tracer.asContextElement()` method execution or if you have Reactor Kotlin Coroutine integration on the classpath, we will retrieve it from Reactor's context. To retrieve the current span you can call the `currentSpan()` method within the Kotlin Coroutine.
+
+[[sleuth-prometheus-exemplars-integration]]
+== Prometheus Exemplars
+
+This feature is available for all tracer implementations.
+
+https://prometheus.io/docs/prometheus/latest/feature_flags/#exemplars-storage[Prometheus Exemplars] are supported through `SpanContextSupplier`. If you use https://micrometer.io[Micrometer], this will be auto-configured for you, but you can register `SpanContextSupplier` directly to Prometheus 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.
diff --git a/spring-cloud-sleuth-autoconfigure/pom.xml b/spring-cloud-sleuth-autoconfigure/pom.xml
index 028d8bb18..d08c895c4 100644
--- a/spring-cloud-sleuth-autoconfigure/pom.xml
+++ b/spring-cloud-sleuth-autoconfigure/pom.xml
@@ -222,6 +222,11 @@
HikariCP
true
+
+ io.prometheus
+ simpleclient_common
+ true
+
org.springframework.session
spring-session-data-redis
diff --git a/spring-cloud-sleuth-autoconfigure/src/main/java/org/springframework/cloud/sleuth/autoconfig/instrument/prometheus/PrometheusExemplarsAutoConfiguration.java b/spring-cloud-sleuth-autoconfigure/src/main/java/org/springframework/cloud/sleuth/autoconfig/instrument/prometheus/PrometheusExemplarsAutoConfiguration.java
new file mode 100644
index 000000000..9fb5ba61e
--- /dev/null
+++ b/spring-cloud-sleuth-autoconfigure/src/main/java/org/springframework/cloud/sleuth/autoconfig/instrument/prometheus/PrometheusExemplarsAutoConfiguration.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2022-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.cloud.sleuth.autoconfig.instrument.prometheus;
+
+import io.prometheus.client.exemplars.tracer.common.SpanContextSupplier;
+
+import org.springframework.beans.factory.ObjectProvider;
+import org.springframework.boot.autoconfigure.AutoConfigureBefore;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.cloud.sleuth.Tracer;
+import org.springframework.cloud.sleuth.instrument.prometheus.prometheus.LazySleuthSpanContextSupplier;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * {@link org.springframework.boot.autoconfigure.EnableAutoConfiguration
+ * Auto-configuration} enables Prometheus Exemplars support.
+ *
+ * @author Jonatan Ivanov
+ */
+@Configuration(proxyBeanMethods = false)
+@ConditionalOnClass(SpanContextSupplier.class)
+@ConditionalOnProperty(value = "spring.sleuth.enabled", matchIfMissing = true)
+@AutoConfigureBefore(
+ name = "org.springframework.boot.actuate.autoconfigure.metrics.export.prometheus.PrometheusMetricsExportAutoConfiguration")
+public class PrometheusExemplarsAutoConfiguration {
+
+ @Bean
+ @ConditionalOnMissingBean
+ @ConditionalOnProperty(value = "spring.sleuth.prometheus.exemplars.enabled", matchIfMissing = true)
+ SpanContextSupplier sleuthSpanContextSupplier(ObjectProvider tracerProvider) {
+ return new LazySleuthSpanContextSupplier(tracerProvider);
+ }
+
+}
diff --git a/spring-cloud-sleuth-autoconfigure/src/main/resources/META-INF/spring.factories b/spring-cloud-sleuth-autoconfigure/src/main/resources/META-INF/spring.factories
index 7f3273cbf..040841570 100644
--- a/spring-cloud-sleuth-autoconfigure/src/main/resources/META-INF/spring.factories
+++ b/spring-cloud-sleuth-autoconfigure/src/main/resources/META-INF/spring.factories
@@ -15,6 +15,7 @@ org.springframework.cloud.sleuth.autoconfig.instrument.circuitbreaker.TraceCircu
org.springframework.cloud.sleuth.autoconfig.instrument.deployer.TraceDeployerAutoConfiguration,\
org.springframework.cloud.sleuth.autoconfig.instrument.jdbc.TraceJdbcAutoConfiguration,\
org.springframework.cloud.sleuth.autoconfig.instrument.mongodb.TraceMongoDbAutoConfiguration,\
+org.springframework.cloud.sleuth.autoconfig.instrument.prometheus.PrometheusExemplarsAutoConfiguration,\
org.springframework.cloud.sleuth.autoconfig.instrument.rxjava.TraceRxJavaAutoConfiguration,\
org.springframework.cloud.sleuth.autoconfig.instrument.quartz.TraceQuartzAutoConfiguration,\
org.springframework.cloud.sleuth.autoconfig.instrument.task.TraceTaskAutoConfiguration,\
diff --git a/spring-cloud-sleuth-autoconfigure/src/test/java/org/springframework/cloud/sleuth/autoconfig/instrument/prometheus/PrometheusExemplarsAutoConfigurationTests.java b/spring-cloud-sleuth-autoconfigure/src/test/java/org/springframework/cloud/sleuth/autoconfig/instrument/prometheus/PrometheusExemplarsAutoConfigurationTests.java
new file mode 100644
index 000000000..64e852483
--- /dev/null
+++ b/spring-cloud-sleuth-autoconfigure/src/test/java/org/springframework/cloud/sleuth/autoconfig/instrument/prometheus/PrometheusExemplarsAutoConfigurationTests.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2022-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.cloud.sleuth.autoconfig.instrument.prometheus;
+
+import io.prometheus.client.exemplars.tracer.common.SpanContextSupplier;
+import org.junit.jupiter.api.Test;
+
+import org.springframework.boot.autoconfigure.AutoConfigurations;
+import org.springframework.boot.test.context.FilteredClassLoader;
+import org.springframework.boot.test.context.runner.ApplicationContextRunner;
+import org.springframework.cloud.sleuth.autoconfig.TraceNoOpAutoConfiguration;
+import org.springframework.cloud.sleuth.instrument.prometheus.prometheus.SleuthSpanContextSupplier;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Tests for {@link PrometheusExemplarsAutoConfiguration}.
+ *
+ * @author Jonatan Ivanov
+ */
+class PrometheusExemplarsAutoConfigurationTests {
+
+ private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
+ .withPropertyValues("spring.sleuth.noop.enabled=true").withConfiguration(AutoConfigurations
+ .of(TraceNoOpAutoConfiguration.class, PrometheusExemplarsAutoConfiguration.class));
+
+ @Test
+ void should_register_span_context_supplier() {
+ contextRunner.run(context -> assertThat(context).hasSingleBean(SpanContextSupplier.class));
+ }
+
+ @Test
+ void should_not_register_span_context_supplier_if_sleuth_disabled() {
+ contextRunner.withPropertyValues("spring.sleuth.enabled=false")
+ .run(context -> assertThat(context).doesNotHaveBean(SpanContextSupplier.class));
+ }
+
+ @Test
+ void should_not_register_span_context_supplier_if_exemplars_disabled() {
+ contextRunner.withPropertyValues("spring.sleuth.prometheus.exemplars.enabled=false")
+ .run(context -> assertThat(context).doesNotHaveBean(SpanContextSupplier.class));
+ }
+
+ @Test
+ void should_not_register_span_context_supplier_if_class_is_missing() {
+ contextRunner.withClassLoader(new FilteredClassLoader(SpanContextSupplier.class))
+ .run(context -> assertThat(context).doesNotHaveBean(SpanContextSupplier.class));
+ }
+
+ @Test
+ void should_not_register_span_context_supplier_if_bean_exists() {
+ contextRunner.withUserConfiguration(TestPrometheusExemplarsAutoConfiguration.class).run(context -> {
+ assertThat(context).hasSingleBean(SpanContextSupplier.class);
+ assertThat(context.getBean(SpanContextSupplier.class))
+ .isSameAs(TestPrometheusExemplarsAutoConfiguration.SUPPLIER);
+ });
+ }
+
+ @Configuration(proxyBeanMethods = false)
+ static class TestPrometheusExemplarsAutoConfiguration {
+
+ static final SpanContextSupplier SUPPLIER = new SleuthSpanContextSupplier(null);
+
+ @Bean
+ SpanContextSupplier testSpanContextSupplier() {
+ return SUPPLIER;
+ }
+
+ }
+
+}
diff --git a/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/prometheus/prometheus/LazySleuthSpanContextSupplier.java b/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/prometheus/prometheus/LazySleuthSpanContextSupplier.java
new file mode 100644
index 000000000..7b8d380e9
--- /dev/null
+++ b/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/prometheus/prometheus/LazySleuthSpanContextSupplier.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2021-2021 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.cloud.sleuth.instrument.prometheus.prometheus;
+
+import io.prometheus.client.exemplars.tracer.common.SpanContextSupplier;
+
+import org.springframework.beans.factory.ObjectProvider;
+import org.springframework.beans.factory.SmartInitializingSingleton;
+import org.springframework.cloud.sleuth.Tracer;
+
+/**
+ * A {@link SpanContextSupplier} that fetches the {@link Tracer} lazily after singletons
+ * are instantiated and uses it onwards.
+ *
+ * @author Jonatan Ivanov
+ */
+public class LazySleuthSpanContextSupplier implements SpanContextSupplier, SmartInitializingSingleton {
+
+ private final ObjectProvider tracerProvider;
+
+ private SpanContextSupplier delegate;
+
+ public LazySleuthSpanContextSupplier(ObjectProvider tracerProvider) {
+ this.tracerProvider = tracerProvider;
+ }
+
+ @Override
+ public String getTraceId() {
+ return (this.delegate != null) ? this.delegate.getTraceId() : null;
+ }
+
+ @Override
+ public String getSpanId() {
+ return (this.delegate != null) ? this.delegate.getSpanId() : null;
+ }
+
+ @Override
+ public void afterSingletonsInstantiated() {
+ Tracer tracer = tracerProvider.getIfAvailable();
+ if (tracer != null) {
+ this.delegate = new SleuthSpanContextSupplier(tracer);
+ }
+ }
+
+}
diff --git a/spring-cloud-sleuth-instrumentation/src/test/java/org/springframework/cloud/sleuth/instrument/prometheus/LazySleuthSpanContextSupplierTests.java b/spring-cloud-sleuth-instrumentation/src/test/java/org/springframework/cloud/sleuth/instrument/prometheus/LazySleuthSpanContextSupplierTests.java
new file mode 100644
index 000000000..1fe70d0a8
--- /dev/null
+++ b/spring-cloud-sleuth-instrumentation/src/test/java/org/springframework/cloud/sleuth/instrument/prometheus/LazySleuthSpanContextSupplierTests.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2022-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.cloud.sleuth.instrument.prometheus;
+
+import org.junit.jupiter.api.Test;
+
+import org.springframework.beans.factory.ObjectProvider;
+import org.springframework.cloud.sleuth.Span;
+import org.springframework.cloud.sleuth.TraceContext;
+import org.springframework.cloud.sleuth.Tracer;
+import org.springframework.cloud.sleuth.instrument.prometheus.prometheus.LazySleuthSpanContextSupplier;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+/**
+ * Tests for {@link LazySleuthSpanContextSupplier}.
+ *
+ * @author Jonatan Ivanov
+ */
+class LazySleuthSpanContextSupplierTests {
+
+ @Test
+ void should_provide_null_values_if_not_initialized() {
+ LazySleuthSpanContextSupplier spanContextSupplier = new LazySleuthSpanContextSupplier(null);
+ assertThat(spanContextSupplier.getTraceId()).isNull();
+ assertThat(spanContextSupplier.getSpanId()).isNull();
+ }
+
+ @Test
+ void should_provide_null_values_if_initialized_without_delegate() {
+ ObjectProvider tracerProvider = mock(ObjectProvider.class);
+ when(tracerProvider.getIfAvailable()).thenReturn(null);
+
+ LazySleuthSpanContextSupplier spanContextSupplier = new LazySleuthSpanContextSupplier(tracerProvider);
+ spanContextSupplier.afterSingletonsInstantiated();
+ assertThat(spanContextSupplier.getTraceId()).isNull();
+ assertThat(spanContextSupplier.getSpanId()).isNull();
+ }
+
+ @Test
+ void should_provide_values_from_delegate_if_initialized() {
+ ObjectProvider tracerProvider = mock(ObjectProvider.class);
+ Tracer tracer = mock(Tracer.class);
+ Span span = mock(Span.class);
+ TraceContext context = mock(TraceContext.class);
+
+ when(tracerProvider.getIfAvailable()).thenReturn(tracer);
+ when(tracer.currentSpan()).thenReturn(span);
+ when(span.context()).thenReturn(context);
+ when(context.sampled()).thenReturn(true);
+ when(context.traceId()).thenReturn("42");
+ when(context.spanId()).thenReturn("24");
+
+ LazySleuthSpanContextSupplier spanContextSupplier = new LazySleuthSpanContextSupplier(tracerProvider);
+ spanContextSupplier.afterSingletonsInstantiated();
+ assertThat(spanContextSupplier.getTraceId()).isEqualTo("42");
+ assertThat(spanContextSupplier.getSpanId()).isEqualTo("24");
+ }
+
+}