-
Notifications
You must be signed in to change notification settings - Fork 911
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement a Call.Factory for okhttp 3.x+ library instrumentation (#3812)
* Add a README for the okhttp library instrumentation. * Create new instrumentation for okhttp3 4.x+ This exposes a `Call.Factory` which will properly handle internal context propagation when used with async callbacks. * update the "4.x" instrumentation to support 3.x * Get rid of the 4.x instrumentation, and just update the 3.x instrumentation to work * updates from PR review * replace old reflection with method handle usage * Apply suggestions from code review Co-authored-by: Trask Stalnaker <trask.stalnaker@gmail.com> Co-authored-by: Trask Stalnaker <trask.stalnaker@gmail.com>
- Loading branch information
Showing
10 changed files
with
293 additions
and
56 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | ||
} | ||
} | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
158 changes: 158 additions & 0 deletions
158
...ibrary/src/main/java/io/opentelemetry/instrumentation/okhttp/v3_0/TracingCallFactory.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.