Skip to content

Commit

Permalink
Use config properties for spring starter (http server) (#11667)
Browse files Browse the repository at this point in the history
Co-authored-by: Lauri Tulmin <tulmin@gmail.com>
Co-authored-by: Trask Stalnaker <trask.stalnaker@gmail.com>
  • Loading branch information
3 people committed Jul 10, 2024
1 parent e36fc46 commit 9bbfe7f
Show file tree
Hide file tree
Showing 31 changed files with 671 additions and 382 deletions.
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ private static RestClient addRestClientInterceptorIfNotPresent(

static ClientHttpRequestInterceptor getInterceptor(
OpenTelemetry openTelemetry, ConfigProperties config) {
return InstrumentationConfigUtil.configureBuilder(
return InstrumentationConfigUtil.configureClientBuilder(
config,
SpringWebTelemetry.builder(openTelemetry),
WebTelemetryUtil.getBuilderExtractor())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ static RestTemplate addIfNotPresent(
RestTemplate restTemplate, OpenTelemetry openTelemetry, ConfigProperties config) {

ClientHttpRequestInterceptor instrumentationInterceptor =
InstrumentationConfigUtil.configureBuilder(
InstrumentationConfigUtil.configureClientBuilder(
config,
SpringWebTelemetry.builder(openTelemetry),
WebTelemetryUtil.getBuilderExtractor())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

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;
Expand All @@ -30,14 +30,14 @@ public 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();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -19,9 +22,23 @@
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
Expand All @@ -38,7 +55,8 @@ public Object postProcessAfterInitialization(Object bean, String beanName) {

private WebClient.Builder wrapBuilder(WebClient.Builder webClientBuilder) {
SpringWebfluxTelemetry instrumentation =
SpringWebfluxTelemetry.create(openTelemetryProvider.getObject());
getWebfluxTelemetry(
openTelemetryProvider.getObject(), configPropertiesProvider.getObject());
return webClientBuilder.filters(instrumentation::addClientTracingFilter);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@

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;
Expand All @@ -22,7 +25,12 @@
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();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@

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;
Expand All @@ -22,7 +25,12 @@
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();
}
}
Loading

0 comments on commit 9bbfe7f

Please sign in to comment.