diff --git a/instrumentation/okhttp/okhttp-3.0/library/build.gradle.kts b/instrumentation/okhttp/okhttp-3.0/library/build.gradle.kts index 9251deeffa1e..00bf4670b04a 100644 --- a/instrumentation/okhttp/okhttp-3.0/library/build.gradle.kts +++ b/instrumentation/okhttp/okhttp-3.0/library/build.gradle.kts @@ -5,7 +5,7 @@ plugins { } dependencies { - library("com.squareup.okhttp3:okhttp:3.0.0") + library("com.squareup.okhttp3:okhttp:3.11.0") testImplementation(project(":instrumentation:okhttp:okhttp-3.0:testing")) } diff --git a/instrumentation/okhttp/okhttp-3.0/library/src/http2Test/java/io/opentelemetry/instrumentation/okhttp/v3_0/OkHttp3Http2Test.java b/instrumentation/okhttp/okhttp-3.0/library/src/http2Test/java/io/opentelemetry/instrumentation/okhttp/v3_0/OkHttp3Http2Test.java index 1ce2679d818a..71e24283a707 100644 --- a/instrumentation/okhttp/okhttp-3.0/library/src/http2Test/java/io/opentelemetry/instrumentation/okhttp/v3_0/OkHttp3Http2Test.java +++ b/instrumentation/okhttp/okhttp-3.0/library/src/http2Test/java/io/opentelemetry/instrumentation/okhttp/v3_0/OkHttp3Http2Test.java @@ -6,7 +6,9 @@ package io.opentelemetry.instrumentation.okhttp.v3_0; import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; +import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; import io.opentelemetry.instrumentation.testing.junit.http.AbstractHttpClientTest; import io.opentelemetry.instrumentation.testing.junit.http.HttpClientInstrumentationExtension; @@ -14,6 +16,7 @@ import okhttp3.Call; import okhttp3.OkHttpClient; import okhttp3.Protocol; +import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; class OkHttp3Http2Test extends AbstractOkHttp3Test { @@ -39,4 +42,41 @@ protected void configure(HttpClientTestOptions.Builder optionsBuilder) { optionsBuilder.disableTestHttps(); optionsBuilder.setHttpProtocolVersion(uri -> "2"); } + + public Call.Factory createCallFactoryWithNetworkTiming(OkHttpClient.Builder clientBuilder) { + clientBuilder.protocols(singletonList(Protocol.H2_PRIOR_KNOWLEDGE)); + return OkHttpTelemetry.builder(testing.getOpenTelemetry()) + .setCapturedRequestHeaders(singletonList(AbstractHttpClientTest.TEST_REQUEST_HEADER)) + .setCapturedResponseHeaders(singletonList(AbstractHttpClientTest.TEST_RESPONSE_HEADER)) + .build() + .newCallFactoryWithNetworkTiming(clientBuilder.build()); + } + + @Test + void networkTimingClient() throws Exception { + okhttp3.Request request = + new okhttp3.Request.Builder().url(resolveAddress("/success").toString()).build(); + okhttp3.Response response = + createCallFactoryWithNetworkTiming(new OkHttpClient.Builder()).newCall(request).execute(); + assertThat(response.code()).isEqualTo(200); + + testing.waitAndAssertTraces( + trace -> { + trace.hasSpansSatisfyingExactly( + span -> { + assertClientSpan(span, resolveAddress("/success"), "GET", 200, null) + .hasNoParent() + .hasAttributesSatisfying( + attrs -> { + boolean hasTiming = + attrs.asMap().keySet().stream() + .map(AttributeKey::getKey) + .anyMatch( + k -> k.endsWith(".start_time") || k.endsWith(".end_time")); + assertThat(hasTiming).isTrue(); + }); + }, + span -> assertServerSpan(span).hasParent(trace.getSpan(0))); + }); + } } diff --git a/instrumentation/okhttp/okhttp-3.0/library/src/main/java/io/opentelemetry/instrumentation/okhttp/v3_0/OkHttpTelemetry.java b/instrumentation/okhttp/okhttp-3.0/library/src/main/java/io/opentelemetry/instrumentation/okhttp/v3_0/OkHttpTelemetry.java index 6bb85070e31b..5886e0eb7ad2 100644 --- a/instrumentation/okhttp/okhttp-3.0/library/src/main/java/io/opentelemetry/instrumentation/okhttp/v3_0/OkHttpTelemetry.java +++ b/instrumentation/okhttp/okhttp-3.0/library/src/main/java/io/opentelemetry/instrumentation/okhttp/v3_0/OkHttpTelemetry.java @@ -9,6 +9,7 @@ import io.opentelemetry.context.propagation.ContextPropagators; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; import io.opentelemetry.instrumentation.okhttp.v3_0.internal.ConnectionErrorSpanInterceptor; +import io.opentelemetry.instrumentation.okhttp.v3_0.internal.NetworkTimingEventListener; import io.opentelemetry.instrumentation.okhttp.v3_0.internal.TracingInterceptor; import okhttp3.Call; import okhttp3.Callback; @@ -59,4 +60,27 @@ public Call.Factory newCallFactory(OkHttpClient baseClient) { OkHttpClient tracingClient = builder.build(); return new TracingCallFactory(tracingClient); } + + /** + * Construct a new OpenTelemetry tracing-enabled {@link okhttp3.Call.Factory} using the provided + * {@link OkHttpClient} instance, with a NetworkTimingEventListener added to capture timing + * attributes. + * + *
Using this method will result in proper propagation and span parenting, for both {@linkplain
+ * Call#execute() synchronous} and {@linkplain Call#enqueue(Callback) asynchronous} usages.
+ *
+ * @param baseClient An instance of OkHttpClient configured as desired.
+ * @return a {@link Call.Factory} for creating new {@link Call} instances.
+ */
+ public Call.Factory newCallFactoryWithNetworkTiming(OkHttpClient baseClient) {
+ OkHttpClient.Builder builder = baseClient.newBuilder();
+ // add our interceptors before other interceptors
+ builder.interceptors().add(0, new ContextInterceptor());
+ builder.interceptors().add(1, new ConnectionErrorSpanInterceptor(instrumenter));
+ builder.networkInterceptors().add(0, new TracingInterceptor(instrumenter, propagators));
+ // Add NetworkTimingEventListener to capture timing attributes
+ builder.eventListenerFactory(new NetworkTimingEventListener.Factory());
+ OkHttpClient tracingClient = builder.build();
+ return new TracingCallFactory(tracingClient);
+ }
}
diff --git a/instrumentation/okhttp/okhttp-3.0/library/src/main/java/io/opentelemetry/instrumentation/okhttp/v3_0/TracingCallFactory.java b/instrumentation/okhttp/okhttp-3.0/library/src/main/java/io/opentelemetry/instrumentation/okhttp/v3_0/TracingCallFactory.java
index 479e107c51ae..768bc5ec009a 100644
--- a/instrumentation/okhttp/okhttp-3.0/library/src/main/java/io/opentelemetry/instrumentation/okhttp/v3_0/TracingCallFactory.java
+++ b/instrumentation/okhttp/okhttp-3.0/library/src/main/java/io/opentelemetry/instrumentation/okhttp/v3_0/TracingCallFactory.java
@@ -76,16 +76,24 @@ public void cancel() {
}
@Override
- public Call clone() throws CloneNotSupportedException {
+ public Call clone() {
if (cloneMethod == null) {
- return (Call) super.clone();
+ try {
+ return (Call) super.clone();
+ } catch (CloneNotSupportedException e) {
+ throw new AssertionError(e);
+ }
}
try {
// we pull the current context here, because the cloning might be happening in a different
// context than the original call creation.
return new TracingCall((Call) cloneMethod.invoke(delegate), Context.current());
} catch (IllegalAccessException | InvocationTargetException e) {
- return (Call) super.clone();
+ try {
+ return (Call) super.clone();
+ } catch (CloneNotSupportedException cloneException) {
+ throw new AssertionError(cloneException);
+ }
}
}
diff --git a/instrumentation/okhttp/okhttp-3.0/library/src/main/java/io/opentelemetry/instrumentation/okhttp/v3_0/internal/NetworkTimingEventListener.java b/instrumentation/okhttp/okhttp-3.0/library/src/main/java/io/opentelemetry/instrumentation/okhttp/v3_0/internal/NetworkTimingEventListener.java
new file mode 100644
index 000000000000..0f9485208a98
--- /dev/null
+++ b/instrumentation/okhttp/okhttp-3.0/library/src/main/java/io/opentelemetry/instrumentation/okhttp/v3_0/internal/NetworkTimingEventListener.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.instrumentation.okhttp.v3_0.internal;
+
+import io.opentelemetry.api.common.AttributeKey;
+import io.opentelemetry.api.trace.Span;
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.Proxy;
+import java.util.List;
+import javax.annotation.Nullable;
+import okhttp3.Call;
+import okhttp3.Connection;
+import okhttp3.EventListener;
+import okhttp3.Handshake;
+import okhttp3.Protocol;
+import okhttp3.Request;
+import okhttp3.Response;
+
+/**
+ * This class is internal and is hence not for public use. Its APIs are unstable and can change at
+ * any time.
+ */
+public final class NetworkTimingEventListener extends EventListener {
+
+ // Raw timestamp attribute keys
+ private static final AttributeKey NetworkTimingEventListener captures raw network timing timestamps and adds them as
+ * attributes to the current OpenTelemetry span.
+ *
+ * Works with both synchronous and asynchronous OkHttp calls when used with proper context
+ * propagation.
+ *
+ * This class is internal and is hence not for public use. Its APIs are unstable and can change
+ * at any time.
+ */
+ public static final class Factory implements EventListener.Factory {
+ @Override
+ public EventListener create(Call call) {
+ return INSTANCE;
+ }
+ }
+}
diff --git a/instrumentation/okhttp/okhttp-3.0/library/src/main/java/io/opentelemetry/instrumentation/okhttp/v3_0/internal/OkHttpAttributesGetter.java b/instrumentation/okhttp/okhttp-3.0/library/src/main/java/io/opentelemetry/instrumentation/okhttp/v3_0/internal/OkHttpAttributesGetter.java
index 0043f7dd52ed..2443a897a8f9 100644
--- a/instrumentation/okhttp/okhttp-3.0/library/src/main/java/io/opentelemetry/instrumentation/okhttp/v3_0/internal/OkHttpAttributesGetter.java
+++ b/instrumentation/okhttp/okhttp-3.0/library/src/main/java/io/opentelemetry/instrumentation/okhttp/v3_0/internal/OkHttpAttributesGetter.java
@@ -62,12 +62,13 @@ public String getNetworkProtocolName(Interceptor.Chain chain, @Nullable Response
return "http";
case SPDY_3:
return "spdy";
+ default:
+ // added in 3.11.0
+ if ("H2_PRIOR_KNOWLEDGE".equals(response.protocol().name())) {
+ return "http";
+ }
+ return null;
}
- // added in 3.11.0
- if ("H2_PRIOR_KNOWLEDGE".equals(response.protocol().name())) {
- return "http";
- }
- return null;
}
@Nullable
@@ -85,12 +86,13 @@ public String getNetworkProtocolVersion(Interceptor.Chain chain, @Nullable Respo
return "2";
case SPDY_3:
return "3.1";
+ default:
+ // added in 3.11.0
+ if ("H2_PRIOR_KNOWLEDGE".equals(response.protocol().name())) {
+ return "2";
+ }
+ return null;
}
- // added in 3.11.0
- if ("H2_PRIOR_KNOWLEDGE".equals(response.protocol().name())) {
- return "2";
- }
- return null;
}
@Override
diff --git a/instrumentation/okhttp/okhttp-3.0/library/src/test/java/io/opentelemetry/instrumentation/okhttp/v3_0/OkHttp3Test.java b/instrumentation/okhttp/okhttp-3.0/library/src/test/java/io/opentelemetry/instrumentation/okhttp/v3_0/OkHttp3Test.java
index b2f8bbddc636..9334af46d627 100644
--- a/instrumentation/okhttp/okhttp-3.0/library/src/test/java/io/opentelemetry/instrumentation/okhttp/v3_0/OkHttp3Test.java
+++ b/instrumentation/okhttp/okhttp-3.0/library/src/test/java/io/opentelemetry/instrumentation/okhttp/v3_0/OkHttp3Test.java
@@ -6,13 +6,16 @@
package io.opentelemetry.instrumentation.okhttp.v3_0;
import static java.util.Collections.singletonList;
+import static org.assertj.core.api.Assertions.assertThat;
+import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension;
import io.opentelemetry.instrumentation.testing.junit.http.AbstractHttpClientTest;
import io.opentelemetry.instrumentation.testing.junit.http.HttpClientInstrumentationExtension;
import okhttp3.Call;
import okhttp3.OkHttpClient;
import okhttp3.Protocol;
+import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
class OkHttp3Test extends AbstractOkHttp3Test {
@@ -29,4 +32,41 @@ public Call.Factory createCallFactory(OkHttpClient.Builder clientBuilder) {
.build()
.newCallFactory(clientBuilder.build());
}
+
+ public Call.Factory createCallFactoryWithNetworkTiming(OkHttpClient.Builder clientBuilder) {
+ clientBuilder.protocols(singletonList(Protocol.HTTP_1_1));
+ return OkHttpTelemetry.builder(testing.getOpenTelemetry())
+ .setCapturedRequestHeaders(singletonList(AbstractHttpClientTest.TEST_REQUEST_HEADER))
+ .setCapturedResponseHeaders(singletonList(AbstractHttpClientTest.TEST_RESPONSE_HEADER))
+ .build()
+ .newCallFactoryWithNetworkTiming(clientBuilder.build());
+ }
+
+ @Test
+ void networkTimingClient() throws Exception {
+ okhttp3.Request request =
+ new okhttp3.Request.Builder().url(resolveAddress("/success").toString()).build();
+ okhttp3.Response response =
+ createCallFactoryWithNetworkTiming(new OkHttpClient.Builder()).newCall(request).execute();
+ assertThat(response.code()).isEqualTo(200);
+
+ testing.waitAndAssertTraces(
+ trace -> {
+ trace.hasSpansSatisfyingExactly(
+ span -> {
+ assertClientSpan(span, resolveAddress("/success"), "GET", 200, null)
+ .hasNoParent()
+ .hasAttributesSatisfying(
+ attrs -> {
+ boolean hasTiming =
+ attrs.asMap().keySet().stream()
+ .map(AttributeKey::getKey)
+ .anyMatch(
+ k -> k.endsWith(".start_time") || k.endsWith(".end_time"));
+ assertThat(hasTiming).isTrue();
+ });
+ },
+ span -> assertServerSpan(span).hasParent(trace.getSpan(0)));
+ });
+ }
}