Skip to content

Commit

Permalink
Split Webflux into client and server
Browse files Browse the repository at this point in the history
  • Loading branch information
trask committed Dec 9, 2024
1 parent 4af0dd5 commit 79beda9
Show file tree
Hide file tree
Showing 10 changed files with 557 additions and 37 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.instrumentation.spring.webflux.v5_3;

import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.context.propagation.ContextPropagators;
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
import io.opentelemetry.instrumentation.spring.webflux.v5_3.internal.WebClientTracingFilter;
import java.util.List;
import org.springframework.web.reactive.function.client.ClientRequest;
import org.springframework.web.reactive.function.client.ClientResponse;
import org.springframework.web.reactive.function.client.ExchangeFilterFunction;

/** Entrypoint for instrumenting Spring Webflux HTTP clients. */
public final class SpringWebfluxClientTelemetry {

/**
* Returns a new {@link SpringWebfluxClientTelemetry} configured with the given {@link
* OpenTelemetry}.
*/
public static SpringWebfluxClientTelemetry create(OpenTelemetry openTelemetry) {
return builder(openTelemetry).build();
}

/**
* Returns a new {@link SpringWebfluxClientTelemetryBuilder} configured with the given {@link
* OpenTelemetry}.
*/
public static SpringWebfluxClientTelemetryBuilder builder(OpenTelemetry openTelemetry) {
return new SpringWebfluxClientTelemetryBuilder(openTelemetry);
}

private final Instrumenter<ClientRequest, ClientResponse> clientInstrumenter;
private final ContextPropagators propagators;

SpringWebfluxClientTelemetry(
Instrumenter<ClientRequest, ClientResponse> clientInstrumenter,
ContextPropagators propagators) {
this.clientInstrumenter = clientInstrumenter;
this.propagators = propagators;
}

public void addTracingFilter(List<ExchangeFilterFunction> exchangeFilterFunctions) {
for (ExchangeFilterFunction filterFunction : exchangeFilterFunctions) {
if (filterFunction instanceof WebClientTracingFilter) {
return;
}
}
exchangeFilterFunctions.add(new WebClientTracingFilter(clientInstrumenter, propagators));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

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.builder.internal.DefaultHttpClientInstrumenterBuilder;
import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor;
import io.opentelemetry.instrumentation.api.instrumenter.SpanNameExtractor;
import io.opentelemetry.instrumentation.api.semconv.http.HttpClientAttributesExtractorBuilder;
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.Function;
import org.springframework.web.reactive.function.client.ClientRequest;
import org.springframework.web.reactive.function.client.ClientResponse;

/** A builder of {@link SpringWebfluxClientTelemetry}. */
public final class SpringWebfluxClientTelemetryBuilder {
private static final String INSTRUMENTATION_NAME = "io.opentelemetry.spring-webflux-5.3";

private final DefaultHttpClientInstrumenterBuilder<ClientRequest, ClientResponse> builder;
private final OpenTelemetry openTelemetry;

static {
SpringWebfluxBuilderUtil.setClientBuilderExtractor(
SpringWebfluxClientTelemetryBuilder::getBuilder);
}

SpringWebfluxClientTelemetryBuilder(OpenTelemetry openTelemetry) {
builder =
DefaultHttpClientInstrumenterBuilder.create(
INSTRUMENTATION_NAME, openTelemetry, WebClientHttpAttributesGetter.INSTANCE);
this.openTelemetry = openTelemetry;
}

/**
* Adds an additional {@link AttributesExtractor} to invoke to set attributes to instrumented
* items for WebClient.
*/
@CanIgnoreReturnValue
public SpringWebfluxClientTelemetryBuilder addAttributesExtractor(
AttributesExtractor<ClientRequest, ClientResponse> attributesExtractor) {
builder.addAttributeExtractor(attributesExtractor);
return this;
}

/**
* Configures the HTTP WebClient request headers that will be captured as span attributes.
*
* @param requestHeaders A list of HTTP header names.
*/
@CanIgnoreReturnValue
public SpringWebfluxClientTelemetryBuilder setCapturedRequestHeaders(
List<String> requestHeaders) {
builder.setCapturedRequestHeaders(requestHeaders);
return this;
}

/**
* Configures the HTTP WebClient response headers that will be captured as span attributes.
*
* @param responseHeaders A list of HTTP header names.
*/
@CanIgnoreReturnValue
public SpringWebfluxClientTelemetryBuilder setCapturedResponseHeaders(
List<String> responseHeaders) {
builder.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 HttpClientAttributesExtractorBuilder#setKnownMethods(Set)
*/
@CanIgnoreReturnValue
public SpringWebfluxClientTelemetryBuilder setKnownMethods(Set<String> knownMethods) {
builder.setKnownMethods(knownMethods);
return this;
}

/**
* Configures the instrumentation to emit experimental HTTP client metrics.
*
* @param emitExperimentalHttpClientTelemetry {@code true} if the experimental HTTP client metrics
* are to be emitted.
*/
@CanIgnoreReturnValue
public SpringWebfluxClientTelemetryBuilder setEmitExperimentalHttpClientTelemetry(
boolean emitExperimentalHttpClientTelemetry) {
builder.setEmitExperimentalHttpClientMetrics(emitExperimentalHttpClientTelemetry);
return this;
}

/** Sets custom client {@link SpanNameExtractor} via transform function. */
@CanIgnoreReturnValue
public SpringWebfluxClientTelemetryBuilder setSpanNameExtractor(
Function<
SpanNameExtractor<? super ClientRequest>,
? extends SpanNameExtractor<? super ClientRequest>>
clientSpanNameExtractor) {
builder.setSpanNameExtractor(clientSpanNameExtractor);
return this;
}

/**
* Returns a new {@link SpringWebfluxTelemetry} with the settings of this {@link
* SpringWebfluxClientTelemetryBuilder}.
*/
public SpringWebfluxClientTelemetry build() {
return new SpringWebfluxClientTelemetry(builder.build(), openTelemetry.getPropagators());
}

private DefaultHttpClientInstrumenterBuilder<ClientRequest, ClientResponse> getBuilder() {
return builder;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.instrumentation.spring.webflux.v5_3;

import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
import io.opentelemetry.instrumentation.reactor.v3_1.ContextPropagationOperator;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;

/** Entrypoint for instrumenting Spring Webflux HTTP services. */
public final class SpringWebfluxServerTelemetry {

/**
* Returns a new {@link SpringWebfluxServerTelemetry} configured with the given {@link
* OpenTelemetry}.
*/
public static SpringWebfluxServerTelemetry create(OpenTelemetry openTelemetry) {
return builder(openTelemetry).build();
}

/**
* Returns a new {@link SpringWebfluxServerTelemetryBuilder} configured with the given {@link
* OpenTelemetry}.
*/
public static SpringWebfluxServerTelemetryBuilder builder(OpenTelemetry openTelemetry) {
return new SpringWebfluxServerTelemetryBuilder(openTelemetry);
}

// We use ServerWebExchange (which holds both the request and response)
// because we need it to get the HTTP route while instrumenting.
private final Instrumenter<ServerWebExchange, ServerWebExchange> serverInstrumenter;

SpringWebfluxServerTelemetry(
Instrumenter<ServerWebExchange, ServerWebExchange> serverInstrumenter) {
this.serverInstrumenter = serverInstrumenter;
}

public WebFilter createWebFilter() {
return new TelemetryProducingWebFilter(serverInstrumenter);
}

public WebFilter createWebFilterAndRegisterReactorHook() {
registerReactorHook();
return this.createWebFilter();
}

private static void registerReactorHook() {
ContextPropagationOperator.builder().build().registerOnEachOperator();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

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.builder.internal.DefaultHttpServerInstrumenterBuilder;
import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor;
import io.opentelemetry.instrumentation.api.instrumenter.SpanNameExtractor;
import io.opentelemetry.instrumentation.api.semconv.http.HttpServerAttributesExtractorBuilder;
import io.opentelemetry.instrumentation.spring.webflux.v5_3.internal.Experimental;
import io.opentelemetry.instrumentation.spring.webflux.v5_3.internal.SpringWebfluxBuilderUtil;
import java.util.List;
import java.util.Set;
import java.util.function.Function;
import org.springframework.web.server.ServerWebExchange;

/** A builder of {@link SpringWebfluxServerTelemetry}. */
public final class SpringWebfluxServerTelemetryBuilder {
private static final String INSTRUMENTATION_NAME = "io.opentelemetry.spring-webflux-5.3";

private final DefaultHttpServerInstrumenterBuilder<ServerWebExchange, ServerWebExchange>
serverBuilder;

static {
SpringWebfluxBuilderUtil.setServerBuilderExtractor(
SpringWebfluxServerTelemetryBuilder::getServerBuilder);
}

SpringWebfluxServerTelemetryBuilder(OpenTelemetry openTelemetry) {
serverBuilder =
DefaultHttpServerInstrumenterBuilder.create(
INSTRUMENTATION_NAME,
openTelemetry,
WebfluxServerHttpAttributesGetter.INSTANCE,
WebfluxTextMapGetter.INSTANCE);
}

/**
* Adds an additional {@link AttributesExtractor} to invoke to set attributes to instrumented
* items.
*/
@CanIgnoreReturnValue
public SpringWebfluxServerTelemetryBuilder addAttributesExtractor(
AttributesExtractor<ServerWebExchange, ServerWebExchange> attributesExtractor) {
serverBuilder.addAttributesExtractor(attributesExtractor);
return this;
}

/**
* Configures the HTTP request headers that will be captured as span attributes from server
* instrumentation.
*
* @param requestHeaders A list of HTTP header names.
*/
@CanIgnoreReturnValue
public SpringWebfluxServerTelemetryBuilder setCapturedRequestHeaders(
List<String> requestHeaders) {
serverBuilder.setCapturedRequestHeaders(requestHeaders);
return this;
}

/**
* Configures the HTTP response headers that will be captured as span attributes from server
* instrumentation.
*
* @param responseHeaders A list of HTTP header names.
*/
@CanIgnoreReturnValue
public SpringWebfluxServerTelemetryBuilder setCapturedResponseHeaders(
List<String> responseHeaders) {
serverBuilder.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 SpringWebfluxServerTelemetryBuilder setKnownMethods(Set<String> knownMethods) {
serverBuilder.setKnownMethods(knownMethods);
return this;
}

/**
* Configures the instrumentation to emit experimental HTTP server metrics.
*
* @param emitExperimentalHttpServerTelemetry {@code true} if the experimental HTTP server metrics
* are to be emitted.
* @deprecated Use {@link
* Experimental#setEmitExperimentalHttpServerMetrics(SpringWebfluxServerTelemetryBuilder,
* boolean)} instead.
*/
@Deprecated
@CanIgnoreReturnValue
public SpringWebfluxServerTelemetryBuilder setEmitExperimentalHttpServerTelemetry(
boolean emitExperimentalHttpServerTelemetry) {
serverBuilder.setEmitExperimentalHttpServerMetrics(emitExperimentalHttpServerTelemetry);
return this;
}

/** Sets custom server {@link SpanNameExtractor} via transform function. */
@CanIgnoreReturnValue
public SpringWebfluxServerTelemetryBuilder setSpanNameExtractor(
Function<
SpanNameExtractor<? super ServerWebExchange>,
? extends SpanNameExtractor<? super ServerWebExchange>>
serverSpanNameExtractor) {
serverBuilder.setSpanNameExtractor(serverSpanNameExtractor);
return this;
}

/**
* Returns a new {@link SpringWebfluxTelemetry} with the settings of this {@link
* SpringWebfluxServerTelemetryBuilder}.
*/
public SpringWebfluxServerTelemetry build() {
return new SpringWebfluxServerTelemetry(serverBuilder.build());
}

private DefaultHttpServerInstrumenterBuilder<ServerWebExchange, ServerWebExchange>
getServerBuilder() {
return serverBuilder;
}
}
Loading

0 comments on commit 79beda9

Please sign in to comment.