Add instrumentation for okhttp 2.2+ (DataDog/dd-trace-java#1402)

This commit is contained in:
Tyler Benson 2020-04-29 14:11:39 -04:00 committed by Trask Stalnaker
parent c63b4fd9a3
commit 5ecd8cb81e
17 changed files with 406 additions and 18 deletions

View File

@ -6,7 +6,7 @@ spotless {
target 'src/**/*.java'
}
groovy {
licenseHeaderFile rootProject.file('gradle/enforcement/spotless.license.java'), '(package|import|public)'
licenseHeaderFile rootProject.file('gradle/enforcement/spotless.license.java'), '(package|import|class)'
}
scala {
licenseHeaderFile rootProject.file('gradle/enforcement/spotless.license.java'), '(package|import|public)'

View File

@ -0,0 +1,35 @@
apply from: "${rootDir}/gradle/instrumentation.gradle"
apply plugin: 'org.unbroken-dome.test-sets'
/*
Note: The Interceptor class for OkHttp was not introduced until 2.2+, so we need to make sure the
instrumentation is not loaded unless the dependency is 2.2+.
*/
muzzle {
pass {
group = "com.squareup.okhttp"
module = "okhttp"
versions = "[2.2,3)"
assertInverse = true
}
}
testSets {
latestDepTest {
dirName = 'test'
}
}
dependencies {
compileOnly(group: 'com.squareup.okhttp', name: 'okhttp', version: '2.2.0')
compile project(':auto-tooling')
testCompile project(':testing')
testCompile project(':instrumentation:java-concurrent')
testCompile group: 'com.squareup.okhttp', name: 'okhttp', version: '2.2.0'
latestDepTestCompile group: 'com.squareup.okhttp', name: 'okhttp', version: '[2.6,3)'
}

View File

@ -0,0 +1,70 @@
/*
* Copyright The OpenTelemetry Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.opentelemetry.auto.instrumentation.okhttp.v2_2;
import static net.bytebuddy.matcher.ElementMatchers.isConstructor;
import static net.bytebuddy.matcher.ElementMatchers.named;
import com.google.auto.service.AutoService;
import com.squareup.okhttp.Interceptor;
import com.squareup.okhttp.OkHttpClient;
import io.opentelemetry.auto.tooling.Instrumenter;
import java.util.Collections;
import java.util.Map;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;
@AutoService(Instrumenter.class)
public class OkHttp2Instrumentation extends Instrumenter.Default {
public OkHttp2Instrumentation() {
super("okhttp", "okhttp-2");
}
@Override
public ElementMatcher<? super TypeDescription> typeMatcher() {
return named("com.squareup.okhttp.OkHttpClient");
}
@Override
public String[] helperClassNames() {
return new String[] {
packageName + ".RequestBuilderInjectAdapter",
packageName + ".OkHttpClientDecorator",
packageName + ".TracingInterceptor",
};
}
@Override
public Map<? extends ElementMatcher<? super MethodDescription>, String> transformers() {
return Collections.singletonMap(
isConstructor(), OkHttp2Instrumentation.class.getName() + "$OkHttp2ClientAdvice");
}
public static class OkHttp2ClientAdvice {
@Advice.OnMethodExit
public static void addTracingInterceptor(@Advice.This final OkHttpClient client) {
for (final Interceptor interceptor : client.interceptors()) {
if (interceptor instanceof TracingInterceptor) {
return;
}
}
client.interceptors().add(new TracingInterceptor());
}
}
}

View File

@ -0,0 +1,46 @@
/*
* Copyright The OpenTelemetry Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.opentelemetry.auto.instrumentation.okhttp.v2_2;
import com.squareup.okhttp.Request;
import com.squareup.okhttp.Response;
import io.opentelemetry.OpenTelemetry;
import io.opentelemetry.auto.bootstrap.instrumentation.decorator.HttpClientDecorator;
import io.opentelemetry.trace.Tracer;
import java.net.URI;
import java.net.URISyntaxException;
public class OkHttpClientDecorator extends HttpClientDecorator<Request, Response> {
public static final OkHttpClientDecorator DECORATE = new OkHttpClientDecorator();
public static final Tracer TRACER =
OpenTelemetry.getTracerProvider().get("io.opentelemetry.auto.okhttp-2.2");
@Override
protected String method(final Request request) {
return request.method();
}
@Override
protected URI url(final Request request) throws URISyntaxException {
return request.url().toURI();
}
@Override
protected Integer status(final Response response) {
return response.code();
}
}

View File

@ -0,0 +1,28 @@
/*
* Copyright The OpenTelemetry Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.opentelemetry.auto.instrumentation.okhttp.v2_2;
import com.squareup.okhttp.Request;
import io.opentelemetry.context.propagation.HttpTextFormat;
public class RequestBuilderInjectAdapter implements HttpTextFormat.Setter<Request.Builder> {
public static final RequestBuilderInjectAdapter SETTER = new RequestBuilderInjectAdapter();
@Override
public void set(final Request.Builder carrier, final String key, final String value) {
carrier.addHeader(key, value);
}
}

View File

@ -0,0 +1,65 @@
/*
* Copyright The OpenTelemetry Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.opentelemetry.auto.instrumentation.okhttp.v2_2;
import static io.opentelemetry.auto.instrumentation.okhttp.v2_2.OkHttpClientDecorator.DECORATE;
import static io.opentelemetry.auto.instrumentation.okhttp.v2_2.OkHttpClientDecorator.TRACER;
import static io.opentelemetry.context.ContextUtils.withScopedContext;
import static io.opentelemetry.trace.Span.Kind.CLIENT;
import static io.opentelemetry.trace.TracingContextUtils.withSpan;
import com.squareup.okhttp.Interceptor;
import com.squareup.okhttp.Request;
import com.squareup.okhttp.Response;
import io.grpc.Context;
import io.opentelemetry.OpenTelemetry;
import io.opentelemetry.context.Scope;
import io.opentelemetry.trace.Span;
import java.io.IOException;
public class TracingInterceptor implements Interceptor {
@Override
public Response intercept(final Chain chain) throws IOException {
final Span span =
TRACER
.spanBuilder(DECORATE.spanNameForRequest(chain.request()))
.setSpanKind(CLIENT)
.startSpan();
DECORATE.afterStart(span);
DECORATE.onRequest(span, chain.request());
final Context context = withSpan(span, Context.current());
final Request.Builder requestBuilder = chain.request().newBuilder();
OpenTelemetry.getPropagators()
.getHttpTextFormat()
.inject(context, requestBuilder, RequestBuilderInjectAdapter.SETTER);
final Response response;
try (final Scope scope = withScopedContext(context)) {
response = chain.proceed(requestBuilder.build());
} catch (final Exception e) {
DECORATE.onError(span, e);
span.end();
throw e;
}
DECORATE.onResponse(span, response);
DECORATE.beforeFinish(span);
span.end();
return response;
}
}

View File

@ -0,0 +1,26 @@
/*
* Copyright The OpenTelemetry Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
class HeadersUtil {
static headersToArray(Map<String, String> headers) {
String[] headersArr = new String[headers.size() * 2]
headers.eachWithIndex { k, v, i ->
headersArr[i] = k
headersArr[i + 1] = v
}
headersArr
}
}

View File

@ -0,0 +1,61 @@
/*
* Copyright The OpenTelemetry Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import com.squareup.okhttp.Callback
import com.squareup.okhttp.Headers
import com.squareup.okhttp.MediaType
import com.squareup.okhttp.Request
import com.squareup.okhttp.RequestBody
import com.squareup.okhttp.Response
import com.squareup.okhttp.internal.http.HttpMethod
import java.util.concurrent.CountDownLatch
import java.util.concurrent.atomic.AtomicReference
import static java.util.concurrent.TimeUnit.SECONDS
class OkHttp2AsyncTest extends OkHttp2Test {
@Override
int doRequest(String method, URI uri, Map<String, String> headers, Closure callback) {
def body = HttpMethod.requiresRequestBody(method) ? RequestBody.create(MediaType.parse("text/plain"), "") : null
def request = new Request.Builder()
.url(uri.toURL())
.method(method, body)
.headers(Headers.of(HeadersUtil.headersToArray(headers)))
.build()
AtomicReference<Response> responseRef = new AtomicReference()
AtomicReference<Exception> exRef = new AtomicReference()
def latch = new CountDownLatch(1)
client.newCall(request).enqueue(new Callback() {
void onResponse(Response response) {
responseRef.set(response)
callback?.call()
latch.countDown()
}
void onFailure(Request req, IOException e) {
exRef.set(e)
latch.countDown()
}
})
latch.await(10, SECONDS)
if (exRef.get() != null) {
throw exRef.get()
}
return responseRef.get().code()
}
}

View File

@ -0,0 +1,56 @@
/*
* Copyright The OpenTelemetry Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import com.squareup.okhttp.Headers
import com.squareup.okhttp.MediaType
import com.squareup.okhttp.OkHttpClient
import com.squareup.okhttp.Request
import com.squareup.okhttp.RequestBody
import com.squareup.okhttp.internal.http.HttpMethod
import io.opentelemetry.auto.test.base.HttpClientTest
import spock.lang.Shared
import spock.lang.Timeout
import java.util.concurrent.TimeUnit
@Timeout(5)
class OkHttp2Test extends HttpClientTest {
@Shared
def client = new OkHttpClient()
def setupSpec() {
client.setConnectTimeout(CONNECT_TIMEOUT_MS, TimeUnit.MILLISECONDS)
client.setReadTimeout(READ_TIMEOUT_MS, TimeUnit.MILLISECONDS)
client.setWriteTimeout(READ_TIMEOUT_MS, TimeUnit.MILLISECONDS)
}
@Override
int doRequest(String method, URI uri, Map<String, String> headers, Closure callback) {
def body = HttpMethod.requiresRequestBody(method) ? RequestBody.create(MediaType.parse("text/plain"), "") : null
def request = new Request.Builder()
.url(uri.toURL())
.method(method, body)
.headers(Headers.of(HeadersUtil.headersToArray(headers)))
.build()
def response = client.newCall(request).execute()
callback?.call()
return response.code()
}
boolean testRedirects() {
false
}
}

View File

@ -28,16 +28,12 @@ not transitive.
dependencies {
compileOnly(group: 'com.squareup.okhttp3', name: 'okhttp', version: '3.0.0')
compile(project(':auto-tooling')) {
exclude module: 'okhttp'
}
compile project(':auto-tooling')
testCompile(project(':testing')) {
exclude module: 'okhttp'
}
testCompile(project(':instrumentation:java-concurrent')) {
exclude module: 'okhttp'
}
testCompile project(':instrumentation:java-concurrent')
testCompile group: 'com.squareup.okhttp3', name: 'okhttp', version: '3.0.0'
// 4.x.x-alpha has been released and it looks like there are lots of incompatible changes

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.opentelemetry.auto.instrumentation.okhttp;
package io.opentelemetry.auto.instrumentation.okhttp.v3_0;
import static java.util.Collections.singletonMap;
import static net.bytebuddy.matcher.ElementMatchers.isConstructor;

View File

@ -13,9 +13,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.opentelemetry.auto.instrumentation.okhttp;
package io.opentelemetry.auto.instrumentation.okhttp.v3_0;
import io.opentelemetry.OpenTelemetry;
import io.opentelemetry.auto.bootstrap.instrumentation.decorator.HttpClientDecorator;
import io.opentelemetry.trace.Tracer;
import java.net.URI;
import okhttp3.Request;
import okhttp3.Response;
@ -23,6 +25,9 @@ import okhttp3.Response;
public class OkHttpClientDecorator extends HttpClientDecorator<Request, Response> {
public static final OkHttpClientDecorator DECORATE = new OkHttpClientDecorator();
public static final Tracer TRACER =
OpenTelemetry.getTracerProvider().get("io.opentelemetry.auto.okhttp-3.0");
@Override
protected String method(final Request httpRequest) {
return httpRequest.method();

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.opentelemetry.auto.instrumentation.okhttp;
package io.opentelemetry.auto.instrumentation.okhttp.v3_0;
import io.opentelemetry.context.propagation.HttpTextFormat;
import okhttp3.Request;

View File

@ -13,10 +13,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.opentelemetry.auto.instrumentation.okhttp;
package io.opentelemetry.auto.instrumentation.okhttp.v3_0;
import static io.opentelemetry.auto.instrumentation.okhttp.OkHttpClientDecorator.DECORATE;
import static io.opentelemetry.auto.instrumentation.okhttp.RequestBuilderInjectAdapter.SETTER;
import static io.opentelemetry.auto.instrumentation.okhttp.v3_0.OkHttpClientDecorator.DECORATE;
import static io.opentelemetry.auto.instrumentation.okhttp.v3_0.OkHttpClientDecorator.TRACER;
import static io.opentelemetry.context.ContextUtils.withScopedContext;
import static io.opentelemetry.trace.Span.Kind.CLIENT;
import static io.opentelemetry.trace.TracingContextUtils.withSpan;
@ -25,7 +25,6 @@ import io.grpc.Context;
import io.opentelemetry.OpenTelemetry;
import io.opentelemetry.context.Scope;
import io.opentelemetry.trace.Span;
import io.opentelemetry.trace.Tracer;
import java.io.IOException;
import lombok.extern.slf4j.Slf4j;
import okhttp3.Interceptor;
@ -34,8 +33,6 @@ import okhttp3.Response;
@Slf4j
public class TracingInterceptor implements Interceptor {
public static final Tracer TRACER =
OpenTelemetry.getTracerProvider().get("io.opentelemetry.auto.okhttp-3.0");
@Override
public Response intercept(final Chain chain) throws IOException {
@ -51,7 +48,9 @@ public class TracingInterceptor implements Interceptor {
final Context context = withSpan(span, Context.current());
final Request.Builder requestBuilder = chain.request().newBuilder();
OpenTelemetry.getPropagators().getHttpTextFormat().inject(context, requestBuilder, SETTER);
OpenTelemetry.getPropagators()
.getHttpTextFormat()
.inject(context, requestBuilder, RequestBuilderInjectAdapter.SETTER);
final Response response;
try (final Scope scope = withScopedContext(context)) {

View File

@ -111,7 +111,8 @@ include ':instrumentation:mongo:mongo-common'
include ':instrumentation:netty:netty-3.8'
include ':instrumentation:netty:netty-4.0'
include ':instrumentation:netty:netty-4.1'
include ':instrumentation:okhttp-3.0'
include ':instrumentation:okhttp:okhttp-2.2'
include ':instrumentation:okhttp:okhttp-3.0'
include ':instrumentation:opentelemetry-api-0.4'
include ':instrumentation:play:play-2.3'
include ':instrumentation:play:play-2.4'