Merge pull request #809 from DataDog/tyler/httpasyncclient
Add instrumentation for Apache HttpAsyncClient
This commit is contained in:
commit
25d109753d
|
@ -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));
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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: '+'
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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()");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -73,7 +73,7 @@ class AWSClientTest extends AgentTestRunner {
|
|||
client.requestHandler2s != null
|
||||
client.requestHandler2s.size() == size
|
||||
client.requestHandler2s.get(0).getClass().getSimpleName() == "TracingRequestHandler"
|
||||
|
||||
|
||||
where:
|
||||
addHandler | size
|
||||
true | 2
|
||||
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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'
|
||||
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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'
|
||||
|
|
Loading…
Reference in New Issue