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:
@@ -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.
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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,\
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user