ApacheHttpClient bytebuddy instrumentation and tests.
MODIFY
dd-java-agent-ittests/dd-java-agent-ittests.gradle
dd-java-agent-ittests/dd-java-agent-ittests.gradle
@@ -21,11 +21,10 @@ dependencies {
testCompile group: 'org.eclipse.jetty', name: 'jetty-servlet', version: '9.4.1.v20170120'
testCompile group: 'org.apache.tomcat.embed', name: 'tomcat-embed-core', version: '8.0.41'
testCompile group: 'org.apache.tomcat.embed', name: 'tomcat-embed-jasper', version: '8.0.41'
- testCompile(group: 'com.amazonaws', name: 'aws-java-sdk', version: '1.11.119') {
- exclude(module: 'httpclient')
- }
+ // note: aws transitively pulls in apache-httpclient, which we also test against:
+ // testCompile group: 'org.apache.httpcomponents', name: 'httpclient', version: '4.5.3'
+ testCompile(group: 'com.amazonaws', name: 'aws-java-sdk', version: '1.11.119')
testCompile group: 'com.squareup.okhttp3', name: 'okhttp', version: '3.6.0'
- testCompile group: 'org.apache.httpcomponents', name: 'httpclient', version: '4.5.3'
testCompile(group: 'com.datastax.cassandra', name: 'cassandra-driver-core', version: '3.2.0')
testCompile(group: 'org.cassandraunit', name: 'cassandra-unit', version: '3.1.3.2') {
exclude(module: 'netty-handler')

MODIFY
dd-java-agent-ittests/src/test/java/com/datadoghq/agent/integration/ApacheHTTPClientTest.java
dd-java-agent-ittests/src/test/java/com/datadoghq/agent/integration/ApacheHTTPClientTest.java
@@ -2,24 +2,135 @@ package com.datadoghq.agent.integration;
import static org.assertj.core.api.Assertions.assertThat;
+import com.datadoghq.trace.DDBaseSpan;
+import com.datadoghq.trace.DDTracer;
+import com.datadoghq.trace.writer.ListWriter;
+import io.opentracing.tag.Tags;
+import java.net.URI;
+import java.util.List;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.HttpClientBuilder;
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.BeforeClass;
import org.junit.Test;
public class ApacheHTTPClientTest {
- static {
+ private static final ListWriter writer = new ListWriter();
+ private static final DDTracer tracer = new DDTracer(writer);
+
+ @BeforeClass
+ public static void setup() throws Exception {
+ TestUtils.registerOrReplaceGlobalTracer(tracer);
+ TestHttpServer.startServer();
+ }
+
+ @AfterClass
+ public static void stopServer() throws Exception {
+ TestHttpServer.stopServer();
+ }
+
+ @Before
+ public void beforeEachTest() {
+ writer.clear();
+ }
+
+ @Test
+ public void propagatedTrace() throws Exception {
+ final HttpClientBuilder builder = HttpClientBuilder.create();
+
+ final HttpClient client = builder.build();
+ TestUtils.runUnderTrace(
+ "someTrace",
+ new Runnable() {
+ @Override
+ public void run() {
try {
- // Since the HttpClientBuilder initializer doesn't work, invoke manually.
- Class.forName("com.datadoghq.agent.InstrumentationRulesManager")
- .getMethod("registerClassLoad", Object.class)
- .invoke(null, Thread.currentThread().getContextClassLoader());
+ HttpResponse response =
+ client.execute(new HttpGet(new URI("http://localhost:" + TestHttpServer.PORT)));
+ assertThat(response.getStatusLine().getStatusCode()).isEqualTo(200);
} catch (Exception e) {
- System.err.println("clinit error: " + e.getMessage());
+ e.printStackTrace();
+ Assert.fail("Error running client");
}
}
+ });
+ // one trace on the server, one trace on the client
+ assertThat(writer.size()).isEqualTo(2);
+ final List<DDBaseSpan<?>> serverTrace = writer.get(0);
+ assertThat(serverTrace.size()).isEqualTo(1);
+
+ final List<DDBaseSpan<?>> clientTrace = writer.get(1);
+ assertThat(clientTrace.size()).isEqualTo(3);
+ assertThat(clientTrace.get(0).getOperationName()).isEqualTo("someTrace");
+ // our instrumentation makes 2 spans for apache-httpclient
+ final DDBaseSpan<?> localSpan = clientTrace.get(1);
+ assertThat(localSpan.getTags().get(Tags.COMPONENT.getKey())).isEqualTo("apache-httpclient");
+ assertThat(localSpan.getOperationName()).isEqualTo("GET");
+
+ final DDBaseSpan<?> clientSpan = clientTrace.get(2);
+ assertThat(clientSpan.getOperationName()).isEqualTo("GET");
+ assertThat(clientSpan.getTags().get(Tags.HTTP_METHOD.getKey())).isEqualTo("GET");
+ assertThat(clientSpan.getTags().get(Tags.HTTP_STATUS.getKey())).isEqualTo(200);
+ assertThat(clientSpan.getTags().get(Tags.HTTP_URL.getKey())).isEqualTo("http://localhost:8089");
+ assertThat(clientSpan.getTags().get(Tags.PEER_HOSTNAME.getKey())).isEqualTo("localhost");
+ assertThat(clientSpan.getTags().get(Tags.PEER_PORT.getKey())).isEqualTo(8089);
+ assertThat(clientSpan.getTags().get(Tags.SPAN_KIND.getKey())).isEqualTo(Tags.SPAN_KIND_CLIENT);
+
+ // client trace propagates to server
+ assertThat(clientSpan.getTraceId()).isEqualTo(serverTrace.get(0).getTraceId());
+ // server span is parented under http client
+ assertThat(clientSpan.getSpanId()).isEqualTo(serverTrace.get(0).getParentId());
+ }
@Test
- public void test() throws Exception {
+ public void unPropagatedTrace() throws Exception {
final HttpClientBuilder builder = HttpClientBuilder.create();
- assertThat(builder.getClass().getSimpleName()).isEqualTo("TracingHttpClientBuilder");
+
+ final HttpClient client = builder.build();
+ TestUtils.runUnderTrace(
+ "someTrace",
+ new Runnable() {
+ @Override
+ public void run() {
+ try {
+ HttpResponse response =
+ client.execute(
+ new HttpGet(
+ new URI(
+ "http://localhost:"
+ + TestHttpServer.PORT
+ + "?"
+ + TestHttpServer.IS_DD_SERVER
+ + "=false")));
+ assertThat(response.getStatusLine().getStatusCode()).isEqualTo(200);
+ } catch (Exception e) {
+ e.printStackTrace();
+ Assert.fail("Error running client");
+ }
+ }
+ });
+ // only one trace (client).
+ assertThat(writer.size()).isEqualTo(1);
+ final List<DDBaseSpan<?>> clientTrace = writer.get(0);
+ assertThat(clientTrace.size()).isEqualTo(3);
+ assertThat(clientTrace.get(0).getOperationName()).isEqualTo("someTrace");
+ // our instrumentation makes 2 spans for apache-httpclient
+ final DDBaseSpan<?> localSpan = clientTrace.get(1);
+ assertThat(localSpan.getTags().get(Tags.COMPONENT.getKey())).isEqualTo("apache-httpclient");
+ assertThat(localSpan.getOperationName()).isEqualTo("GET");
+
+ final DDBaseSpan<?> clientSpan = clientTrace.get(2);
+ assertThat(clientSpan.getOperationName()).isEqualTo("GET");
+ assertThat(clientSpan.getTags().get(Tags.HTTP_METHOD.getKey())).isEqualTo("GET");
+ assertThat(clientSpan.getTags().get(Tags.HTTP_STATUS.getKey())).isEqualTo(200);
+ assertThat(clientSpan.getTags().get(Tags.HTTP_URL.getKey()))
+ .isEqualTo("http://localhost:8089?is-dd-server=false");
+ assertThat(clientSpan.getTags().get(Tags.PEER_HOSTNAME.getKey())).isEqualTo("localhost");
+ assertThat(clientSpan.getTags().get(Tags.PEER_PORT.getKey())).isEqualTo(8089);
+ assertThat(clientSpan.getTags().get(Tags.SPAN_KIND.getKey())).isEqualTo(Tags.SPAN_KIND_CLIENT);
}
}

MODIFY
dd-java-agent-ittests/src/test/java/com/datadoghq/agent/integration/TestUtils.java
dd-java-agent-ittests/src/test/java/com/datadoghq/agent/integration/TestUtils.java
package com.datadoghq.agent.integration;
+import io.opentracing.ActiveSpan;
import io.opentracing.Tracer;
import io.opentracing.util.GlobalTracer;
import java.lang.reflect.Field;
@@ -15,4 +16,13 @@ public class TestUtils {
field.set(null, tracer);
}
}
+
+ public static void runUnderTrace(final String rootOperationName, Runnable r) {
+ ActiveSpan rootSpan = GlobalTracer.get().buildSpan(rootOperationName).startActive();
+ try {
+ r.run();
+ } finally {
+ rootSpan.deactivate();
+ }
+ }
}

MODIFY
dd-java-agent/dd-java-agent.gradle
dd-java-agent/dd-java-agent.gradle
@@ -21,6 +21,9 @@ dependencies {
compile project(':dd-java-agent:tooling')
compile project(':dd-trace-annotations')
+ compile(project(':dd-java-agent:integrations:apache-httpclient-4.3')) {
+ transitive = false
+ }
compile(project(':dd-java-agent:integrations:aws-sdk')) {
transitive = false
}

ADD
dd-java-agent/integrations/apache-httpclient-4.3/apache-httpclient-4.3.gradle
+apply plugin: 'version-scan'
+
+versionScan {
+ group = "org.apache.httpcomponents"
+ module = "httpclient"
+ versions = "[4.3,)"
+ legacyGroup = "commons-httpclient"
+ legacyModule = "commons-httpclient"
+ verifyPresent = [
+ // The commented out classes are required for the instrumentation (this is checked by our bytebuddy rules).
+ // Once the verifier can report on the failed versions these classes can be commented out and the pass range can be widened.
+
+ // "org.apache.http.HttpException" : null,
+ // "org.apache.http.HttpRequest" : null,
+ // "org.apache.http.client.RedirectStrategy" : null,
+ "org.apache.http.client.methods.CloseableHttpResponse" : null,
+ "org.apache.http.client.methods.HttpExecutionAware" : null,
+ "org.apache.http.client.methods.HttpRequestWrapper" : null,
+ "org.apache.http.client.protocol.HttpClientContext" : null,
+ // "org.apache.http.conn.routing.HttpRoute" : null,
+ "org.apache.http.impl.execchain.ClientExecChain": null
+ ]
+}
+
+apply from: "${rootDir}/gradle/java.gradle"
+
+dependencies {
+ compile group: 'org.apache.httpcomponents', name: 'httpclient', version: '4.3'
+
+ compile project(':dd-trace')
+ compile project(':dd-java-agent:integrations:helpers')
+ compile project(':dd-java-agent:tooling')
+
+ compile deps.bytebuddy
+ compile deps.opentracing
+}

ADD
dd-java-agent/integrations/apache-httpclient-4.3/src/main/java/dd/inst/apachehttpclient/ApacheHttpClientInstrumentation.java
+package dd.inst.apachehttpclient;
+
+import static dd.trace.ClassLoaderMatcher.classLoaderHasClasses;
+import static dd.trace.ExceptionHandlers.defaultExceptionHandler;
+import static net.bytebuddy.matcher.ElementMatchers.*;
+
+import com.datadoghq.agent.integration.DDTracingClientExec;
+import com.google.auto.service.AutoService;
+import dd.trace.Instrumenter;
+import io.opentracing.util.GlobalTracer;
+import net.bytebuddy.agent.builder.AgentBuilder;
+import net.bytebuddy.asm.Advice;
+import org.apache.http.impl.client.DefaultRedirectStrategy;
+import org.apache.http.impl.execchain.ClientExecChain;
+
+@AutoService(Instrumenter.class)
+public class ApacheHttpClientInstrumentation implements Instrumenter {
+
+ @Override
+ public AgentBuilder instrument(AgentBuilder agentBuilder) {
+ return agentBuilder
+ .type(
+ named("org.apache.http.impl.client.HttpClientBuilder"),
+ classLoaderHasClasses(
+ "org.apache.http.HttpException",
+ "org.apache.http.HttpRequest",
+ "org.apache.http.client.RedirectStrategy",
+ "org.apache.http.client.methods.CloseableHttpResponse",
+ "org.apache.http.client.methods.HttpExecutionAware",
+ "org.apache.http.client.methods.HttpRequestWrapper",
+ "org.apache.http.client.protocol.HttpClientContext",
+ "org.apache.http.conn.routing.HttpRoute",
+ "org.apache.http.impl.execchain.ClientExecChain"))
+ .transform(
+ new AgentBuilder.Transformer.ForAdvice()
+ .advice(
+ isMethod().and(named("decorateProtocolExec")),
+ ApacheHttpClientAdvice.class.getName())
+ .withExceptionHandler(defaultExceptionHandler()))
+ .asDecorator();
+ }
+
+ public static class ApacheHttpClientAdvice {
+ /** Strategy: add our tracing exec to the apache exec chain. */
+ @Advice.OnMethodExit(suppress = Throwable.class)
+ public static void addTracingExec(@Advice.Return(readOnly = false) ClientExecChain execChain) {
+ execChain =
+ new DDTracingClientExec(
+ execChain, DefaultRedirectStrategy.INSTANCE, false, GlobalTracer.get());
+ }
+ }
+}

DELETE
dd-java-agent/integrations/apache-httpclient/apache-httpclient.gradle
-apply plugin: 'version-scan'
-
-versionScan {
- group = "org.apache.httpcomponents"
- module = "httpclient"
- versions = "[4.3,)"
- legacyGroup = "commons-httpclient"
- legacyModule = "commons-httpclient"
- verifyPresent = [
- "org.apache.http.conn.SchemePortResolver": null,
- "org.apache.http.conn.ssl.SSLContexts" : null,
- ]
-}

MODIFY
dd-java-agent/integrations/helpers/helpers.gradle
dd-java-agent/integrations/helpers/helpers.gradle
@@ -17,14 +17,13 @@ dependencies {
compile group: 'io.opentracing.contrib', name: 'opentracing-okhttp3', version: '0.0.5'
compile group: 'io.opentracing.contrib', name: 'opentracing-aws-sdk', version: '0.0.2'
compile group: 'io.opentracing.contrib', name: 'opentracing-cassandra-driver', version: '0.0.2'
- compile group: 'io.opentracing.contrib', name: 'opentracing-apache-httpclient', version: '0.0.2'
compileOnly group: 'org.mongodb', name: 'mongo-java-driver', version: '3.4.2'
compileOnly group: 'org.mongodb', name: 'mongodb-driver-async', version: '3.4.2'
compileOnly group: 'com.squareup.okhttp3', name: 'okhttp', version: '3.6.0'
compileOnly group: 'javax.jms', name: 'javax.jms-api', version: '2.0.1'
compileOnly group: 'com.datastax.cassandra', name: 'cassandra-driver-core', version: '3.2.0'
- compileOnly group: 'org.apache.httpcomponents', name: 'httpclient', version: '4.5.3'
+ compileOnly group: 'org.apache.httpcomponents', name: 'httpclient', version: '4.3'
}
jar {

DELETE
dd-java-agent/integrations/helpers/src/main/java/com/datadoghq/agent/integration/ApacheHTTPClientHelper.java
-package com.datadoghq.agent.integration;
-
-import io.opentracing.contrib.apache.http.client.TracingHttpClientBuilder;
-import org.apache.http.impl.client.HttpClientBuilder;
-import org.jboss.byteman.rule.Rule;
-
-/** Patch the Apache HTTP Client during the building steps */
-public class ApacheHTTPClientHelper extends DDAgentTracingHelper<HttpClientBuilder> {
-
- public ApacheHTTPClientHelper(Rule rule) {
- super(rule);
- }
-
- @Override
- public HttpClientBuilder patch(HttpClientBuilder builder) {
- return super.patch(builder);
- }
-
- /**
- * Strategy: We replace the legacy builder by a new instance providing by the opentracing
- * contribution when the builder is instantiate. @see
- * org.apache.http.impl.client.HttpClientBuilder.create() method
- *
- * @param builder The legacy builder instance
- * @return A tracing builder instance (new reference)
- * @throws Exception
- */
- protected HttpClientBuilder doPatch(HttpClientBuilder builder) throws Exception {
- return new TracingHttpClientBuilder();
- }
-}

ADD
dd-java-agent/integrations/helpers/src/main/java/com/datadoghq/agent/integration/DDTracingClientExec.java
+package com.datadoghq.agent.integration;
+
+import io.opentracing.ActiveSpan;
+import io.opentracing.Tracer;
+import io.opentracing.propagation.Format;
+import io.opentracing.propagation.TextMap;
+import io.opentracing.tag.Tags;
+import java.io.IOException;
+import java.net.URI;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.Map;
+import org.apache.http.HttpException;
+import org.apache.http.HttpRequest;
+import org.apache.http.client.RedirectStrategy;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpExecutionAware;
+import org.apache.http.client.methods.HttpRequestWrapper;
+import org.apache.http.client.protocol.HttpClientContext;
+import org.apache.http.conn.routing.HttpRoute;
+import org.apache.http.impl.execchain.ClientExecChain;
+
+/**
+ * Tracing is added before {@link org.apache.http.impl.execchain.ProtocolExec} which is invoked as
+ * the next to last. Note that {@link org.apache.http.impl.execchain.RedirectExec} is invoked before
+ * so this exec has to deal with redirects.
+ */
+public class DDTracingClientExec implements ClientExecChain {
+ private static final String COMPONENT_NAME = "apache-httpclient";
+ /**
+ * Id of {@link HttpClientContext#setAttribute(String, Object)} representing span associated with
+ * the current client processing. Referenced span is local span not a span representing HTTP
+ * communication.
+ */
+ private static final String ACTIVE_SPAN = DDTracingClientExec.class.getName() + ".activeSpan";
+ /**
+ * Tracing {@link ClientExecChain} is executed after redirect exec, so on redirects it is called
+ * multiple times. This is used as an id for {@link HttpClientContext#setAttribute(String,
+ * Object)} to store number of redirects.
+ */
+ private static final String REDIRECT_COUNT =
+ DDTracingClientExec.class.getName() + ".redirectCount";
+
+ private final RedirectStrategy redirectStrategy;
+ private final ClientExecChain requestExecutor;
+ private final boolean redirectHandlingDisabled;
+
+ private final Tracer tracer;
+
+ public DDTracingClientExec(
+ ClientExecChain clientExecChain,
+ RedirectStrategy redirectStrategy,
+ boolean redirectHandlingDisabled,
+ Tracer tracer) {
+ this.requestExecutor = clientExecChain;
+ this.redirectStrategy = redirectStrategy;
+ this.redirectHandlingDisabled = redirectHandlingDisabled;
+ this.tracer = tracer;
+ }
+
+ @Override
+ public CloseableHttpResponse execute(
+ HttpRoute route,
+ HttpRequestWrapper request,
+ HttpClientContext clientContext,
+ HttpExecutionAware execAware)
+ throws IOException, HttpException {
+
+ ActiveSpan localSpan = clientContext.getAttribute(ACTIVE_SPAN, ActiveSpan.class);
+ CloseableHttpResponse response = null;
+ try {
+ if (localSpan == null) {
+ localSpan = createLocalSpan(request, clientContext);
+ }
+
+ return (response = createNetworkSpan(localSpan, route, request, clientContext, execAware));
+ } catch (Exception e) {
+ localSpan.deactivate();
+ throw e;
+ } finally {
+ if (response != null) {
+ /**
+ * This exec runs after {@link org.apache.http.impl.execchain.RedirectExec} which loops
+ * until there is no redirect or reaches max redirect count. {@link RedirectStrategy} is
+ * used to decide whether localSpan should be finished or not. If there is a redirect
+ * localSpan is not finished and redirect is logged.
+ */
+ Integer redirectCount = clientContext.getAttribute(REDIRECT_COUNT, Integer.class);
+ if (!redirectHandlingDisabled
+ && clientContext.getRequestConfig().isRedirectsEnabled()
+ && redirectStrategy.isRedirected(request, response, clientContext)
+ && ++redirectCount < clientContext.getRequestConfig().getMaxRedirects()) {
+ clientContext.setAttribute(REDIRECT_COUNT, redirectCount);
+ } else {
+ localSpan.deactivate();
+ }
+ }
+ }
+ }
+
+ private ActiveSpan createLocalSpan(HttpRequest httpRequest, HttpClientContext clientContext) {
+ Tracer.SpanBuilder spanBuilder =
+ tracer
+ .buildSpan(httpRequest.getRequestLine().getMethod())
+ .withTag(Tags.COMPONENT.getKey(), COMPONENT_NAME);
+
+ ActiveSpan localSpan = spanBuilder.startActive();
+ clientContext.setAttribute(ACTIVE_SPAN, localSpan);
+ clientContext.setAttribute(REDIRECT_COUNT, 0);
+ return localSpan;
+ }
+
+ private CloseableHttpResponse createNetworkSpan(
+ ActiveSpan parentSpan,
+ HttpRoute route,
+ HttpRequestWrapper request,
+ HttpClientContext clientContext,
+ HttpExecutionAware execAware)
+ throws IOException, HttpException {
+ ActiveSpan networkSpan =
+ tracer
+ .buildSpan(request.getMethod())
+ .withTag(Tags.SPAN_KIND.getKey(), Tags.SPAN_KIND_CLIENT)
+ .asChildOf(parentSpan)
+ .startActive();
+ tracer.inject(
+ networkSpan.context(), Format.Builtin.HTTP_HEADERS, new HttpHeadersInjectAdapter(request));
+
+ try {
+ // request tags
+ Tags.HTTP_METHOD.set(networkSpan, request.getRequestLine().getMethod());
+ final URI uri = request.getURI();
+ Tags.HTTP_URL.set(networkSpan, request.getRequestLine().getUri());
+ Tags.PEER_PORT.set(networkSpan, uri.getPort() == -1 ? 80 : uri.getPort());
+ Tags.PEER_HOSTNAME.set(networkSpan, uri.getHost());
+
+ final CloseableHttpResponse response =
+ requestExecutor.execute(route, request, clientContext, execAware);
+
+ // response tags
+ Tags.HTTP_STATUS.set(networkSpan, response.getStatusLine().getStatusCode());
+
+ return response;
+ } catch (IOException | HttpException | RuntimeException e) {
+ // error tags
+ Tags.ERROR.set(networkSpan, Boolean.TRUE);
+ networkSpan.log(Collections.singletonMap("error.object", e));
+
+ throw e;
+ } finally {
+ networkSpan.deactivate();
+ }
+ }
+
+ public static class HttpHeadersInjectAdapter implements TextMap {
+
+ private HttpRequest httpRequest;
+
+ public HttpHeadersInjectAdapter(HttpRequest httpRequest) {
+ this.httpRequest = httpRequest;
+ }
+
+ @Override
+ public void put(String key, String value) {
+ httpRequest.addHeader(key, value);
+ }
+
+ @Override
+ public Iterator<Map.Entry<String, String>> iterator() {
+ throw new UnsupportedOperationException(
+ "This class should be used only with tracer#inject()");
+ }
+ }
+}

MODIFY
dd-java-agent/src/main/resources/initializer-rules.btm
dd-java-agent/src/main/resources/initializer-rules.btm
-# Instrument Apache HTTP Client
-# This rule isn't working because HttpClientBuilder doesn't have any static fields, thus no static initializer.
-# =============================
-#RULE HttpClientBuilder-clinit
-#CLASS org.apache.http.impl.client.HttpClientBuilder
-#METHOD <clinit>
-#AT EXIT
-#IF TRUE
-#DO
-# com.datadoghq.agent.InstrumentationRulesManager.registerClassLoad($0);
-#ENDRULE
-
-
# Instrument OkHttp
# ===========================
RULE OkHttpClient$Builder-init

MODIFY
dd-java-agent/src/main/resources/integration-rules.btm
dd-java-agent/src/main/resources/integration-rules.btm
###
-# Instrument Apache HTTP Client
-# =============================
-RULE opentracing-apache-httpclient
-CLASS org.apache.http.impl.client.HttpClientBuilder
-METHOD create()
-HELPER com.datadoghq.agent.integration.ApacheHTTPClientHelper
-AT EXIT
-IF TRUE
-DO
- $! = patch($!);
-ENDRULE
-
-
# Instrument OkHttp
# ===========================
RULE opentracing-okhttp3

MODIFY
settings.gradle
settings.gradle
@@ -13,7 +13,7 @@ include ':dd-trace-annotations'
// integrations:
include ':dd-java-agent:integrations:helpers'
-include ':dd-java-agent:integrations:apache-httpclient'
+include ':dd-java-agent:integrations:apache-httpclient-4.3'
include ':dd-java-agent:integrations:aws-sdk'
include ':dd-java-agent:integrations:datastax-cassandra-3.2'
include ':dd-java-agent:integrations:jms-1'

ApacheHttpClient bytebuddy instrumentation and tests.