Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding okhttp autoinstrumentation #64

Merged
Merged
Show file tree
Hide file tree
Changes from 30 commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
69270e6
Created okhttp library module
LikeTheSalad Sep 3, 2023
4534dec
Setting up the bytebuddy plugin
LikeTheSalad Sep 3, 2023
4be77cb
Creating a README file for the automatic okhttp instrumentation
LikeTheSalad Sep 3, 2023
107d3ec
Spotless
LikeTheSalad Sep 3, 2023
e1e2214
Created okhttp library module
LikeTheSalad Sep 3, 2023
ba8b966
Setting up the bytebuddy plugin
LikeTheSalad Sep 3, 2023
ffb3d61
Creating a README file for the automatic okhttp instrumentation
LikeTheSalad Sep 3, 2023
51121f7
Spotless
LikeTheSalad Sep 3, 2023
3c47996
Merge remote-tracking branch 'origin/adding-okhttp-autoinstrumentatio…
LikeTheSalad Sep 11, 2023
fb23ca4
Merge branch 'main' into adding-okhttp-autoinstrumentation
LikeTheSalad Sep 11, 2023
ab229ce
Merge remote-tracking branch 'origin/adding-okhttp-autoinstrumentatio…
LikeTheSalad Sep 11, 2023
a065e72
Automatically prepending library name to instrumentation subprojects
LikeTheSalad Sep 11, 2023
9e40596
Temporarily fixing OkHttp3Singletons
LikeTheSalad Sep 11, 2023
ec62fbd
Merge branch 'corelib-requirements' into testing-auto-okhttp-instrume…
LikeTheSalad Sep 11, 2023
e35ce51
Creating okhttp testing module
LikeTheSalad Sep 12, 2023
71ffde6
Configuring android tests with junit5
LikeTheSalad Sep 12, 2023
077e186
Adding testing app manifest
LikeTheSalad Sep 12, 2023
deb0ff6
Adding junit5 android test runner arg
LikeTheSalad Sep 12, 2023
bf274fa
Adding basic okhttp instrumentation test
LikeTheSalad Sep 12, 2023
82a159f
Merge branch 'main' into adding-okhttp-autoinstrumentation
LikeTheSalad Sep 18, 2023
3e51fa0
Merge branch 'adding-okhttp-autoinstrumentation' into testing-auto-ok…
LikeTheSalad Sep 18, 2023
36d9a4d
Adding byte buddy version to properties
LikeTheSalad Sep 18, 2023
b778cb0
Created OkHttpInstrumentationConfig to allow configuring automatic tr…
LikeTheSalad Sep 18, 2023
128cf65
Merge branch 'adding-okhttp-autoinstrumentation' into testing-auto-ok…
LikeTheSalad Sep 18, 2023
65944e1
Clean up
LikeTheSalad Sep 18, 2023
eadd418
Spotless
LikeTheSalad Sep 18, 2023
dd9f4cb
Making static okhttp config final
LikeTheSalad Sep 18, 2023
7b2cdff
Setting bytebuddy maven last version link
LikeTheSalad Sep 18, 2023
78d9954
Updating the readme
LikeTheSalad Sep 18, 2023
107a1d6
Spotless
LikeTheSalad Sep 18, 2023
96c09ee
Update auto-instrumentation/okhttp/okhttp-3.0/README.md
LikeTheSalad Sep 22, 2023
4a5b9f0
Update auto-instrumentation/okhttp/okhttp-3.0/README.md
LikeTheSalad Sep 22, 2023
4065e40
Update auto-instrumentation/okhttp/okhttp-3.0/library/src/main/java/i…
LikeTheSalad Sep 22, 2023
3a88f08
Update auto-instrumentation/okhttp/okhttp-3.0/library/src/main/java/i…
LikeTheSalad Sep 22, 2023
cd99ea9
Update auto-instrumentation/okhttp/okhttp-3.0/library/src/main/java/i…
LikeTheSalad Sep 22, 2023
79a184c
Update auto-instrumentation/okhttp/okhttp-3.0/library/src/main/java/i…
LikeTheSalad Sep 22, 2023
fe77d9f
Moving android compileSdk to global properties
LikeTheSalad Sep 22, 2023
d97a8c5
Adding internal class message to OkHttp3Singletons
LikeTheSalad Sep 22, 2023
df1e71a
Fixing otel version link in okhttp3 auto instrumentation readme
LikeTheSalad Sep 22, 2023
afd74f3
Merge remote-tracking branch 'origin/adding-okhttp-autoinstrumentatio…
LikeTheSalad Sep 22, 2023
2defc86
Removed nonnull params annotations from OkHttpInstrumentationConfig
LikeTheSalad Sep 25, 2023
d391489
Added package-info.java to set nonnull to all params
LikeTheSalad Sep 25, 2023
3295024
Spotless
LikeTheSalad Sep 25, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions auto-instrumentation/okhttp/okhttp-3.0/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Android Instrumentation for OkHttp version 3.0 and higher

