Use config properties for spring starter (http server) (#11667)

Co-authored-by: Lauri Tulmin <tulmin@gmail.com>
Co-authored-by: Trask Stalnaker <trask.stalnaker@gmail.com>
This commit is contained in:
Gregor Zeitlinger 2024-07-10 19:32:17 +02:00 committed by GitHub
parent e36fc46fd5
commit 9bbfe7fe4e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
31 changed files with 671 additions and 382 deletions

View File

@ -0,0 +1,219 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.api.incubator.builder.internal;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.context.propagation.TextMapGetter;
import io.opentelemetry.instrumentation.api.incubator.config.internal.CommonConfig;
import io.opentelemetry.instrumentation.api.incubator.semconv.http.HttpExperimentalAttributesExtractor;
import io.opentelemetry.instrumentation.api.incubator.semconv.http.HttpServerExperimentalMetrics;
import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor;
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
import io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder;
import io.opentelemetry.instrumentation.api.instrumenter.SpanKindExtractor;
import io.opentelemetry.instrumentation.api.instrumenter.SpanNameExtractor;
import io.opentelemetry.instrumentation.api.instrumenter.SpanStatusExtractor;
import io.opentelemetry.instrumentation.api.semconv.http.HttpServerAttributesExtractor;
import io.opentelemetry.instrumentation.api.semconv.http.HttpServerAttributesExtractorBuilder;
import io.opentelemetry.instrumentation.api.semconv.http.HttpServerAttributesGetter;
import io.opentelemetry.instrumentation.api.semconv.http.HttpServerMetrics;
import io.opentelemetry.instrumentation.api.semconv.http.HttpServerRoute;
import io.opentelemetry.instrumentation.api.semconv.http.HttpServerRouteBuilder;
import io.opentelemetry.instrumentation.api.semconv.http.HttpSpanNameExtractor;
import io.opentelemetry.instrumentation.api.semconv.http.HttpSpanNameExtractorBuilder;
import io.opentelemetry.instrumentation.api.semconv.http.HttpSpanStatusExtractor;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import javax.annotation.Nullable;
/**
* This class is internal and is hence not for public use. Its APIs are unstable and can change at
* any time.
*/
public final class DefaultHttpServerInstrumenterBuilder<REQUEST, RESPONSE> {
private final String instrumentationName;
private final OpenTelemetry openTelemetry;
private final List<AttributesExtractor<? super REQUEST, ? super RESPONSE>> additionalExtractors =
new ArrayList<>();
private Function<
SpanStatusExtractor<? super REQUEST, ? super RESPONSE>,
? extends SpanStatusExtractor<? super REQUEST, ? super RESPONSE>>
statusExtractorTransformer = Function.identity();
private final HttpServerAttributesExtractorBuilder<REQUEST, RESPONSE>
httpAttributesExtractorBuilder;
private final HttpSpanNameExtractorBuilder<REQUEST> httpSpanNameExtractorBuilder;
@Nullable private TextMapGetter<REQUEST> headerGetter;
private Function<SpanNameExtractor<REQUEST>, ? extends SpanNameExtractor<? super REQUEST>>
spanNameExtractorTransformer = Function.identity();
private final HttpServerRouteBuilder<REQUEST> httpServerRouteBuilder;
private final HttpServerAttributesGetter<REQUEST, RESPONSE> attributesGetter;
private boolean emitExperimentalHttpServerMetrics = false;
public DefaultHttpServerInstrumenterBuilder(
String instrumentationName,
OpenTelemetry openTelemetry,
HttpServerAttributesGetter<REQUEST, RESPONSE> attributesGetter) {
this.instrumentationName = instrumentationName;
this.openTelemetry = openTelemetry;
httpAttributesExtractorBuilder = HttpServerAttributesExtractor.builder(attributesGetter);
httpSpanNameExtractorBuilder = HttpSpanNameExtractor.builder(attributesGetter);
httpServerRouteBuilder = HttpServerRoute.builder(attributesGetter);
this.attributesGetter = attributesGetter;
}
/**
* Adds an additional {@link AttributesExtractor} to invoke to set attributes to instrumented
* items.
*/
@CanIgnoreReturnValue
public DefaultHttpServerInstrumenterBuilder<REQUEST, RESPONSE> addAttributesExtractor(
AttributesExtractor<? super REQUEST, ? super RESPONSE> attributesExtractor) {
additionalExtractors.add(attributesExtractor);
return this;
}
@CanIgnoreReturnValue
public DefaultHttpServerInstrumenterBuilder<REQUEST, RESPONSE> setStatusExtractor(
Function<
SpanStatusExtractor<? super REQUEST, ? super RESPONSE>,
? extends SpanStatusExtractor<? super REQUEST, ? super RESPONSE>>
statusExtractor) {
this.statusExtractorTransformer = statusExtractor;
return this;
}
/**
* Configures the HTTP request headers that will be captured as span attributes.
*
* @param requestHeaders A list of HTTP header names.
*/
@CanIgnoreReturnValue
public DefaultHttpServerInstrumenterBuilder<REQUEST, RESPONSE> setCapturedRequestHeaders(
List<String> requestHeaders) {
httpAttributesExtractorBuilder.setCapturedRequestHeaders(requestHeaders);
return this;
}
/**
* Configures the HTTP response headers that will be captured as span attributes.
*
* @param responseHeaders A list of HTTP header names.
*/
@CanIgnoreReturnValue
public DefaultHttpServerInstrumenterBuilder<REQUEST, RESPONSE> setCapturedResponseHeaders(
List<String> responseHeaders) {
httpAttributesExtractorBuilder.setCapturedResponseHeaders(responseHeaders);
return this;
}
/**
* Configures the instrumentation to recognize an alternative set of HTTP request methods.
*
* <p>By default, this instrumentation defines "known" methods as the ones listed in <a
* href="https://www.rfc-editor.org/rfc/rfc9110.html#name-methods">RFC9110</a> and the PATCH
* method defined in <a href="https://www.rfc-editor.org/rfc/rfc5789.html">RFC5789</a>.
*
* <p>Note: calling this method <b>overrides</b> the default known method sets completely; it does
* not supplement it.
*
* @param knownMethods A set of recognized HTTP request methods.
* @see HttpServerAttributesExtractorBuilder#setKnownMethods(Set)
*/
@CanIgnoreReturnValue
public DefaultHttpServerInstrumenterBuilder<REQUEST, RESPONSE> setKnownMethods(
Set<String> knownMethods) {
httpAttributesExtractorBuilder.setKnownMethods(knownMethods);
httpSpanNameExtractorBuilder.setKnownMethods(knownMethods);
httpServerRouteBuilder.setKnownMethods(knownMethods);
return this;
}
@CanIgnoreReturnValue
public DefaultHttpServerInstrumenterBuilder<REQUEST, RESPONSE> setHeaderGetter(
@Nullable TextMapGetter<REQUEST> headerGetter) {
this.headerGetter = headerGetter;
return this;
}
/**
* Configures the instrumentation to emit experimental HTTP server metrics.
*
* @param emitExperimentalHttpServerMetrics {@code true} if the experimental HTTP server metrics
* are to be emitted.
*/
@CanIgnoreReturnValue
public DefaultHttpServerInstrumenterBuilder<REQUEST, RESPONSE>
setEmitExperimentalHttpServerMetrics(boolean emitExperimentalHttpServerMetrics) {
this.emitExperimentalHttpServerMetrics = emitExperimentalHttpServerMetrics;
return this;
}
/** Sets custom {@link SpanNameExtractor} via transform function. */
@CanIgnoreReturnValue
public DefaultHttpServerInstrumenterBuilder<REQUEST, RESPONSE> setSpanNameExtractor(
Function<SpanNameExtractor<REQUEST>, ? extends SpanNameExtractor<? super REQUEST>>
spanNameExtractorTransformer) {
this.spanNameExtractorTransformer = spanNameExtractorTransformer;
return this;
}
public Instrumenter<REQUEST, RESPONSE> build() {
InstrumenterBuilder<REQUEST, RESPONSE> builder = builder();
if (headerGetter != null) {
return builder.buildServerInstrumenter(headerGetter);
}
return builder.buildInstrumenter(SpanKindExtractor.alwaysServer());
}
private InstrumenterBuilder<REQUEST, RESPONSE> builder() {
SpanNameExtractor<? super REQUEST> spanNameExtractor =
spanNameExtractorTransformer.apply(httpSpanNameExtractorBuilder.build());
InstrumenterBuilder<REQUEST, RESPONSE> builder =
Instrumenter.<REQUEST, RESPONSE>builder(
openTelemetry, instrumentationName, spanNameExtractor)
.setSpanStatusExtractor(
statusExtractorTransformer.apply(HttpSpanStatusExtractor.create(attributesGetter)))
.addAttributesExtractor(httpAttributesExtractorBuilder.build())
.addAttributesExtractors(additionalExtractors)
.addContextCustomizer(httpServerRouteBuilder.build())
.addOperationMetrics(HttpServerMetrics.get());
if (emitExperimentalHttpServerMetrics) {
builder
.addAttributesExtractor(HttpExperimentalAttributesExtractor.create(attributesGetter))
.addOperationMetrics(HttpServerExperimentalMetrics.get());
}
return builder;
}
@CanIgnoreReturnValue
public DefaultHttpServerInstrumenterBuilder<REQUEST, RESPONSE> configure(CommonConfig config) {
set(config::getKnownHttpRequestMethods, this::setKnownMethods);
set(config::getServerRequestHeaders, this::setCapturedRequestHeaders);
set(config::getServerResponseHeaders, this::setCapturedResponseHeaders);
set(
config::shouldEmitExperimentalHttpServerTelemetry,
this::setEmitExperimentalHttpServerMetrics);
return this;
}
private static <T> void set(Supplier<T> supplier, Consumer<T> consumer) {
T t = supplier.get();
if (t != null) {
consumer.accept(t);
}
}
}

View File

@ -56,7 +56,7 @@ public final class RestClientBeanPostProcessor implements BeanPostProcessor {
static ClientHttpRequestInterceptor getInterceptor(
OpenTelemetry openTelemetry, ConfigProperties config) {
return InstrumentationConfigUtil.configureBuilder(
return InstrumentationConfigUtil.configureClientBuilder(
config,
SpringWebTelemetry.builder(openTelemetry),
WebTelemetryUtil.getBuilderExtractor())

View File

@ -8,7 +8,7 @@ package io.opentelemetry.instrumentation.spring.autoconfigure.instrumentation.jd
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.instrumentation.jdbc.datasource.JdbcTelemetry;
import io.opentelemetry.instrumentation.spring.autoconfigure.internal.InstrumentationConfigUtil;
import io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties.InstrumentationConfigUtil;
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
import javax.sql.DataSource;
import org.springframework.aop.scope.ScopedProxyUtils;

View File

@ -9,7 +9,7 @@ import com.mongodb.MongoClientSettings;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.instrumentation.mongo.v3_1.MongoTelemetry;
import io.opentelemetry.instrumentation.spring.autoconfigure.internal.ConditionalOnEnabledInstrumentation;
import io.opentelemetry.instrumentation.spring.autoconfigure.internal.InstrumentationConfigUtil;
import io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties.InstrumentationConfigUtil;
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;

View File

@ -7,7 +7,7 @@ package io.opentelemetry.instrumentation.spring.autoconfigure.instrumentation.r2
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.instrumentation.r2dbc.v1_0.R2dbcTelemetry;
import io.opentelemetry.instrumentation.spring.autoconfigure.internal.InstrumentationConfigUtil;
import io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties.InstrumentationConfigUtil;
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
import io.r2dbc.spi.ConnectionFactory;
import io.r2dbc.spi.ConnectionFactoryOptions;

View File

@ -24,7 +24,7 @@ class RestTemplateInstrumentation {
RestTemplate restTemplate, OpenTelemetry openTelemetry, ConfigProperties config) {
ClientHttpRequestInterceptor instrumentationInterceptor =
InstrumentationConfigUtil.configureBuilder(
InstrumentationConfigUtil.configureClientBuilder(
config,
SpringWebTelemetry.builder(openTelemetry),
WebTelemetryUtil.getBuilderExtractor())

View File

@ -7,7 +7,7 @@ package io.opentelemetry.instrumentation.spring.autoconfigure.instrumentation.we
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.instrumentation.spring.autoconfigure.internal.ConditionalOnEnabledInstrumentation;
import io.opentelemetry.instrumentation.spring.webflux.v5_3.SpringWebfluxTelemetry;
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Bean;
@ -30,14 +30,14 @@ public class SpringWebfluxInstrumentationAutoConfiguration {
// static to avoid "is not eligible for getting processed by all BeanPostProcessors" warning
@Bean
static WebClientBeanPostProcessor otelWebClientBeanPostProcessor(
ObjectProvider<OpenTelemetry> openTelemetryProvider) {
return new WebClientBeanPostProcessor(openTelemetryProvider);
ObjectProvider<OpenTelemetry> openTelemetryProvider,
ObjectProvider<ConfigProperties> configPropertiesProvider) {
return new WebClientBeanPostProcessor(openTelemetryProvider, configPropertiesProvider);
}
@Bean
WebFilter telemetryFilter(OpenTelemetry openTelemetry) {
return SpringWebfluxTelemetry.builder(openTelemetry)
.build()
WebFilter telemetryFilter(OpenTelemetry openTelemetry, ConfigProperties config) {
return WebClientBeanPostProcessor.getWebfluxTelemetry(openTelemetry, config)
.createWebFilterAndRegisterReactorHook();
}
}

View File

@ -6,7 +6,10 @@
package io.opentelemetry.instrumentation.spring.autoconfigure.instrumentation.webflux;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties.InstrumentationConfigUtil;
import io.opentelemetry.instrumentation.spring.webflux.v5_3.SpringWebfluxTelemetry;
import io.opentelemetry.instrumentation.spring.webflux.v5_3.internal.SpringWebfluxBuilderUtil;
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.web.reactive.function.client.WebClient;
@ -19,9 +22,23 @@ import org.springframework.web.reactive.function.client.WebClient;
final class WebClientBeanPostProcessor implements BeanPostProcessor {
private final ObjectProvider<OpenTelemetry> openTelemetryProvider;
private final ObjectProvider<ConfigProperties> configPropertiesProvider;
WebClientBeanPostProcessor(ObjectProvider<OpenTelemetry> openTelemetryProvider) {
WebClientBeanPostProcessor(
ObjectProvider<OpenTelemetry> openTelemetryProvider,
ObjectProvider<ConfigProperties> configPropertiesProvider) {
this.openTelemetryProvider = openTelemetryProvider;
this.configPropertiesProvider = configPropertiesProvider;
}
static SpringWebfluxTelemetry getWebfluxTelemetry(
OpenTelemetry openTelemetry, ConfigProperties config) {
return InstrumentationConfigUtil.configureClientAndServerBuilder(
config,
SpringWebfluxTelemetry.builder(openTelemetry),
SpringWebfluxBuilderUtil.getClientBuilderExtractor(),
SpringWebfluxBuilderUtil.getServerBuilderExtractor())
.build();
}
@Override
@ -38,7 +55,8 @@ final class WebClientBeanPostProcessor implements BeanPostProcessor {
private WebClient.Builder wrapBuilder(WebClient.Builder webClientBuilder) {
SpringWebfluxTelemetry instrumentation =
SpringWebfluxTelemetry.create(openTelemetryProvider.getObject());
getWebfluxTelemetry(
openTelemetryProvider.getObject(), configPropertiesProvider.getObject());
return webClientBuilder.filters(instrumentation::addClientTracingFilter);
}
}

View File

@ -7,7 +7,10 @@ package io.opentelemetry.instrumentation.spring.autoconfigure.instrumentation.we
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.instrumentation.spring.autoconfigure.internal.ConditionalOnEnabledInstrumentation;
import io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties.InstrumentationConfigUtil;
import io.opentelemetry.instrumentation.spring.webmvc.v5_3.SpringWebMvcTelemetry;
import io.opentelemetry.instrumentation.spring.webmvc.v5_3.internal.SpringMvcBuilderUtil;
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
import javax.servlet.Filter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Bean;
@ -22,7 +25,12 @@ import org.springframework.web.servlet.DispatcherServlet;
public class SpringWebMvc5InstrumentationAutoConfiguration {
@Bean
Filter otelWebMvcFilter(OpenTelemetry openTelemetry) {
return SpringWebMvcTelemetry.create(openTelemetry).createServletFilter();
Filter otelWebMvcFilter(OpenTelemetry openTelemetry, ConfigProperties config) {
return InstrumentationConfigUtil.configureServerBuilder(
config,
SpringWebMvcTelemetry.builder(openTelemetry),
SpringMvcBuilderUtil.getBuilderExtractor())
.build()
.createServletFilter();
}
}

View File

@ -7,7 +7,10 @@ package io.opentelemetry.instrumentation.spring.autoconfigure.instrumentation.we
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.instrumentation.spring.autoconfigure.internal.ConditionalOnEnabledInstrumentation;
import io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties.InstrumentationConfigUtil;
import io.opentelemetry.instrumentation.spring.webmvc.v6_0.SpringWebMvcTelemetry;
import io.opentelemetry.instrumentation.spring.webmvc.v6_0.internal.SpringMvcBuilderUtil;
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
import jakarta.servlet.Filter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Bean;
@ -22,7 +25,12 @@ import org.springframework.web.servlet.DispatcherServlet;
public class SpringWebMvc6InstrumentationAutoConfiguration {
@Bean
Filter otelWebMvcFilter(OpenTelemetry openTelemetry) {
return SpringWebMvcTelemetry.create(openTelemetry).createServletFilter();
Filter otelWebMvcFilter(OpenTelemetry openTelemetry, ConfigProperties config) {
return InstrumentationConfigUtil.configureServerBuilder(
config,
SpringWebMvcTelemetry.builder(openTelemetry),
SpringMvcBuilderUtil.getBuilderExtractor())
.build()
.createServletFilter();
}
}

View File

@ -1,21 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.spring.autoconfigure.internal;
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
/**
* This class is internal and is hence not for public use. Its APIs are unstable and can change at
* any time.
*/
public class InstrumentationConfigUtil {
private InstrumentationConfigUtil() {}
public static boolean isStatementSanitizationEnabled(ConfigProperties config, String key) {
return config.getBoolean(
key, config.getBoolean("otel.instrumentation.common.db-statement-sanitizer.enabled", true));
}
}

View File

@ -7,6 +7,7 @@ package io.opentelemetry.instrumentation.spring.autoconfigure.internal.propertie
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import io.opentelemetry.instrumentation.api.incubator.builder.internal.DefaultHttpClientInstrumenterBuilder;
import io.opentelemetry.instrumentation.api.incubator.builder.internal.DefaultHttpServerInstrumenterBuilder;
import io.opentelemetry.instrumentation.api.incubator.config.internal.CommonConfig;
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
import java.util.function.Function;
@ -15,15 +16,48 @@ import java.util.function.Function;
* This class is internal and is hence not for public use. Its APIs are unstable and can change at
* any time.
*/
public class InstrumentationConfigUtil {
public final class InstrumentationConfigUtil {
private InstrumentationConfigUtil() {}
@CanIgnoreReturnValue
public static <T, REQUEST, RESPONSE> T configureBuilder(
public static <T, CLIENTREQUEST, CLIENTRESPONSE, SERVERREQUEST, SERVERRESPONSE>
T configureClientAndServerBuilder(
ConfigProperties config,
T builder,
Function<T, DefaultHttpClientInstrumenterBuilder<CLIENTREQUEST, CLIENTRESPONSE>>
getClientBuilder,
Function<T, DefaultHttpServerInstrumenterBuilder<SERVERREQUEST, SERVERRESPONSE>>
getServerBuilder) {
CommonConfig commonConfig = getConfig(config);
getClientBuilder.apply(builder).configure(commonConfig);
getServerBuilder.apply(builder).configure(commonConfig);
return builder;
}
@CanIgnoreReturnValue
public static <T, REQUEST, RESPONSE> T configureClientBuilder(
ConfigProperties config,
T builder,
Function<T, DefaultHttpClientInstrumenterBuilder<REQUEST, RESPONSE>> getBuilder) {
getBuilder.apply(builder).configure(new CommonConfig(new ConfigPropertiesBridge(config)));
getBuilder.apply(builder).configure(getConfig(config));
return builder;
}
@CanIgnoreReturnValue
public static <T, REQUEST, RESPONSE> T configureServerBuilder(
ConfigProperties config,
T builder,
Function<T, DefaultHttpServerInstrumenterBuilder<REQUEST, RESPONSE>> getBuilder) {
getBuilder.apply(builder).configure(getConfig(config));
return builder;
}
private static CommonConfig getConfig(ConfigProperties config) {
return new CommonConfig(new ConfigPropertiesBridge(config));
}
public static boolean isStatementSanitizationEnabled(ConfigProperties config, String key) {
return config.getBoolean(
key, config.getBoolean("otel.instrumentation.common.db-statement-sanitizer.enabled", true));
}
}

View File

@ -312,6 +312,22 @@
"description": "Configures the instrumentation to recognize an alternative set of HTTP request methods. All other methods will be treated as <code>_OTHER</code>.",
"defaultValue": "CONNECT,DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT,TRACE"
},
{
"name": "otel.instrumentation.http.server.capture-request-headers",
"type": "java.util.List<java.lang.String>",
"description": "List of HTTP request headers to capture in HTTP servers."
},
{
"name": "otel.instrumentation.http.server.capture-response-headers",
"type": "java.util.List<java.lang.String>",
"description": "List of HTTP response headers to capture in HTTP servers."
},
{
"name": "otel.instrumentation.http.server.emit-experimental-telemetry",
"type": "java.lang.Boolean",
"description": "Enable the capture of experimental HTTP server telemetry. Add the <code>http.request.body.size</code> and <code>http.response.body.size</code> attributes to spans, and record the <code>http.server.request.body.size</code> and <code>http.server.response.body.size</code> metrics.",
"defaultValue": false
},
{
"name": "otel.instrumentation.jdbc.enabled",
"type": "java.lang.Boolean",

View File

@ -8,6 +8,9 @@ package io.opentelemetry.instrumentation.spring.autoconfigure.instrumentation.we
import static org.assertj.core.api.Assertions.assertThat;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties;
import java.util.Collections;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
@ -17,6 +20,9 @@ class SpringWebfluxInstrumentationAutoConfigurationTest {
private final ApplicationContextRunner contextRunner =
new ApplicationContextRunner()
.withBean(OpenTelemetry.class, OpenTelemetry::noop)
.withBean(
ConfigProperties.class,
() -> DefaultConfigProperties.createFromMap(Collections.emptyMap()))
.withConfiguration(
AutoConfigurations.of(SpringWebfluxInstrumentationAutoConfiguration.class));

View File

@ -8,6 +8,9 @@ package io.opentelemetry.instrumentation.spring.autoconfigure.instrumentation.we
import static org.assertj.core.api.Assertions.assertThat;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties;
import java.util.Collections;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.config.BeanPostProcessor;
@ -20,6 +23,8 @@ class WebClientBeanPostProcessorTest {
static {
beanFactory.registerSingleton("openTelemetry", OpenTelemetry.noop());
beanFactory.registerSingleton(
"configProperties", DefaultConfigProperties.createFromMap(Collections.emptyMap()));
}
@Test
@ -27,7 +32,9 @@ class WebClientBeanPostProcessorTest {
"when processed bean is NOT of type WebClient or WebClientBuilder should return Object")
void returnsObject() {
BeanPostProcessor underTest =
new WebClientBeanPostProcessor(beanFactory.getBeanProvider(OpenTelemetry.class));
new WebClientBeanPostProcessor(
beanFactory.getBeanProvider(OpenTelemetry.class),
beanFactory.getBeanProvider(ConfigProperties.class));
assertThat(underTest.postProcessAfterInitialization(new Object(), "testObject"))
.isExactlyInstanceOf(Object.class);
@ -37,7 +44,9 @@ class WebClientBeanPostProcessorTest {
@DisplayName("when processed bean is of type WebClient should return WebClient")
void returnsWebClient() {
BeanPostProcessor underTest =
new WebClientBeanPostProcessor(beanFactory.getBeanProvider(OpenTelemetry.class));
new WebClientBeanPostProcessor(
beanFactory.getBeanProvider(OpenTelemetry.class),
beanFactory.getBeanProvider(ConfigProperties.class));
assertThat(underTest.postProcessAfterInitialization(WebClient.create(), "testWebClient"))
.isInstanceOf(WebClient.class);
@ -47,7 +56,9 @@ class WebClientBeanPostProcessorTest {
@DisplayName("when processed bean is of type WebClientBuilder should return WebClientBuilder")
void returnsWebClientBuilder() {
BeanPostProcessor underTest =
new WebClientBeanPostProcessor(beanFactory.getBeanProvider(OpenTelemetry.class));
new WebClientBeanPostProcessor(
beanFactory.getBeanProvider(OpenTelemetry.class),
beanFactory.getBeanProvider(ConfigProperties.class));
assertThat(
underTest.postProcessAfterInitialization(WebClient.builder(), "testWebClientBuilder"))
@ -58,7 +69,9 @@ class WebClientBeanPostProcessorTest {
@DisplayName("when processed bean is of type WebClient should add exchange filter to WebClient")
void addsExchangeFilterWebClient() {
BeanPostProcessor underTest =
new WebClientBeanPostProcessor(beanFactory.getBeanProvider(OpenTelemetry.class));
new WebClientBeanPostProcessor(
beanFactory.getBeanProvider(OpenTelemetry.class),
beanFactory.getBeanProvider(ConfigProperties.class));
WebClient webClient = WebClient.create();
Object processedWebClient =
@ -81,7 +94,9 @@ class WebClientBeanPostProcessorTest {
"when processed bean is of type WebClientBuilder should add ONE exchange filter to WebClientBuilder")
void addsExchangeFilterWebClientBuilder() {
BeanPostProcessor underTest =
new WebClientBeanPostProcessor(beanFactory.getBeanProvider(OpenTelemetry.class));
new WebClientBeanPostProcessor(
beanFactory.getBeanProvider(OpenTelemetry.class),
beanFactory.getBeanProvider(ConfigProperties.class));
WebClient.Builder webClientBuilder = WebClient.builder();
underTest.postProcessAfterInitialization(webClientBuilder, "testWebClientBuilder");

View File

@ -9,6 +9,9 @@ import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assumptions.assumeFalse;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties;
import java.util.Collections;
import javax.servlet.Filter;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@ -20,6 +23,9 @@ class SpringWebMvcInstrumentation5AutoConfigurationTest {
private final ApplicationContextRunner contextRunner =
new ApplicationContextRunner()
.withBean(OpenTelemetry.class, OpenTelemetry::noop)
.withBean(
ConfigProperties.class,
() -> DefaultConfigProperties.createFromMap(Collections.emptyMap()))
.withConfiguration(
AutoConfigurations.of(SpringWebMvc5InstrumentationAutoConfiguration.class));

View File

@ -9,7 +9,10 @@ import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assumptions.assumeTrue;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties;
import jakarta.servlet.Filter;
import java.util.Collections;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
@ -20,6 +23,9 @@ class SpringWebMvcInstrumentation6AutoConfigurationTest {
private final ApplicationContextRunner contextRunner =
new ApplicationContextRunner()
.withBean(OpenTelemetry.class, OpenTelemetry::noop)
.withBean(
ConfigProperties.class,
() -> DefaultConfigProperties.createFromMap(Collections.emptyMap()))
.withConfiguration(
AutoConfigurations.of(SpringWebMvc6InstrumentationAutoConfiguration.class));

View File

@ -5,17 +5,12 @@
package io.opentelemetry.javaagent.instrumentation.spring.webflux.v5_0.client;
import static java.util.Collections.singletonList;
import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.instrumentation.api.incubator.semconv.http.HttpClientPeerServiceAttributesExtractor;
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
import io.opentelemetry.instrumentation.spring.webflux.v5_3.internal.ClientInstrumenterFactory;
import io.opentelemetry.instrumentation.spring.webflux.v5_3.internal.WebClientHttpAttributesGetter;
import io.opentelemetry.instrumentation.spring.webflux.v5_3.internal.WebClientTracingFilter;
import io.opentelemetry.javaagent.bootstrap.internal.AgentCommonConfig;
import io.opentelemetry.javaagent.bootstrap.internal.JavaagentHttpClientInstrumenters;
import java.util.List;
import java.util.function.Function;
import org.springframework.web.reactive.function.client.ClientRequest;
import org.springframework.web.reactive.function.client.ClientResponse;
import org.springframework.web.reactive.function.client.ExchangeFilterFunction;
@ -23,20 +18,8 @@ import org.springframework.web.reactive.function.client.ExchangeFilterFunction;
public final class WebClientHelper {
private static final Instrumenter<ClientRequest, ClientResponse> instrumenter =
ClientInstrumenterFactory.create(
GlobalOpenTelemetry.get(),
builder ->
builder
.setCapturedRequestHeaders(AgentCommonConfig.get().getClientRequestHeaders())
.setCapturedResponseHeaders(AgentCommonConfig.get().getClientResponseHeaders())
.setKnownMethods(AgentCommonConfig.get().getKnownHttpRequestMethods()),
builder -> builder.setKnownMethods(AgentCommonConfig.get().getKnownHttpRequestMethods()),
Function.identity(),
singletonList(
HttpClientPeerServiceAttributesExtractor.create(
WebClientHttpAttributesGetter.INSTANCE,
AgentCommonConfig.get().getPeerServiceResolver())),
AgentCommonConfig.get().shouldEmitExperimentalHttpClientTelemetry());
JavaagentHttpClientInstrumenters.create(
"io.opentelemetry.spring-webflux-5.0", WebClientHttpAttributesGetter.INSTANCE);
public static void addFilter(List<ExchangeFilterFunction> exchangeFilterFunctions) {
for (ExchangeFilterFunction filterFunction : exchangeFilterFunctions) {

View File

@ -7,26 +7,16 @@ package io.opentelemetry.instrumentation.spring.webflux.v5_3;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.instrumentation.api.incubator.semconv.http.HttpExperimentalAttributesExtractor;
import io.opentelemetry.instrumentation.api.incubator.semconv.http.HttpServerExperimentalMetrics;
import io.opentelemetry.instrumentation.api.incubator.builder.internal.DefaultHttpClientInstrumenterBuilder;
import io.opentelemetry.instrumentation.api.incubator.builder.internal.DefaultHttpServerInstrumenterBuilder;
import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor;
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
import io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder;
import io.opentelemetry.instrumentation.api.instrumenter.SpanNameExtractor;
import io.opentelemetry.instrumentation.api.semconv.http.HttpClientAttributesExtractorBuilder;
import io.opentelemetry.instrumentation.api.semconv.http.HttpServerAttributesExtractor;
import io.opentelemetry.instrumentation.api.semconv.http.HttpServerAttributesExtractorBuilder;
import io.opentelemetry.instrumentation.api.semconv.http.HttpServerMetrics;
import io.opentelemetry.instrumentation.api.semconv.http.HttpServerRoute;
import io.opentelemetry.instrumentation.api.semconv.http.HttpServerRouteBuilder;
import io.opentelemetry.instrumentation.api.semconv.http.HttpSpanNameExtractor;
import io.opentelemetry.instrumentation.api.semconv.http.HttpSpanNameExtractorBuilder;
import io.opentelemetry.instrumentation.api.semconv.http.HttpSpanStatusExtractor;
import io.opentelemetry.instrumentation.spring.webflux.v5_3.internal.ClientInstrumenterFactory;
import java.util.ArrayList;
import io.opentelemetry.instrumentation.spring.webflux.v5_3.internal.SpringWebfluxBuilderUtil;
import io.opentelemetry.instrumentation.spring.webflux.v5_3.internal.WebClientHttpAttributesGetter;
import java.util.List;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Function;
import org.springframework.web.reactive.function.client.ClientRequest;
import org.springframework.web.reactive.function.client.ClientResponse;
@ -36,38 +26,25 @@ import org.springframework.web.server.ServerWebExchange;
public final class SpringWebfluxTelemetryBuilder {
private static final String INSTRUMENTATION_NAME = "io.opentelemetry.spring-webflux-5.3";
private final OpenTelemetry openTelemetry;
private final DefaultHttpClientInstrumenterBuilder<ClientRequest, ClientResponse> clientBuilder;
private final DefaultHttpServerInstrumenterBuilder<ServerWebExchange, ServerWebExchange>
serverBuilder;
private final List<AttributesExtractor<ClientRequest, ClientResponse>>
clientAdditionalExtractors = new ArrayList<>();
private final List<AttributesExtractor<ServerWebExchange, ServerWebExchange>>
serverAdditionalExtractors = new ArrayList<>();
private final HttpServerAttributesExtractorBuilder<ServerWebExchange, ServerWebExchange>
httpServerAttributesExtractorBuilder =
HttpServerAttributesExtractor.builder(WebfluxServerHttpAttributesGetter.INSTANCE);
private final HttpSpanNameExtractorBuilder<ServerWebExchange> httpServerSpanNameExtractorBuilder =
HttpSpanNameExtractor.builder(WebfluxServerHttpAttributesGetter.INSTANCE);
private final HttpServerRouteBuilder<ServerWebExchange> httpServerRouteBuilder =
HttpServerRoute.builder(WebfluxServerHttpAttributesGetter.INSTANCE);
private Function<
SpanNameExtractor<ClientRequest>, ? extends SpanNameExtractor<? super ClientRequest>>
clientSpanNameExtractorTransformer = Function.identity();
private Function<
SpanNameExtractor<ServerWebExchange>,
? extends SpanNameExtractor<? super ServerWebExchange>>
serverSpanNameExtractorTransformer = Function.identity();
private Consumer<HttpClientAttributesExtractorBuilder<ClientRequest, ClientResponse>>
clientExtractorConfigurer = builder -> {};
private Consumer<HttpSpanNameExtractorBuilder<ClientRequest>> clientSpanNameExtractorConfigurer =
builder -> {};
private boolean emitExperimentalHttpClientTelemetry = false;
private boolean emitExperimentalHttpServerTelemetry = false;
static {
SpringWebfluxBuilderUtil.setClientBuilderExtractor(
SpringWebfluxTelemetryBuilder::getClientBuilder);
SpringWebfluxBuilderUtil.setServerBuilderExtractor(
SpringWebfluxTelemetryBuilder::getServerBuilder);
}
SpringWebfluxTelemetryBuilder(OpenTelemetry openTelemetry) {
this.openTelemetry = openTelemetry;
clientBuilder =
new DefaultHttpClientInstrumenterBuilder<>(
INSTRUMENTATION_NAME, openTelemetry, WebClientHttpAttributesGetter.INSTANCE);
serverBuilder =
new DefaultHttpServerInstrumenterBuilder<>(
INSTRUMENTATION_NAME, openTelemetry, WebfluxServerHttpAttributesGetter.INSTANCE)
.setHeaderGetter(WebfluxTextMapGetter.INSTANCE);
}
/**
@ -77,7 +54,7 @@ public final class SpringWebfluxTelemetryBuilder {
@CanIgnoreReturnValue
public SpringWebfluxTelemetryBuilder addClientAttributesExtractor(
AttributesExtractor<ClientRequest, ClientResponse> attributesExtractor) {
clientAdditionalExtractors.add(attributesExtractor);
clientBuilder.addAttributeExtractor(attributesExtractor);
return this;
}
@ -89,9 +66,7 @@ public final class SpringWebfluxTelemetryBuilder {
@CanIgnoreReturnValue
public SpringWebfluxTelemetryBuilder setCapturedClientRequestHeaders(
List<String> requestHeaders) {
clientExtractorConfigurer =
clientExtractorConfigurer.andThen(
builder -> builder.setCapturedRequestHeaders(requestHeaders));
clientBuilder.setCapturedRequestHeaders(requestHeaders);
return this;
}
@ -103,9 +78,7 @@ public final class SpringWebfluxTelemetryBuilder {
@CanIgnoreReturnValue
public SpringWebfluxTelemetryBuilder setCapturedClientResponseHeaders(
List<String> responseHeaders) {
clientExtractorConfigurer =
clientExtractorConfigurer.andThen(
builder -> builder.setCapturedResponseHeaders(responseHeaders));
clientBuilder.setCapturedResponseHeaders(responseHeaders);
return this;
}
@ -116,7 +89,7 @@ public final class SpringWebfluxTelemetryBuilder {
@CanIgnoreReturnValue
public SpringWebfluxTelemetryBuilder addServerAttributesExtractor(
AttributesExtractor<ServerWebExchange, ServerWebExchange> attributesExtractor) {
serverAdditionalExtractors.add(attributesExtractor);
serverBuilder.addAttributesExtractor(attributesExtractor);
return this;
}
@ -129,7 +102,7 @@ public final class SpringWebfluxTelemetryBuilder {
@CanIgnoreReturnValue
public SpringWebfluxTelemetryBuilder setCapturedServerRequestHeaders(
List<String> requestHeaders) {
httpServerAttributesExtractorBuilder.setCapturedRequestHeaders(requestHeaders);
serverBuilder.setCapturedRequestHeaders(requestHeaders);
return this;
}
@ -142,7 +115,7 @@ public final class SpringWebfluxTelemetryBuilder {
@CanIgnoreReturnValue
public SpringWebfluxTelemetryBuilder setCapturedServerResponseHeaders(
List<String> responseHeaders) {
httpServerAttributesExtractorBuilder.setCapturedResponseHeaders(responseHeaders);
serverBuilder.setCapturedResponseHeaders(responseHeaders);
return this;
}
@ -162,13 +135,8 @@ public final class SpringWebfluxTelemetryBuilder {
*/
@CanIgnoreReturnValue
public SpringWebfluxTelemetryBuilder setKnownMethods(Set<String> knownMethods) {
clientExtractorConfigurer =
clientExtractorConfigurer.andThen(builder -> builder.setKnownMethods(knownMethods));
clientSpanNameExtractorConfigurer =
clientSpanNameExtractorConfigurer.andThen(builder -> builder.setKnownMethods(knownMethods));
httpServerAttributesExtractorBuilder.setKnownMethods(knownMethods);
httpServerSpanNameExtractorBuilder.setKnownMethods(knownMethods);
httpServerRouteBuilder.setKnownMethods(knownMethods);
clientBuilder.setKnownMethods(knownMethods);
serverBuilder.setKnownMethods(knownMethods);
return this;
}
@ -181,7 +149,7 @@ public final class SpringWebfluxTelemetryBuilder {
@CanIgnoreReturnValue
public SpringWebfluxTelemetryBuilder setEmitExperimentalHttpClientTelemetry(
boolean emitExperimentalHttpClientTelemetry) {
this.emitExperimentalHttpClientTelemetry = emitExperimentalHttpClientTelemetry;
clientBuilder.setEmitExperimentalHttpClientMetrics(emitExperimentalHttpClientTelemetry);
return this;
}
@ -194,7 +162,7 @@ public final class SpringWebfluxTelemetryBuilder {
@CanIgnoreReturnValue
public SpringWebfluxTelemetryBuilder setEmitExperimentalHttpServerTelemetry(
boolean emitExperimentalHttpServerTelemetry) {
this.emitExperimentalHttpServerTelemetry = emitExperimentalHttpServerTelemetry;
serverBuilder.setEmitExperimentalHttpServerMetrics(emitExperimentalHttpServerTelemetry);
return this;
}
@ -203,7 +171,7 @@ public final class SpringWebfluxTelemetryBuilder {
public SpringWebfluxTelemetryBuilder setClientSpanNameExtractor(
Function<SpanNameExtractor<ClientRequest>, ? extends SpanNameExtractor<? super ClientRequest>>
clientSpanNameExtractor) {
this.clientSpanNameExtractorTransformer = clientSpanNameExtractor;
clientBuilder.setSpanNameExtractor(clientSpanNameExtractor);
return this;
}
@ -214,7 +182,7 @@ public final class SpringWebfluxTelemetryBuilder {
SpanNameExtractor<ServerWebExchange>,
? extends SpanNameExtractor<? super ServerWebExchange>>
serverSpanNameExtractor) {
this.serverSpanNameExtractorTransformer = serverSpanNameExtractor;
serverBuilder.setSpanNameExtractor(serverSpanNameExtractor);
return this;
}
@ -223,40 +191,18 @@ public final class SpringWebfluxTelemetryBuilder {
* SpringWebfluxTelemetryBuilder}.
*/
public SpringWebfluxTelemetry build() {
Instrumenter<ClientRequest, ClientResponse> clientInstrumenter =
ClientInstrumenterFactory.create(
openTelemetry,
clientExtractorConfigurer,
clientSpanNameExtractorConfigurer,
clientSpanNameExtractorTransformer,
clientAdditionalExtractors,
emitExperimentalHttpClientTelemetry);
Instrumenter<ServerWebExchange, ServerWebExchange> serverInstrumenter =
buildServerInstrumenter();
return new SpringWebfluxTelemetry(
clientInstrumenter, serverInstrumenter, openTelemetry.getPropagators());
clientBuilder.build(),
serverBuilder.build(),
clientBuilder.getOpenTelemetry().getPropagators());
}
private Instrumenter<ServerWebExchange, ServerWebExchange> buildServerInstrumenter() {
WebfluxServerHttpAttributesGetter getter = WebfluxServerHttpAttributesGetter.INSTANCE;
SpanNameExtractor<? super ServerWebExchange> spanNameExtractor =
serverSpanNameExtractorTransformer.apply(httpServerSpanNameExtractorBuilder.build());
private DefaultHttpClientInstrumenterBuilder<ClientRequest, ClientResponse> getClientBuilder() {
return clientBuilder;
}
InstrumenterBuilder<ServerWebExchange, ServerWebExchange> builder =
Instrumenter.<ServerWebExchange, ServerWebExchange>builder(
openTelemetry, INSTRUMENTATION_NAME, spanNameExtractor)
.setSpanStatusExtractor(HttpSpanStatusExtractor.create(getter))
.addAttributesExtractor(httpServerAttributesExtractorBuilder.build())
.addAttributesExtractors(serverAdditionalExtractors)
.addContextCustomizer(httpServerRouteBuilder.build())
.addOperationMetrics(HttpServerMetrics.get());
if (emitExperimentalHttpServerTelemetry) {
builder
.addAttributesExtractor(HttpExperimentalAttributesExtractor.create(getter))
.addOperationMetrics(HttpServerExperimentalMetrics.get());
}
return builder.buildServerInstrumenter(WebfluxTextMapGetter.INSTANCE);
private DefaultHttpServerInstrumenterBuilder<ServerWebExchange, ServerWebExchange>
getServerBuilder() {
return serverBuilder;
}
}

View File

@ -1,80 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.spring.webflux.v5_3.internal;
import static io.opentelemetry.instrumentation.api.instrumenter.SpanKindExtractor.alwaysClient;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.instrumentation.api.incubator.semconv.http.HttpClientExperimentalMetrics;
import io.opentelemetry.instrumentation.api.incubator.semconv.http.HttpExperimentalAttributesExtractor;
import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor;
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
import io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder;
import io.opentelemetry.instrumentation.api.instrumenter.SpanNameExtractor;
import io.opentelemetry.instrumentation.api.semconv.http.HttpClientAttributesExtractor;
import io.opentelemetry.instrumentation.api.semconv.http.HttpClientAttributesExtractorBuilder;
import io.opentelemetry.instrumentation.api.semconv.http.HttpClientMetrics;
import io.opentelemetry.instrumentation.api.semconv.http.HttpSpanNameExtractor;
import io.opentelemetry.instrumentation.api.semconv.http.HttpSpanNameExtractorBuilder;
import io.opentelemetry.instrumentation.api.semconv.http.HttpSpanStatusExtractor;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Function;
import org.springframework.web.reactive.function.client.ClientRequest;
import org.springframework.web.reactive.function.client.ClientResponse;
/**
* This class is internal and is hence not for public use. Its APIs are unstable and can change at
* any time.
*/
// client builder is separate so that it can be used by javaagent instrumentation
// which supports 5.0, without triggering the server instrumentation which depends on webflux 5.3
public final class ClientInstrumenterFactory {
private static final String INSTRUMENTATION_NAME = "io.opentelemetry.spring-webflux-5.3";
public static Instrumenter<ClientRequest, ClientResponse> create(
OpenTelemetry openTelemetry,
Consumer<HttpClientAttributesExtractorBuilder<ClientRequest, ClientResponse>>
extractorConfigurer,
Consumer<HttpSpanNameExtractorBuilder<ClientRequest>> spanNameExtractorConfigurer,
Function<SpanNameExtractor<ClientRequest>, ? extends SpanNameExtractor<? super ClientRequest>>
spanNameExtractorTransformer,
List<AttributesExtractor<ClientRequest, ClientResponse>> additionalExtractors,
boolean emitExperimentalHttpClientTelemetry) {
WebClientHttpAttributesGetter httpAttributesGetter = WebClientHttpAttributesGetter.INSTANCE;
HttpClientAttributesExtractorBuilder<ClientRequest, ClientResponse> extractorBuilder =
HttpClientAttributesExtractor.builder(httpAttributesGetter);
extractorConfigurer.accept(extractorBuilder);
HttpSpanNameExtractorBuilder<ClientRequest> httpSpanNameExtractorBuilder =
HttpSpanNameExtractor.builder(httpAttributesGetter);
spanNameExtractorConfigurer.accept(httpSpanNameExtractorBuilder);
SpanNameExtractor<? super ClientRequest> spanNameExtractor =
spanNameExtractorTransformer.apply(httpSpanNameExtractorBuilder.build());
InstrumenterBuilder<ClientRequest, ClientResponse> clientBuilder =
Instrumenter.<ClientRequest, ClientResponse>builder(
openTelemetry, INSTRUMENTATION_NAME, spanNameExtractor)
.setSpanStatusExtractor(HttpSpanStatusExtractor.create(httpAttributesGetter))
.addAttributesExtractor(extractorBuilder.build())
.addAttributesExtractors(additionalExtractors)
.addOperationMetrics(HttpClientMetrics.get());
if (emitExperimentalHttpClientTelemetry) {
clientBuilder
.addAttributesExtractor(HttpExperimentalAttributesExtractor.create(httpAttributesGetter))
.addOperationMetrics(HttpClientExperimentalMetrics.get());
}
// headers are injected elsewhere; ClientRequest is immutable
return clientBuilder.buildInstrumenter(alwaysClient());
}
private ClientInstrumenterFactory() {}
}

View File

@ -0,0 +1,64 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.spring.webflux.v5_3.internal;
import io.opentelemetry.instrumentation.api.incubator.builder.internal.DefaultHttpClientInstrumenterBuilder;
import io.opentelemetry.instrumentation.api.incubator.builder.internal.DefaultHttpServerInstrumenterBuilder;
import io.opentelemetry.instrumentation.spring.webflux.v5_3.SpringWebfluxTelemetryBuilder;
import java.util.function.Function;
import org.springframework.web.reactive.function.client.ClientRequest;
import org.springframework.web.reactive.function.client.ClientResponse;
import org.springframework.web.server.ServerWebExchange;
/**
* This class is internal and is hence not for public use. Its APIs are unstable and can change at
* any time.
*/
public final class SpringWebfluxBuilderUtil {
private SpringWebfluxBuilderUtil() {}
// allows access to the private field for the spring starter
private static Function<
SpringWebfluxTelemetryBuilder,
DefaultHttpClientInstrumenterBuilder<ClientRequest, ClientResponse>>
clientBuilderExtractor;
// allows access to the private field for the spring starter
private static Function<
SpringWebfluxTelemetryBuilder,
DefaultHttpServerInstrumenterBuilder<ServerWebExchange, ServerWebExchange>>
serverBuilderExtractor;
public static Function<
SpringWebfluxTelemetryBuilder,
DefaultHttpServerInstrumenterBuilder<ServerWebExchange, ServerWebExchange>>
getServerBuilderExtractor() {
return serverBuilderExtractor;
}
public static void setServerBuilderExtractor(
Function<
SpringWebfluxTelemetryBuilder,
DefaultHttpServerInstrumenterBuilder<ServerWebExchange, ServerWebExchange>>
serverBuilderExtractor) {
SpringWebfluxBuilderUtil.serverBuilderExtractor = serverBuilderExtractor;
}
public static Function<
SpringWebfluxTelemetryBuilder,
DefaultHttpClientInstrumenterBuilder<ClientRequest, ClientResponse>>
getClientBuilderExtractor() {
return clientBuilderExtractor;
}
public static void setClientBuilderExtractor(
Function<
SpringWebfluxTelemetryBuilder,
DefaultHttpClientInstrumenterBuilder<ClientRequest, ClientResponse>>
clientBuilderExtractor) {
SpringWebfluxBuilderUtil.clientBuilderExtractor = clientBuilderExtractor;
}
}

View File

@ -7,21 +7,11 @@ package io.opentelemetry.instrumentation.spring.webmvc.v5_3;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.instrumentation.api.incubator.semconv.http.HttpExperimentalAttributesExtractor;
import io.opentelemetry.instrumentation.api.incubator.semconv.http.HttpServerExperimentalMetrics;
import io.opentelemetry.instrumentation.api.incubator.builder.internal.DefaultHttpServerInstrumenterBuilder;
import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor;
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
import io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder;
import io.opentelemetry.instrumentation.api.instrumenter.SpanNameExtractor;
import io.opentelemetry.instrumentation.api.semconv.http.HttpServerAttributesExtractor;
import io.opentelemetry.instrumentation.api.semconv.http.HttpServerAttributesExtractorBuilder;
import io.opentelemetry.instrumentation.api.semconv.http.HttpServerMetrics;
import io.opentelemetry.instrumentation.api.semconv.http.HttpServerRoute;
import io.opentelemetry.instrumentation.api.semconv.http.HttpServerRouteBuilder;
import io.opentelemetry.instrumentation.api.semconv.http.HttpSpanNameExtractor;
import io.opentelemetry.instrumentation.api.semconv.http.HttpSpanNameExtractorBuilder;
import io.opentelemetry.instrumentation.api.semconv.http.HttpSpanStatusExtractor;
import java.util.ArrayList;
import io.opentelemetry.instrumentation.spring.webmvc.v5_3.internal.SpringMvcBuilderUtil;
import java.util.List;
import java.util.Set;
import java.util.function.Function;
@ -33,24 +23,18 @@ public final class SpringWebMvcTelemetryBuilder {
private static final String INSTRUMENTATION_NAME = "io.opentelemetry.spring-webmvc-5.3";
private final OpenTelemetry openTelemetry;
private final List<AttributesExtractor<HttpServletRequest, HttpServletResponse>>
additionalExtractors = new ArrayList<>();
private final HttpServerAttributesExtractorBuilder<HttpServletRequest, HttpServletResponse>
httpAttributesExtractorBuilder =
HttpServerAttributesExtractor.builder(SpringWebMvcHttpAttributesGetter.INSTANCE);
private final HttpSpanNameExtractorBuilder<HttpServletRequest> httpSpanNameExtractorBuilder =
HttpSpanNameExtractor.builder(SpringWebMvcHttpAttributesGetter.INSTANCE);
private final HttpServerRouteBuilder<HttpServletRequest> httpServerRouteBuilder =
HttpServerRoute.builder(SpringWebMvcHttpAttributesGetter.INSTANCE);
private Function<
SpanNameExtractor<HttpServletRequest>,
? extends SpanNameExtractor<? super HttpServletRequest>>
spanNameExtractorTransformer = Function.identity();
private boolean emitExperimentalHttpServerMetrics = false;
private final DefaultHttpServerInstrumenterBuilder<HttpServletRequest, HttpServletResponse>
builder;
static {
SpringMvcBuilderUtil.setBuilderExtractor(SpringWebMvcTelemetryBuilder::getBuilder);
}
SpringWebMvcTelemetryBuilder(OpenTelemetry openTelemetry) {
this.openTelemetry = openTelemetry;
builder =
new DefaultHttpServerInstrumenterBuilder<>(
INSTRUMENTATION_NAME, openTelemetry, SpringWebMvcHttpAttributesGetter.INSTANCE)
.setHeaderGetter(JavaxHttpServletRequestGetter.INSTANCE);
}
/**
@ -60,7 +44,7 @@ public final class SpringWebMvcTelemetryBuilder {
@CanIgnoreReturnValue
public SpringWebMvcTelemetryBuilder addAttributesExtractor(
AttributesExtractor<HttpServletRequest, HttpServletResponse> attributesExtractor) {
additionalExtractors.add(attributesExtractor);
builder.addAttributesExtractor(attributesExtractor);
return this;
}
@ -71,7 +55,7 @@ public final class SpringWebMvcTelemetryBuilder {
*/
@CanIgnoreReturnValue
public SpringWebMvcTelemetryBuilder setCapturedRequestHeaders(List<String> requestHeaders) {
httpAttributesExtractorBuilder.setCapturedRequestHeaders(requestHeaders);
builder.setCapturedRequestHeaders(requestHeaders);
return this;
}
@ -82,7 +66,7 @@ public final class SpringWebMvcTelemetryBuilder {
*/
@CanIgnoreReturnValue
public SpringWebMvcTelemetryBuilder setCapturedResponseHeaders(List<String> responseHeaders) {
httpAttributesExtractorBuilder.setCapturedResponseHeaders(responseHeaders);
builder.setCapturedResponseHeaders(responseHeaders);
return this;
}
@ -93,7 +77,7 @@ public final class SpringWebMvcTelemetryBuilder {
SpanNameExtractor<HttpServletRequest>,
? extends SpanNameExtractor<? super HttpServletRequest>>
spanNameExtractor) {
this.spanNameExtractorTransformer = spanNameExtractor;
builder.setSpanNameExtractor(spanNameExtractor);
return this;
}
@ -112,9 +96,7 @@ public final class SpringWebMvcTelemetryBuilder {
*/
@CanIgnoreReturnValue
public SpringWebMvcTelemetryBuilder setKnownMethods(Set<String> knownMethods) {
httpAttributesExtractorBuilder.setKnownMethods(knownMethods);
httpSpanNameExtractorBuilder.setKnownMethods(knownMethods);
httpServerRouteBuilder.setKnownMethods(knownMethods);
builder.setKnownMethods(knownMethods);
return this;
}
@ -127,7 +109,7 @@ public final class SpringWebMvcTelemetryBuilder {
@CanIgnoreReturnValue
public SpringWebMvcTelemetryBuilder setEmitExperimentalHttpServerMetrics(
boolean emitExperimentalHttpServerMetrics) {
this.emitExperimentalHttpServerMetrics = emitExperimentalHttpServerMetrics;
builder.setEmitExperimentalHttpServerMetrics(emitExperimentalHttpServerMetrics);
return this;
}
@ -136,26 +118,11 @@ public final class SpringWebMvcTelemetryBuilder {
* SpringWebMvcTelemetryBuilder}.
*/
public SpringWebMvcTelemetry build() {
SpringWebMvcHttpAttributesGetter httpAttributesGetter =
SpringWebMvcHttpAttributesGetter.INSTANCE;
SpanNameExtractor<? super HttpServletRequest> spanNameExtractor =
spanNameExtractorTransformer.apply(httpSpanNameExtractorBuilder.build());
return new SpringWebMvcTelemetry(builder.build());
}
InstrumenterBuilder<HttpServletRequest, HttpServletResponse> builder =
Instrumenter.<HttpServletRequest, HttpServletResponse>builder(
openTelemetry, INSTRUMENTATION_NAME, spanNameExtractor)
.setSpanStatusExtractor(HttpSpanStatusExtractor.create(httpAttributesGetter))
.addAttributesExtractor(httpAttributesExtractorBuilder.build())
.addAttributesExtractors(additionalExtractors)
.addContextCustomizer(httpServerRouteBuilder.build())
.addOperationMetrics(HttpServerMetrics.get());
if (emitExperimentalHttpServerMetrics) {
builder
.addAttributesExtractor(HttpExperimentalAttributesExtractor.create(httpAttributesGetter))
.addOperationMetrics(HttpServerExperimentalMetrics.get());
}
return new SpringWebMvcTelemetry(
builder.buildServerInstrumenter(JavaxHttpServletRequestGetter.INSTANCE));
public DefaultHttpServerInstrumenterBuilder<HttpServletRequest, HttpServletResponse>
getBuilder() {
return builder;
}
}

View File

@ -0,0 +1,41 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.spring.webmvc.v5_3.internal;
import io.opentelemetry.instrumentation.api.incubator.builder.internal.DefaultHttpServerInstrumenterBuilder;
import io.opentelemetry.instrumentation.spring.webmvc.v5_3.SpringWebMvcTelemetryBuilder;
import java.util.function.Function;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* This class is internal and is hence not for public use. Its APIs are unstable and can change at
* any time.
*/
public final class SpringMvcBuilderUtil {
private SpringMvcBuilderUtil() {}
// allows access to the private field for the spring starter
private static Function<
SpringWebMvcTelemetryBuilder,
DefaultHttpServerInstrumenterBuilder<HttpServletRequest, HttpServletResponse>>
builderExtractor;
public static Function<
SpringWebMvcTelemetryBuilder,
DefaultHttpServerInstrumenterBuilder<HttpServletRequest, HttpServletResponse>>
getBuilderExtractor() {
return builderExtractor;
}
public static void setBuilderExtractor(
Function<
SpringWebMvcTelemetryBuilder,
DefaultHttpServerInstrumenterBuilder<HttpServletRequest, HttpServletResponse>>
builderExtractor) {
SpringMvcBuilderUtil.builderExtractor = builderExtractor;
}
}

View File

@ -7,23 +7,13 @@ package io.opentelemetry.instrumentation.spring.webmvc.v6_0;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.instrumentation.api.incubator.semconv.http.HttpExperimentalAttributesExtractor;
import io.opentelemetry.instrumentation.api.incubator.semconv.http.HttpServerExperimentalMetrics;
import io.opentelemetry.instrumentation.api.incubator.builder.internal.DefaultHttpServerInstrumenterBuilder;
import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor;
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
import io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder;
import io.opentelemetry.instrumentation.api.instrumenter.SpanNameExtractor;
import io.opentelemetry.instrumentation.api.semconv.http.HttpServerAttributesExtractor;
import io.opentelemetry.instrumentation.api.semconv.http.HttpServerAttributesExtractorBuilder;
import io.opentelemetry.instrumentation.api.semconv.http.HttpServerMetrics;
import io.opentelemetry.instrumentation.api.semconv.http.HttpServerRoute;
import io.opentelemetry.instrumentation.api.semconv.http.HttpServerRouteBuilder;
import io.opentelemetry.instrumentation.api.semconv.http.HttpSpanNameExtractor;
import io.opentelemetry.instrumentation.api.semconv.http.HttpSpanNameExtractorBuilder;
import io.opentelemetry.instrumentation.api.semconv.http.HttpSpanStatusExtractor;
import io.opentelemetry.instrumentation.spring.webmvc.v6_0.internal.SpringMvcBuilderUtil;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.function.Function;
@ -32,25 +22,18 @@ import java.util.function.Function;
public final class SpringWebMvcTelemetryBuilder {
private static final String INSTRUMENTATION_NAME = "io.opentelemetry.spring-webmvc-6.0";
private final DefaultHttpServerInstrumenterBuilder<HttpServletRequest, HttpServletResponse>
builder;
private final OpenTelemetry openTelemetry;
private final List<AttributesExtractor<HttpServletRequest, HttpServletResponse>>
additionalExtractors = new ArrayList<>();
private final HttpServerAttributesExtractorBuilder<HttpServletRequest, HttpServletResponse>
httpAttributesExtractorBuilder =
HttpServerAttributesExtractor.builder(SpringWebMvcHttpAttributesGetter.INSTANCE);
private final HttpSpanNameExtractorBuilder<HttpServletRequest> httpSpanNameExtractorBuilder =
HttpSpanNameExtractor.builder(SpringWebMvcHttpAttributesGetter.INSTANCE);
private final HttpServerRouteBuilder<HttpServletRequest> httpServerRouteBuilder =
HttpServerRoute.builder(SpringWebMvcHttpAttributesGetter.INSTANCE);
private Function<
SpanNameExtractor<HttpServletRequest>,
? extends SpanNameExtractor<? super HttpServletRequest>>
spanNameExtractorTransformer = Function.identity();
private boolean emitExperimentalHttpServerMetrics = false;
static {
SpringMvcBuilderUtil.setBuilderExtractor(SpringWebMvcTelemetryBuilder::getBuilder);
}
SpringWebMvcTelemetryBuilder(OpenTelemetry openTelemetry) {
this.openTelemetry = openTelemetry;
builder =
new DefaultHttpServerInstrumenterBuilder<>(
INSTRUMENTATION_NAME, openTelemetry, SpringWebMvcHttpAttributesGetter.INSTANCE)
.setHeaderGetter(JakartaHttpServletRequestGetter.INSTANCE);
}
/**
@ -60,7 +43,7 @@ public final class SpringWebMvcTelemetryBuilder {
@CanIgnoreReturnValue
public SpringWebMvcTelemetryBuilder addAttributesExtractor(
AttributesExtractor<HttpServletRequest, HttpServletResponse> attributesExtractor) {
additionalExtractors.add(attributesExtractor);
builder.addAttributesExtractor(attributesExtractor);
return this;
}
@ -71,7 +54,7 @@ public final class SpringWebMvcTelemetryBuilder {
*/
@CanIgnoreReturnValue
public SpringWebMvcTelemetryBuilder setCapturedRequestHeaders(List<String> requestHeaders) {
httpAttributesExtractorBuilder.setCapturedRequestHeaders(requestHeaders);
builder.setCapturedRequestHeaders(requestHeaders);
return this;
}
@ -82,7 +65,7 @@ public final class SpringWebMvcTelemetryBuilder {
*/
@CanIgnoreReturnValue
public SpringWebMvcTelemetryBuilder setCapturedResponseHeaders(List<String> responseHeaders) {
httpAttributesExtractorBuilder.setCapturedResponseHeaders(responseHeaders);
builder.setCapturedResponseHeaders(responseHeaders);
return this;
}
@ -93,7 +76,7 @@ public final class SpringWebMvcTelemetryBuilder {
SpanNameExtractor<HttpServletRequest>,
? extends SpanNameExtractor<? super HttpServletRequest>>
spanNameExtractor) {
this.spanNameExtractorTransformer = spanNameExtractor;
builder.setSpanNameExtractor(spanNameExtractor);
return this;
}
@ -112,9 +95,7 @@ public final class SpringWebMvcTelemetryBuilder {
*/
@CanIgnoreReturnValue
public SpringWebMvcTelemetryBuilder setKnownMethods(Set<String> knownMethods) {
httpAttributesExtractorBuilder.setKnownMethods(knownMethods);
httpSpanNameExtractorBuilder.setKnownMethods(knownMethods);
httpServerRouteBuilder.setKnownMethods(knownMethods);
builder.setKnownMethods(knownMethods);
return this;
}
@ -127,7 +108,7 @@ public final class SpringWebMvcTelemetryBuilder {
@CanIgnoreReturnValue
public SpringWebMvcTelemetryBuilder setEmitExperimentalHttpServerMetrics(
boolean emitExperimentalHttpServerMetrics) {
this.emitExperimentalHttpServerMetrics = emitExperimentalHttpServerMetrics;
builder.setEmitExperimentalHttpServerMetrics(emitExperimentalHttpServerMetrics);
return this;
}
@ -136,26 +117,11 @@ public final class SpringWebMvcTelemetryBuilder {
* SpringWebMvcTelemetryBuilder}.
*/
public SpringWebMvcTelemetry build() {
SpringWebMvcHttpAttributesGetter httpAttributesGetter =
SpringWebMvcHttpAttributesGetter.INSTANCE;
SpanNameExtractor<? super HttpServletRequest> spanNameExtractor =
spanNameExtractorTransformer.apply(httpSpanNameExtractorBuilder.build());
return new SpringWebMvcTelemetry(builder.build());
}
InstrumenterBuilder<HttpServletRequest, HttpServletResponse> builder =
Instrumenter.<HttpServletRequest, HttpServletResponse>builder(
openTelemetry, INSTRUMENTATION_NAME, spanNameExtractor)
.setSpanStatusExtractor(HttpSpanStatusExtractor.create(httpAttributesGetter))
.addAttributesExtractor(httpAttributesExtractorBuilder.build())
.addAttributesExtractors(additionalExtractors)
.addContextCustomizer(httpServerRouteBuilder.build())
.addOperationMetrics(HttpServerMetrics.get());
if (emitExperimentalHttpServerMetrics) {
builder
.addAttributesExtractor(HttpExperimentalAttributesExtractor.create(httpAttributesGetter))
.addOperationMetrics(HttpServerExperimentalMetrics.get());
}
return new SpringWebMvcTelemetry(
builder.buildServerInstrumenter(JakartaHttpServletRequestGetter.INSTANCE));
public DefaultHttpServerInstrumenterBuilder<HttpServletRequest, HttpServletResponse>
getBuilder() {
return builder;
}
}

View File

@ -0,0 +1,41 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.spring.webmvc.v6_0.internal;
import io.opentelemetry.instrumentation.api.incubator.builder.internal.DefaultHttpServerInstrumenterBuilder;
import io.opentelemetry.instrumentation.spring.webmvc.v6_0.SpringWebMvcTelemetryBuilder;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.util.function.Function;
/**
* This class is internal and is hence not for public use. Its APIs are unstable and can change at
* any time.
*/
public final class SpringMvcBuilderUtil {
private SpringMvcBuilderUtil() {}
// allows access to the private field for the spring starter
private static Function<
SpringWebMvcTelemetryBuilder,
DefaultHttpServerInstrumenterBuilder<HttpServletRequest, HttpServletResponse>>
builderExtractor;
public static Function<
SpringWebMvcTelemetryBuilder,
DefaultHttpServerInstrumenterBuilder<HttpServletRequest, HttpServletResponse>>
getBuilderExtractor() {
return builderExtractor;
}
public static void setBuilderExtractor(
Function<
SpringWebMvcTelemetryBuilder,
DefaultHttpServerInstrumenterBuilder<HttpServletRequest, HttpServletResponse>>
builderExtractor) {
SpringMvcBuilderUtil.builderExtractor = builderExtractor;
}
}

View File

@ -51,7 +51,7 @@ class OtelSpringStarterSmokeTest extends AbstractSpringStarterSmokeTest {
private static void assertClient(TraceAssert traceAssert) {
traceAssert.hasSpansSatisfyingExactly(
span -> AbstractOtelSpringStarterSmokeTest.assertClientSpan(span, "/ping"),
span -> HttpSpanDataAssert.create(span).assertClientGetRequest("/ping"),
span -> span.hasKind(SpanKind.SERVER).hasAttribute(HttpAttributes.HTTP_ROUTE, "/ping"),
span -> withSpanAssert(span));
}

View File

@ -21,20 +21,17 @@ import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties;
import io.opentelemetry.sdk.logs.data.LogRecordData;
import io.opentelemetry.sdk.resources.Resource;
import io.opentelemetry.sdk.testing.assertj.SpanDataAssert;
import io.opentelemetry.semconv.ClientAttributes;
import io.opentelemetry.semconv.HttpAttributes;
import io.opentelemetry.semconv.ServerAttributes;
import io.opentelemetry.semconv.UrlAttributes;
import io.opentelemetry.semconv.incubating.CodeIncubatingAttributes;
import io.opentelemetry.semconv.incubating.DbIncubatingAttributes;
import io.opentelemetry.semconv.incubating.HttpIncubatingAttributes;
import io.opentelemetry.semconv.incubating.ServiceIncubatingAttributes;
import java.util.Collections;
import java.util.List;
import org.assertj.core.api.AbstractCharSequenceAssert;
import org.assertj.core.api.AbstractIterableAssert;
import org.assertj.core.api.AbstractLongAssert;
import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
@ -155,8 +152,8 @@ class AbstractOtelSpringStarterSmokeTest extends AbstractSpringStarterSmokeTest
ServerAttributes.SERVER_PORT,
integerAssert -> integerAssert.isNotZero())),
serverSpan ->
serverSpan
.hasKind(SpanKind.SERVER)
HttpSpanDataAssert.create(serverSpan)
.assertServerGetRequest("/ping")
.hasResourceSatisfying(
r ->
r.hasAttribute(
@ -235,18 +232,9 @@ class AbstractOtelSpringStarterSmokeTest extends AbstractSpringStarterSmokeTest
testing.waitAndAssertTraces(
traceAssert ->
traceAssert.hasSpansSatisfyingExactly(
span -> assertClientSpan(span, "/ping"),
span -> HttpSpanDataAssert.create(span).assertClientGetRequest("/ping"),
span ->
span.hasKind(SpanKind.SERVER).hasAttribute(HttpAttributes.HTTP_ROUTE, "/ping"),
span -> withSpanAssert(span)));
}
public static void assertClientSpan(SpanDataAssert span, String path) {
span.hasKind(SpanKind.CLIENT)
.hasAttributesSatisfying(
satisfies(UrlAttributes.URL_FULL, a -> a.endsWith(path)),
// this attribute is set by the experimental http instrumentation
satisfies(
HttpIncubatingAttributes.HTTP_RESPONSE_BODY_SIZE, AbstractLongAssert::isPositive));
}
}

View File

@ -11,6 +11,8 @@ otel:
http:
client:
emit-experimental-telemetry: true
server:
emit-experimental-telemetry: true
propagators:
- b3
resource:

View File

@ -8,8 +8,6 @@ package io.opentelemetry.spring.smoketest;
import static org.assertj.core.api.Assertions.assertThat;
import io.opentelemetry.api.trace.SpanKind;
import io.opentelemetry.semconv.HttpAttributes;
import io.opentelemetry.semconv.UrlAttributes;
import io.opentelemetry.semconv.incubating.DbIncubatingAttributes;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@ -52,17 +50,8 @@ public class AbstractOtelReactiveSpringStarterSmokeTest extends AbstractSpringSt
trace.hasSpansSatisfyingExactly(span -> span.hasName("CREATE TABLE testdb.player")),
trace ->
trace.hasSpansSatisfyingExactly(
span ->
span.hasKind(SpanKind.CLIENT)
.hasName("GET")
.hasAttributesSatisfying(
a -> assertThat(a.get(UrlAttributes.URL_FULL)).endsWith("/webflux")),
span ->
span.hasKind(SpanKind.SERVER)
.hasName("GET /webflux")
.hasAttribute(HttpAttributes.HTTP_REQUEST_METHOD, "GET")
.hasAttribute(HttpAttributes.HTTP_RESPONSE_STATUS_CODE, 200L)
.hasAttribute(HttpAttributes.HTTP_ROUTE, "/webflux"),
span -> HttpSpanDataAssert.create(span).assertClientGetRequest("/webflux"),
span -> HttpSpanDataAssert.create(span).assertServerGetRequest("/webflux"),
span ->
span.hasKind(SpanKind.CLIENT)
.satisfies(

View File

@ -3,6 +3,11 @@ otel:
common:
db-statement-sanitizer:
enabled: false
http:
client:
emit-experimental-telemetry: true
server:
emit-experimental-telemetry: true
spring:
r2dbc:

View File

@ -0,0 +1,62 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.spring.smoketest;
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo;
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.satisfies;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import io.opentelemetry.api.trace.SpanKind;
import io.opentelemetry.sdk.testing.assertj.ResourceAssert;
import io.opentelemetry.sdk.testing.assertj.SpanDataAssert;
import io.opentelemetry.semconv.HttpAttributes;
import io.opentelemetry.semconv.UrlAttributes;
import io.opentelemetry.semconv.incubating.HttpIncubatingAttributes;
import java.util.function.Consumer;
import org.assertj.core.api.AbstractLongAssert;
public final class HttpSpanDataAssert {
private final SpanDataAssert span;
private HttpSpanDataAssert(SpanDataAssert span) {
this.span = span;
}
public static HttpSpanDataAssert create(SpanDataAssert serverSpan) {
return new HttpSpanDataAssert(serverSpan);
}
@CanIgnoreReturnValue
public HttpSpanDataAssert assertClientGetRequest(String path) {
span.hasKind(SpanKind.CLIENT)
.hasAttributesSatisfying(
satisfies(UrlAttributes.URL_FULL, a -> a.endsWith(path)),
// this attribute is set by the experimental http instrumentation
satisfies(
HttpIncubatingAttributes.HTTP_RESPONSE_BODY_SIZE,
AbstractLongAssert::isNotNegative));
return this;
}
@CanIgnoreReturnValue
public HttpSpanDataAssert assertServerGetRequest(String route) {
span.hasKind(SpanKind.SERVER)
.hasAttributesSatisfying(
equalTo(HttpAttributes.HTTP_REQUEST_METHOD, "GET"),
equalTo(HttpAttributes.HTTP_RESPONSE_STATUS_CODE, 200L),
equalTo(HttpAttributes.HTTP_ROUTE, route),
// this attribute is set by the experimental http instrumentation
satisfies(
HttpIncubatingAttributes.HTTP_RESPONSE_BODY_SIZE,
AbstractLongAssert::isNotNegative));
return this;
}
public SpanDataAssert hasResourceSatisfying(Consumer<ResourceAssert> resource) {
return span.hasResourceSatisfying(resource);
}
}