Skip to content

Commit 1ac8c4d

Browse files
author
Mateusz Rzeszutek
authored
Implement error.type in spring-webflux and reactor-netty instrumentations (#9967)
1 parent df8b334 commit 1ac8c4d

File tree

10 files changed

+95
-92
lines changed

10 files changed

+95
-92
lines changed

instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/semconv/http/HttpCommonAttributesGetter.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ public interface HttpCommonAttributesGetter<REQUEST, RESPONSE> {
7676
*/
7777
@Nullable
7878
default String getErrorType(
79-
REQUEST request, @Nullable RESPONSE response, @Nullable Throwable throwable) {
79+
REQUEST request, @Nullable RESPONSE response, @Nullable Throwable error) {
8080
return null;
8181
}
8282
}

instrumentation/reactor/reactor-netty/reactor-netty-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/reactornetty/v1_0/ReactorNettyHttpClientAttributesGetter.java

+13-11
Original file line numberDiff line numberDiff line change
@@ -72,13 +72,15 @@ public String getNetworkProtocolVersion(
7272
@Nullable
7373
@Override
7474
public String getServerAddress(HttpClientRequest request) {
75-
return getHost(request);
75+
String resourceUrl = request.resourceUrl();
76+
return resourceUrl == null ? null : UrlParser.getHost(resourceUrl);
7677
}
7778

7879
@Nullable
7980
@Override
8081
public Integer getServerPort(HttpClientRequest request) {
81-
return getPort(request);
82+
String resourceUrl = request.resourceUrl();
83+
return resourceUrl == null ? null : UrlParser.getPort(resourceUrl);
8284
}
8385

8486
@Nullable
@@ -99,14 +101,14 @@ public InetSocketAddress getNetworkPeerInetSocketAddress(
99101
}
100102

101103
@Nullable
102-
private static String getHost(HttpClientRequest request) {
103-
String resourceUrl = request.resourceUrl();
104-
return resourceUrl == null ? null : UrlParser.getHost(resourceUrl);
105-
}
106-
107-
@Nullable
108-
private static Integer getPort(HttpClientRequest request) {
109-
String resourceUrl = request.resourceUrl();
110-
return resourceUrl == null ? null : UrlParser.getPort(resourceUrl);
104+
@Override
105+
public String getErrorType(
106+
HttpClientRequest request, @Nullable HttpClientResponse response, @Nullable Throwable error) {
107+
// if both response and error are null it means the request has been cancelled -- see the
108+
// ConnectionWrapper class
109+
if (response == null && error == null) {
110+
return "cancelled";
111+
}
112+
return null;
111113
}
112114
}

instrumentation/reactor/reactor-netty/reactor-netty-1.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/reactornetty/v1_0/AbstractReactorNettyHttpClientTest.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -310,7 +310,7 @@ void shouldEndSpanOnMonoTimeout() {
310310
equalTo(SemanticAttributes.URL_FULL, uri.toString()),
311311
equalTo(SemanticAttributes.SERVER_ADDRESS, "localhost"),
312312
equalTo(SemanticAttributes.SERVER_PORT, uri.getPort()),
313-
equalTo(HttpAttributes.ERROR_TYPE, "_OTHER")),
313+
equalTo(HttpAttributes.ERROR_TYPE, "cancelled")),
314314
span ->
315315
span.hasName("test-http-server")
316316
.hasKind(SpanKind.SERVER)

instrumentation/spring/spring-webflux/spring-webflux-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v5_0/client/WebClientHelper.java

-4
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
import io.opentelemetry.instrumentation.spring.webflux.v5_3.internal.WebClientHttpAttributesGetter;
1515
import io.opentelemetry.instrumentation.spring.webflux.v5_3.internal.WebClientTracingFilter;
1616
import io.opentelemetry.javaagent.bootstrap.internal.CommonConfig;
17-
import io.opentelemetry.javaagent.bootstrap.internal.InstrumentationConfig;
1817
import java.util.List;
1918
import org.springframework.web.reactive.function.client.ClientRequest;
2019
import org.springframework.web.reactive.function.client.ClientResponse;
@@ -35,9 +34,6 @@ public final class WebClientHelper {
3534
HttpClientPeerServiceAttributesExtractor.create(
3635
WebClientHttpAttributesGetter.INSTANCE,
3736
CommonConfig.get().getPeerServiceResolver())),
38-
InstrumentationConfig.get()
39-
.getBoolean(
40-
"otel.instrumentation.spring-webflux.experimental-span-attributes", false),
4137
CommonConfig.get().shouldEmitExperimentalHttpClientTelemetry());
4238