Provides OpenTelemetry instrumentation for [okhttp3](https://square.github.io/okhttp/).

## Quickstart

### Add these dependencies to your project

Replace `OPENTELEMETRY_VERSION` with the [latest
release](https://search.maven.org/search?q=g:io.opentelemetry.instrumentation%20AND%20a:opentelemetry-okhttp-3.0).

Replace `BYTEBUDDY_VERSION` with the [latest
release](https://search.maven.org/search?q=g:net.bytebuddy%20AND%20a:byte-buddy).
breedx-splk marked this conversation as resolved.
Show resolved Hide resolved

#### Byte buddy compilation plugin

This plugin leverages
Android's [ASM API](https://developer.android.com/reference/tools/gradle-api/8.0/com/android/build/api/instrumentation/AsmClassVisitorFactory)
to instrument bytecode at compile time. You can find more info on
its [repo page](https://github.com/raphw/byte-buddy/tree/master/byte-buddy-gradle-plugin/android-plugin.

```groovy
plugins {
id 'net.bytebuddy.byte-buddy-gradle-plugin' version 'BYTEBUDDY_VERSION'
}
```

#### Project dependencies

```groovy
implementation "io.opentelemetry.android:okhttp-3.0-library:OPENTELEMETRY_VERSION"
byteBuddy "io.opentelemetry.android:okhttp-3.0-agent:OPENTELEMETRY_VERSION"
```
LikeTheSalad marked this conversation as resolved.
Show resolved Hide resolved

After adding the plugin and the dependencies in your project, you'll automatically get your OkHttp
requests traced.
LikeTheSalad marked this conversation as resolved.
Show resolved Hide resolved

### Configuration

You can configure the automatic instrumentation by using the setters
in [OkHttpInstrumentationConfig](library/src/main/java/io/opentelemetry/instrumentation/library/okhttp/v3_0/OkHttpInstrumentationConfig.java)).
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
plugins {
id("otel.java-library-conventions")
id("otel.publish-conventions")
}

dependencies {
implementation(project(":auto-instrumentation:okhttp:okhttp-3.0:library"))
implementation("net.bytebuddy:byte-buddy:${property("bytebuddy.version")}")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.instrumentation.agent.okhttp.v3_0;

import io.opentelemetry.instrumentation.library.okhttp.v3_0.internal.OkHttp3Singletons;
import net.bytebuddy.asm.Advice;
import okhttp3.OkHttpClient;

public class OkHttpClientAdvice {

@Advice.OnMethodEnter
public static void enter(@Advice.Argument(0) OkHttpClient.Builder builder) {
if (!builder.interceptors().contains(OkHttp3Singletons.CONTEXT_INTERCEPTOR)) {
builder.interceptors().add(0, OkHttp3Singletons.CONTEXT_INTERCEPTOR);
builder.interceptors().add(1, OkHttp3Singletons.CONNECTION_ERROR_INTERCEPTOR);
}
if (!builder.networkInterceptors().contains(OkHttp3Singletons.TRACING_INTERCEPTOR)) {
builder.addNetworkInterceptor(OkHttp3Singletons.TRACING_INTERCEPTOR);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.instrumentation.agent.okhttp.v3_0;

import java.io.IOException;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.build.Plugin;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.dynamic.ClassFileLocator;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.matcher.ElementMatchers;
import okhttp3.OkHttpClient;

public class OkHttpClientPlugin implements Plugin {

@Override
public DynamicType.Builder<?> apply(
DynamicType.Builder<?> builder,
TypeDescription typeDescription,
ClassFileLocator classFileLocator) {
return builder.visit(
Advice.to(OkHttpClientAdvice.class)
.on(
ElementMatchers.isConstructor()
.and(
ElementMatchers.takesArguments(
OkHttpClient.Builder.class))));
}

@Override
public void close() throws IOException {
// No operation.
}

@Override
public boolean matches(TypeDescription target) {
return target.getTypeName().equals("okhttp3.OkHttpClient");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
io.opentelemetry.instrumentation.agent.okhttp.v3_0.OkHttpClientPlugin
11 changes: 11 additions & 0 deletions auto-instrumentation/okhttp/okhttp-3.0/library/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
plugins {
id("otel.java-library-conventions")
id("otel.publish-conventions")
}

val otelVersion = project.property("otel.sdk.version")
dependencies {
api("com.squareup.okhttp3:okhttp:3.0.0")
api("io.opentelemetry.instrumentation:opentelemetry-okhttp-3.0:$otelVersion-alpha")
implementation("io.opentelemetry.instrumentation:opentelemetry-instrumentation-api-semconv:$otelVersion-alpha")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.instrumentation.library.okhttp.v3_0;

import io.opentelemetry.instrumentation.api.internal.HttpConstants;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.Nonnull;

/** Configuration for automatic instrumentation of okhttp requests. */
public final class OkHttpInstrumentationConfig {
private static List<String> capturedRequestHeaders = new ArrayList<>();
private static List<String> capturedResponseHeaders = new ArrayList<>();
private static Set<String> knownMethods = new HashSet<>();
private static Map<String, String> peerServiceMapping = new HashMap<>();
private static boolean emitExperimentalHttpClientMetrics;

private OkHttpInstrumentationConfig() {}

/**
* Configures the HTTP request headers that will be captured as span attributes as described in
* <a
* href="https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/http.md#http-request-and-response-headers">HTTP
* semantic conventions</a>.
*
* <p>The HTTP request header values will be captured under the {@code
* http.request.header.<name>} attribute key. The {@code <name>} part in the attribute key is
* the normalized header name: lowercase, with dashes replaced by underscores.
*
* @param requestHeaders A list of HTTP header names.
*/
public static void setCapturedRequestHeaders(@Nonnull List<String> requestHeaders) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove the @Nonnull everywhere. We have a wide assumption that parameters are non-null by default unless a @Nullable is present, and nullaway will catch those.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it, I wasn't aware of it, I'll make the changes

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to confirm something though, after checking it a bit further I remembered that these methods are supposed to be called by our users, I was thinking that if they're not using nullaway in their projects, then this annotation could let their IDEs know that they shouldn't pass nulls here... Do you think we should still remove them for public functions? We might also want to validate null params now that I think about it in case our users decide to pass null regardless of any warnings, though I'm not sure if that'd be too much.

Copy link
Member

@trask trask Sep 22, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool! I've applied the changes, thanks

OkHttpInstrumentationConfig.capturedRequestHeaders = requestHeaders;
LikeTheSalad marked this conversation as resolved.
Show resolved Hide resolved
}

public static List<String> getCapturedRequestHeaders() {
return capturedRequestHeaders;
}

/**
* Configures the HTTP response headers that will be captured as span attributes as described in
* <a
* href="https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/http.md#http-request-and-response-headers">HTTP
* semantic conventions</a>.
*
* <p>The HTTP response header values will be captured under the {@code
* http.response.header.<name>} attribute key. The {@code <name>} part in the attribute key is
* the normalized header name: lowercase, with dashes replaced by underscores.
*
* @param responseHeaders A list of HTTP header names.
*/
public static void setCapturedResponseHeaders(@Nonnull List<String> responseHeaders) {
OkHttpInstrumentationConfig.capturedResponseHeaders = responseHeaders;
LikeTheSalad marked this conversation as resolved.
Show resolved Hide resolved
}

public static List<String> getCapturedResponseHeaders() {
return capturedResponseHeaders;
}

/**
* Configures the attrs extractor to recognize an alternative set of HTTP request methods.
*
* <p>By default, the extractor 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>. If an
* unknown method is encountered, the extractor will use the value {@value HttpConstants#_OTHER}
* instead of it and put the original value in an extra {@code http.request.method_original}
* attribute.
*
* <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.
*/
public static void setKnownMethods(@Nonnull Set<String> knownMethods) {
OkHttpInstrumentationConfig.knownMethods = knownMethods;
LikeTheSalad marked this conversation as resolved.
Show resolved Hide resolved
}

public static Set<String> getKnownMethods() {
return knownMethods;
}

/**
* Configures the extractor of the {@code peer.service} span attribute, described in <a
* href="https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/span-general.md#general-remote-service-attributes">the
* specification</a>.
*/
public static void setPeerServiceMapping(@Nonnull Map<String, String> peerServiceMapping) {
OkHttpInstrumentationConfig.peerServiceMapping = peerServiceMapping;
LikeTheSalad marked this conversation as resolved.
Show resolved Hide resolved
}

public static Map<String, String> getPeerServiceMapping() {
return peerServiceMapping;
}

/**
* When enabled keeps track of <a
* href="https://github.com/open-telemetry/semantic-conventions/blob/main/specification/metrics/semantic_conventions/http-metrics.md#http-client">non-stable
* HTTP client metrics</a>: <a
* href="https://github.com/open-telemetry/semantic-conventions/blob/main/specification/metrics/semantic_conventions/http-metrics.md#metric-httpclientrequestsize">the
* request size </a> and the <a
* href="https://github.com/open-telemetry/semantic-conventions/blob/main/specification/metrics/semantic_conventions/http-metrics.md#metric-httpclientresponsesize">
* the response size</a>.
*/
public static void setEmitExperimentalHttpClientMetrics(
boolean emitExperimentalHttpClientMetrics) {
OkHttpInstrumentationConfig.emitExperimentalHttpClientMetrics =
emitExperimentalHttpClientMetrics;
}

public static boolean emitExperimentalHttpClientMetrics() {
return emitExperimentalHttpClientMetrics;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.instrumentation.library.okhttp.v3_0.internal;

import static java.util.Collections.singletonList;

import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.context.Context;
import io.opentelemetry.context.Scope;
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientResend;
import io.opentelemetry.instrumentation.api.instrumenter.net.PeerServiceAttributesExtractor;
import io.opentelemetry.instrumentation.library.okhttp.v3_0.OkHttpInstrumentationConfig;
import io.opentelemetry.instrumentation.okhttp.v3_0.internal.ConnectionErrorSpanInterceptor;
import io.opentelemetry.instrumentation.okhttp.v3_0.internal.OkHttpAttributesGetter;
import io.opentelemetry.instrumentation.okhttp.v3_0.internal.OkHttpInstrumenterFactory;
import io.opentelemetry.instrumentation.okhttp.v3_0.internal.TracingInterceptor;
import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;

/** Holder of singleton interceptors for adding to instrumented clients. */
public final class OkHttp3Singletons {
LikeTheSalad marked this conversation as resolved.
Show resolved Hide resolved

private static final Instrumenter<Request, Response> INSTRUMENTER =
OkHttpInstrumenterFactory.create(
GlobalOpenTelemetry.get(),
builder ->
builder.setCapturedRequestHeaders(
OkHttpInstrumentationConfig.getCapturedRequestHeaders())
.setCapturedResponseHeaders(
OkHttpInstrumentationConfig
.getCapturedResponseHeaders())
.setKnownMethods(OkHttpInstrumentationConfig.getKnownMethods()),
singletonList(
PeerServiceAttributesExtractor.create(
OkHttpAttributesGetter.INSTANCE,
OkHttpInstrumentationConfig.getPeerServiceMapping())),
OkHttpInstrumentationConfig.emitExperimentalHttpClientMetrics());

public static final Interceptor CONTEXT_INTERCEPTOR =
chain -> {
try (Scope ignored = HttpClientResend.initialize(Context.current()).makeCurrent()) {
return chain.proceed(chain.request());
}
};

public static final Interceptor CONNECTION_ERROR_INTERCEPTOR =
new ConnectionErrorSpanInterceptor(INSTRUMENTER);

public static final Interceptor TRACING_INTERCEPTOR =
new TracingInterceptor(INSTRUMENTER, GlobalOpenTelemetry.getPropagators());

private OkHttp3Singletons() {}
}
11 changes: 11 additions & 0 deletions auto-instrumentation/okhttp/okhttp-3.0/testing/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
plugins {
id("otel.android-app-conventions")
id("net.bytebuddy.byte-buddy-gradle-plugin")
}

dependencies {
byteBuddy(project(":auto-instrumentation:okhttp:okhttp-3.0:agent"))
implementation(project(":auto-instrumentation:okhttp:okhttp-3.0:library"))
implementation("com.squareup.okhttp3:okhttp:4.11.0")
androidTestImplementation("com.squareup.okhttp3:mockwebserver:4.11.0")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.instrumentation.library.okhttp.v3_0;

import static org.junit.Assert.assertEquals;

import androidx.annotation.NonNull;
import io.opentelemetry.sdk.OpenTelemetrySdk;
import io.opentelemetry.sdk.testing.exporter.InMemorySpanExporter;
import io.opentelemetry.sdk.trace.SdkTracerProvider;
import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor;
import java.io.IOException;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

public class InstrumentationTest {
private MockWebServer server;

@Before
public void setUp() throws IOException {
server = new MockWebServer();
server.start();
}

@After
public void tearDown() throws IOException {
server.shutdown();
}

@Test
public void okhttpTraces() throws IOException {
InMemorySpanExporter spanExporter = InMemorySpanExporter.create();
OpenTelemetrySdk.builder()
.setTracerProvider(getSimpleTracerProvider(spanExporter))
.buildAndRegisterGlobal();

server.enqueue(new MockResponse().setResponseCode(200));

OkHttpClient client = new OkHttpClient();
executeRequest(client, "/test/");

assertEquals(1, spanExporter.getFinishedSpanItems().size());
}

private void executeRequest(OkHttpClient client, String urlPath) throws IOException {
Request request = new Request.Builder().url(server.url(urlPath)).build();
Response response = client.newCall(request).execute();
response.body().bytes();
}

@NonNull
private static SdkTracerProvider getSimpleTracerProvider(InMemorySpanExporter spanExporter) {
return SdkTracerProvider.builder()
.addSpanProcessor(SimpleSpanProcessor.create(spanExporter))
.build();
}
}
Loading