Merge pull request #340 from DataDog/tyler/http-url-connection
Add instrumentation for Java’s UrlConnection
This commit is contained in:
commit
cc2e364b27
|
@ -5,6 +5,7 @@ import static net.bytebuddy.matcher.ElementMatchers.any;
|
|||
import static net.bytebuddy.matcher.ElementMatchers.nameContains;
|
||||
import static net.bytebuddy.matcher.ElementMatchers.nameMatches;
|
||||
import static net.bytebuddy.matcher.ElementMatchers.nameStartsWith;
|
||||
import static net.bytebuddy.matcher.ElementMatchers.named;
|
||||
import static net.bytebuddy.matcher.ElementMatchers.not;
|
||||
|
||||
import datadog.trace.agent.tooling.muzzle.Reference.Mismatch;
|
||||
|
@ -49,9 +50,15 @@ public class AgentInstaller {
|
|||
.or(nameStartsWith("datadog.trace."))
|
||||
.or(nameStartsWith("datadog.opentracing."))
|
||||
.or(nameStartsWith("datadog.slf4j."))
|
||||
.or(nameStartsWith("java.").and(not(nameStartsWith("java.util.concurrent."))))
|
||||
.or(
|
||||
nameStartsWith("java.")
|
||||
.and(
|
||||
not(
|
||||
named("java.net.URL")
|
||||
.or(named("java.net.HttpURLConnection"))
|
||||
.or(nameStartsWith("java.util.concurrent.")))))
|
||||
.or(nameStartsWith("com.sun."))
|
||||
.or(nameStartsWith("sun."))
|
||||
.or(nameStartsWith("sun.").and(not(nameStartsWith("sun.net.www.protocol."))))
|
||||
.or(nameStartsWith("jdk."))
|
||||
.or(nameStartsWith("org.aspectj."))
|
||||
.or(nameStartsWith("org.groovy."))
|
||||
|
@ -87,7 +94,7 @@ public class AgentInstaller {
|
|||
if (throwable instanceof MismatchException) {
|
||||
final MismatchException mismatchException = (MismatchException) throwable;
|
||||
log.debug("{}", mismatchException.getMessage());
|
||||
for (Mismatch mismatch : mismatchException.getMismatches()) {
|
||||
for (final Mismatch mismatch : mismatchException.getMismatches()) {
|
||||
log.debug("--{}", mismatch);
|
||||
}
|
||||
} else {
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
package datadog.trace.instrumentation.url_connection;
|
||||
|
||||
import io.opentracing.propagation.TextMap;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
|
||||
public class MessageHeadersInjectAdapter implements TextMap {
|
||||
|
||||
private final HttpURLConnection connection;
|
||||
|
||||
public MessageHeadersInjectAdapter(final HttpURLConnection connection) {
|
||||
this.connection = connection;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void put(final String key, final String value) {
|
||||
if (connection.getRequestProperty(key) == null) {
|
||||
connection.setRequestProperty(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,174 @@
|
|||
package datadog.trace.instrumentation.url_connection;
|
||||
|
||||
import static io.opentracing.log.Fields.ERROR_OBJECT;
|
||||
import static net.bytebuddy.matcher.ElementMatchers.is;
|
||||
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
|
||||
import static net.bytebuddy.matcher.ElementMatchers.isPublic;
|
||||
import static net.bytebuddy.matcher.ElementMatchers.isSubTypeOf;
|
||||
import static net.bytebuddy.matcher.ElementMatchers.nameStartsWith;
|
||||
import static net.bytebuddy.matcher.ElementMatchers.named;
|
||||
import static net.bytebuddy.matcher.ElementMatchers.not;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import datadog.trace.agent.tooling.DDAdvice;
|
||||
import datadog.trace.agent.tooling.DDTransformers;
|
||||
import datadog.trace.agent.tooling.HelperInjector;
|
||||
import datadog.trace.agent.tooling.Instrumenter;
|
||||
import datadog.trace.api.DDSpanTypes;
|
||||
import datadog.trace.api.DDTags;
|
||||
import datadog.trace.bootstrap.CallDepthThreadLocalMap;
|
||||
import io.opentracing.Scope;
|
||||
import io.opentracing.Span;
|
||||
import io.opentracing.Tracer;
|
||||
import io.opentracing.propagation.Format;
|
||||
import io.opentracing.tag.Tags;
|
||||
import io.opentracing.util.GlobalTracer;
|
||||
import java.io.IOException;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.util.Collections;
|
||||
import javax.net.ssl.HttpsURLConnection;
|
||||
import net.bytebuddy.agent.builder.AgentBuilder;
|
||||
import net.bytebuddy.asm.Advice;
|
||||
import sun.net.www.protocol.ftp.FtpURLConnection;
|
||||
import sun.net.www.protocol.mailto.MailToURLConnection;
|
||||
|
||||
@AutoService(Instrumenter.class)
|
||||
public class UrlConnectionInstrumentation extends Instrumenter.Configurable {
|
||||
|
||||
public UrlConnectionInstrumentation() {
|
||||
super("urlconnection", "httpurlconnection");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean defaultEnabled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AgentBuilder apply(final AgentBuilder agentBuilder) {
|
||||
return agentBuilder
|
||||
.type(
|
||||
not(
|
||||
is(sun.net.www.protocol.jar.JarURLConnection.class)
|
||||
.or(is(sun.net.www.protocol.file.FileURLConnection.class)))
|
||||
.and(isSubTypeOf(URLConnection.class)))
|
||||
.transform(
|
||||
new HelperInjector(
|
||||
"datadog.trace.instrumentation.url_connection.MessageHeadersInjectAdapter"))
|
||||
.transform(DDTransformers.defaultTransformers())
|
||||
.transform(
|
||||
DDAdvice.create()
|
||||
.advice(
|
||||
isMethod()
|
||||
.and(isPublic())
|
||||
.and(
|
||||
named("getResponseCode")
|
||||
.or(named("getOutputStream"))
|
||||
.or(named("getInputStream"))
|
||||
.or(nameStartsWith("getHeaderField"))),
|
||||
UrlConnectionAdvice.class.getName()))
|
||||
.asDecorator();
|
||||
}
|
||||
|
||||
public static class UrlConnectionAdvice {
|
||||
|
||||
@Advice.OnMethodEnter(suppress = Throwable.class)
|
||||
public static Scope startSpan(
|
||||
@Advice.This final URLConnection connection, @Advice.Origin("#m") final String methodName) {
|
||||
String protocol = "url";
|
||||
if (connection != null) {
|
||||
final URL url = connection.getURL();
|
||||
protocol = url.getProtocol();
|
||||
}
|
||||
|
||||
final boolean isValidProtocol =
|
||||
protocol.equals("http")
|
||||
|| protocol.equals("https")
|
||||
|| protocol.equals("ftp")
|
||||
|| protocol.equals("mailto");
|
||||
final boolean isTraceRequest =
|
||||
Thread.currentThread().getName().equals("dd-agent-writer")
|
||||
|| (connection != null && connection.getRequestProperty("Datadog-Meta-Lang") != null);
|
||||
if (!isValidProtocol || isTraceRequest) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final int callDepth = CallDepthThreadLocalMap.incrementCallDepth(URLConnection.class);
|
||||
if (callDepth > 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String command = ".request.response_code";
|
||||
if (methodName.equals("getOutputStream")) {
|
||||
command = ".request.output_stream";
|
||||
} else if (methodName.equals("getInputStream")) {
|
||||
command = ".request.input_stream";
|
||||
}
|
||||
|
||||
final Tracer tracer = GlobalTracer.get();
|
||||
final Scope scope =
|
||||
tracer
|
||||
.buildSpan(protocol + command)
|
||||
.withTag(Tags.SPAN_KIND.getKey(), Tags.SPAN_KIND_CLIENT)
|
||||
.withTag(DDTags.SPAN_TYPE, DDSpanTypes.HTTP_CLIENT)
|
||||
.startActive(true);
|
||||
|
||||
if (connection != null) {
|
||||
final URL url = connection.getURL();
|
||||
final Span span = scope.span();
|
||||
Tags.COMPONENT.set(scope.span(), connection.getClass().getSimpleName());
|
||||
Tags.HTTP_URL.set(span, url.toString());
|
||||
Tags.PEER_HOSTNAME.set(span, url.getHost());
|
||||
if (url.getPort() > 0) {
|
||||
Tags.PEER_PORT.set(span, url.getPort());
|
||||
} else if (connection instanceof HttpsURLConnection) {
|
||||
Tags.PEER_PORT.set(span, 443);
|
||||
} else if (connection instanceof HttpURLConnection) {
|
||||
Tags.PEER_PORT.set(span, 80);
|
||||
} else if (connection instanceof FtpURLConnection) {
|
||||
Tags.PEER_PORT.set(span, 21);
|
||||
} else if (connection instanceof MailToURLConnection) {
|
||||
Tags.PEER_PORT.set(span, 25);
|
||||
}
|
||||
|
||||
if (connection instanceof HttpURLConnection) {
|
||||
|
||||
Tags.HTTP_METHOD.set(span, ((HttpURLConnection) connection).getRequestMethod());
|
||||
|
||||
tracer.inject(
|
||||
span.context(),
|
||||
Format.Builtin.HTTP_HEADERS,
|
||||
new MessageHeadersInjectAdapter((HttpURLConnection) connection));
|
||||
}
|
||||
}
|
||||
return scope;
|
||||
}
|
||||
|
||||
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
|
||||
public static void stopSpan(
|
||||
@Advice.This final URLConnection connection,
|
||||
@Advice.Enter final Scope scope,
|
||||
@Advice.Thrown final Throwable throwable) {
|
||||
|
||||
if (scope == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final Span span = scope.span();
|
||||
|
||||
if (throwable != null) {
|
||||
Tags.ERROR.set(span, true);
|
||||
span.log(Collections.singletonMap(ERROR_OBJECT, throwable));
|
||||
} else if (connection instanceof HttpURLConnection) {
|
||||
try {
|
||||
Tags.HTTP_STATUS.set(span, ((HttpURLConnection) connection).getResponseCode());
|
||||
} catch (final IOException e) {
|
||||
}
|
||||
}
|
||||
scope.close();
|
||||
CallDepthThreadLocalMap.reset(URLConnection.class);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
package datadog.trace.instrumentation.url_connection;
|
||||
|
||||
import static io.opentracing.log.Fields.ERROR_OBJECT;
|
||||
import static net.bytebuddy.matcher.ElementMatchers.is;
|
||||
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
|
||||
import static net.bytebuddy.matcher.ElementMatchers.isPublic;
|
||||
import static net.bytebuddy.matcher.ElementMatchers.named;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import datadog.trace.agent.tooling.DDAdvice;
|
||||
import datadog.trace.agent.tooling.DDTransformers;
|
||||
import datadog.trace.agent.tooling.Instrumenter;
|
||||
import datadog.trace.api.DDSpanTypes;
|
||||
import datadog.trace.api.DDTags;
|
||||
import io.opentracing.Scope;
|
||||
import io.opentracing.Span;
|
||||
import io.opentracing.tag.Tags;
|
||||
import io.opentracing.util.GlobalTracer;
|
||||
import java.net.URL;
|
||||
import java.util.Collections;
|
||||
import net.bytebuddy.agent.builder.AgentBuilder;
|
||||
import net.bytebuddy.asm.Advice;
|
||||
|
||||
@AutoService(Instrumenter.class)
|
||||
public class UrlInstrumentation extends Instrumenter.Configurable {
|
||||
|
||||
public UrlInstrumentation() {
|
||||
super("urlconnection", "httpurlconnection");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean defaultEnabled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AgentBuilder apply(final AgentBuilder agentBuilder) {
|
||||
return agentBuilder
|
||||
.type(is(URL.class))
|
||||
.transform(DDTransformers.defaultTransformers())
|
||||
.transform(
|
||||
DDAdvice.create()
|
||||
.advice(
|
||||
isMethod().and(isPublic()).and(named("openConnection")),
|
||||
ConnectionErrorAdvice.class.getName()))
|
||||
.asDecorator();
|
||||
}
|
||||
|
||||
public static class ConnectionErrorAdvice {
|
||||
|
||||
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
|
||||
public static void errorSpan(
|
||||
@Advice.This final URL url, @Advice.Thrown final Throwable throwable) {
|
||||
if (throwable != null) {
|
||||
final boolean isTraceRequest = Thread.currentThread().getName().equals("dd-agent-writer");
|
||||
if (isTraceRequest) {
|
||||
return;
|
||||
}
|
||||
|
||||
String protocol = url.getProtocol();
|
||||
protocol = protocol != null ? protocol : "url";
|
||||
|
||||
final Scope scope =
|
||||
GlobalTracer.get()
|
||||
.buildSpan(protocol + ".request")
|
||||
.withTag(Tags.SPAN_KIND.getKey(), Tags.SPAN_KIND_CLIENT)
|
||||
.withTag(DDTags.SPAN_TYPE, DDSpanTypes.HTTP_CLIENT)
|
||||
.startActive(true);
|
||||
|
||||
final Span span = scope.span();
|
||||
Tags.HTTP_URL.set(span, url.toString());
|
||||
Tags.PEER_PORT.set(span, url.getPort() == -1 ? 80 : url.getPort());
|
||||
Tags.PEER_HOSTNAME.set(span, url.getHost());
|
||||
|
||||
Tags.ERROR.set(span, true);
|
||||
span.log(Collections.singletonMap(ERROR_OBJECT, throwable));
|
||||
scope.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,433 @@
|
|||
import datadog.trace.agent.test.AgentTestRunner
|
||||
import datadog.trace.api.DDSpanTypes
|
||||
import datadog.trace.api.DDTags
|
||||
import io.opentracing.Scope
|
||||
import io.opentracing.SpanContext
|
||||
import io.opentracing.propagation.Format
|
||||
import io.opentracing.propagation.TextMap
|
||||
import io.opentracing.tag.Tags
|
||||
import io.opentracing.util.GlobalTracer
|
||||
import ratpack.handling.Context
|
||||
import spock.lang.Shared
|
||||
|
||||
import static datadog.trace.agent.test.ListWriterAssert.assertTraces
|
||||
import static datadog.trace.agent.test.TestUtils.runUnderTrace
|
||||
import static ratpack.groovy.test.embed.GroovyEmbeddedApp.ratpack
|
||||
import static ratpack.http.HttpMethod.HEAD
|
||||
import static ratpack.http.HttpMethod.POST
|
||||
|
||||
class HttpUrlConnectionTest extends AgentTestRunner {
|
||||
static {
|
||||
System.setProperty("dd.integration.urlconnection.enabled", "true")
|
||||
}
|
||||
|
||||
@Shared
|
||||
def server = ratpack {
|
||||
handlers {
|
||||
all {
|
||||
String msg = "<html><body><h1>Hello test.</h1>\n"
|
||||
boolean isDDServer = true
|
||||
if (context.request.getHeaders().contains("is-dd-server")) {
|
||||
isDDServer = Boolean.parseBoolean(context.request.getHeaders().get("is-dd-server"))
|
||||
}
|
||||
if (isDDServer) {
|
||||
final SpanContext extractedContext =
|
||||
GlobalTracer.get()
|
||||
.extract(Format.Builtin.HTTP_HEADERS, new RatpackResponseAdapter(context))
|
||||
Scope scope =
|
||||
GlobalTracer.get()
|
||||
.buildSpan("test-http-server")
|
||||
.asChildOf((SpanContext) extractedContext)
|
||||
.startActive(true)
|
||||
scope.close()
|
||||
}
|
||||
|
||||
response.status(201).send(msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def "trace request with propagation"() {
|
||||
setup:
|
||||
runUnderTrace("someTrace") {
|
||||
HttpURLConnection connection = server.address.toURL().openConnection()
|
||||
assert GlobalTracer.get().scopeManager().active() != null
|
||||
def stream = connection.inputStream
|
||||
def lines = stream.readLines()
|
||||
stream.close()
|
||||
assert connection.getResponseCode() == 201
|
||||
assert lines == ["<html><body><h1>Hello test.</h1>"]
|
||||
|
||||
// call again to ensure the cycling is ok
|
||||
connection = server.getAddress().toURL().openConnection()
|
||||
assert GlobalTracer.get().scopeManager().active() != null
|
||||
stream = connection.inputStream
|
||||
lines = stream.readLines()
|
||||
stream.close()
|
||||
assert connection.getResponseCode() == 201
|
||||
assert lines == ["<html><body><h1>Hello test.</h1>"]
|
||||
}
|
||||
|
||||
expect:
|
||||
assertTraces(TEST_WRITER, 3) {
|
||||
trace(0, 1) {
|
||||
span(0) {
|
||||
operationName "test-http-server"
|
||||
childOf(TEST_WRITER[2][4])
|
||||
errored false
|
||||
tags {
|
||||
defaultTags()
|
||||
}
|
||||
}
|
||||
}
|
||||
trace(1, 1) {
|
||||
span(0) {
|
||||
operationName "test-http-server"
|
||||
childOf(TEST_WRITER[2][2])
|
||||
errored false
|
||||
tags {
|
||||
defaultTags()
|
||||
}
|
||||
}
|
||||
}
|
||||
trace(2, 5) {
|
||||
span(0) {
|
||||
operationName "someTrace"
|
||||
parent()
|
||||
errored false
|
||||
tags {
|
||||
defaultTags()
|
||||
}
|
||||
}
|
||||
span(1) {
|
||||
operationName "http.request.response_code"
|
||||
childOf span(0)
|
||||
errored false
|
||||
tags {
|
||||
"$Tags.COMPONENT.key" "HttpURLConnection"
|
||||
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT
|
||||
"$DDTags.SPAN_TYPE" DDSpanTypes.HTTP_CLIENT
|
||||
"$Tags.HTTP_URL.key" "$server.address"
|
||||
"$Tags.HTTP_METHOD.key" "GET"
|
||||
"$Tags.HTTP_STATUS.key" 201
|
||||
"$Tags.PEER_HOSTNAME.key" "localhost"
|
||||
"$Tags.PEER_PORT.key" server.address.port
|
||||
defaultTags()
|
||||
}
|
||||
}
|
||||
span(2) {
|
||||
operationName "http.request.input_stream"
|
||||
childOf span(0)
|
||||
errored false
|
||||
tags {
|
||||
"$Tags.COMPONENT.key" "HttpURLConnection"
|
||||
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT
|
||||
"$DDTags.SPAN_TYPE" DDSpanTypes.HTTP_CLIENT
|
||||
"$Tags.HTTP_URL.key" "$server.address"
|
||||
"$Tags.HTTP_METHOD.key" "GET"
|
||||
"$Tags.HTTP_STATUS.key" 201
|
||||
"$Tags.PEER_HOSTNAME.key" "localhost"
|
||||
"$Tags.PEER_PORT.key" server.address.port
|
||||
defaultTags()
|
||||
}
|
||||
}
|
||||
span(3) {
|
||||
operationName "http.request.response_code"
|
||||
childOf span(0)
|
||||
errored false
|
||||
tags {
|
||||
"$Tags.COMPONENT.key" "HttpURLConnection"
|
||||
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT
|
||||
"$DDTags.SPAN_TYPE" DDSpanTypes.HTTP_CLIENT
|
||||
"$Tags.HTTP_URL.key" "$server.address"
|
||||
"$Tags.HTTP_METHOD.key" "GET"
|
||||
"$Tags.HTTP_STATUS.key" 201
|
||||
"$Tags.PEER_HOSTNAME.key" "localhost"
|
||||
"$Tags.PEER_PORT.key" server.address.port
|
||||
defaultTags()
|
||||
}
|
||||
}
|
||||
span(4) {
|
||||
operationName "http.request.input_stream"
|
||||
childOf span(0)
|
||||
errored false
|
||||
tags {
|
||||
"$Tags.COMPONENT.key" "HttpURLConnection"
|
||||
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT
|
||||
"$DDTags.SPAN_TYPE" DDSpanTypes.HTTP_CLIENT
|
||||
"$Tags.HTTP_URL.key" "$server.address"
|
||||
"$Tags.HTTP_METHOD.key" "GET"
|
||||
"$Tags.HTTP_STATUS.key" 201
|
||||
"$Tags.PEER_HOSTNAME.key" "localhost"
|
||||
"$Tags.PEER_PORT.key" server.address.port
|
||||
defaultTags()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def "trace request without propagation"() {
|
||||
setup:
|
||||
runUnderTrace("someTrace") {
|
||||
HttpURLConnection connection = server.address.toURL().openConnection()
|
||||
connection.addRequestProperty("is-dd-server", "false")
|
||||
assert GlobalTracer.get().scopeManager().active() != null
|
||||
def stream = connection.inputStream
|
||||
def lines = stream.readLines()
|
||||
stream.close()
|
||||
assert connection.getResponseCode() == 201
|
||||
assert lines == ["<html><body><h1>Hello test.</h1>"]
|
||||
|
||||
// call again to ensure the cycling is ok
|
||||
connection = server.getAddress().toURL().openConnection()
|
||||
connection.addRequestProperty("is-dd-server", "false")
|
||||
assert GlobalTracer.get().scopeManager().active() != null
|
||||
stream = connection.inputStream
|
||||
lines = stream.readLines()
|
||||
stream.close()
|
||||
assert connection.getResponseCode() == 201
|
||||
assert lines == ["<html><body><h1>Hello test.</h1>"]
|
||||
}
|
||||
|
||||
expect:
|
||||
assertTraces(TEST_WRITER, 1) {
|
||||
trace(0, 5) {
|
||||
span(0) {
|
||||
operationName "someTrace"
|
||||
parent()
|
||||
errored false
|
||||
tags {
|
||||
defaultTags()
|
||||
}
|
||||
}
|
||||
span(1) {
|
||||
operationName "http.request.response_code"
|
||||
childOf span(0)
|
||||
errored false
|
||||
tags {
|
||||
"$Tags.COMPONENT.key" "HttpURLConnection"
|
||||
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT
|
||||
"$DDTags.SPAN_TYPE" DDSpanTypes.HTTP_CLIENT
|
||||
"$Tags.HTTP_URL.key" "$server.address"
|
||||
"$Tags.HTTP_METHOD.key" "GET"
|
||||
"$Tags.HTTP_STATUS.key" 201
|
||||
"$Tags.PEER_HOSTNAME.key" "localhost"
|
||||
"$Tags.PEER_PORT.key" server.address.port
|
||||
defaultTags()
|
||||
}
|
||||
}
|
||||
span(2) {
|
||||
operationName "http.request.input_stream"
|
||||
childOf span(0)
|
||||
errored false
|
||||
tags {
|
||||
"$Tags.COMPONENT.key" "HttpURLConnection"
|
||||
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT
|
||||
"$DDTags.SPAN_TYPE" DDSpanTypes.HTTP_CLIENT
|
||||
"$Tags.HTTP_URL.key" "$server.address"
|
||||
"$Tags.HTTP_METHOD.key" "GET"
|
||||
"$Tags.HTTP_STATUS.key" 201
|
||||
"$Tags.PEER_HOSTNAME.key" "localhost"
|
||||
"$Tags.PEER_PORT.key" server.address.port
|
||||
defaultTags()
|
||||
}
|
||||
}
|
||||
span(3) {
|
||||
operationName "http.request.response_code"
|
||||
childOf span(0)
|
||||
errored false
|
||||
tags {
|
||||
"$Tags.COMPONENT.key" "HttpURLConnection"
|
||||
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT
|
||||
"$DDTags.SPAN_TYPE" DDSpanTypes.HTTP_CLIENT
|
||||
"$Tags.HTTP_URL.key" "$server.address"
|
||||
"$Tags.HTTP_METHOD.key" "GET"
|
||||
"$Tags.HTTP_STATUS.key" 201
|
||||
"$Tags.PEER_HOSTNAME.key" "localhost"
|
||||
"$Tags.PEER_PORT.key" server.address.port
|
||||
defaultTags()
|
||||
}
|
||||
}
|
||||
span(4) {
|
||||
operationName "http.request.input_stream"
|
||||
childOf span(0)
|
||||
errored false
|
||||
tags {
|
||||
"$Tags.COMPONENT.key" "HttpURLConnection"
|
||||
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT
|
||||
"$DDTags.SPAN_TYPE" DDSpanTypes.HTTP_CLIENT
|
||||
"$Tags.HTTP_URL.key" "$server.address"
|
||||
"$Tags.HTTP_METHOD.key" "GET"
|
||||
"$Tags.HTTP_STATUS.key" 201
|
||||
"$Tags.PEER_HOSTNAME.key" "localhost"
|
||||
"$Tags.PEER_PORT.key" server.address.port
|
||||
defaultTags()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def "test response code"() {
|
||||
setup:
|
||||
runUnderTrace("someTrace") {
|
||||
HttpURLConnection connection = server.address.toURL().openConnection()
|
||||
connection.setRequestMethod(HEAD.name)
|
||||
connection.addRequestProperty("is-dd-server", "false")
|
||||
assert GlobalTracer.get().scopeManager().active() != null
|
||||
assert connection.getResponseCode() == 201
|
||||
}
|
||||
|
||||
expect:
|
||||
assertTraces(TEST_WRITER, 1) {
|
||||
trace(0, 2) {
|
||||
span(0) {
|
||||
operationName "someTrace"
|
||||
parent()
|
||||
errored false
|
||||
tags {
|
||||
defaultTags()
|
||||
}
|
||||
}
|
||||
span(1) {
|
||||
operationName "http.request.response_code"
|
||||
childOf span(0)
|
||||
errored false
|
||||
tags {
|
||||
"$Tags.COMPONENT.key" "HttpURLConnection"
|
||||
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT
|
||||
"$DDTags.SPAN_TYPE" DDSpanTypes.HTTP_CLIENT
|
||||
"$Tags.HTTP_URL.key" "$server.address"
|
||||
"$Tags.HTTP_METHOD.key" "HEAD"
|
||||
"$Tags.HTTP_STATUS.key" 201
|
||||
"$Tags.PEER_HOSTNAME.key" "localhost"
|
||||
"$Tags.PEER_PORT.key" server.address.port
|
||||
defaultTags()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def "test post request"() {
|
||||
setup:
|
||||
runUnderTrace("someTrace") {
|
||||
HttpURLConnection connection = server.address.toURL().openConnection()
|
||||
connection.setRequestMethod(POST.name)
|
||||
|
||||
String urlParameters = "q=ASDF&w=&e=&r=12345&t="
|
||||
|
||||
// Send post request
|
||||
connection.setDoOutput(true)
|
||||
DataOutputStream wr = new DataOutputStream(connection.getOutputStream())
|
||||
wr.writeBytes(urlParameters)
|
||||
wr.flush()
|
||||
wr.close()
|
||||
|
||||
assert connection.getResponseCode() == 201
|
||||
}
|
||||
|
||||
expect:
|
||||
assertTraces(TEST_WRITER, 2) {
|
||||
trace(0, 1) {
|
||||
span(0) {
|
||||
operationName "test-http-server"
|
||||
childOf(TEST_WRITER[1][2])
|
||||
errored false
|
||||
tags {
|
||||
defaultTags()
|
||||
}
|
||||
}
|
||||
}
|
||||
trace(1, 3) {
|
||||
span(0) {
|
||||
operationName "someTrace"
|
||||
parent()
|
||||
errored false
|
||||
tags {
|
||||
defaultTags()
|
||||
}
|
||||
}
|
||||
span(1) {
|
||||
operationName "http.request.response_code"
|
||||
childOf span(0)
|
||||
errored false
|
||||
tags {
|
||||
"$Tags.COMPONENT.key" "HttpURLConnection"
|
||||
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT
|
||||
"$DDTags.SPAN_TYPE" DDSpanTypes.HTTP_CLIENT
|
||||
"$Tags.HTTP_URL.key" "$server.address"
|
||||
"$Tags.HTTP_METHOD.key" "POST"
|
||||
"$Tags.HTTP_STATUS.key" 201
|
||||
"$Tags.PEER_HOSTNAME.key" "localhost"
|
||||
"$Tags.PEER_PORT.key" server.address.port
|
||||
defaultTags()
|
||||
}
|
||||
}
|
||||
span(2) {
|
||||
operationName "http.request.output_stream"
|
||||
childOf span(0)
|
||||
errored false
|
||||
tags {
|
||||
"$Tags.COMPONENT.key" "HttpURLConnection"
|
||||
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT
|
||||
"$DDTags.SPAN_TYPE" DDSpanTypes.HTTP_CLIENT
|
||||
"$Tags.HTTP_URL.key" "$server.address"
|
||||
"$Tags.HTTP_METHOD.key" "POST"
|
||||
"$Tags.HTTP_STATUS.key" 201
|
||||
"$Tags.PEER_HOSTNAME.key" "localhost"
|
||||
"$Tags.PEER_PORT.key" server.address.port
|
||||
defaultTags()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def "request that looks like a trace submission is ignored"() {
|
||||
setup:
|
||||
runUnderTrace("someTrace") {
|
||||
HttpURLConnection connection = server.address.toURL().openConnection()
|
||||
connection.addRequestProperty("Datadog-Meta-Lang", "false")
|
||||
connection.addRequestProperty("is-dd-server", "false")
|
||||
def stream = connection.inputStream
|
||||
def lines = stream.readLines()
|
||||
stream.close()
|
||||
assert connection.getResponseCode() == 201
|
||||
assert lines == ["<html><body><h1>Hello test.</h1>"]
|
||||
}
|
||||
|
||||
expect:
|
||||
assertTraces(TEST_WRITER, 1) {
|
||||
trace(0, 1) {
|
||||
span(0) {
|
||||
operationName "someTrace"
|
||||
parent()
|
||||
errored false
|
||||
tags {
|
||||
defaultTags()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class RatpackResponseAdapter implements TextMap {
|
||||
final Context context
|
||||
|
||||
RatpackResponseAdapter(Context context) {
|
||||
this.context = context
|
||||
}
|
||||
|
||||
@Override
|
||||
void put(String key, String value) {
|
||||
context.response.set(key, value)
|
||||
}
|
||||
|
||||
@Override
|
||||
Iterator<Map.Entry<String, String>> iterator() {
|
||||
return context.request.getHeaders().asMultiValueMap().entrySet().iterator()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
import datadog.trace.agent.test.AgentTestRunner
|
||||
import datadog.trace.agent.test.TestUtils
|
||||
import datadog.trace.api.DDSpanTypes
|
||||
import datadog.trace.api.DDTags
|
||||
import io.opentracing.tag.Tags
|
||||
import io.opentracing.util.GlobalTracer
|
||||
|
||||
import static datadog.trace.agent.test.ListWriterAssert.assertTraces
|
||||
import static datadog.trace.agent.test.TestUtils.runUnderTrace
|
||||
|
||||
class UrlConnectionTest extends AgentTestRunner {
|
||||
static {
|
||||
System.setProperty("dd.integration.urlconnection.enabled", "true")
|
||||
}
|
||||
|
||||
private static final int INVALID_PORT = TestUtils.randomOpenPort()
|
||||
|
||||
def "trace request with connection failure"() {
|
||||
when:
|
||||
runUnderTrace("someTrace") {
|
||||
URLConnection connection = url.openConnection()
|
||||
connection.setConnectTimeout(10)
|
||||
connection.setReadTimeout(10)
|
||||
assert GlobalTracer.get().scopeManager().active() != null
|
||||
connection.inputStream
|
||||
}
|
||||
|
||||
then:
|
||||
thrown ConnectException
|
||||
|
||||
expect:
|
||||
assertTraces(TEST_WRITER, 1) {
|
||||
trace(0, 2) {
|
||||
span(0) {
|
||||
operationName "someTrace"
|
||||
parent()
|
||||
errored true
|
||||
tags {
|
||||
errorTags ConnectException, "Connection refused (Connection refused)"
|
||||
defaultTags()
|
||||
}
|
||||
}
|
||||
span(1) {
|
||||
operationName "${scheme}.request.input_stream"
|
||||
childOf span(0)
|
||||
errored true
|
||||
tags {
|
||||
"$Tags.COMPONENT.key" component
|
||||
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT
|
||||
"$DDTags.SPAN_TYPE" DDSpanTypes.HTTP_CLIENT
|
||||
"$Tags.HTTP_URL.key" "$url"
|
||||
if (scheme.startsWith("http")) {
|
||||
"$Tags.HTTP_METHOD.key" "GET"
|
||||
}
|
||||
"$Tags.PEER_HOSTNAME.key" "localhost"
|
||||
"$Tags.PEER_PORT.key" INVALID_PORT
|
||||
errorTags ConnectException, "Connection refused (Connection refused)"
|
||||
defaultTags()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
where:
|
||||
scheme | component
|
||||
"ftp" | "FtpURLConnection"
|
||||
"http" | "HttpURLConnection"
|
||||
"https" | "HttpsURLConnectionImpl"
|
||||
|
||||
url = new URI("$scheme://localhost:$INVALID_PORT").toURL()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
apply from: "${rootDir}/gradle/java.gradle"
|
||||
|
||||
// These classes use Ratpack which requires Java 8. (Currently also incompatible with Java 9.)
|
||||
testJava8Only += '**/*Test.class'
|
||||
|
||||
dependencies {
|
||||
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')
|
||||
}
|
|
@ -1,8 +1,12 @@
|
|||
package datadog.trace.agent.test;
|
||||
|
||||
import static io.opentracing.log.Fields.ERROR_OBJECT;
|
||||
|
||||
import datadog.trace.agent.tooling.Utils;
|
||||
import io.opentracing.Scope;
|
||||
import io.opentracing.Span;
|
||||
import io.opentracing.Tracer;
|
||||
import io.opentracing.tag.Tags;
|
||||
import io.opentracing.util.GlobalTracer;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
|
@ -12,6 +16,7 @@ import java.io.InputStream;
|
|||
import java.lang.reflect.Field;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.URL;
|
||||
import java.util.Collections;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.jar.JarEntry;
|
||||
|
@ -77,12 +82,16 @@ public class TestUtils {
|
|||
}
|
||||
|
||||
public static <T extends Object> Object runUnderTrace(
|
||||
final String rootOperationName, final Callable<T> r) {
|
||||
final String rootOperationName, final Callable<T> r) throws Exception {
|
||||
final Scope scope = GlobalTracer.get().buildSpan(rootOperationName).startActive(true);
|
||||
try {
|
||||
return r.call();
|
||||
} catch (final Exception e) {
|
||||
throw new IllegalStateException(e);
|
||||
final Span span = scope.span();
|
||||
Tags.ERROR.set(span, true);
|
||||
span.log(Collections.singletonMap(ERROR_OBJECT, e));
|
||||
|
||||
throw e;
|
||||
} finally {
|
||||
scope.close();
|
||||
}
|
||||
|
|
|
@ -178,6 +178,7 @@ public class DDApi {
|
|||
objectMapper.writeValue(out, data);
|
||||
out.flush();
|
||||
out.close();
|
||||
|
||||
return httpCon.getResponseCode() == 200;
|
||||
} catch (final IOException e) {
|
||||
if (retry) {
|
||||
|
@ -208,7 +209,7 @@ public class DDApi {
|
|||
return "DDApi { tracesEndpoint=" + tracesEndpoint + " }";
|
||||
}
|
||||
|
||||
public static interface ResponseListener {
|
||||
public interface ResponseListener {
|
||||
/** Invoked after the api receives a response from the core agent. */
|
||||
void onResponse(String endpoint, JsonNode responseJson);
|
||||
}
|
||||
|
|
|
@ -43,6 +43,7 @@ include ':dd-java-agent:instrumentation:servlet-3'
|
|||
include ':dd-java-agent:instrumentation:sparkjava-2.4'
|
||||
include ':dd-java-agent:instrumentation:spring-web'
|
||||
include ':dd-java-agent:instrumentation:trace-annotation'
|
||||
include ':dd-java-agent:instrumentation:url-connection'
|
||||
|
||||
// benchmark
|
||||
include ':dd-java-agent:benchmark'
|
||||
|
|
Loading…
Reference in New Issue