4339
public static void addFilter(List<ExchangeFilterFunction> exchangeFilterFunctions) {

instrumentation/spring/spring-webflux/spring-webflux-5.3/library/README.md

+5
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,11 @@ interface.
4545
a `WebFilter` and using the OpenTelemetry Reactor instrumentation to ensure context is
4646
passed around correctly.
4747

48+
### Web client instrumentation
49+
50+
The `WebClient` instrumentation will emit the `error.type` attribute with value `cancelled` whenever
51+
an outgoing HTTP request is cancelled.
52+
4853
### Setup
4954

5055
Here is how to set up client and server instrumentation respectively:

instrumentation/spring/spring-webflux/spring-webflux-5.3/library/src/main/java/io/opentelemetry/instrumentation/spring/webflux/v5_3/SpringWebfluxTelemetryBuilder.java

+12-26
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,8 @@ public final class SpringWebfluxTelemetryBuilder {
5353
clientExtractorConfigurer = builder -> {};
5454
private Consumer<HttpSpanNameExtractorBuilder<ClientRequest>> clientSpanNameExtractorConfigurer =
5555
builder -> {};
56-
private boolean captureExperimentalSpanAttributes = false;
57-
private boolean emitExperimentalHttpClientMetrics = false;
58-
private boolean emitExperimentalHttpServerMetrics = false;
56+
private boolean emitExperimentalHttpClientTelemetry = false;
57+
private boolean emitExperimentalHttpServerTelemetry = false;
5958

6059
SpringWebfluxTelemetryBuilder(OpenTelemetry openTelemetry) {
6160
this.openTelemetry = openTelemetry;
@@ -100,18 +99,6 @@ public SpringWebfluxTelemetryBuilder setCapturedClientResponseHeaders(
10099
return this;
101100
}
102101

103-
/**
104-
* Sets whether experimental attributes should be set to spans. These attributes may be changed or
105-
* removed in the future, so only enable this if you know you do not require attributes filled by
106-
* this instrumentation to be stable across versions.
107-
*/
108-
@CanIgnoreReturnValue
109-
public SpringWebfluxTelemetryBuilder setCaptureExperimentalSpanAttributes(
110-
boolean captureExperimentalSpanAttributes) {
111-
this.captureExperimentalSpanAttributes = captureExperimentalSpanAttributes;
112-
return this;
113-
}
114-
115102
/**
116103
* Adds an additional {@link AttributesExtractor} to invoke to set attributes to instrumented
117104
* items.
@@ -178,26 +165,26 @@ public SpringWebfluxTelemetryBuilder setKnownMethods(Set<String> knownMethods) {
178165
/**
179166
* Configures the instrumentation to emit experimental HTTP client metrics.
180167
*
181-
* @param emitExperimentalHttpClientMetrics {@code true} if the experimental HTTP client metrics
168+
* @param emitExperimentalHttpClientTelemetry {@code true} if the experimental HTTP client metrics
182169
* are to be emitted.
183170
*/
184171
@CanIgnoreReturnValue
185-
public SpringWebfluxTelemetryBuilder setEmitExperimentalHttpClientMetrics(
186-
boolean emitExperimentalHttpClientMetrics) {
187-
this.emitExperimentalHttpClientMetrics = emitExperimentalHttpClientMetrics;
172+
public SpringWebfluxTelemetryBuilder setEmitExperimentalHttpClientTelemetry(
173+
boolean emitExperimentalHttpClientTelemetry) {
174+
this.emitExperimentalHttpClientTelemetry = emitExperimentalHttpClientTelemetry;
188175
return this;
189176
}
190177

191178
/**
192179
* Configures the instrumentation to emit experimental HTTP server metrics.
193180
*
194-
* @param emitExperimentalHttpServerMetrics {@code true} if the experimental HTTP server metrics
181+
* @param emitExperimentalHttpServerTelemetry {@code true} if the experimental HTTP server metrics
195182
* are to be emitted.
196183
*/
197184
@CanIgnoreReturnValue
198-
public SpringWebfluxTelemetryBuilder setEmitExperimentalHttpServerMetrics(
199-
boolean emitExperimentalHttpServerMetrics) {
200-
this.emitExperimentalHttpServerMetrics = emitExperimentalHttpServerMetrics;
185+
public SpringWebfluxTelemetryBuilder setEmitExperimentalHttpServerTelemetry(
186+
boolean emitExperimentalHttpServerTelemetry) {
187+
this.emitExperimentalHttpServerTelemetry = emitExperimentalHttpServerTelemetry;
201188
return this;
202189
}
203190

@@ -213,8 +200,7 @@ public SpringWebfluxTelemetry build() {
213200
clientExtractorConfigurer,
214201
clientSpanNameExtractorConfigurer,
215202
clientAdditionalExtractors,
216-
captureExperimentalSpanAttributes,
217-
emitExperimentalHttpClientMetrics);
203+
emitExperimentalHttpClientTelemetry);
218204

219205
Instrumenter<ServerWebExchange, ServerWebExchange> serverInstrumenter =
220206
buildServerInstrumenter();
@@ -234,7 +220,7 @@ private Instrumenter<ServerWebExchange, ServerWebExchange> buildServerInstrument
234220
.addAttributesExtractors(serverAdditionalExtractors)
235221
.addContextCustomizer(httpServerRouteBuilder.build())
236222
.addOperationMetrics(HttpServerMetrics.get());
237-
if (emitExperimentalHttpServerMetrics) {
223+
if (emitExperimentalHttpServerTelemetry) {
238224
builder
239225
.addAttributesExtractor(HttpExperimentalAttributesExtractor.create(getter))
240226
.addOperationMetrics(HttpServerExperimentalMetrics.get());

instrumentation/spring/spring-webflux/spring-webflux-5.3/library/src/main/java/io/opentelemetry/instrumentation/spring/webflux/v5_3/internal/ClientInstrumenterFactory.java

+2-6
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,7 @@ public static Instrumenter<ClientRequest, ClientResponse> create(
4040
extractorConfigurer,
4141
Consumer<HttpSpanNameExtractorBuilder<ClientRequest>> spanNameExtractorConfigurer,
4242
List<AttributesExtractor<ClientRequest, ClientResponse>> additionalExtractors,
43-
boolean captureExperimentalSpanAttributes,
44-
boolean emitExperimentalHttpClientMetrics) {
43+
boolean emitExperimentalHttpClientTelemetry) {
4544

4645
WebClientHttpAttributesGetter httpAttributesGetter = WebClientHttpAttributesGetter.INSTANCE;
4746

@@ -61,10 +60,7 @@ public static Instrumenter<ClientRequest, ClientResponse> create(
6160
.addAttributesExtractors(additionalExtractors)
6261
.addOperationMetrics(HttpClientMetrics.get());
6362

64-
if (captureExperimentalSpanAttributes) {
65-
clientBuilder.addAttributesExtractor(new WebClientExperimentalAttributesExtractor());
66-
}
67-
if (emitExperimentalHttpClientMetrics) {
63+
if (emitExperimentalHttpClientTelemetry) {
6864
clientBuilder
6965
.addAttributesExtractor(HttpExperimentalAttributesExtractor.create(httpAttributesGetter))
7066
.addOperationMetrics(HttpClientExperimentalMetrics.get());

instrumentation/spring/spring-webflux/spring-webflux-5.3/library/src/main/java/io/opentelemetry/instrumentation/spring/webflux/v5_3/internal/WebClientExperimentalAttributesExtractor.java

-43
This file was deleted.

instrumentation/spring/spring-webflux/spring-webflux-5.3/library/src/main/java/io/opentelemetry/instrumentation/spring/webflux/v5_3/internal/WebClientHttpAttributesGetter.java

+12
Original file line numberDiff line numberDiff line change
@@ -59,4 +59,16 @@ public String getServerAddress(ClientRequest request) {
5959
public Integer getServerPort(ClientRequest request) {
6060
return request.url().getPort();
6161
}
62+
63+
@Nullable
64+
@Override
65+
public String getErrorType(
66+
ClientRequest request, @Nullable ClientResponse response, @Nullable Throwable error) {
67+
// if both response and error are null it means the request has been cancelled -- see the
68+
// WebClientTracingFilter class
69+
if (response == null && error == null) {
70+
return "cancelled";
71+
}
72+
return null;
73+
}
6274
}

instrumentation/spring/spring-webflux/spring-webflux-5.3/testing/src/main/java/io/opentelemetry/instrumentation/spring/webflux/client/AbstractSpringWebfluxClientInstrumentationTest.java

+49
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,29 @@
55

66
package io.opentelemetry.instrumentation.spring.webflux.client;
77

8+
import static io.opentelemetry.api.trace.SpanKind.CLIENT;
9+
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo;
10+
import static java.util.Collections.emptyMap;
811
import static java.util.Objects.requireNonNull;
12+
import static org.assertj.core.api.Assertions.catchThrowable;
913

1014
import io.opentelemetry.api.common.AttributeKey;
15+
import io.opentelemetry.api.trace.SpanKind;
16+
import io.opentelemetry.instrumentation.api.semconv.http.internal.HttpAttributes;
1117
import io.opentelemetry.instrumentation.testing.junit.http.AbstractHttpClientTest;
1218
import io.opentelemetry.instrumentation.testing.junit.http.HttpClientResult;
1319
import io.opentelemetry.instrumentation.testing.junit.http.HttpClientTestOptions;
20+
import io.opentelemetry.sdk.trace.data.StatusData;
1421
import io.opentelemetry.semconv.SemanticAttributes;
1522
import java.lang.invoke.MethodHandle;
1623
import java.lang.invoke.MethodHandles;
1724
import java.lang.invoke.MethodType;
1825
import java.net.URI;
26+
import java.time.Duration;
1927
import java.util.HashSet;
2028
import java.util.Map;
2129
import java.util.Set;
30+
import org.junit.jupiter.api.Test;
2231
import org.springframework.http.HttpMethod;
2332
import org.springframework.web.reactive.function.client.ClientResponse;
2433
import org.springframework.web.reactive.function.client.WebClient;
@@ -142,4 +151,44 @@ private static int getStatusCode(ClientResponse response) {
142151
throw new AssertionError(e);
143152
}
144153
}
154+
155+
@Test
156+
void shouldEndSpanOnMonoTimeout() {
157+
URI uri = resolveAddress("/read-timeout");
158+
Throwable thrown =
159+
catchThrowable(
160+
() ->
161+
testing.runWithSpan(
162+
"parent",
163+
() ->
164+
buildRequest("GET", uri, emptyMap())
165+
.exchange()
166+
// apply Mono timeout that is way shorter than HTTP request timeout
167+
.timeout(Duration.ofSeconds(1))
168+
.block()));
169+
170+
testing.waitAndAssertTraces(
171+
trace ->
172+
trace.hasSpansSatisfyingExactly(
173+
span ->
174+
span.hasName("parent")
175+
.hasKind(SpanKind.INTERNAL)
176+
.hasNoParent()
177+
.hasStatus(StatusData.error())
178+
.hasException(thrown),
179+
span ->
180+
span.hasName("GET")
181+
.hasKind(CLIENT)
182+
.hasParent(trace.getSpan(0))
183+
.hasAttributesSatisfyingExactly(
184+
equalTo(SemanticAttributes.HTTP_REQUEST_METHOD, "GET"),
185+
equalTo(SemanticAttributes.URL_FULL, uri.toString()),
186+
equalTo(SemanticAttributes.SERVER_ADDRESS, "localhost"),
187+
equalTo(SemanticAttributes.SERVER_PORT, uri.getPort()),
188+
equalTo(HttpAttributes.ERROR_TYPE, "cancelled")),
189+
span ->
190+
span.hasName("test-http-server")
191+
.hasKind(SpanKind.SERVER)
192+
.hasParent(trace.getSpan(1))));
193+
}
145194
}

0 commit comments

Comments
 (0)