Prometheus exemplars autoconfig (#2143)

* Autoconfigure Prometheus Exemplars support

* Tests for autoconfiguration for prometheus exemplars

* Add docs

* Update PrometheusExemplarsAutoConfiguration.java

Co-authored-by: Marcin Grzejszczak <mgrzejszczak@vmware.com>
This commit is contained in:
Jonatan Ivanov
2022-04-12 09:11:48 -07:00
committed by GitHub
parent 5fd8939585
commit 3323115ec0
7 changed files with 287 additions and 0 deletions

View File

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

View File

@@ -222,6 +222,11 @@
<artifactId>HikariCP</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>io.prometheus</groupId>
<artifactId>simpleclient_common</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>

View File

@@ -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<Tracer> tracerProvider) {
return new LazySleuthSpanContextSupplier(tracerProvider);
}
}

View File

@@ -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,\

View File

@@ -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;
}
}
}

View File

@@ -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<Tracer> tracerProvider;
private SpanContextSupplier delegate;
public LazySleuthSpanContextSupplier(ObjectProvider<Tracer> 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);
}
}
}

View File

@@ -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<Tracer> 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<Tracer> 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");
}
}