Merge pull request #809 from DataDog/tyler/httpasyncclient

Add instrumentation for Apache HttpAsyncClient
This commit is contained in:
Tyler Benson 2019-04-29 08:14:11 -07:00 committed by GitHub
commit 25d109753d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
49 changed files with 1244 additions and 548 deletions

View File

@ -5,12 +5,16 @@ import datadog.trace.api.DDSpanTypes;
import datadog.trace.api.DDTags;
import io.opentracing.Span;
import io.opentracing.tag.Tags;
import java.net.URI;
import java.net.URISyntaxException;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public abstract class HttpClientDecorator<REQUEST, RESPONSE> extends ClientDecorator {
protected abstract String method(REQUEST request);
protected abstract String url(REQUEST request);
protected abstract URI url(REQUEST request) throws URISyntaxException;
protected abstract String hostname(REQUEST request);
@ -32,9 +36,39 @@ public abstract class HttpClientDecorator<REQUEST, RESPONSE> extends ClientDecor
assert span != null;
if (request != null) {
Tags.HTTP_METHOD.set(span, method(request));
Tags.HTTP_URL.set(span, url(request));
// Copy of HttpServerDecorator url handling
try {
final URI url = url(request);
if (url != null) {
final StringBuilder urlNoParams = new StringBuilder();
if (url.getScheme() != null) {
urlNoParams.append(url.getScheme());
urlNoParams.append("://");
}
if (url.getHost() != null) {
urlNoParams.append(url.getHost());
if (url.getPort() > 0 && url.getPort() != 80 && url.getPort() != 443) {
urlNoParams.append(":");
urlNoParams.append(url.getPort());
}
}
final String path = url.getPath();
if (path.isEmpty()) {
urlNoParams.append("/");
} else {
urlNoParams.append(path);
}
Tags.HTTP_URL.set(span, urlNoParams.toString());
}
} catch (final Exception e) {
log.debug("Error tagging url", e);
}
Tags.PEER_HOSTNAME.set(span, hostname(request));
Tags.PEER_PORT.set(span, port(request));
final Integer port = port(request);
Tags.PEER_PORT.set(span, port != null && port > 0 ? port : null);
if (Config.get().isHttpClientSplitByDomain()) {
span.setTag(DDTags.SERVICE_NAME, hostname(request));

View File

@ -5,6 +5,7 @@ import datadog.trace.api.DDSpanTypes;
import io.opentracing.Span;
import io.opentracing.tag.Tags;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.regex.Pattern;
import lombok.extern.slf4j.Slf4j;
@ -17,7 +18,7 @@ public abstract class HttpServerDecorator<REQUEST, CONNECTION, RESPONSE> extends
protected abstract String method(REQUEST request);
protected abstract URI url(REQUEST request);
protected abstract URI url(REQUEST request) throws URISyntaxException;
protected abstract String peerHostname(CONNECTION connection);
@ -42,23 +43,31 @@ public abstract class HttpServerDecorator<REQUEST, CONNECTION, RESPONSE> extends
if (request != null) {
Tags.HTTP_METHOD.set(span, method(request));
// Copy of HttpClientDecorator url handling
try {
final URI url = url(request);
final StringBuilder urlNoParams = new StringBuilder(url.getScheme());
urlNoParams.append("://");
urlNoParams.append(url.getHost());
if (url.getPort() > 0 && url.getPort() != 80 && url.getPort() != 443) {
urlNoParams.append(":");
urlNoParams.append(url.getPort());
}
final String path = url.getPath();
if (path.isEmpty()) {
urlNoParams.append("/");
} else {
urlNoParams.append(path);
}
if (url != null) {
final StringBuilder urlNoParams = new StringBuilder();
if (url.getScheme() != null) {
urlNoParams.append(url.getScheme());
urlNoParams.append("://");
}
if (url.getHost() != null) {
urlNoParams.append(url.getHost());
if (url.getPort() > 0 && url.getPort() != 80 && url.getPort() != 443) {
urlNoParams.append(":");
urlNoParams.append(url.getPort());
}
}
final String path = url.getPath();
if (path.isEmpty()) {
urlNoParams.append("/");
} else {
urlNoParams.append(path);
}
Tags.HTTP_URL.set(span, urlNoParams.toString());
Tags.HTTP_URL.set(span, urlNoParams.toString());
}
} catch (final Exception e) {
log.debug("Error tagging url", e);
}

View File

@ -4,11 +4,15 @@ import datadog.trace.api.Config
import datadog.trace.api.DDTags
import io.opentracing.Span
import io.opentracing.tag.Tags
import spock.lang.Shared
import static datadog.trace.agent.test.utils.TraceUtils.withConfigOverride
class HttpClientDecoratorTest extends ClientDecoratorTest {
@Shared
def testUrl = new URI("http://myhost/somepath")
def span = Mock(Span)
def "test onRequest"() {
@ -23,7 +27,7 @@ class HttpClientDecoratorTest extends ClientDecoratorTest {
then:
if (req) {
1 * span.setTag(Tags.HTTP_METHOD.key, "test-method")
1 * span.setTag(Tags.HTTP_URL.key, "test-url")
1 * span.setTag(Tags.HTTP_URL.key, "$testUrl")
1 * span.setTag(Tags.PEER_HOSTNAME.key, "test-host")
1 * span.setTag(Tags.PEER_PORT.key, 555)
if (renameService) {
@ -36,8 +40,36 @@ class HttpClientDecoratorTest extends ClientDecoratorTest {
renameService | req
false | null
true | null
false | [method: "test-method", url: "test-url", host: "test-host", port: 555]
true | [method: "test-method", url: "test-url", host: "test-host", port: 555]
false | [method: "test-method", url: testUrl, host: "test-host", port: 555]
true | [method: "test-method", url: testUrl, host: "test-host", port: 555]
}
def "test url handling"() {
setup:
def decorator = newDecorator()
when:
decorator.onRequest(span, req)
then:
if (expected) {
1 * span.setTag(Tags.HTTP_URL.key, expected)
}
1 * span.setTag(Tags.HTTP_METHOD.key, null)
1 * span.setTag(Tags.PEER_HOSTNAME.key, null)
1 * span.setTag(Tags.PEER_PORT.key, null)
0 * _
where:
url | expected
null | null
"" | "/"
"/path?query" | "/path"
"https://host:0" | "https://host/"
"https://host/path" | "https://host/path"
"http://host:99/path?query#fragment" | "http://host:99/path"
req = [url: url == null ? null : new URI(url)]
}
def "test onResponse"() {
@ -113,7 +145,7 @@ class HttpClientDecoratorTest extends ClientDecoratorTest {
}
@Override
protected String url(Map m) {
protected URI url(Map m) {
return m.url
}

View File

@ -1,11 +1,11 @@
package datadog.trace.agent.decorator
import static datadog.trace.agent.test.utils.TraceUtils.withConfigOverride
import datadog.trace.api.Config
import io.opentracing.Span
import io.opentracing.tag.Tags
import static datadog.trace.agent.test.utils.TraceUtils.withConfigOverride
class HttpServerDecoratorTest extends ServerDecoratorTest {
def span = Mock(Span)
@ -34,6 +34,32 @@ class HttpServerDecoratorTest extends ServerDecoratorTest {
[method: "test-method", url: URI.create("http://123:8080/some/path")] | "http://123:8080/some/path"
}
def "test url handling"() {
setup:
def decorator = newDecorator()
when:
decorator.onRequest(span, req)
then:
if (expected) {
1 * span.setTag(Tags.HTTP_URL.key, expected)
}
1 * span.setTag(Tags.HTTP_METHOD.key, null)
0 * _
where:
url | expected
null | null
"" | "/"
"/path?query" | "/path"
"https://host:0" | "https://host/"
"https://host/path" | "https://host/path"
"http://host:99/path?query#fragment" | "http://host:99/path"
req = [url: url == null ? null : new URI(url)]
}
def "test onConnection"() {
setup:
def decorator = newDecorator()

View File

@ -3,6 +3,8 @@ package datadog.trace.instrumentation.akkahttp;
import akka.http.scaladsl.model.HttpRequest;
import akka.http.scaladsl.model.HttpResponse;
import datadog.trace.agent.decorator.HttpClientDecorator;
import java.net.URI;
import java.net.URISyntaxException;
public class AkkaHttpClientDecorator extends HttpClientDecorator<HttpRequest, HttpResponse> {
public static final AkkaHttpClientDecorator DECORATE = new AkkaHttpClientDecorator();
@ -23,8 +25,8 @@ public class AkkaHttpClientDecorator extends HttpClientDecorator<HttpRequest, Ht
}
@Override
protected String url(final HttpRequest httpRequest) {
return httpRequest.uri().toString();
protected URI url(final HttpRequest httpRequest) throws URISyntaxException {
return new URI(httpRequest.uri().toString());
}
@Override

View File

@ -4,6 +4,7 @@ import akka.http.scaladsl.model.HttpRequest;
import akka.http.scaladsl.model.HttpResponse;
import datadog.trace.agent.decorator.HttpServerDecorator;
import java.net.URI;
import java.net.URISyntaxException;
public class AkkaHttpServerDecorator
extends HttpServerDecorator<HttpRequest, HttpRequest, HttpResponse> {
@ -25,8 +26,8 @@ public class AkkaHttpServerDecorator
}
@Override
protected URI url(final HttpRequest httpRequest) {
return URI.create(httpRequest.uri().toString());
protected URI url(final HttpRequest httpRequest) throws URISyntaxException {
return new URI(httpRequest.uri().toString());
}
@Override

View File

@ -0,0 +1,38 @@
ext {
minJavaVersionForTests = JavaVersion.VERSION_1_8
}
muzzle {
pass {
group = "org.apache.httpcomponents"
module = "httpasyncclient"
versions = "[4.0,)"
assertInverse = true
}
}
apply from: "${rootDir}/gradle/java.gradle"
apply plugin: 'org.unbroken-dome.test-sets'
testSets {
latestDepTest {
dirName = 'test'
}
}
dependencies {
compileOnly group: 'org.apache.httpcomponents', name: 'httpasyncclient', version: '4.0'
compile project(':dd-java-agent:agent-tooling')
compile deps.bytebuddy
compile deps.opentracing
annotationProcessor deps.autoservice
implementation deps.autoservice
testCompile project(':dd-java-agent:testing')
testCompile group: 'org.apache.httpcomponents', name: 'httpasyncclient', version: '4.0'
latestDepTestCompile group: 'org.apache.httpcomponents', name: 'httpasyncclient', version: '+'
}

View File

@ -0,0 +1,76 @@
package datadog.trace.instrumentation.apachehttpasyncclient;
import datadog.trace.agent.decorator.HttpClientDecorator;
import java.net.URI;
import java.net.URISyntaxException;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.RequestLine;
import org.apache.http.StatusLine;
import org.apache.http.protocol.HttpContext;
import org.apache.http.protocol.HttpCoreContext;
public class ApacheHttpAsyncClientDecorator extends HttpClientDecorator<HttpRequest, HttpContext> {
public static final ApacheHttpAsyncClientDecorator DECORATE =
new ApacheHttpAsyncClientDecorator();
@Override
protected String[] instrumentationNames() {
return new String[] {"httpasyncclient", "apache-httpasyncclient"};
}
@Override
protected String component() {
return "apache-httpasyncclient";
}
@Override
protected String method(final HttpRequest request) {
final RequestLine requestLine = request.getRequestLine();
return requestLine == null ? null : requestLine.getMethod();
}
@Override
protected URI url(final HttpRequest request) throws URISyntaxException {
final RequestLine requestLine = request.getRequestLine();
return requestLine == null ? null : new URI(requestLine.getUri());
}
@Override
protected String hostname(final HttpRequest request) {
final RequestLine requestLine = request.getRequestLine();
if (requestLine != null) {
try {
return new URI(requestLine.getUri()).getHost();
} catch (final URISyntaxException e) {
return null;
}
}
return null;
}
@Override
protected Integer port(final HttpRequest request) {
final RequestLine requestLine = request.getRequestLine();
if (requestLine != null) {
try {
return new URI(requestLine.getUri()).getPort();
} catch (final URISyntaxException e) {
return null;
}
}
return null;
}
@Override
protected Integer status(final HttpContext context) {
final Object responseObject = context.getAttribute(HttpCoreContext.HTTP_RESPONSE);
if (responseObject instanceof HttpResponse) {
final StatusLine statusLine = ((HttpResponse) responseObject).getStatusLine();
if (statusLine != null) {
return statusLine.getStatusCode();
}
}
return null;
}
}

View File

@ -0,0 +1,255 @@
package datadog.trace.instrumentation.apachehttpasyncclient;
import static datadog.trace.agent.tooling.ByteBuddyElementMatchers.safeHasSuperType;
import static datadog.trace.instrumentation.apachehttpasyncclient.ApacheHttpAsyncClientDecorator.DECORATE;
import static java.util.Collections.singletonMap;
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
import static net.bytebuddy.matcher.ElementMatchers.takesArguments;
import com.google.auto.service.AutoService;
import datadog.trace.agent.tooling.Instrumenter;
import datadog.trace.context.TraceScope;
import io.opentracing.Scope;
import io.opentracing.Span;
import io.opentracing.Tracer;
import io.opentracing.propagation.Format;
import io.opentracing.propagation.TextMap;
import io.opentracing.util.GlobalTracer;
import java.io.IOException;
import java.util.Iterator;
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;
import org.apache.http.HttpException;
import org.apache.http.HttpHost;
import org.apache.http.HttpRequest;
import org.apache.http.concurrent.FutureCallback;
import org.apache.http.nio.ContentEncoder;
import org.apache.http.nio.IOControl;
import org.apache.http.nio.protocol.HttpAsyncRequestProducer;
import org.apache.http.protocol.HttpContext;
@AutoService(Instrumenter.class)
public class ApacheHttpAsyncClientInstrumentation extends Instrumenter.Default {
public ApacheHttpAsyncClientInstrumentation() {
super("httpclient", "apache-httpclient", "apache-http-client");
}
@Override
public ElementMatcher<TypeDescription> typeMatcher() {
return safeHasSuperType(named("org.apache.http.nio.client.HttpAsyncClient"));
}
@Override
public String[] helperClassNames() {
return new String[] {
getClass().getName() + "$HttpHeadersInjectAdapter",
getClass().getName() + "$DelegatingRequestProducer",
getClass().getName() + "$TraceContinuedFutureCallback",
"datadog.trace.agent.decorator.BaseDecorator",
"datadog.trace.agent.decorator.ClientDecorator",
"datadog.trace.agent.decorator.HttpClientDecorator",
packageName + ".ApacheHttpAsyncClientDecorator",
};
}
@Override
public Map<? extends ElementMatcher<? super MethodDescription>, String> transformers() {
return singletonMap(
isMethod()
.and(named("execute"))
.and(takesArguments(4))
.and(takesArgument(0, named("org.apache.http.nio.protocol.HttpAsyncRequestProducer")))
.and(takesArgument(1, named("org.apache.http.nio.protocol.HttpAsyncResponseConsumer")))
.and(takesArgument(2, named("org.apache.http.protocol.HttpContext")))
.and(takesArgument(3, named("org.apache.http.concurrent.FutureCallback"))),
ClientAdvice.class.getName());
}
public static class ClientAdvice {
@Advice.OnMethodEnter(suppress = Throwable.class)
public static Span methodEnter(
@Advice.Argument(value = 0, readOnly = false) HttpAsyncRequestProducer requestProducer,
@Advice.Argument(2) final HttpContext context,
@Advice.Argument(value = 3, readOnly = false) FutureCallback<?> futureCallback) {
final Tracer tracer = GlobalTracer.get();
final Scope parentScope = tracer.scopeManager().active();
final Span clientSpan = tracer.buildSpan("http.request").start();
DECORATE.afterStart(clientSpan);
requestProducer = new DelegatingRequestProducer(clientSpan, requestProducer);
futureCallback =
new TraceContinuedFutureCallback(parentScope, clientSpan, context, futureCallback);
return clientSpan;
}
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
public static void methodExit(
@Advice.Enter final Span span,
@Advice.Return final Object result,
@Advice.Thrown final Throwable throwable) {
if (throwable != null) {
DECORATE.onError(span, throwable);
DECORATE.beforeFinish(span);
span.finish();
}
}
}
public static class DelegatingRequestProducer implements HttpAsyncRequestProducer {
final Span span;
final HttpAsyncRequestProducer delegate;
public DelegatingRequestProducer(final Span span, final HttpAsyncRequestProducer delegate) {
this.span = span;
this.delegate = delegate;
}
@Override
public HttpHost getTarget() {
return delegate.getTarget();
}
@Override
public HttpRequest generateRequest() throws IOException, HttpException {
final HttpRequest request = delegate.generateRequest();
DECORATE.onRequest(span, request);
GlobalTracer.get()
.inject(
span.context(), Format.Builtin.HTTP_HEADERS, new HttpHeadersInjectAdapter(request));
return request;
}
@Override
public void produceContent(final ContentEncoder encoder, final IOControl ioctrl)
throws IOException {
delegate.produceContent(encoder, ioctrl);
}
@Override
public void requestCompleted(final HttpContext context) {
delegate.requestCompleted(context);
}
@Override
public void failed(final Exception ex) {
delegate.failed(ex);
}
@Override
public boolean isRepeatable() {
return delegate.isRepeatable();
}
@Override
public void resetRequest() throws IOException {
delegate.resetRequest();
}
@Override
public void close() throws IOException {
delegate.close();
}
}
public static class TraceContinuedFutureCallback<T> implements FutureCallback<T> {
private final TraceScope.Continuation parentContinuation;
private final Span clientSpan;
private final HttpContext context;
private final FutureCallback<T> delegate;
public TraceContinuedFutureCallback(
final Scope parentScope,
final Span clientSpan,
final HttpContext context,
final FutureCallback<T> delegate) {
if (parentScope instanceof TraceScope) {
parentContinuation = ((TraceScope) parentScope).capture();
} else {
parentContinuation = null;
}
this.clientSpan = clientSpan;
this.context = context;
this.delegate = delegate;
}
@Override
public void completed(final T result) {
DECORATE.onResponse(clientSpan, context);
DECORATE.beforeFinish(clientSpan);
clientSpan.finish(); // Finish span before calling delegate
if (parentContinuation == null) {
delegate.completed(result);
} else {
try (final TraceScope scope = parentContinuation.activate()) {
scope.setAsyncPropagation(true);
delegate.completed(result);
}
}
}
@Override
public void failed(final Exception ex) {
DECORATE.onResponse(clientSpan, context);
DECORATE.onError(clientSpan, ex);
DECORATE.beforeFinish(clientSpan);
clientSpan.finish(); // Finish span before calling delegate
if (parentContinuation == null) {
delegate.failed(ex);
} else {
try (final TraceScope scope = parentContinuation.activate()) {
scope.setAsyncPropagation(true);
delegate.failed(ex);
}
}
}
@Override
public void cancelled() {
DECORATE.onResponse(clientSpan, context);
DECORATE.beforeFinish(clientSpan);
clientSpan.finish(); // Finish span before calling delegate
if (parentContinuation == null) {
delegate.cancelled();
} else {
try (final TraceScope scope = parentContinuation.activate()) {
scope.setAsyncPropagation(true);
delegate.cancelled();
}
}
}
}
public static class HttpHeadersInjectAdapter implements TextMap {
private final HttpRequest httpRequest;
public HttpHeadersInjectAdapter(final HttpRequest httpRequest) {
this.httpRequest = httpRequest;
}
@Override
public void put(final String key, final 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()");
}
}
}

View File

@ -0,0 +1,65 @@
package datadog.trace.instrumentation.apachehttpasyncclient;
import static datadog.trace.agent.tooling.ByteBuddyElementMatchers.safeHasSuperType;
import static java.util.Collections.singletonMap;
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
import com.google.auto.service.AutoService;
import datadog.trace.agent.tooling.Instrumenter;
import java.util.Map;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.implementation.bytecode.assign.Assigner;
import net.bytebuddy.matcher.ElementMatcher;
import org.apache.http.Header;
import org.apache.http.HttpRequest;
/**
* Early versions don't copy headers over on redirect. This instrumentation copies our headers over
* manually. Inspired by
* https://github.com/elastic/apm-agent-java/blob/master/apm-agent-plugins/apm-apache-httpclient-plugin/src/main/java/co/elastic/apm/agent/httpclient/ApacheHttpAsyncClientRedirectInstrumentation.java
*/
@AutoService(Instrumenter.class)
public class ApacheHttpClientRedirectInstrumentation extends Instrumenter.Default {
public ApacheHttpClientRedirectInstrumentation() {
super("httpasyncclient", "apache-httpasyncclient");
}
@Override
public ElementMatcher<TypeDescription> typeMatcher() {
return safeHasSuperType(named("org.apache.http.client.RedirectStrategy"));
}
@Override
public Map<? extends ElementMatcher<? super MethodDescription>, String> transformers() {
return singletonMap(
isMethod()
.and(named("getRedirect"))
.and(takesArgument(0, named("org.apache.http.HttpRequest"))),
ClientRedirectAdvice.class.getName());
}
public static class ClientRedirectAdvice {
@Advice.OnMethodExit(suppress = Throwable.class)
private static void onAfterExecute(
@Advice.Argument(value = 0) final HttpRequest original,
@Advice.Return(typing = Assigner.Typing.DYNAMIC) final HttpRequest redirect) {
if (redirect == null) {
return;
}
for (final Header header : original.getAllHeaders()) {
final String name = header.getName().toLowerCase();
if (name.startsWith("x-datadog-") || name.startsWith("x-b3-")) {
if (!redirect.containsHeader(header.getName())) {
redirect.setHeader(header.getName(), header.getValue());
}
}
}
}
}
}

View File

@ -0,0 +1,76 @@
import datadog.trace.agent.test.base.HttpClientTest
import datadog.trace.instrumentation.apachehttpasyncclient.ApacheHttpAsyncClientDecorator
import io.opentracing.util.GlobalTracer
import org.apache.http.HttpResponse
import org.apache.http.client.methods.HttpGet
import org.apache.http.concurrent.FutureCallback
import org.apache.http.impl.nio.client.HttpAsyncClients
import org.apache.http.message.BasicHeader
import spock.lang.AutoCleanup
import spock.lang.Shared
import java.util.concurrent.CompletableFuture
class ApacheHttpAsyncClientCallbackTest extends HttpClientTest<ApacheHttpAsyncClientDecorator> {
@AutoCleanup
@Shared
def client = HttpAsyncClients.createDefault()
def setupSpec() {
client.start()
}
@Override
int doRequest(String method, URI uri, Map<String, String> headers, Closure callback) {
assert method == "GET"
def hasParent = GlobalTracer.get().activeSpan() != null
HttpGet request = new HttpGet(uri)
headers.entrySet().each {
request.addHeader(new BasicHeader(it.key, it.value))
}
def responseFuture = new CompletableFuture<>()
client.execute(request, new FutureCallback<HttpResponse>() {
@Override
void completed(HttpResponse result) {
if (hasParent && GlobalTracer.get().activeSpan() == null) {
responseFuture.completeExceptionally(new Exception("Missing span in scope"))
} else {
responseFuture.complete(result.statusLine.statusCode)
}
callback?.call()
}
@Override
void failed(Exception ex) {
if (hasParent && GlobalTracer.get().activeSpan() == null) {
responseFuture.completeExceptionally(new Exception("Missing span in scope"))
} else {
responseFuture.completeExceptionally(ex)
}
}
@Override
void cancelled() {
responseFuture.cancel(true)
}
})
return responseFuture.get()
}
@Override
ApacheHttpAsyncClientDecorator decorator() {
return ApacheHttpAsyncClientDecorator.DECORATE
}
@Override
Integer statusOnRedirectError() {
return 302
}
}

View File

@ -0,0 +1,59 @@
import datadog.trace.agent.test.base.HttpClientTest
import datadog.trace.instrumentation.apachehttpasyncclient.ApacheHttpAsyncClientDecorator
import org.apache.http.HttpResponse
import org.apache.http.client.methods.HttpGet
import org.apache.http.concurrent.FutureCallback
import org.apache.http.impl.nio.client.HttpAsyncClients
import org.apache.http.message.BasicHeader
import spock.lang.AutoCleanup
import spock.lang.Shared
class ApacheHttpAsyncClientTest extends HttpClientTest<ApacheHttpAsyncClientDecorator> {
@AutoCleanup
@Shared
def client = HttpAsyncClients.createDefault()
def setupSpec() {
client.start()
}
@Override
int doRequest(String method, URI uri, Map<String, String> headers, Closure callback) {
assert method == "GET"
HttpGet request = new HttpGet(uri)
headers.entrySet().each {
request.addHeader(new BasicHeader(it.key, it.value))
}
def handler = callback == null ? null : new FutureCallback<HttpResponse>() {
@Override
void completed(HttpResponse result) {
callback()
}
@Override
void failed(Exception ex) {
}
@Override
void cancelled() {
}
}
def response = client.execute(request, handler).get()
response.entity.getContent().close() // Make sure the connection is closed.
response.statusLine.statusCode
}
@Override
ApacheHttpAsyncClientDecorator decorator() {
return ApacheHttpAsyncClientDecorator.DECORATE
}
@Override
Integer statusOnRedirectError() {
return 302
}
}

View File

@ -1,5 +1,3 @@
apply from: "${rootDir}/gradle/java.gradle"
muzzle {
fail {
group = "commons-httpclient"
@ -21,10 +19,13 @@ muzzle {
}
}
apply from: "${rootDir}/gradle/java.gradle"
apply plugin: 'org.unbroken-dome.test-sets'
testSets {
latestDepTest
latestDepTest {
dirName = 'test'
}
}
dependencies {

View File

@ -1,243 +0,0 @@
import datadog.opentracing.DDSpan
import datadog.trace.agent.test.AgentTestRunner
import datadog.trace.agent.test.asserts.TraceAssert
import datadog.trace.api.Config
import datadog.trace.api.DDSpanTypes
import io.opentracing.tag.Tags
import org.apache.http.HttpResponse
import org.apache.http.client.ClientProtocolException
import org.apache.http.client.HttpClient
import org.apache.http.client.config.RequestConfig
import org.apache.http.client.methods.HttpGet
import org.apache.http.impl.client.BasicResponseHandler
import org.apache.http.impl.client.HttpClientBuilder
import org.apache.http.message.BasicHeader
import spock.lang.AutoCleanup
import spock.lang.Shared
import static datadog.trace.agent.test.server.http.TestHttpServer.httpServer
import static datadog.trace.agent.test.utils.TraceUtils.runUnderTrace
import static datadog.trace.agent.test.utils.TraceUtils.withConfigOverride
class ApacheHttpClientTest extends AgentTestRunner {
@AutoCleanup
@Shared
def server = httpServer {
handlers {
prefix("success") {
handleDistributedRequest()
String msg = "Hello."
response.status(200).send(msg)
}
prefix("redirect") {
handleDistributedRequest()
redirect(server.address.resolve("/success").toURL().toString())
}
prefix("another-redirect") {
handleDistributedRequest()
redirect(server.address.resolve("/redirect").toURL().toString())
}
}
}
@Shared
int port = server.address.port
@Shared
def successUrl = server.address.resolve("/success")
@Shared
def redirectUrl = server.address.resolve("/redirect")
@Shared
def twoRedirectsUrl = server.address.resolve("/another-redirect")
@Shared
def handler = new BasicResponseHandler()
final HttpClientBuilder builder = HttpClientBuilder.create()
final HttpClient client = builder.build()
def "trace request with propagation"() {
when:
String response = withConfigOverride(Config.HTTP_CLIENT_HOST_SPLIT_BY_DOMAIN, "$renameService") {
runUnderTrace("parent") {
if (responseHandler) {
client.execute(new HttpGet(successUrl), responseHandler)
} else {
client.execute(new HttpGet(successUrl)).entity.content.text
}
}
}
then:
response == "Hello."
// one trace on the server, one trace on the client
assertTraces(2) {
server.distributedRequestTrace(it, 0, TEST_WRITER[1][1])
trace(1, 2) {
parentSpan(it, 0)
successClientSpan(it, 1, span(0), renameService)
}
}
where:
responseHandler << [null, handler]
renameService << [false, true]
}
def "trace redirected request with propagation many redirects allowed"() {
setup:
final RequestConfig.Builder requestConfigBuilder = new RequestConfig.Builder()
requestConfigBuilder.setMaxRedirects(10)
HttpGet request = new HttpGet(redirectUrl)
request.setConfig(requestConfigBuilder.build())
when:
HttpResponse response = withConfigOverride(Config.HTTP_CLIENT_HOST_SPLIT_BY_DOMAIN, "$renameService") {
runUnderTrace("parent") {
client.execute(request)
}
}
then:
response.getStatusLine().getStatusCode() == 200
// two traces on the server, one trace on the client
assertTraces(3) {
server.distributedRequestTrace(it, 0, TEST_WRITER[2][1])
server.distributedRequestTrace(it, 1, TEST_WRITER[2][1])
trace(2, 2) {
parentSpan(it, 0)
successClientSpan(it, 1, span(0), renameService, 200, "redirect")
}
}
where:
renameService << [false, true]
}
def "trace redirected request with propagation 1 redirect allowed"() {
setup:
final RequestConfig.Builder requestConfigBuilder = new RequestConfig.Builder()
requestConfigBuilder.setMaxRedirects(1)
HttpGet request = new HttpGet(redirectUrl)
request.setConfig(requestConfigBuilder.build())
when:
HttpResponse response = withConfigOverride(Config.HTTP_CLIENT_HOST_SPLIT_BY_DOMAIN, "$renameService") {
runUnderTrace("parent") {
client.execute(request)
}
}
then:
response.getStatusLine().getStatusCode() == 200
// two traces on the server, one trace on the client
assertTraces(3) {
server.distributedRequestTrace(it, 0, TEST_WRITER[2][1])
server.distributedRequestTrace(it, 1, TEST_WRITER[2][1])
trace(2, 2) {
parentSpan(it, 0)
successClientSpan(it, 1, span(0), renameService, 200, "redirect")
}
}
where:
renameService << [false, true]
}
def "trace redirected request with propagation too many redirects"() {
setup:
final RequestConfig.Builder requestConfigBuilder = new RequestConfig.Builder()
requestConfigBuilder.setMaxRedirects(1)
HttpGet request = new HttpGet(twoRedirectsUrl)
request.setConfig(requestConfigBuilder.build())
when:
withConfigOverride(Config.HTTP_CLIENT_HOST_SPLIT_BY_DOMAIN, "$renameService") {
runUnderTrace("parent") {
client.execute(request)
}
}
then:
def exception = thrown(ClientProtocolException)
// two traces on the server, one trace on the client
assertTraces(3) {
server.distributedRequestTrace(it, 0, TEST_WRITER[2][1])
server.distributedRequestTrace(it, 1, TEST_WRITER[2][1])
trace(2, 2) {
parentSpan(it, 0, exception)
successClientSpan(it, 1, span(0), renameService, null, "another-redirect", exception)
}
}
where:
renameService << [false, true]
}
def "trace request without propagation"() {
setup:
HttpGet request = new HttpGet(successUrl)
request.addHeader(new BasicHeader("is-dd-server", "false"))
when:
HttpResponse response = withConfigOverride(Config.HTTP_CLIENT_HOST_SPLIT_BY_DOMAIN, "$renameService") {
runUnderTrace("parent") {
client.execute(request)
}
}
then:
response.getStatusLine().getStatusCode() == 200
// only one trace (client).
assertTraces(1) {
trace(0, 2) {
parentSpan(it, 0)
successClientSpan(it, 1, span(0), renameService)
}
}
where:
renameService << [false, true]
}
def parentSpan(TraceAssert trace, int index, Throwable exception = null) {
trace.span(index) {
parent()
serviceName "unnamed-java-app"
operationName "parent"
resourceName "parent"
errored exception != null
tags {
defaultTags()
if (exception) {
errorTags(exception.class)
}
}
}
}
def successClientSpan(TraceAssert trace, int index, DDSpan parent, boolean renameService, status = 200, route = "success", Throwable exception = null) {
trace.span(index) {
childOf parent
serviceName renameService ? "localhost" : "unnamed-java-app"
operationName "http.request"
resourceName "GET /$route"
spanType DDSpanTypes.HTTP_CLIENT
errored exception != null
tags {
defaultTags()
if (exception) {
errorTags(exception.class)
}
"$Tags.COMPONENT.key" "apache-httpclient"
"$Tags.HTTP_STATUS.key" status
"$Tags.HTTP_URL.key" "http://localhost:$port/$route"
"$Tags.PEER_HOSTNAME.key" "localhost"
"$Tags.PEER_PORT.key" server.address.port
"$Tags.HTTP_METHOD.key" "GET"
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT
}
}
}
}

View File

@ -2,7 +2,9 @@ package datadog.trace.instrumentation.apachehttpclient;
import datadog.trace.agent.decorator.HttpClientDecorator;
import java.net.URI;
import java.net.URISyntaxException;
import org.apache.http.HttpResponse;
import org.apache.http.RequestLine;
import org.apache.http.client.methods.HttpUriRequest;
public class ApacheHttpClientDecorator extends HttpClientDecorator<HttpUriRequest, HttpResponse> {
@ -24,8 +26,9 @@ public class ApacheHttpClientDecorator extends HttpClientDecorator<HttpUriReques
}
@Override
protected String url(final HttpUriRequest httpRequest) {
return httpRequest.getRequestLine().getUri();
protected URI url(final HttpUriRequest request) throws URISyntaxException {
final RequestLine requestLine = request.getRequestLine();
return requestLine == null ? null : new URI(requestLine.getUri());
}
@Override

View File

@ -0,0 +1,43 @@
import datadog.trace.agent.test.base.HttpClientTest
import datadog.trace.instrumentation.apachehttpclient.ApacheHttpClientDecorator
import org.apache.http.HttpResponse
import org.apache.http.client.ResponseHandler
import org.apache.http.client.methods.HttpGet
import org.apache.http.impl.client.DefaultHttpClient
import org.apache.http.message.BasicHeader
import spock.lang.Shared
class ApacheHttpClientResponseHandlerTest extends HttpClientTest<ApacheHttpClientDecorator> {
@Shared
def client = new DefaultHttpClient()
@Shared
def handler = new ResponseHandler<Integer>() {
@Override
Integer handleResponse(HttpResponse response) {
return response.statusLine.statusCode
}
}
@Override
int doRequest(String method, URI uri, Map<String, String> headers, Closure callback) {
assert method == "GET"
HttpGet request = new HttpGet(uri)
headers.entrySet().each {
request.addHeader(new BasicHeader(it.key, it.value))
}
def status = client.execute(request, handler)
// handler execution is included within the client span, so we can't call the callback there.
callback?.call()
return status
}
@Override
ApacheHttpClientDecorator decorator() {
return ApacheHttpClientDecorator.DECORATE
}
}

View File

@ -1,148 +1,31 @@
import datadog.opentracing.DDSpan
import datadog.trace.agent.test.AgentTestRunner
import datadog.trace.agent.test.asserts.TraceAssert
import datadog.trace.api.Config
import datadog.trace.api.DDSpanTypes
import io.opentracing.tag.Tags
import org.apache.http.HttpResponse
import org.apache.http.client.HttpClient
import datadog.trace.agent.test.base.HttpClientTest
import datadog.trace.instrumentation.apachehttpclient.ApacheHttpClientDecorator
import org.apache.http.client.methods.HttpGet
import org.apache.http.impl.client.BasicResponseHandler
import org.apache.http.impl.client.DefaultHttpClient
import org.apache.http.message.BasicHeader
import spock.lang.AutoCleanup
import spock.lang.Shared
import static datadog.trace.agent.test.server.http.TestHttpServer.httpServer
import static datadog.trace.agent.test.utils.TraceUtils.runUnderTrace
import static datadog.trace.agent.test.utils.TraceUtils.withConfigOverride
class ApacheHttpClientTest extends HttpClientTest<ApacheHttpClientDecorator> {
class ApacheHttpClientTest extends AgentTestRunner {
@Shared
def client = new DefaultHttpClient()
@AutoCleanup
@Shared
def server = httpServer {
handlers {
prefix("success") {
handleDistributedRequest()
String msg = "Hello."
response.status(200).send(msg)
}
prefix("redirect") {
handleDistributedRequest()
redirect(server.address.resolve("/success").toURL().toString())
}
prefix("another-redirect") {
handleDistributedRequest()
redirect(server.address.resolve("/redirect").toURL().toString())
}
}
}
@Shared
int port = server.address.port
@Shared
def successUrl = server.address.resolve("/success")
@Shared
def redirectUrl = server.address.resolve("/redirect")
@Shared
def twoRedirectsUrl = server.address.resolve("/another-redirect")
@Shared
def handler = new BasicResponseHandler()
final HttpClient client = new DefaultHttpClient()
def "trace request with propagation"() {
when:
String response = withConfigOverride(Config.HTTP_CLIENT_HOST_SPLIT_BY_DOMAIN, "$renameService") {
runUnderTrace("parent") {
if (responseHandler) {
client.execute(new HttpGet(successUrl), responseHandler)
} else {
client.execute(new HttpGet(successUrl)).entity.content.text
}
}
@Override
int doRequest(String method, URI uri, Map<String, String> headers, Closure callback) {
assert method == "GET"
HttpGet request = new HttpGet(uri)
headers.entrySet().each {
request.addHeader(new BasicHeader(it.key, it.value))
}
then:
response == "Hello."
// one trace on the server, one trace on the client
assertTraces(2) {
server.distributedRequestTrace(it, 0, TEST_WRITER[1][1])
trace(1, 2) {
parentSpan(it, 0)
successClientSpan(it, 1, span(0), renameService)
}
}
where:
responseHandler << [null, handler]
renameService << [false, true]
def response = client.execute(request)
callback?.call()
response.entity.getContent().close() // Make sure the connection is closed.
response.statusLine.statusCode
}
def "trace request without propagation"() {
setup:
HttpGet request = new HttpGet(successUrl)
request.addHeader(new BasicHeader("is-dd-server", "false"))
when:
HttpResponse response = withConfigOverride(Config.HTTP_CLIENT_HOST_SPLIT_BY_DOMAIN, "$renameService") {
runUnderTrace("parent") {
client.execute(request)
}
}
then:
response.getStatusLine().getStatusCode() == 200
// only one trace (client).
assertTraces(1) {
trace(0, 2) {
parentSpan(it, 0)
successClientSpan(it, 1, span(0), renameService)
}
}
where:
renameService << [false, true]
}
def parentSpan(TraceAssert trace, int index, Throwable exception = null) {
trace.span(index) {
parent()
serviceName "unnamed-java-app"
operationName "parent"
resourceName "parent"
errored exception != null
tags {
defaultTags()
if (exception) {
errorTags(exception.class)
}
}
}
}
def successClientSpan(TraceAssert trace, int index, DDSpan parent, boolean renameService, status = 200, route = "success", Throwable exception = null) {
trace.span(index) {
childOf parent
serviceName renameService ? "localhost" : "unnamed-java-app"
operationName "http.request"
resourceName "GET /$route"
spanType DDSpanTypes.HTTP_CLIENT
errored exception != null
tags {
defaultTags()
if (exception) {
errorTags(exception.class)
}
"$Tags.COMPONENT.key" "apache-httpclient"
"$Tags.HTTP_STATUS.key" status
"$Tags.HTTP_URL.key" "http://localhost:$port/$route"
"$Tags.PEER_HOSTNAME.key" "localhost"
"$Tags.PEER_PORT.key" server.address.port
"$Tags.HTTP_METHOD.key" "GET"
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT
}
}
@Override
ApacheHttpClientDecorator decorator() {
return ApacheHttpClientDecorator.DECORATE
}
}

View File

@ -6,6 +6,8 @@ import com.amazonaws.Response;
import datadog.trace.agent.decorator.HttpClientDecorator;
import datadog.trace.api.DDTags;
import io.opentracing.Span;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@ -81,8 +83,8 @@ public class AwsSdkClientDecorator extends HttpClientDecorator<Request, Response
}
@Override
protected String url(final Request request) {
return request.getEndpoint().toString();
protected URI url(final Request request) throws URISyntaxException {
return new URI(request.getEndpoint().toString());
}
@Override

View File

@ -136,7 +136,7 @@ class AWSClientTest extends AgentTestRunner {
tags {
"$Tags.COMPONENT.key" "java-aws-sdk"
"$Tags.HTTP_STATUS.key" 200
"$Tags.HTTP_URL.key" "$server.address"
"$Tags.HTTP_URL.key" "$server.address/"
"$Tags.HTTP_METHOD.key" "$method"
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT
"aws.service" { it.contains(service) }
@ -209,7 +209,7 @@ class AWSClientTest extends AgentTestRunner {
parent()
tags {
"$Tags.COMPONENT.key" "java-aws-sdk"
"$Tags.HTTP_URL.key" "http://localhost:${UNUSABLE_PORT}"
"$Tags.HTTP_URL.key" "http://localhost:${UNUSABLE_PORT}/"
"$Tags.HTTP_METHOD.key" "$method"
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT
"aws.service" { it.contains(service) }
@ -272,7 +272,7 @@ class AWSClientTest extends AgentTestRunner {
parent()
tags {
"$Tags.COMPONENT.key" "java-aws-sdk"
"$Tags.HTTP_URL.key" "https://s3.amazonaws.com"
"$Tags.HTTP_URL.key" "https://s3.amazonaws.com/"
"$Tags.HTTP_METHOD.key" "HEAD"
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT
"aws.service" "Amazon S3"
@ -318,11 +318,11 @@ class AWSClientTest extends AgentTestRunner {
parent()
tags {
"$Tags.COMPONENT.key" "java-aws-sdk"
"$Tags.HTTP_URL.key" "http://localhost:$server.address.port"
"$Tags.HTTP_URL.key" "$server.address/"
"$Tags.HTTP_METHOD.key" "GET"
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT
"aws.service" "Amazon S3"
"aws.endpoint" "http://localhost:$server.address.port"
"aws.endpoint" "$server.address"
"aws.operation" "GetObjectRequest"
"aws.agent" "java-aws-sdk"
try {
@ -342,7 +342,7 @@ class AWSClientTest extends AgentTestRunner {
childOf(span(0))
tags {
"$Tags.COMPONENT.key" "apache-httpclient"
"$Tags.HTTP_URL.key" "http://localhost:$server.address.port/someBucket/someKey"
"$Tags.HTTP_URL.key" "$server.address/someBucket/someKey"
"$Tags.PEER_HOSTNAME.key" "localhost"
"$Tags.PEER_PORT.key" server.address.port
"$Tags.HTTP_METHOD.key" "GET"

View File

@ -106,7 +106,7 @@ class AWSClientTest extends AgentTestRunner {
tags {
"$Tags.COMPONENT.key" "java-aws-sdk"
"$Tags.HTTP_STATUS.key" 200
"$Tags.HTTP_URL.key" "$server.address"
"$Tags.HTTP_URL.key" "$server.address/"
"$Tags.HTTP_METHOD.key" "$method"
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT
"aws.service" { it.contains(service) }
@ -179,7 +179,7 @@ class AWSClientTest extends AgentTestRunner {
parent()
tags {
"$Tags.COMPONENT.key" "java-aws-sdk"
"$Tags.HTTP_URL.key" "http://localhost:${UNUSABLE_PORT}"
"$Tags.HTTP_URL.key" "http://localhost:${UNUSABLE_PORT}/"
"$Tags.HTTP_METHOD.key" "$method"
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT
"aws.service" { it.contains(service) }
@ -242,7 +242,7 @@ class AWSClientTest extends AgentTestRunner {
parent()
tags {
"$Tags.COMPONENT.key" "java-aws-sdk"
"$Tags.HTTP_URL.key" "https://s3.amazonaws.com"
"$Tags.HTTP_URL.key" "https://s3.amazonaws.com/"
"$Tags.HTTP_METHOD.key" "GET"
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT
"aws.service" "Amazon S3"
@ -288,7 +288,7 @@ class AWSClientTest extends AgentTestRunner {
parent()
tags {
"$Tags.COMPONENT.key" "java-aws-sdk"
"$Tags.HTTP_URL.key" "http://localhost:$server.address.port"
"$Tags.HTTP_URL.key" "$server.address/"
"$Tags.HTTP_METHOD.key" "GET"
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT
"aws.service" "Amazon S3"
@ -308,7 +308,7 @@ class AWSClientTest extends AgentTestRunner {
childOf(span(0))
tags {
"$Tags.COMPONENT.key" "apache-httpclient"
"$Tags.HTTP_URL.key" "http://localhost:$server.address.port/someBucket/someKey"
"$Tags.HTTP_URL.key" "$server.address/someBucket/someKey"
"$Tags.PEER_HOSTNAME.key" "localhost"
"$Tags.PEER_PORT.key" server.address.port
"$Tags.HTTP_METHOD.key" "GET"

View File

@ -3,6 +3,7 @@ package datadog.trace.instrumentation.aws.v2;
import datadog.trace.agent.decorator.HttpClientDecorator;
import datadog.trace.api.DDTags;
import io.opentracing.Span;
import java.net.URI;
import software.amazon.awssdk.awscore.AwsResponse;
import software.amazon.awssdk.core.SdkResponse;
import software.amazon.awssdk.core.interceptor.ExecutionAttributes;
@ -59,13 +60,8 @@ public class AwsSdkClientDecorator extends HttpClientDecorator<SdkHttpRequest, S
}
@Override
protected String url(final SdkHttpRequest request) {
return request.protocol()
+ "://"
+ request.host()
+ ":"
+ request.port()
+ request.encodedPath();
protected URI url(final SdkHttpRequest request) {
return request.getUri();
}
@Override

View File

@ -60,9 +60,6 @@ class AwsClientTest extends AgentTestRunner {
expect:
response != null
// It looks like url doesn't contain trailing slash on empty path for some reason
def expectedUrl = path == "/" ? "${server.address}" : "${server.address}${path}"
assertTraces(1) {
trace(0, 2) {
span(0) {
@ -75,7 +72,7 @@ class AwsClientTest extends AgentTestRunner {
tags {
"$Tags.COMPONENT.key" "java-aws-sdk"
"$Tags.HTTP_STATUS.key" 200
"$Tags.HTTP_URL.key" expectedUrl
"$Tags.HTTP_URL.key" "${server.address}${path}"
"$Tags.HTTP_METHOD.key" "$method"
"$Tags.PEER_HOSTNAME.key" "localhost"
"$Tags.PEER_PORT.key" server.address.port
@ -96,7 +93,7 @@ class AwsClientTest extends AgentTestRunner {
tags {
"$Tags.COMPONENT.key" "apache-httpclient"
"$Tags.HTTP_STATUS.key" 200
"$Tags.HTTP_URL.key" expectedUrl
"$Tags.HTTP_URL.key" "${server.address}${path}"
"$Tags.PEER_HOSTNAME.key" "localhost"
"$Tags.PEER_PORT.key" server.address.port
"$Tags.HTTP_METHOD.key" "$method"
@ -146,9 +143,6 @@ class AwsClientTest extends AgentTestRunner {
expect:
response != null
// It looks like url doesn't contain trailing slash on empty path for some reason
def expectedUrl = path == "/" ? "${server.address}" : "${server.address}${path}"
// Order is not guaranteed in these traces, so reorder them if needed to put aws trace first
if (TEST_WRITER[0][0].serviceName != "java-aws-sdk") {
def tmp = TEST_WRITER[0]
@ -168,7 +162,7 @@ class AwsClientTest extends AgentTestRunner {
tags {
"$Tags.COMPONENT.key" "java-aws-sdk"
"$Tags.HTTP_STATUS.key" 200
"$Tags.HTTP_URL.key" expectedUrl
"$Tags.HTTP_URL.key" "${server.address}${path}"
"$Tags.HTTP_METHOD.key" "$method"
"$Tags.PEER_HOSTNAME.key" "localhost"
"$Tags.PEER_PORT.key" server.address.port
@ -192,7 +186,7 @@ class AwsClientTest extends AgentTestRunner {
tags {
"$Tags.COMPONENT.key" "netty-client"
"$Tags.HTTP_STATUS.key" 200
"$Tags.HTTP_URL.key" expectedUrl
"$Tags.HTTP_URL.key" "${server.address}${path}"
"$Tags.PEER_HOSTNAME.key" "localhost"
"$Tags.PEER_HOST_IPV4.key" "127.0.0.1"
"$Tags.PEER_PORT.key" server.address.port
@ -260,7 +254,7 @@ class AwsClientTest extends AgentTestRunner {
parent()
tags {
"$Tags.COMPONENT.key" "java-aws-sdk"
"$Tags.HTTP_URL.key" "http://localhost:$server.address.port/someBucket/someKey"
"$Tags.HTTP_URL.key" "$server.address/someBucket/someKey"
"$Tags.HTTP_METHOD.key" "GET"
"$Tags.PEER_HOSTNAME.key" "localhost"
"$Tags.PEER_PORT.key" server.address.port
@ -281,7 +275,7 @@ class AwsClientTest extends AgentTestRunner {
childOf(span(0))
tags {
"$Tags.COMPONENT.key" "apache-httpclient"
"$Tags.HTTP_URL.key" "http://localhost:$server.address.port/someBucket/someKey"
"$Tags.HTTP_URL.key" "$server.address/someBucket/someKey"
"$Tags.PEER_HOSTNAME.key" "localhost"
"$Tags.PEER_PORT.key" server.address.port
"$Tags.HTTP_METHOD.key" "GET"

View File

@ -38,10 +38,8 @@ dependencies {
implementation deps.autoservice
testCompile project(':dd-java-agent:testing')
// Include httpclient instrumentation for testing because it is a dependency for elasticsearch-rest-client.
// It doesn't actually work though. They use HttpAsyncClient, which isn't currently instrumented.
// TODO: add Apache's HttpAsyncClient instrumentation when that is complete.
testCompile project(':dd-java-agent:instrumentation:apache-httpclient-4')
testCompile project(':dd-java-agent:instrumentation:apache-httpasyncclient-4')
testCompile group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.11.0'
testCompile group: 'org.apache.logging.log4j', name: 'log4j-api', version: '2.11.0'

View File

@ -78,7 +78,7 @@ class Elasticsearch6RestClientTest extends AgentTestRunner {
result.status == "green"
assertTraces(1) {
trace(0, 1) {
trace(0, 2) {
span(0) {
serviceName "elasticsearch"
resourceName "GET _cluster/health"
@ -96,6 +96,21 @@ class Elasticsearch6RestClientTest extends AgentTestRunner {
defaultTags()
}
}
span(1) {
serviceName "elasticsearch"
resourceName "GET _cluster/health"
operationName "http.request"
spanType DDSpanTypes.HTTP_CLIENT
childOf span(0)
tags {
"$Tags.COMPONENT.key" "apache-httpasyncclient"
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT
"$Tags.HTTP_METHOD.key" "GET"
"$Tags.HTTP_URL.key" "_cluster/health"
"$Tags.HTTP_STATUS.key" 200
defaultTags()
}
}
}
}
}

View File

@ -83,7 +83,7 @@ class Elasticsearch5RestClientTest extends AgentTestRunner {
result.status == "green"
assertTraces(1) {
trace(0, 1) {
trace(0, 2) {
span(0) {
serviceName "elasticsearch"
resourceName "GET _cluster/health"
@ -101,6 +101,21 @@ class Elasticsearch5RestClientTest extends AgentTestRunner {
defaultTags()
}
}
span(1) {
serviceName "elasticsearch"
resourceName "GET _cluster/health"
operationName "http.request"
spanType DDSpanTypes.HTTP_CLIENT
childOf span(0)
tags {
"$Tags.COMPONENT.key" "apache-httpasyncclient"
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT
"$Tags.HTTP_METHOD.key" "GET"
"$Tags.HTTP_URL.key" "_cluster/health"
"$Tags.HTTP_STATUS.key" 200
defaultTags()
}
}
}
}
}

View File

@ -39,10 +39,10 @@ dependencies {
implementation deps.autoservice
testCompile project(':dd-java-agent:testing')
// Include httpclient instrumentation for testing because it is a dependency for elasticsearch-rest-client.
// It doesn't actually work though. They use HttpAsyncClient, which isn't currently instrumented.
// TODO: add Apache's HttpAsyncClient instrumentation when that is complete.
testCompile project(':dd-java-agent:instrumentation:apache-httpclient-4')
testCompile project(':dd-java-agent:instrumentation:apache-httpasyncclient-4')
// Netty is used, but it adds complexity to the tests since we're using embedded ES.
//testCompile project(':dd-java-agent:instrumentation:netty-4.1')
testCompile group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.11.0'
testCompile group: 'org.apache.logging.log4j', name: 'log4j-api', version: '2.11.0'

View File

@ -82,7 +82,7 @@ class Elasticsearch6RestClientTest extends AgentTestRunner {
result.status == "green"
assertTraces(1) {
trace(0, 1) {
trace(0, 2) {
span(0) {
serviceName "elasticsearch"
resourceName "GET _cluster/health"
@ -100,6 +100,21 @@ class Elasticsearch6RestClientTest extends AgentTestRunner {
defaultTags()
}
}
span(1) {
serviceName "elasticsearch"
resourceName "GET _cluster/health"
operationName "http.request"
spanType DDSpanTypes.HTTP_CLIENT
childOf span(0)
tags {
"$Tags.COMPONENT.key" "apache-httpasyncclient"
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT
"$Tags.HTTP_METHOD.key" "GET"
"$Tags.HTTP_URL.key" "_cluster/health"
"$Tags.HTTP_STATUS.key" 200
defaultTags()
}
}
}
}
}

View File

@ -78,7 +78,7 @@ class Elasticsearch6RestClientTest extends AgentTestRunner {
result.status == "green"
assertTraces(1) {
trace(0, 1) {
trace(0, 2) {
span(0) {
serviceName "elasticsearch"
resourceName "GET _cluster/health"
@ -96,6 +96,21 @@ class Elasticsearch6RestClientTest extends AgentTestRunner {
defaultTags()
}
}
span(1) {
serviceName "elasticsearch"
resourceName "GET _cluster/health"
operationName "http.request"
spanType DDSpanTypes.HTTP_CLIENT
childOf span(0)
tags {
"$Tags.COMPONENT.key" "apache-httpasyncclient"
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT
"$Tags.HTTP_METHOD.key" "GET"
"$Tags.HTTP_URL.key" "_cluster/health"
"$Tags.HTTP_STATUS.key" 200
defaultTags()
}
}
}
}
}

View File

@ -36,6 +36,7 @@ dependencies {
// Ensure no cross interference
testCompile project(':dd-java-agent:instrumentation:elasticsearch:rest-5')
testCompile project(':dd-java-agent:instrumentation:elasticsearch:transport-5')
testCompile project(':dd-java-agent:instrumentation:apache-httpasyncclient-4')
testCompile group: 'org.elasticsearch', name: 'elasticsearch', version: '2.0.0'

View File

@ -40,6 +40,8 @@ dependencies {
implementation deps.autoservice
testCompile project(':dd-java-agent:testing')
testCompile project(':dd-java-agent:instrumentation:apache-httpasyncclient-4')
testCompile project(':dd-java-agent:instrumentation:netty-4.1')
testCompile group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.11.0'
testCompile group: 'org.apache.logging.log4j', name: 'log4j-api', version: '2.11.0'

View File

@ -42,11 +42,8 @@ dependencies {
testCompile project(':dd-java-agent:testing')
// Ensure no cross interference
testCompile project(':dd-java-agent:instrumentation:elasticsearch:rest-5')
// Include httpclient instrumentation for testing because it is a dependency for elasticsearch-rest-client.
// It doesn't actually work though. They use HttpAsyncClient, which isn't currently instrumented.
// TODO: add HttpAsyncClient instrumentation when that is complete.
testCompile project(':dd-java-agent:instrumentation:apache-httpclient-4')
// TODO: add netty instrumentation when that is complete.
testCompile project(':dd-java-agent:instrumentation:apache-httpasyncclient-4')
testCompile project(':dd-java-agent:instrumentation:netty-4.1')
testCompile group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.11.0'
testCompile group: 'org.apache.logging.log4j', name: 'log4j-api', version: '2.11.0'

View File

@ -42,10 +42,8 @@ dependencies {
testCompile project(':dd-java-agent:testing')
// Ensure no cross interference
testCompile project(':dd-java-agent:instrumentation:elasticsearch:rest-5')
// Include httpclient instrumentation for testing because it is a dependency for elasticsearch-rest-client.
// It doesn't actually work though. They use HttpAsyncClient, which isn't currently instrumented.
// TODO: add HttpAsyncClient instrumentation when that is complete.
testCompile project(':dd-java-agent:instrumentation:apache-httpclient-4')
testCompile project(':dd-java-agent:instrumentation:apache-httpasyncclient-4')
testCompile project(':dd-java-agent:instrumentation:netty-4.1')
testCompile group: 'org.elasticsearch.plugin', name: 'transport-netty4-client', version: '6.0.0'
testCompile group: 'org.elasticsearch.client', name: 'transport', version: '6.0.0'

View File

@ -2,6 +2,8 @@ package datadog.trace.instrumentation.http_url_connection;
import datadog.trace.agent.decorator.HttpClientDecorator;
import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URISyntaxException;
import javax.net.ssl.HttpsURLConnection;
public class HttpUrlConnectionDecorator extends HttpClientDecorator<HttpURLConnection, Integer> {
@ -23,8 +25,8 @@ public class HttpUrlConnectionDecorator extends HttpClientDecorator<HttpURLConne
}
@Override
protected String url(final HttpURLConnection connection) {
return connection.getURL().toString();
protected URI url(final HttpURLConnection connection) throws URISyntaxException {
return connection.getURL().toURI();
}
@Override

View File

@ -80,7 +80,7 @@ class HttpUrlConnectionTest extends AgentTestRunner {
tags {
"$Tags.COMPONENT.key" "http-url-connection"
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT
"$Tags.HTTP_URL.key" "$server.address"
"$Tags.HTTP_URL.key" "$server.address/"
"$Tags.HTTP_METHOD.key" "GET"
"$Tags.HTTP_STATUS.key" STATUS
"$Tags.PEER_HOSTNAME.key" "localhost"
@ -98,7 +98,7 @@ class HttpUrlConnectionTest extends AgentTestRunner {
tags {
"$Tags.COMPONENT.key" "http-url-connection"
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT
"$Tags.HTTP_URL.key" "$server.address"
"$Tags.HTTP_URL.key" "$server.address/"
"$Tags.HTTP_METHOD.key" "GET"
"$Tags.HTTP_STATUS.key" STATUS
"$Tags.PEER_HOSTNAME.key" "localhost"
@ -163,7 +163,7 @@ class HttpUrlConnectionTest extends AgentTestRunner {
tags {
"$Tags.COMPONENT.key" "http-url-connection"
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT
"$Tags.HTTP_URL.key" "$server.address"
"$Tags.HTTP_URL.key" "$server.address/"
"$Tags.HTTP_METHOD.key" "GET"
"$Tags.HTTP_STATUS.key" STATUS
"$Tags.PEER_HOSTNAME.key" "localhost"
@ -181,7 +181,7 @@ class HttpUrlConnectionTest extends AgentTestRunner {
tags {
"$Tags.COMPONENT.key" "http-url-connection"
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT
"$Tags.HTTP_URL.key" "$server.address"
"$Tags.HTTP_URL.key" "$server.address/"
"$Tags.HTTP_METHOD.key" "GET"
"$Tags.HTTP_STATUS.key" STATUS
"$Tags.PEER_HOSTNAME.key" "localhost"
@ -230,7 +230,7 @@ class HttpUrlConnectionTest extends AgentTestRunner {
tags {
"$Tags.COMPONENT.key" "http-url-connection"
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT
"$Tags.HTTP_URL.key" "$server.address"
"$Tags.HTTP_URL.key" "$server.address/"
"$Tags.HTTP_METHOD.key" "HEAD"
"$Tags.HTTP_STATUS.key" STATUS
"$Tags.PEER_HOSTNAME.key" "localhost"
@ -279,7 +279,7 @@ class HttpUrlConnectionTest extends AgentTestRunner {
tags {
"$Tags.COMPONENT.key" "http-url-connection"
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT
"$Tags.HTTP_URL.key" "$server.address"
"$Tags.HTTP_URL.key" "$server.address/"
"$Tags.HTTP_METHOD.key" "GET"
"$Tags.HTTP_STATUS.key" STATUS
"$Tags.PEER_HOSTNAME.key" "localhost"
@ -345,7 +345,7 @@ class HttpUrlConnectionTest extends AgentTestRunner {
tags {
"$Tags.COMPONENT.key" "http-url-connection"
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT
"$Tags.HTTP_URL.key" "$server.address"
"$Tags.HTTP_URL.key" "$server.address/"
"$Tags.HTTP_METHOD.key" "POST"
"$Tags.HTTP_STATUS.key" STATUS
"$Tags.PEER_HOSTNAME.key" "localhost"
@ -413,7 +413,7 @@ class HttpUrlConnectionTest extends AgentTestRunner {
tags {
"$Tags.COMPONENT.key" "http-url-connection"
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT
"$Tags.HTTP_URL.key" "$server.address"
"$Tags.HTTP_URL.key" "$server.address/"
"$Tags.HTTP_METHOD.key" "GET"
"$Tags.HTTP_STATUS.key" STATUS
"$Tags.PEER_HOSTNAME.key" "localhost"
@ -460,7 +460,7 @@ class HttpUrlConnectionTest extends AgentTestRunner {
tags {
"$Tags.COMPONENT.key" "http-url-connection"
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT
"$Tags.HTTP_URL.key" "$server.address"
"$Tags.HTTP_URL.key" "$server.address/"
"$Tags.HTTP_METHOD.key" "POST"
"$Tags.HTTP_STATUS.key" STATUS
"$Tags.PEER_HOSTNAME.key" "localhost"

View File

@ -49,7 +49,7 @@ class UrlConnectionTest extends AgentTestRunner {
tags {
"$Tags.COMPONENT.key" "http-url-connection"
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT
"$Tags.HTTP_URL.key" "$url"
"$Tags.HTTP_URL.key" "$url/"
"$Tags.HTTP_METHOD.key" "GET"
"$Tags.PEER_HOSTNAME.key" "localhost"
"$Tags.PEER_PORT.key" UNUSABLE_PORT

View File

@ -1,6 +1,7 @@
package datadog.trace.instrumentation.jaxrs;
import datadog.trace.agent.decorator.HttpClientDecorator;
import java.net.URI;
import javax.ws.rs.client.ClientRequestContext;
import javax.ws.rs.client.ClientResponseContext;
@ -24,8 +25,8 @@ public class JaxRsClientDecorator
}
@Override
protected String url(final ClientRequestContext httpRequest) {
return httpRequest.getUri().toString();
protected URI url(final ClientRequestContext httpRequest) {
return httpRequest.getUri();
}
@Override

View File

@ -3,6 +3,7 @@ package datadog.trace.instrumentation.jetty8;
import datadog.trace.agent.decorator.HttpServerDecorator;
import io.opentracing.Span;
import java.net.URI;
import java.net.URISyntaxException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@ -26,8 +27,8 @@ public class JettyDecorator
}
@Override
protected URI url(final HttpServletRequest httpServletRequest) {
return URI.create(httpServletRequest.getRequestURL().toString());
protected URI url(final HttpServletRequest httpServletRequest) throws URISyntaxException {
return new URI(httpServletRequest.getRequestURL().toString());
}
@Override

View File

@ -29,18 +29,12 @@ public class NettyHttpClientDecorator extends HttpClientDecorator<HttpRequest, H
}
@Override
protected String url(final HttpRequest request) {
// FIXME: This code is duplicated across netty integrations.
try {
URI uri = new URI(request.getUri());
if ((uri.getHost() == null || uri.getHost().equals("")) && request.headers().contains(HOST)) {
uri = new URI("http://" + request.headers().get(HOST) + request.getUri());
}
return new URI(uri.getScheme(), null, uri.getHost(), uri.getPort(), uri.getPath(), null, null)
.toString();
} catch (final URISyntaxException e) {
log.debug("Cannot parse netty uri: {}", request.getUri());
return request.getUri();
protected URI url(final HttpRequest request) throws URISyntaxException {
final URI uri = new URI(request.getUri());
if ((uri.getHost() == null || uri.getHost().equals("")) && request.headers().contains(HOST)) {
return new URI("http://" + request.headers().get(HOST) + request.getUri());
} else {
return uri;
}
}

View File

@ -33,18 +33,12 @@ public class NettyHttpServerDecorator
}
@Override
protected URI url(final HttpRequest request) {
// FIXME: This code is duplicated across netty integrations.
try {
URI uri = new URI(request.getUri());
if ((uri.getHost() == null || uri.getHost().equals("")) && request.headers().contains(HOST)) {
uri = new URI("http://" + request.headers().get(HOST) + request.getUri());
}
return new URI(
uri.getScheme(), null, uri.getHost(), uri.getPort(), uri.getPath(), null, null);
} catch (final URISyntaxException e) {
log.debug("Cannot parse netty uri: {}", request.getUri());
return null;
protected URI url(final HttpRequest request) throws URISyntaxException {
final URI uri = new URI(request.getUri());
if ((uri.getHost() == null || uri.getHost().equals("")) && request.headers().contains(HOST)) {
return new URI("http://" + request.headers().get(HOST) + request.getUri());
} else {
return uri;
}
}

View File

@ -29,18 +29,12 @@ public class NettyHttpClientDecorator extends HttpClientDecorator<HttpRequest, H
}
@Override
protected String url(final HttpRequest request) {
// FIXME: This code is duplicated across netty integrations.
try {
URI uri = new URI(request.uri());
if ((uri.getHost() == null || uri.getHost().equals("")) && request.headers().contains(HOST)) {
uri = new URI("http://" + request.headers().get(HOST) + request.uri());
}
return new URI(uri.getScheme(), null, uri.getHost(), uri.getPort(), uri.getPath(), null, null)
.toString();
} catch (final URISyntaxException e) {
log.debug("Cannot parse netty uri: {}", request.uri());
return request.uri();
protected URI url(final HttpRequest request) throws URISyntaxException {
final URI uri = new URI(request.uri());
if ((uri.getHost() == null || uri.getHost().equals("")) && request.headers().contains(HOST)) {
return new URI("http://" + request.headers().get(HOST) + request.uri());
} else {
return uri;
}
}

View File

@ -33,18 +33,12 @@ public class NettyHttpServerDecorator
}
@Override
protected URI url(final HttpRequest request) {
// FIXME: This code is duplicated across netty integrations.
try {
URI uri = new URI(request.uri());
if ((uri.getHost() == null || uri.getHost().equals("")) && request.headers().contains(HOST)) {
uri = new URI("http://" + request.headers().get(HOST) + request.uri());
}
return new URI(
uri.getScheme(), null, uri.getHost(), uri.getPort(), uri.getPath(), null, null);
} catch (final URISyntaxException e) {
log.debug("Cannot parse netty uri: {}", request.uri());
return null;
protected URI url(final HttpRequest request) throws URISyntaxException {
final URI uri = new URI(request.uri());
if ((uri.getHost() == null || uri.getHost().equals("")) && request.headers().contains(HOST)) {
return new URI("http://" + request.headers().get(HOST) + request.uri());
} else {
return uri;
}
}

View File

@ -1,6 +1,7 @@
package datadog.trace.instrumentation.okhttp3;
import datadog.trace.agent.decorator.HttpClientDecorator;
import java.net.URI;
import okhttp3.Request;
import okhttp3.Response;
@ -35,8 +36,8 @@ public class OkHttpClientDecorator extends HttpClientDecorator<Request, Response
}
@Override
protected String url(final Request httpRequest) {
return httpRequest.url().toString();
protected URI url(final Request httpRequest) {
return httpRequest.url().uri();
}
@Override

View File

@ -31,19 +31,8 @@ public class PlayHttpServerDecorator extends HttpServerDecorator<Request, Reques
}
@Override
protected URI url(final Request request) {
// FIXME: This code is similar to that from the netty integrations.
try {
URI uri = new URI(request.uri());
if ((uri.getHost() == null || uri.getHost().equals("")) && !request.host().isEmpty()) {
uri = new URI("http://" + request.host() + request.uri());
}
return new URI(
uri.getScheme(), null, uri.getHost(), uri.getPort(), uri.getPath(), null, null);
} catch (final URISyntaxException e) {
log.debug("Cannot parse uri: {}", request.uri());
return null;
}
protected URI url(final Request request) throws URISyntaxException {
return new URI(request.secure() ? "https://" : "http://" + request.host() + request.uri());
}
@Override

View File

@ -3,6 +3,7 @@ package datadog.trace.instrumentation.servlet2;
import datadog.trace.agent.decorator.HttpServerDecorator;
import io.opentracing.Span;
import java.net.URI;
import java.net.URISyntaxException;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
@ -26,8 +27,8 @@ public class Servlet2Decorator
}
@Override
protected URI url(final HttpServletRequest httpServletRequest) {
return URI.create(httpServletRequest.getRequestURL().toString());
protected URI url(final HttpServletRequest httpServletRequest) throws URISyntaxException {
return new URI(httpServletRequest.getRequestURL().toString());
}
@Override

View File

@ -3,6 +3,7 @@ package datadog.trace.instrumentation.servlet3;
import datadog.trace.agent.decorator.HttpServerDecorator;
import io.opentracing.Span;
import java.net.URI;
import java.net.URISyntaxException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@ -26,8 +27,8 @@ public class Servlet3Decorator
}
@Override
protected URI url(final HttpServletRequest httpServletRequest) {
return URI.create(httpServletRequest.getRequestURL().toString());
protected URI url(final HttpServletRequest httpServletRequest) throws URISyntaxException {
return new URI(httpServletRequest.getRequestURL().toString());
}
@Override

View File

@ -6,6 +6,7 @@ import datadog.trace.api.DDTags;
import io.opentracing.Scope;
import io.opentracing.Span;
import java.net.URI;
import java.net.URISyntaxException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
@ -40,8 +41,8 @@ public class SpringWebHttpServerDecorator
}
@Override
protected URI url(final HttpServletRequest httpServletRequest) {
return URI.create(httpServletRequest.getRequestURL().toString());
protected URI url(final HttpServletRequest httpServletRequest) throws URISyntaxException {
return new URI(httpServletRequest.getRequestURL().toString());
}
@Override

View File

@ -0,0 +1,313 @@
package datadog.trace.agent.test.base
import datadog.opentracing.DDSpan
import datadog.trace.agent.decorator.HttpClientDecorator
import datadog.trace.agent.test.AgentTestRunner
import datadog.trace.agent.test.asserts.TraceAssert
import datadog.trace.api.Config
import datadog.trace.api.DDSpanTypes
import io.opentracing.tag.Tags
import spock.lang.AutoCleanup
import spock.lang.Shared
import java.util.concurrent.ExecutionException
import static datadog.trace.agent.test.server.http.TestHttpServer.httpServer
import static datadog.trace.agent.test.utils.TraceUtils.runUnderTrace
import static datadog.trace.agent.test.utils.TraceUtils.withConfigOverride
abstract class HttpClientTest<T extends HttpClientDecorator> extends AgentTestRunner {
@AutoCleanup
@Shared
def server = httpServer {
handlers {
prefix("success") {
handleDistributedRequest()
String msg = "Hello."
response.status(200).send(msg)
}
prefix("error") {
handleDistributedRequest()
String msg = "Sorry."
response.status(500).send(msg)
}
prefix("redirect") {
handleDistributedRequest()
redirect(server.address.resolve("/success").toURL().toString())
}
prefix("another-redirect") {
handleDistributedRequest()
redirect(server.address.resolve("/redirect").toURL().toString())
}
prefix("circular-redirect") {
handleDistributedRequest()
redirect(server.address.resolve("/circular-redirect").toURL().toString())
}
}
}
@Shared
T decorator = decorator()
/**
* Make the request and return the status code response
* @param method
* @return
*/
abstract int doRequest(String method, URI uri, Map<String, String> headers = [:], Closure callback = null)
abstract T decorator()
Integer statusOnRedirectError() {
return null
}
def "basic #method request"() {
when:
def status = doRequest(method, server.address.resolve(url))
then:
status == 200
assertTraces(2) {
server.distributedRequestTrace(it, 0, trace(1).get(0))
trace(1, 1) {
clientSpan(it, 0, null, false)
}
}
where:
method = "GET"
url << ["/success", "/success?with=params"]
}
def "basic #method request with parent"() {
when:
def status = runUnderTrace("parent") {
doRequest(method, server.address.resolve("/success"))
}
then:
status == 200
assertTraces(2) {
server.distributedRequestTrace(it, 0, trace(1).get(1))
trace(1, 2) {
parentSpan(it, 0)
clientSpan(it, 1, span(0), false)
}
}
where:
method = "GET"
}
def "basic #method request with split-by-domain"() {
when:
def status = withConfigOverride(Config.HTTP_CLIENT_HOST_SPLIT_BY_DOMAIN, "true") {
doRequest(method, server.address.resolve("/success"))
}
then:
status == 200
assertTraces(2) {
server.distributedRequestTrace(it, 0, trace(1).get(0))
trace(1, 1) {
clientSpan(it, 0, null, true)
}
}
where:
method = "GET"
}
def "trace request without propagation"() {
when:
def status = withConfigOverride(Config.HTTP_CLIENT_HOST_SPLIT_BY_DOMAIN, "$renameService") {
runUnderTrace("parent") {
doRequest(method, server.address.resolve("/success"), ["is-dd-server": "false"])
}
}
then:
status == 200
// only one trace (client).
assertTraces(1) {
trace(0, 2) {
parentSpan(it, 0)
clientSpan(it, 1, span(0), renameService)
}
}
where:
method = "GET"
renameService << [false, true]
}
def "trace request with callback and parent"() {
when:
def status = runUnderTrace("parent") {
doRequest(method, server.address.resolve("/success"), ["is-dd-server": "false"]) {
runUnderTrace("child") {}
}
}
then:
status == 200
// only one trace (client).
assertTraces(1) {
trace(0, 3) {
parentSpan(it, 0)
span(1) {
operationName "child"
childOf span(0)
}
clientSpan(it, 2, span(0), false)
}
}
where:
method = "GET"
}
def "trace request with callback and no parent"() {
when:
def status = doRequest(method, server.address.resolve("/success"), ["is-dd-server": "false"]) {
runUnderTrace("child") {
// Ensure consistent ordering of traces for assertion.
TEST_WRITER.waitForTraces(1)
}
}
then:
status == 200
// only one trace (client).
assertTraces(2) {
trace(0, 1) {
clientSpan(it, 0, null, false)
}
trace(1, 1) {
span(0) {
operationName "child"
parent()
}
}
}
where:
method = "GET"
}
def "basic #method request with 1 redirect"() {
setup:
def uri = server.address.resolve("/redirect")
when:
def status = doRequest(method, uri)
then:
status == 200
assertTraces(3) {
server.distributedRequestTrace(it, 0, trace(2).get(0))
server.distributedRequestTrace(it, 1, trace(2).get(0))
trace(2, 1) {
clientSpan(it, 0, null, false, uri)
}
}
where:
method = "GET"
}
def "basic #method request with 2 redirects"() {
setup:
def uri = server.address.resolve("/another-redirect")
when:
def status = doRequest(method, uri)
then:
status == 200
assertTraces(4) {
server.distributedRequestTrace(it, 0, trace(3).get(0))
server.distributedRequestTrace(it, 1, trace(3).get(0))
server.distributedRequestTrace(it, 2, trace(3).get(0))
trace(3, 1) {
clientSpan(it, 0, null, false, uri)
}
}
where:
method = "GET"
}
def "basic #method request with circular redirects"() {
setup:
def uri = server.address.resolve("/circular-redirect")
when:
doRequest(method, uri)//, ["is-dd-server": "false"])
then:
def ex = thrown(Exception)
def thrownException = ex instanceof ExecutionException ? ex.cause : ex
and:
assertTraces(3) {
server.distributedRequestTrace(it, 0, trace(2).get(0))
server.distributedRequestTrace(it, 1, trace(2).get(0))
trace(2, 1) {
clientSpan(it, 0, null, false, uri, statusOnRedirectError(), thrownException)
}
}
where:
method = "GET"
}
void parentSpan(TraceAssert trace, int index, Throwable exception = null) {
trace.span(index) {
parent()
serviceName "unnamed-java-app"
operationName "parent"
resourceName "parent"
errored exception != null
tags {
defaultTags()
if (exception) {
errorTags(exception.class)
}
}
}
}
// parent span must be cast otherwise it breaks debugging classloading (junit loads it early)
void clientSpan(TraceAssert trace, int index, Object parentSpan, boolean renameService, URI uri = server.address.resolve("/success"), Integer status = 200, Throwable exception = null) {
trace.span(index) {
if (parentSpan == null) {
parent()
} else {
childOf((DDSpan) parentSpan)
}
serviceName renameService ? "localhost" : "unnamed-java-app"
operationName "http.request"
resourceName "GET $uri.path"
spanType DDSpanTypes.HTTP_CLIENT
errored exception != null
tags {
defaultTags()
if (exception) {
errorTags(exception.class, exception.message)
}
"$Tags.COMPONENT.key" decorator.component()
if (status) {
"$Tags.HTTP_STATUS.key" status
}
"$Tags.HTTP_URL.key" "$uri"
"$Tags.PEER_HOSTNAME.key" "localhost"
"$Tags.PEER_PORT.key" server.address.port
"$Tags.HTTP_METHOD.key" "GET"
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT
}
}
}
}

View File

@ -4,6 +4,7 @@ minimumBranchCoverage = 0.5
minimumInstructionCoverage = 0.5
excludedClassesConverage += [
'datadog.trace.agent.test.asserts.*Assert',
'datadog.trace.agent.test.base.*',
'datadog.trace.agent.test.AgentTestRunner.ErrorCountingListener',
'datadog.trace.agent.test.utils.*',
// Avoid applying jacoco instrumentation to classes instrumented by tested agent

View File

@ -25,6 +25,7 @@ include ':dd-smoke-tests:wildfly'
// instrumentation:
include ':dd-java-agent:instrumentation:akka-http-10.0'
include ':dd-java-agent:instrumentation:apache-httpasyncclient-4'
include ':dd-java-agent:instrumentation:apache-httpclient-4'
include ':dd-java-agent:instrumentation:aws-java-sdk-1.11.0'
include ':dd-java-agent:instrumentation:aws-java-sdk-2.2'