Merge pull request #340 from DataDog/tyler/http-url-connection

Add instrumentation for Java’s UrlConnection
This commit is contained in:
Tyler Benson 2018-06-06 11:32:56 +10:00 committed by GitHub
commit cc2e364b27
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 826 additions and 6 deletions

View File

@ -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 {

View File

@ -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()");
}
}

View File

@ -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);
}
}
}

View File

@ -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();
}
}
}
}

View File

@ -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()
}
}
}

View File

@ -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()
}
}

View File

@ -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')
}

View File

@ -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();
}

View File

@ -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);
}

View File

@ -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'