-
Notifications
You must be signed in to change notification settings - Fork 870
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
Implement a Call.Factory for okhttp 3.x+ library instrumentation #3812
Changes from all commits
3991690
126bc4a
8497876
1d194df
c1d65a2
b2a7208
6a07679
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
# Manual Instrumentation for OkHttp3 version 3.0.0+ | ||
|
||
Provides OpenTelemetry instrumentation for [okhttp3](https://square.github.io/okhttp/). | ||
|
||
## Quickstart | ||
|
||
### Add these dependencies to your project: | ||
|
||
Replace `OPENTELEMETRY_VERSION` with the latest stable | ||
[release](https://mvnrepository.com/artifact/io.opentelemetry). `Minimum version: 1.5.0` | ||
|
||
For Maven, add to your `pom.xml` dependencies: | ||
|
||
```xml | ||
|
||
<dependencies> | ||
<dependency> | ||
<groupId>io.opentelemetry.instrumentation</groupId> | ||
<artifactId>opentelemetry-okhttp-3.0</artifactId> | ||
<version>OPENTELEMETRY_VERSION</version> | ||
</dependency> | ||
</dependencies> | ||
``` | ||
|
||
For Gradle, add to your dependencies: | ||
|
||
```groovy | ||
implementation("io.opentelemetry.instrumentation:opentelemetry-okhttp-3.0:OPENTELEMETRY_VERSION") | ||
``` | ||
|
||
### Usage | ||
|
||
The instrumentation library provides an OkHttp `Call.Factory` implementation that wraps | ||
an instance of the `OkHttpClient` to provide OpenTelemetry-based spans and context | ||
propagation. | ||
|
||
```java | ||
import io.opentelemetry.api.OpenTelemetry; | ||
import io.opentelemetry.instrumentation.okhttp.v3_0.OkHttpTracing; | ||
import okhttp3.Call; | ||
import okhttp3.OkHttpClient; | ||
|
||
import java.util.concurrent.ExecutorService; | ||
|
||
public class OkHttpConfiguration { | ||
|
||
//Use this Call.Factory implementation for making standard http client calls. | ||
public Call.Factory createTracedClient(OpenTelemetry openTelemetry) { | ||
return OkHttpTracing.newBuilder(openTelemetry).build().newCallFactory(createClient()); | ||
} | ||
|
||
//your configuration of the OkHttpClient goes here: | ||
private OkHttpClient createClient() { | ||
return new OkHttpClient.Builder().build(); | ||
} | ||
} | ||
``` |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,158 @@ | ||
/* | ||
* Copyright The OpenTelemetry Authors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
package io.opentelemetry.instrumentation.okhttp.v3_0; | ||
|
||
import io.opentelemetry.context.Context; | ||
import io.opentelemetry.context.Scope; | ||
import io.opentelemetry.instrumentation.api.caching.Cache; | ||
import java.io.IOException; | ||
import java.lang.invoke.MethodHandle; | ||
import java.lang.invoke.MethodHandles; | ||
import java.lang.invoke.MethodType; | ||
import okhttp3.Call; | ||
import okhttp3.Callback; | ||
import okhttp3.OkHttpClient; | ||
import okhttp3.Request; | ||
import okhttp3.Response; | ||
import okio.Timeout; | ||
import org.checkerframework.checker.nullness.qual.Nullable; | ||
|
||
class TracingCallFactory implements Call.Factory { | ||
private static final Cache<Request, Context> contextsByRequest = | ||
Cache.newBuilder().setWeakKeys().build(); | ||
|
||
@Nullable private static MethodHandle timeoutMethodHandle; | ||
@Nullable private static MethodHandle cloneMethodHandle; | ||
|
||
static { | ||
MethodHandles.Lookup lookup = MethodHandles.publicLookup(); | ||
try { | ||
MethodType methodType = MethodType.methodType(Timeout.class); | ||
timeoutMethodHandle = lookup.findVirtual(Call.class, "timeout", methodType); | ||
} catch (NoSuchMethodException | IllegalAccessException e) { | ||
timeoutMethodHandle = null; | ||
} | ||
try { | ||
MethodType methodType = MethodType.methodType(Call.class); | ||
cloneMethodHandle = lookup.findVirtual(Call.class, "clone", methodType); | ||
} catch (NoSuchMethodException | IllegalAccessException e) { | ||
cloneMethodHandle = null; | ||
} | ||
} | ||
|
||
private final OkHttpClient okHttpClient; | ||
|
||
TracingCallFactory(OkHttpClient okHttpClient) { | ||
this.okHttpClient = okHttpClient; | ||
} | ||
|
||
@Nullable | ||
static Context getCallingContextForRequest(Request request) { | ||
return contextsByRequest.get(request); | ||
} | ||
|
||
@Override | ||
public Call newCall(Request request) { | ||
Context callingContext = Context.current(); | ||
Request requestCopy = request.newBuilder().build(); | ||
contextsByRequest.put(requestCopy, callingContext); | ||
return new TracingCall(okHttpClient.newCall(requestCopy), callingContext); | ||
} | ||
|
||
static class TracingCall implements Call { | ||
private final Call delegate; | ||
private final Context callingContext; | ||
|
||
TracingCall(Call delegate, Context callingContext) { | ||
this.delegate = delegate; | ||
this.callingContext = callingContext; | ||
} | ||
|
||
@Override | ||
public void cancel() { | ||
delegate.cancel(); | ||
} | ||
|
||
@Override | ||
public Call clone() throws CloneNotSupportedException { | ||
if (cloneMethodHandle == null) { | ||
return (Call) super.clone(); | ||
} | ||
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) cloneMethodHandle.invoke(delegate), Context.current()); | ||
} catch (Throwable e) { | ||
return (Call) super.clone(); | ||
} | ||
} | ||
|
||
@Override | ||
public void enqueue(Callback callback) { | ||
delegate.enqueue(new TracingCallback(callback, callingContext)); | ||
} | ||
|
||
@Override | ||
public Response execute() throws IOException { | ||
try (Scope scope = callingContext.makeCurrent()) { | ||
return delegate.execute(); | ||
} | ||
} | ||
|
||
@Override | ||
public boolean isCanceled() { | ||
return delegate.isCanceled(); | ||
} | ||
|
||
@Override | ||
public boolean isExecuted() { | ||
return delegate.isExecuted(); | ||
} | ||
|
||
@Override | ||
public Request request() { | ||
return delegate.request(); | ||
} | ||
|
||
// @Override method was introduced in 3.12 | ||
public Timeout timeout() { | ||
if (timeoutMethodHandle == null) { | ||
return Timeout.NONE; | ||
} | ||
try { | ||
return (Timeout) timeoutMethodHandle.invoke(delegate); | ||
} catch (Throwable e) { | ||
// do nothing...we're before 3.12, or something else has gone wrong that we can't do | ||
// anything about. | ||
return Timeout.NONE; | ||
} | ||
} | ||
|
||
private static class TracingCallback implements Callback { | ||
private final Callback delegate; | ||
private final Context callingContext; | ||
|
||
public TracingCallback(Callback delegate, Context callingContext) { | ||
this.delegate = delegate; | ||
this.callingContext = callingContext; | ||
} | ||
|
||
@Override | ||
public void onFailure(Call call, IOException e) { | ||
try (Scope scope = callingContext.makeCurrent()) { | ||
delegate.onFailure(call, e); | ||
} | ||
} | ||
|
||
@Override | ||
public void onResponse(Call call, Response response) throws IOException { | ||
try (Scope scope = callingContext.makeCurrent()) { | ||
delegate.onResponse(call, response); | ||
} | ||
} | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -26,8 +26,11 @@ final class TracingInterceptor implements Interceptor { | |
|
||
@Override | ||
public Response intercept(Chain chain) throws IOException { | ||
Context parentContext = Context.current(); | ||
Request request = chain.request(); | ||
Context parentContext = TracingCallFactory.getCallingContextForRequest(request); | ||
if (parentContext == null) { | ||
parentContext = Context.current(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. does this seem like the right default, or should we not try to propagate at all if we don't have one in the cache? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's not propagate at all There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we don't propagate, then interceptor-only users who have wired up a Dispatcher and only use synchronous calls will lose the context propagation, correct? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah ok |
||
} | ||
|
||
if (!instrumenter.shouldStart(parentContext, request)) { | ||
return chain.proceed(chain.request()); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,18 +5,16 @@ | |
|
||
package io.opentelemetry.instrumentation.okhttp.v3_0 | ||
|
||
import io.opentelemetry.context.Context | ||
|
||
import io.opentelemetry.instrumentation.test.LibraryTestTrait | ||
import okhttp3.Dispatcher | ||
import okhttp3.Call | ||
import okhttp3.OkHttpClient | ||
|
||
class OkHttp3Test extends AbstractOkHttp3Test implements LibraryTestTrait { | ||
|
||
@Override | ||
OkHttpClient.Builder configureClient(OkHttpClient.Builder clientBuilder) { | ||
return clientBuilder | ||
// The double "new Dispatcher" style is the simplest way to decorate the default executor. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Well it's also nice for this to be gone :) |
||
.dispatcher(new Dispatcher(Context.taskWrapping(new Dispatcher().executorService()))) | ||
.addInterceptor(OkHttpTracing.create(getOpenTelemetry()).newInterceptor()) | ||
Call.Factory createCallFactory(OkHttpClient.Builder clientBuilder) { | ||
return OkHttpTracing.create(getOpenTelemetry()).newCallFactory(clientBuilder.build()) | ||
} | ||
|
||
// library instrumentation doesn't have a good way of suppressing nested CLIENT spans yet | ||
|
@@ -25,8 +23,4 @@ class OkHttp3Test extends AbstractOkHttp3Test implements LibraryTestTrait { | |
false | ||
} | ||
|
||
@Override | ||
boolean testCausalityWithCallback() { | ||
false | ||
} | ||
Comment on lines
-28
to
-31
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🥳 |
||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good move