diff --git a/dd-java-agent/instrumentation/akka-http-10.0/src/test/groovy/AkkaHttpClientInstrumentationTest.groovy b/dd-java-agent/instrumentation/akka-http-10.0/src/test/groovy/AkkaHttpClientInstrumentationTest.groovy index 3ae9770f324..696d153a67d 100644 --- a/dd-java-agent/instrumentation/akka-http-10.0/src/test/groovy/AkkaHttpClientInstrumentationTest.groovy +++ b/dd-java-agent/instrumentation/akka-http-10.0/src/test/groovy/AkkaHttpClientInstrumentationTest.groovy @@ -25,8 +25,16 @@ class AkkaHttpClientInstrumentationTest extends HttpClientTest + // FIXME: Callback should be here instead. + // callback?.call() + //} + .toCompletableFuture() + .get() } finally { + // FIXME: remove this when callback above works. blockUntilChildSpansFinished(1) } callback?.call() diff --git a/dd-java-agent/instrumentation/apache-httpasyncclient-4/src/test/groovy/ApacheHttpAsyncClientCallbackTest.groovy b/dd-java-agent/instrumentation/apache-httpasyncclient-4/src/test/groovy/ApacheHttpAsyncClientCallbackTest.groovy index fde0e1bfad6..44b31ab31c4 100644 --- a/dd-java-agent/instrumentation/apache-httpasyncclient-4/src/test/groovy/ApacheHttpAsyncClientCallbackTest.groovy +++ b/dd-java-agent/instrumentation/apache-httpasyncclient-4/src/test/groovy/ApacheHttpAsyncClientCallbackTest.groovy @@ -1,6 +1,5 @@ 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.concurrent.FutureCallback import org.apache.http.impl.nio.client.HttpAsyncClients @@ -22,8 +21,6 @@ class ApacheHttpAsyncClientCallbackTest extends HttpClientTest headers, Closure callback) { - def hasParent = GlobalTracer.get().activeSpan() != null - def request = new HttpUriRequest(method, uri) headers.entrySet().each { request.addHeader(new BasicHeader(it.key, it.value)) @@ -35,21 +32,13 @@ class ApacheHttpAsyncClientCallbackTest extends HttpClientTest AsyncInvoker request = builder.async() def body = BODY_METHODS.contains(method) ? Entity.text("") : null - Response response = request.method(method, (Entity) body).get() - callback?.call() + Response response = request.method(method, (Entity) body, new InvocationCallback(){ + @Override + void completed(Response s) { + callback?.call() + } + + @Override + void failed(Throwable throwable) { + } + }).get() return response.status } diff --git a/dd-java-agent/instrumentation/netty-4.0/src/main/java/datadog/trace/instrumentation/netty40/AttributeKeys.java b/dd-java-agent/instrumentation/netty-4.0/src/main/java/datadog/trace/instrumentation/netty40/AttributeKeys.java index b1b1ecaafa3..7ad60405c9e 100644 --- a/dd-java-agent/instrumentation/netty-4.0/src/main/java/datadog/trace/instrumentation/netty40/AttributeKeys.java +++ b/dd-java-agent/instrumentation/netty-4.0/src/main/java/datadog/trace/instrumentation/netty40/AttributeKeys.java @@ -16,4 +16,7 @@ public class AttributeKeys { public static final AttributeKey CLIENT_ATTRIBUTE_KEY = new AttributeKey<>(HttpClientTracingHandler.class.getName() + ".span"); + + public static final AttributeKey CLIENT_PARENT_ATTRIBUTE_KEY = + new AttributeKey<>(HttpClientTracingHandler.class.getName() + ".parent"); } diff --git a/dd-java-agent/instrumentation/netty-4.0/src/main/java/datadog/trace/instrumentation/netty40/client/HttpClientRequestTracingHandler.java b/dd-java-agent/instrumentation/netty-4.0/src/main/java/datadog/trace/instrumentation/netty40/client/HttpClientRequestTracingHandler.java index 6dbb99246a4..1e9ea59338d 100644 --- a/dd-java-agent/instrumentation/netty-4.0/src/main/java/datadog/trace/instrumentation/netty40/client/HttpClientRequestTracingHandler.java +++ b/dd-java-agent/instrumentation/netty-4.0/src/main/java/datadog/trace/instrumentation/netty40/client/HttpClientRequestTracingHandler.java @@ -10,6 +10,7 @@ import io.netty.handler.codec.http.HttpRequest; import io.opentracing.Scope; import io.opentracing.Span; +import io.opentracing.Tracer; import io.opentracing.propagation.Format; import io.opentracing.util.GlobalTracer; import java.net.InetSocketAddress; @@ -34,19 +35,19 @@ public void write(final ChannelHandlerContext ctx, final Object msg, final Chann final HttpRequest request = (HttpRequest) msg; - final Span span = GlobalTracer.get().buildSpan("netty.client.request").start(); - try (final Scope scope = GlobalTracer.get().scopeManager().activate(span, false)) { + final Tracer tracer = GlobalTracer.get(); + ctx.channel().attr(AttributeKeys.CLIENT_PARENT_ATTRIBUTE_KEY).set(tracer.activeSpan()); + + final Span span = tracer.buildSpan("netty.client.request").start(); + try (final Scope scope = tracer.scopeManager().activate(span, false)) { DECORATE.afterStart(span); DECORATE.onRequest(span, request); DECORATE.onPeerConnection(span, (InetSocketAddress) ctx.channel().remoteAddress()); // AWS calls are often signed, so we can't add headers without breaking the signature. if (!request.headers().contains("amz-sdk-invocation-id")) { - GlobalTracer.get() - .inject( - span.context(), - Format.Builtin.HTTP_HEADERS, - new NettyResponseInjectAdapter(request)); + tracer.inject( + span.context(), Format.Builtin.HTTP_HEADERS, new NettyResponseInjectAdapter(request)); } ctx.channel().attr(AttributeKeys.CLIENT_ATTRIBUTE_KEY).set(span); diff --git a/dd-java-agent/instrumentation/netty-4.0/src/main/java/datadog/trace/instrumentation/netty40/client/HttpClientResponseTracingHandler.java b/dd-java-agent/instrumentation/netty-4.0/src/main/java/datadog/trace/instrumentation/netty40/client/HttpClientResponseTracingHandler.java index cb6979e17dc..ac1865b6bfc 100644 --- a/dd-java-agent/instrumentation/netty-4.0/src/main/java/datadog/trace/instrumentation/netty40/client/HttpClientResponseTracingHandler.java +++ b/dd-java-agent/instrumentation/netty-4.0/src/main/java/datadog/trace/instrumentation/netty40/client/HttpClientResponseTracingHandler.java @@ -7,44 +7,38 @@ import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.handler.codec.http.HttpResponse; +import io.netty.util.Attribute; import io.opentracing.Scope; import io.opentracing.Span; -import io.opentracing.tag.Tags; +import io.opentracing.noop.NoopSpan; import io.opentracing.util.GlobalTracer; public class HttpClientResponseTracingHandler extends ChannelInboundHandlerAdapter { @Override public void channelRead(final ChannelHandlerContext ctx, final Object msg) { + final Attribute parentAttr = + ctx.channel().attr(AttributeKeys.CLIENT_PARENT_ATTRIBUTE_KEY); + parentAttr.setIfAbsent(NoopSpan.INSTANCE); + final Span parent = parentAttr.get(); final Span span = ctx.channel().attr(AttributeKeys.CLIENT_ATTRIBUTE_KEY).get(); - if (span == null) { - ctx.fireChannelRead(msg); - return; - } - try (final Scope scope = GlobalTracer.get().scopeManager().activate(span, false)) { - final boolean finishSpan = msg instanceof HttpResponse; - - if (scope instanceof TraceScope) { - ((TraceScope) scope).setAsyncPropagation(true); - } - try { - ctx.fireChannelRead(msg); - } catch (final Throwable throwable) { - if (finishSpan) { - DECORATE.onError(span, throwable); - DECORATE.beforeFinish(span); - Tags.HTTP_STATUS.set(span, 500); - span.finish(); // Finish the span manually since finishSpanOnClose was false - throw throwable; - } - } + final boolean finishSpan = msg instanceof HttpResponse; - if (finishSpan) { + if (span != null && finishSpan) { + try (final Scope scope = GlobalTracer.get().scopeManager().activate(span, false)) { DECORATE.onResponse(span, (HttpResponse) msg); DECORATE.beforeFinish(span); - span.finish(); // Finish the span manually since finishSpanOnClose was false + span.finish(); } } + + // We want the callback in the scope of the parent, not the client span + try (final Scope scope = GlobalTracer.get().scopeManager().activate(parent, false)) { + if (scope instanceof TraceScope) { + ((TraceScope) scope).setAsyncPropagation(true); + } + ctx.fireChannelRead(msg); + } } } diff --git a/dd-java-agent/instrumentation/netty-4.0/src/test/groovy/Netty40ClientTest.groovy b/dd-java-agent/instrumentation/netty-4.0/src/test/groovy/Netty40ClientTest.groovy index cdf7c74d77a..0e08c2244ac 100644 --- a/dd-java-agent/instrumentation/netty-4.0/src/test/groovy/Netty40ClientTest.groovy +++ b/dd-java-agent/instrumentation/netty-4.0/src/test/groovy/Netty40ClientTest.groovy @@ -1,8 +1,10 @@ import datadog.trace.agent.test.base.HttpClientTest import datadog.trace.instrumentation.netty40.client.NettyHttpClientDecorator import io.opentracing.tag.Tags +import org.asynchttpclient.AsyncCompletionHandler import org.asynchttpclient.AsyncHttpClient import org.asynchttpclient.DefaultAsyncHttpClientConfig +import org.asynchttpclient.Response import spock.lang.Shared import java.util.concurrent.ExecutionException @@ -25,8 +27,13 @@ class Netty40ClientTest extends HttpClientTest { def methodName = "prepare" + method.toLowerCase().capitalize() def requestBuilder = asyncHttpClient."$methodName"(uri.toString()) headers.each { requestBuilder.setHeader(it.key, it.value) } - def response = requestBuilder.execute().get() - callback?.call() + def response = requestBuilder.execute(new AsyncCompletionHandler() { + @Override + Object onCompleted(Response response) throws Exception { + callback?.call() + return response + } + }).get() return response.statusCode } @@ -66,7 +73,7 @@ class Netty40ClientTest extends HttpClientTest { and: assertTraces(1) { trace(0, 2) { - basicSpan(it, 0, "parent", thrownException) + basicSpan(it, 0, "parent", null, thrownException) span(1) { operationName "netty.connect" diff --git a/dd-java-agent/instrumentation/netty-4.1/src/main/java/datadog/trace/instrumentation/netty41/AttributeKeys.java b/dd-java-agent/instrumentation/netty-4.1/src/main/java/datadog/trace/instrumentation/netty41/AttributeKeys.java index 9a8ea9a0a02..e4eb6159923 100644 --- a/dd-java-agent/instrumentation/netty-4.1/src/main/java/datadog/trace/instrumentation/netty41/AttributeKeys.java +++ b/dd-java-agent/instrumentation/netty-4.1/src/main/java/datadog/trace/instrumentation/netty41/AttributeKeys.java @@ -20,4 +20,7 @@ public class AttributeKeys { public static final AttributeKey CLIENT_ATTRIBUTE_KEY = AttributeKey.valueOf(HttpClientTracingHandler.class.getName() + ".span"); + + public static final AttributeKey CLIENT_PARENT_ATTRIBUTE_KEY = + AttributeKey.valueOf(HttpClientTracingHandler.class.getName() + ".parent"); } diff --git a/dd-java-agent/instrumentation/netty-4.1/src/main/java/datadog/trace/instrumentation/netty41/client/HttpClientRequestTracingHandler.java b/dd-java-agent/instrumentation/netty-4.1/src/main/java/datadog/trace/instrumentation/netty41/client/HttpClientRequestTracingHandler.java index 5cc082ec8c1..d309c96dfa7 100644 --- a/dd-java-agent/instrumentation/netty-4.1/src/main/java/datadog/trace/instrumentation/netty41/client/HttpClientRequestTracingHandler.java +++ b/dd-java-agent/instrumentation/netty-4.1/src/main/java/datadog/trace/instrumentation/netty41/client/HttpClientRequestTracingHandler.java @@ -10,6 +10,7 @@ import io.netty.handler.codec.http.HttpRequest; import io.opentracing.Scope; import io.opentracing.Span; +import io.opentracing.Tracer; import io.opentracing.propagation.Format; import io.opentracing.util.GlobalTracer; import java.net.InetSocketAddress; @@ -34,19 +35,19 @@ public void write(final ChannelHandlerContext ctx, final Object msg, final Chann final HttpRequest request = (HttpRequest) msg; - final Span span = GlobalTracer.get().buildSpan("netty.client.request").start(); - try (final Scope scope = GlobalTracer.get().scopeManager().activate(span, false)) { + final Tracer tracer = GlobalTracer.get(); + ctx.channel().attr(AttributeKeys.CLIENT_PARENT_ATTRIBUTE_KEY).set(tracer.activeSpan()); + + final Span span = tracer.buildSpan("netty.client.request").start(); + try (final Scope scope = tracer.scopeManager().activate(span, false)) { DECORATE.afterStart(span); DECORATE.onRequest(span, request); DECORATE.onPeerConnection(span, (InetSocketAddress) ctx.channel().remoteAddress()); // AWS calls are often signed, so we can't add headers without breaking the signature. if (!request.headers().contains("amz-sdk-invocation-id")) { - GlobalTracer.get() - .inject( - span.context(), - Format.Builtin.HTTP_HEADERS, - new NettyResponseInjectAdapter(request)); + tracer.inject( + span.context(), Format.Builtin.HTTP_HEADERS, new NettyResponseInjectAdapter(request)); } ctx.channel().attr(AttributeKeys.CLIENT_ATTRIBUTE_KEY).set(span); diff --git a/dd-java-agent/instrumentation/netty-4.1/src/main/java/datadog/trace/instrumentation/netty41/client/HttpClientResponseTracingHandler.java b/dd-java-agent/instrumentation/netty-4.1/src/main/java/datadog/trace/instrumentation/netty41/client/HttpClientResponseTracingHandler.java index a82d8e6eb40..c13a7663612 100644 --- a/dd-java-agent/instrumentation/netty-4.1/src/main/java/datadog/trace/instrumentation/netty41/client/HttpClientResponseTracingHandler.java +++ b/dd-java-agent/instrumentation/netty-4.1/src/main/java/datadog/trace/instrumentation/netty41/client/HttpClientResponseTracingHandler.java @@ -7,44 +7,38 @@ import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.handler.codec.http.HttpResponse; +import io.netty.util.Attribute; import io.opentracing.Scope; import io.opentracing.Span; -import io.opentracing.tag.Tags; +import io.opentracing.noop.NoopSpan; import io.opentracing.util.GlobalTracer; public class HttpClientResponseTracingHandler extends ChannelInboundHandlerAdapter { @Override public void channelRead(final ChannelHandlerContext ctx, final Object msg) { + final Attribute parentAttr = + ctx.channel().attr(AttributeKeys.CLIENT_PARENT_ATTRIBUTE_KEY); + parentAttr.setIfAbsent(NoopSpan.INSTANCE); + final Span parent = parentAttr.get(); final Span span = ctx.channel().attr(AttributeKeys.CLIENT_ATTRIBUTE_KEY).get(); - if (span == null) { - ctx.fireChannelRead(msg); - return; - } - try (final Scope scope = GlobalTracer.get().scopeManager().activate(span, false)) { - final boolean finishSpan = msg instanceof HttpResponse; - - if (scope instanceof TraceScope) { - ((TraceScope) scope).setAsyncPropagation(true); - } - try { - ctx.fireChannelRead(msg); - } catch (final Throwable throwable) { - if (finishSpan) { - DECORATE.onError(span, throwable); - DECORATE.beforeFinish(span); - Tags.HTTP_STATUS.set(span, 500); - span.finish(); // Finish the span manually since finishSpanOnClose was false - throw throwable; - } - } + final boolean finishSpan = msg instanceof HttpResponse; - if (finishSpan) { + if (span != null && finishSpan) { + try (final Scope scope = GlobalTracer.get().scopeManager().activate(span, false)) { DECORATE.onResponse(span, (HttpResponse) msg); DECORATE.beforeFinish(span); - span.finish(); // Finish the span manually since finishSpanOnClose was false + span.finish(); } } + + // We want the callback in the scope of the parent, not the client span + try (final Scope scope = GlobalTracer.get().scopeManager().activate(parent, false)) { + if (scope instanceof TraceScope) { + ((TraceScope) scope).setAsyncPropagation(true); + } + ctx.fireChannelRead(msg); + } } } diff --git a/dd-java-agent/instrumentation/netty-4.1/src/test/groovy/Netty41ClientTest.groovy b/dd-java-agent/instrumentation/netty-4.1/src/test/groovy/Netty41ClientTest.groovy index 99ccbe3fd5d..ea0e0fb2c45 100644 --- a/dd-java-agent/instrumentation/netty-4.1/src/test/groovy/Netty41ClientTest.groovy +++ b/dd-java-agent/instrumentation/netty-4.1/src/test/groovy/Netty41ClientTest.groovy @@ -9,8 +9,10 @@ import io.netty.channel.ChannelInitializer import io.netty.channel.embedded.EmbeddedChannel import io.netty.handler.codec.http.HttpClientCodec import io.opentracing.tag.Tags +import org.asynchttpclient.AsyncCompletionHandler import org.asynchttpclient.AsyncHttpClient import org.asynchttpclient.DefaultAsyncHttpClientConfig +import org.asynchttpclient.Response import spock.lang.Shared import java.util.concurrent.ExecutionException @@ -33,8 +35,13 @@ class Netty41ClientTest extends HttpClientTest { def methodName = "prepare" + method.toLowerCase().capitalize() def requestBuilder = asyncHttpClient."$methodName"(uri.toString()) headers.each { requestBuilder.setHeader(it.key, it.value) } - def response = requestBuilder.execute().get() - callback?.call() + def response = requestBuilder.execute(new AsyncCompletionHandler() { + @Override + Object onCompleted(Response response) throws Exception { + callback?.call() + return response + } + }).get() return response.statusCode } @@ -75,7 +82,7 @@ class Netty41ClientTest extends HttpClientTest { and: assertTraces(1) { trace(0, 2) { - basicSpan(it, 0, "parent", thrownException) + basicSpan(it, 0, "parent", null, thrownException) span(1) { operationName "netty.connect" diff --git a/dd-java-agent/instrumentation/ratpack-1.4/src/latestDepTest/groovy/RatpackTest.groovy b/dd-java-agent/instrumentation/ratpack-1.4/src/latestDepTest/groovy/RatpackTest.groovy index adf95f13ef6..c6276ae6360 100644 --- a/dd-java-agent/instrumentation/ratpack-1.4/src/latestDepTest/groovy/RatpackTest.groovy +++ b/dd-java-agent/instrumentation/ratpack-1.4/src/latestDepTest/groovy/RatpackTest.groovy @@ -1,7 +1,3 @@ -import static datadog.trace.agent.test.server.http.TestHttpServer.distributedRequestTrace -import static datadog.trace.agent.test.server.http.TestHttpServer.httpServer -import static datadog.trace.agent.test.utils.PortUtils.UNUSABLE_PORT - import datadog.trace.agent.test.AgentTestRunner import datadog.trace.agent.test.utils.OkHttpUtils import datadog.trace.api.DDSpanTypes @@ -12,8 +8,6 @@ import io.opentracing.Scope import io.opentracing.Span import io.opentracing.tag.Tags import io.opentracing.util.GlobalTracer -import java.util.concurrent.CountDownLatch -import java.util.regex.Pattern import okhttp3.HttpUrl import okhttp3.OkHttpClient import okhttp3.Request @@ -26,87 +20,55 @@ import ratpack.http.client.HttpClient import ratpack.path.PathBinding import ratpack.test.exec.ExecHarness +import java.util.concurrent.CountDownLatch +import java.util.regex.Pattern + +import static datadog.trace.agent.test.server.http.TestHttpServer.distributedRequestTrace +import static datadog.trace.agent.test.server.http.TestHttpServer.httpServer +import static datadog.trace.agent.test.utils.PortUtils.UNUSABLE_PORT + class RatpackTest extends AgentTestRunner { OkHttpClient client = OkHttpUtils.client() - def "test path call"() { + def "test bindings for #path"() { setup: def app = GroovyEmbeddedApp.ratpack { handlers { - get { - context.render("success") + prefix("a") { + all { + context.render(context.get(PathBinding).description) + } } - } - } - def request = new Request.Builder() - .url(app.address.toURL()) - .get() - .build() - - when: - def resp = client.newCall(request).execute() - - then: - resp.code() == 200 - resp.body.string() == "success" - - assertTraces(1) { - trace(0, 2) { - span(0) { - resourceName "GET /" - serviceName "unnamed-java-app" - operationName "netty.request" - spanType DDSpanTypes.HTTP_SERVER - parent() - errored false - tags { - "$Tags.COMPONENT.key" "netty" - "$Tags.SPAN_KIND.key" Tags.SPAN_KIND_SERVER - "$Tags.HTTP_METHOD.key" "GET" - "$Tags.HTTP_STATUS.key" 200 - "$Tags.HTTP_URL.key" "$app.address" - "$Tags.PEER_HOSTNAME.key" "$app.address.host" - "$Tags.PEER_HOST_IPV4.key" "127.0.0.1" - "$Tags.PEER_PORT.key" Integer - defaultTags() + prefix("b/::\\d+") { + all { + context.render(context.get(PathBinding).description) } } - span(1) { - resourceName "GET /" - serviceName "unnamed-java-app" - operationName "ratpack.handler" - spanType DDSpanTypes.HTTP_SERVER - childOf(span(0)) - errored false - tags { - "$Tags.COMPONENT.key" "ratpack" - "$Tags.SPAN_KIND.key" Tags.SPAN_KIND_SERVER - "$Tags.HTTP_METHOD.key" "GET" - "$Tags.HTTP_STATUS.key" 200 - "$Tags.HTTP_URL.key" "$app.address" - "$Tags.PEER_HOSTNAME.key" "$app.address.host" - "$Tags.PEER_PORT.key" Integer - defaultTags() + prefix("c/:val?") { + all { + context.render(context.get(PathBinding).description) } } - } - } - } - - def "test path with bindings call"() { - setup: - def app = GroovyEmbeddedApp.ratpack { - handlers { - prefix(":foo/:bar?") { - get("baz") {ctx -> - context.render(ctx.get(PathBinding).description) + prefix("d/:val") { + all { + context.render(context.get(PathBinding).description) + } + } + prefix("e/:val?:\\d+") { + all { + context.render(context.get(PathBinding).description) + } + } + prefix("f/:val:\\d+") { + all { + context.render(context.get(PathBinding).description) } } } } def request = new Request.Builder() - .url(HttpUrl.get(app.address).newBuilder().addPathSegments("a/b/baz").build()) + .url(HttpUrl.get(app.address).newBuilder().addPathSegments(path).build()) .get() .build() @@ -115,12 +77,12 @@ class RatpackTest extends AgentTestRunner { then: resp.code() == 200 - resp.body.string() == ":foo/:bar?/baz" + resp.body.string() == route assertTraces(1) { trace(0, 2) { span(0) { - resourceName "GET /:foo/:bar?/baz" + resourceName "GET /$route" serviceName "unnamed-java-app" operationName "netty.request" spanType DDSpanTypes.HTTP_SERVER @@ -131,7 +93,7 @@ class RatpackTest extends AgentTestRunner { "$Tags.SPAN_KIND.key" Tags.SPAN_KIND_SERVER "$Tags.HTTP_METHOD.key" "GET" "$Tags.HTTP_STATUS.key" 200 - "$Tags.HTTP_URL.key" "${app.address}a/b/baz" + "$Tags.HTTP_URL.key" "${app.address.resolve(path)}" "$Tags.PEER_HOSTNAME.key" "$app.address.host" "$Tags.PEER_HOST_IPV4.key" "127.0.0.1" "$Tags.PEER_PORT.key" Integer @@ -139,7 +101,7 @@ class RatpackTest extends AgentTestRunner { } } span(1) { - resourceName "GET /:foo/:bar?/baz" + resourceName "GET /$route" serviceName "unnamed-java-app" operationName "ratpack.handler" spanType DDSpanTypes.HTTP_SERVER @@ -150,7 +112,7 @@ class RatpackTest extends AgentTestRunner { "$Tags.SPAN_KIND.key" Tags.SPAN_KIND_SERVER "$Tags.HTTP_METHOD.key" "GET" "$Tags.HTTP_STATUS.key" 200 - "$Tags.HTTP_URL.key" "${app.address}a/b/baz" + "$Tags.HTTP_URL.key" "${app.address.resolve(path)}" "$Tags.PEER_HOSTNAME.key" "$app.address.host" "$Tags.PEER_PORT.key" Integer defaultTags() @@ -158,19 +120,35 @@ class RatpackTest extends AgentTestRunner { } } } + + where: + path | route + "a" | "a" + "b/123" | "b/::\\d+" + "c" | "c/:val?" + "c/123" | "c/:val?" + "c/foo" | "c/:val?" + "d/123" | "d/:val" + "d/foo" | "d/:val" + "e" | "e/:val?:\\d+" + "e/123" | "e/:val?:\\d+" + "e/foo" | "e/:val?:\\d+" + "f/123" | "f/:val:\\d+" } def "test handler error response"() { setup: def app = GroovyEmbeddedApp.ratpack { handlers { - get { - 0 / 0 + prefix("handler-error") { + all { + 0 / 0 + } } } } def request = new Request.Builder() - .url(app.address.toURL()) + .url(app.address.resolve("/handler-error?query=param").toURL()) .get() .build() when: @@ -181,7 +159,7 @@ class RatpackTest extends AgentTestRunner { assertTraces(1) { trace(0, 2) { span(0) { - resourceName "GET /" + resourceName "GET /handler-error" serviceName "unnamed-java-app" operationName "netty.request" spanType DDSpanTypes.HTTP_SERVER @@ -192,7 +170,7 @@ class RatpackTest extends AgentTestRunner { "$Tags.SPAN_KIND.key" Tags.SPAN_KIND_SERVER "$Tags.HTTP_METHOD.key" "GET" "$Tags.HTTP_STATUS.key" 500 - "$Tags.HTTP_URL.key" "$app.address" + "$Tags.HTTP_URL.key" "${app.address.resolve('handler-error')}" "$Tags.PEER_HOSTNAME.key" "$app.address.host" "$Tags.PEER_HOST_IPV4.key" "127.0.0.1" "$Tags.PEER_PORT.key" Integer @@ -201,7 +179,7 @@ class RatpackTest extends AgentTestRunner { } } span(1) { - resourceName "GET /" + resourceName "GET /handler-error" serviceName "unnamed-java-app" operationName "ratpack.handler" spanType DDSpanTypes.HTTP_SERVER @@ -212,7 +190,7 @@ class RatpackTest extends AgentTestRunner { "$Tags.SPAN_KIND.key" Tags.SPAN_KIND_SERVER "$Tags.HTTP_METHOD.key" "GET" "$Tags.HTTP_STATUS.key" 500 - "$Tags.HTTP_URL.key" "$app.address" + "$Tags.HTTP_URL.key" "${app.address.resolve('handler-error')}" "$Tags.PEER_HOSTNAME.key" "$app.address.host" "$Tags.PEER_PORT.key" Integer errorTags(HandlerException, Pattern.compile("java.lang.ArithmeticException: Division( is)? undefined")) @@ -227,7 +205,7 @@ class RatpackTest extends AgentTestRunner { setup: def app = GroovyEmbeddedApp.ratpack { handlers { - get { + get("promise-error") { Promise.async { 0 / 0 }.then { @@ -237,7 +215,7 @@ class RatpackTest extends AgentTestRunner { } } def request = new Request.Builder() - .url(app.address.toURL()) + .url(app.address.resolve("promise-error?query=param").toURL()) .get() .build() when: @@ -248,7 +226,7 @@ class RatpackTest extends AgentTestRunner { assertTraces(1) { trace(0, 2) { span(0) { - resourceName "GET /" + resourceName "GET /promise-error" serviceName "unnamed-java-app" operationName "netty.request" spanType DDSpanTypes.HTTP_SERVER @@ -259,7 +237,7 @@ class RatpackTest extends AgentTestRunner { "$Tags.SPAN_KIND.key" Tags.SPAN_KIND_SERVER "$Tags.HTTP_METHOD.key" "GET" "$Tags.HTTP_STATUS.key" 500 - "$Tags.HTTP_URL.key" "$app.address" + "$Tags.HTTP_URL.key" "${app.address.resolve('promise-error')}" "$Tags.PEER_HOSTNAME.key" "$app.address.host" "$Tags.PEER_HOST_IPV4.key" "127.0.0.1" "$Tags.PEER_PORT.key" Integer @@ -268,7 +246,7 @@ class RatpackTest extends AgentTestRunner { } } span(1) { - resourceName "GET /" + resourceName "GET /promise-error" serviceName "unnamed-java-app" operationName "ratpack.handler" spanType DDSpanTypes.HTTP_SERVER @@ -279,7 +257,7 @@ class RatpackTest extends AgentTestRunner { "$Tags.SPAN_KIND.key" Tags.SPAN_KIND_SERVER "$Tags.HTTP_METHOD.key" "GET" "$Tags.HTTP_STATUS.key" 500 - "$Tags.HTTP_URL.key" "$app.address" + "$Tags.HTTP_URL.key" "${app.address.resolve('promise-error')}" "$Tags.PEER_HOSTNAME.key" "$app.address.host" "$Tags.PEER_PORT.key" Integer "$Tags.ERROR.key" true @@ -294,7 +272,7 @@ class RatpackTest extends AgentTestRunner { setup: def app = GroovyEmbeddedApp.ratpack { handlers { - get { + all { context.render(Promise.sync { return "fail " + 0 / 0 }) @@ -302,7 +280,7 @@ class RatpackTest extends AgentTestRunner { } } def request = new Request.Builder() - .url(app.address.toURL()) + .url(app.address.resolve("?query=param").toURL()) .get() .build() when: @@ -374,13 +352,13 @@ class RatpackTest extends AgentTestRunner { def app = GroovyEmbeddedApp.ratpack { handlers { - get {HttpClient httpClient -> + get { HttpClient httpClient -> // 1st internal http client call to nested httpClient.get(HttpUrlBuilder.base(external.address).path("nested").build()) - .map {it.body.text} - .flatMap {t -> + .map { it.body.text } + .flatMap { t -> // make a 2nd http request and concatenate the two bodies together - httpClient.get(HttpUrlBuilder.base(external.address).path("nested2").build()) map {t + it.body.text} + httpClient.get(HttpUrlBuilder.base(external.address).path("nested2").build()) map { t + it.body.text } } .then { context.render(it) @@ -452,7 +430,7 @@ class RatpackTest extends AgentTestRunner { serviceName "unnamed-java-app" operationName "netty.client.request" spanType DDSpanTypes.HTTP_CLIENT - childOf(span(3)) + childOf(span(1)) errored false tags { "$Tags.COMPONENT.key" "netty-client" @@ -496,9 +474,9 @@ class RatpackTest extends AgentTestRunner { def app = GroovyEmbeddedApp.ratpack { handlers { - get {HttpClient httpClient -> + get { HttpClient httpClient -> httpClient.get(badAddress) - .map {it.body.text} + .map { it.body.text } .then { context.render(it) } @@ -681,7 +659,7 @@ class RatpackTest extends AgentTestRunner { scope.span().setBaggageItem("test-baggage", "foo") ParallelBatch.of(testPromise(), testPromise()) .yield() - .map({now -> + .map({ now -> // close the scope now that we got the baggage inside the promises scope.close() return now diff --git a/dd-java-agent/instrumentation/ratpack-1.4/src/test/groovy/RatpackTest.groovy b/dd-java-agent/instrumentation/ratpack-1.4/src/test/groovy/RatpackTest.groovy index d97795e5d0b..a27f60cff8d 100644 --- a/dd-java-agent/instrumentation/ratpack-1.4/src/test/groovy/RatpackTest.groovy +++ b/dd-java-agent/instrumentation/ratpack-1.4/src/test/groovy/RatpackTest.groovy @@ -429,7 +429,7 @@ class RatpackTest extends AgentTestRunner { serviceName "unnamed-java-app" operationName "netty.client.request" spanType DDSpanTypes.HTTP_CLIENT - childOf(span(3)) + childOf(span(1)) errored false tags { "$Tags.COMPONENT.key" "netty-client" diff --git a/dd-java-agent/instrumentation/vertx/src/test/groovy/VertxHttpClientTest.groovy b/dd-java-agent/instrumentation/vertx/src/test/groovy/VertxHttpClientTest.groovy index 2875bb82690..0fc2aa4e974 100644 --- a/dd-java-agent/instrumentation/vertx/src/test/groovy/VertxHttpClientTest.groovy +++ b/dd-java-agent/instrumentation/vertx/src/test/groovy/VertxHttpClientTest.groovy @@ -1,151 +1,54 @@ -import datadog.trace.agent.test.AgentTestRunner -import datadog.trace.agent.test.utils.PortUtils -import datadog.trace.api.DDSpanTypes -import io.netty.channel.AbstractChannel -import io.opentracing.tag.Tags +import datadog.trace.agent.test.base.HttpClientTest +import datadog.trace.instrumentation.netty41.client.NettyHttpClientDecorator import io.vertx.core.Vertx import io.vertx.core.VertxOptions import io.vertx.core.http.HttpClient -import io.vertx.core.http.HttpClientRequest import io.vertx.core.http.HttpClientResponse import io.vertx.core.http.HttpMethod -import spock.lang.AutoCleanup import spock.lang.Shared +import spock.lang.Timeout import java.util.concurrent.CompletableFuture -import static datadog.trace.agent.test.server.http.TestHttpServer.httpServer -import static datadog.trace.agent.test.utils.TraceUtils.runUnderTrace - -class VertxHttpClientTest extends AgentTestRunner { - - private static final String MESSAGE = "hello world" - - @AutoCleanup - @Shared - def server = httpServer { - handlers { - prefix("success") { - handleDistributedRequest() - - response.status(200).send(MESSAGE) - } - - prefix("error") { - handleDistributedRequest() - - throw new RuntimeException("error") - } - } - } +@Timeout(10) +class VertxHttpClientTest extends HttpClientTest { @Shared Vertx vertx = Vertx.vertx(new VertxOptions()) @Shared HttpClient httpClient = vertx.createHttpClient() - def "#route request trace"() { - setup: - def responseFuture = new CompletableFuture() - def messageFuture = new CompletableFuture() - httpClient.getNow(server.address.port, server.address.host, "/" + route, { response -> - responseFuture.complete(response) - response.bodyHandler({ buffer -> - messageFuture.complete(buffer.toString()) - }) - }) - - when: - HttpClientResponse response = responseFuture.get() - String message = messageFuture.get() - - then: - response.statusCode() == expectedStatus - if (expectedMessage != null) { - message == expectedMessage - } - - assertTraces(2) { - server.distributedRequestTrace(it, 0, TEST_WRITER[1][0]) - trace(1, 1) { - span(0) { - parent() - serviceName "unnamed-java-app" - operationName "netty.client.request" - resourceName "GET /$route" - spanType DDSpanTypes.HTTP_CLIENT - errored expectedError - tags { - defaultTags() - "$Tags.HTTP_STATUS.key" expectedStatus - "$Tags.HTTP_URL.key" "${server.address}/$route" - "$Tags.PEER_HOSTNAME.key" server.address.host - "$Tags.PEER_HOST_IPV4.key" "127.0.0.1" - "$Tags.PEER_PORT.key" server.address.port - "$Tags.HTTP_METHOD.key" "GET" - "$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT - "$Tags.COMPONENT.key" "netty-client" - if (expectedError) { - "$Tags.ERROR.key" true - } - } - } - } + @Override + int doRequest(String method, URI uri, Map headers, Closure callback) { + CompletableFuture future = new CompletableFuture<>() + def request = httpClient.request(HttpMethod.valueOf(method), uri.port, uri.host, "$uri") + headers.each { request.putHeader(it.key, it.value) } + request.handler { response -> + callback?.call() + future.complete(response) } + request.end() - where: - route | expectedStatus | expectedError | expectedMessage - "success" | 200 | false | MESSAGE - "error" | 500 | true | null + return future.get().statusCode() } - def "test connection failure"() { - setup: - def invalidPort = PortUtils.randomOpenPort() - - def errorFuture = new CompletableFuture() - - runUnderTrace("parent") { - HttpClientRequest request = httpClient.request( - HttpMethod.GET, - invalidPort, - server.address.host, - "/", - { response -> - // We expect to never get here since our request is expected to fail - errorFuture.complete(null) - }) - request.exceptionHandler({ error -> - errorFuture.complete(error) - }) - request.end() - } + @Override + NettyHttpClientDecorator decorator() { + return NettyHttpClientDecorator.DECORATE + } - when: - def throwable = errorFuture.get() + @Override + String expectedOperationName() { + return "netty.client.request" + } - then: - throwable.cause instanceof ConnectException + @Override + boolean testRedirects() { + false + } - and: - assertTraces(1) { - trace(0, 2) { - span(0) { - operationName "parent" - parent() - } - span(1) { - operationName "netty.connect" - resourceName "netty.connect" - childOf span(0) - errored true - tags { - "$Tags.COMPONENT.key" "netty" - errorTags AbstractChannel.AnnotatedConnectException, "Connection refused: localhost/127.0.0.1:$invalidPort" - defaultTags() - } - } - } - } + @Override + boolean testConnectionFailure() { + false } } diff --git a/dd-java-agent/instrumentation/vertx/src/test/groovy/VertxRxServerTest.groovy b/dd-java-agent/instrumentation/vertx/src/test/groovy/VertxRxServerTest.groovy new file mode 100644 index 00000000000..b2d960269a9 --- /dev/null +++ b/dd-java-agent/instrumentation/vertx/src/test/groovy/VertxRxServerTest.groovy @@ -0,0 +1,157 @@ +import datadog.trace.agent.test.AgentTestRunner +import datadog.trace.agent.test.utils.OkHttpUtils +import datadog.trace.agent.test.utils.PortUtils +import datadog.trace.api.DDSpanTypes +import io.netty.handler.codec.http.HttpResponseStatus +import io.opentracing.tag.Tags +import io.vertx.core.Vertx +import okhttp3.OkHttpClient +import okhttp3.Request +import spock.lang.Shared + +class VertxRxServerTest extends AgentTestRunner { + + @Shared + OkHttpClient client = OkHttpUtils.client() + + @Shared + int port + @Shared + Vertx server + + def setupSpec() { + port = PortUtils.randomOpenPort() + server = VertxRxWebTestServer.start(port) + } + + def cleanupSpec() { + server.close() + } + + def "test server request/response"() { + setup: + def request = new Request.Builder() + .url("http://localhost:$port/proxy") + .header("x-datadog-trace-id", "123") + .header("x-datadog-parent-id", "456") + .get() + .build() + def response = client.newCall(request).execute() + + expect: + response.code() == 200 + response.body().string() == "Hello World" + + and: + assertTraces(2) { + trace(0, 2) { + span(0) { + serviceName "unnamed-java-app" + operationName "netty.request" + resourceName "GET /test" + childOf(trace(1).get(1)) + spanType DDSpanTypes.HTTP_SERVER + errored false + tags { + "$Tags.COMPONENT.key" "netty" + "$Tags.HTTP_METHOD.key" "GET" + "$Tags.HTTP_STATUS.key" 200 + "$Tags.HTTP_URL.key" "http://localhost:$port/test" + "$Tags.PEER_HOSTNAME.key" "localhost" + "$Tags.PEER_HOST_IPV4.key" "127.0.0.1" + "$Tags.PEER_PORT.key" Integer + "$Tags.SPAN_KIND.key" Tags.SPAN_KIND_SERVER + defaultTags(true) + } + } + span(1) { + childOf span(0) + assert span(1).operationName.endsWith('.tracedMethod') + } + } + trace(1, 2) { + span(0) { + serviceName "unnamed-java-app" + operationName "netty.request" + resourceName "GET /proxy" + traceId "123" + parentId "456" + spanType DDSpanTypes.HTTP_SERVER + errored false + tags { + "$Tags.COMPONENT.key" "netty" + "$Tags.HTTP_METHOD.key" "GET" + "$Tags.HTTP_STATUS.key" 200 + "$Tags.HTTP_URL.key" "http://localhost:$port/proxy" + "$Tags.PEER_HOSTNAME.key" "localhost" + "$Tags.PEER_HOST_IPV4.key" "127.0.0.1" + "$Tags.PEER_PORT.key" Integer + "$Tags.SPAN_KIND.key" Tags.SPAN_KIND_SERVER + defaultTags(true) + } + } + span(1) { + serviceName "unnamed-java-app" + operationName "netty.client.request" + resourceName "GET /test" + childOf(span(0)) + spanType DDSpanTypes.HTTP_CLIENT + errored false + tags { + "$Tags.COMPONENT.key" "netty-client" + "$Tags.HTTP_METHOD.key" "GET" + "$Tags.HTTP_STATUS.key" 200 + "$Tags.HTTP_URL.key" "http://localhost:$port/test" + "$Tags.PEER_HOSTNAME.key" "localhost" + "$Tags.PEER_HOST_IPV4.key" "127.0.0.1" + "$Tags.PEER_PORT.key" Integer + "$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT + defaultTags() + } + } + } + } + } + + def "test #responseCode response handling"() { + setup: + def request = new Request.Builder().url("http://localhost:$port/$path").get().build() + def response = client.newCall(request).execute() + + expect: + response.code() == responseCode.code() + + and: + assertTraces(1) { + trace(0, 1) { + span(0) { + serviceName "unnamed-java-app" + operationName "netty.request" + resourceName name + spanType DDSpanTypes.HTTP_SERVER + errored error + tags { + "$Tags.COMPONENT.key" "netty" + "$Tags.HTTP_METHOD.key" "GET" + "$Tags.HTTP_STATUS.key" responseCode.code() + "$Tags.HTTP_URL.key" "http://localhost:$port/$path" + "$Tags.PEER_HOSTNAME.key" "localhost" + "$Tags.PEER_HOST_IPV4.key" "127.0.0.1" + "$Tags.PEER_PORT.key" Integer + "$Tags.SPAN_KIND.key" Tags.SPAN_KIND_SERVER + if (error) { + tag("error", true) + } + defaultTags() + } + } + } + } + + where: + responseCode | name | path | error + HttpResponseStatus.OK | "GET /" | "" | false + HttpResponseStatus.NOT_FOUND | "404" | "doesnt-exit" | false + HttpResponseStatus.INTERNAL_SERVER_ERROR | "GET /error" | "error" | true + } +} diff --git a/dd-java-agent/instrumentation/vertx/src/test/groovy/VertxRxWebClientTest.groovy b/dd-java-agent/instrumentation/vertx/src/test/groovy/VertxRxWebClientTest.groovy new file mode 100644 index 00000000000..fc556865c41 --- /dev/null +++ b/dd-java-agent/instrumentation/vertx/src/test/groovy/VertxRxWebClientTest.groovy @@ -0,0 +1,49 @@ +import datadog.trace.agent.test.base.HttpClientTest +import datadog.trace.instrumentation.netty41.client.NettyHttpClientDecorator +import io.vertx.core.VertxOptions +import io.vertx.core.http.HttpMethod +import io.vertx.reactivex.core.Vertx +import io.vertx.reactivex.ext.web.client.WebClient +import spock.lang.Shared +import spock.lang.Timeout + +@Timeout(10) +class VertxRxWebClientTest extends HttpClientTest { + + @Shared + Vertx vertx = Vertx.vertx(new VertxOptions()) + @Shared + WebClient client = WebClient.create(vertx) + + @Override + int doRequest(String method, URI uri, Map headers, Closure callback) { + def request = client.request(HttpMethod.valueOf(method), uri.port, uri.host, "$uri") + headers.each { request.putHeader(it.key, it.value) } + return request + .rxSend() + .doOnSuccess { response -> callback?.call() } + .map { it.statusCode() } + .toObservable() + .blockingFirst() + } + + @Override + NettyHttpClientDecorator decorator() { + return NettyHttpClientDecorator.DECORATE + } + + @Override + String expectedOperationName() { + return "netty.client.request" + } + + @Override + boolean testRedirects() { + false + } + + @Override + boolean testConnectionFailure() { + false + } +} diff --git a/dd-java-agent/instrumentation/vertx/src/test/groovy/VertxServerTest.groovy b/dd-java-agent/instrumentation/vertx/src/test/groovy/VertxServerTest.groovy index 83a7cf6091a..eff0eeab0e4 100644 --- a/dd-java-agent/instrumentation/vertx/src/test/groovy/VertxServerTest.groovy +++ b/dd-java-agent/instrumentation/vertx/src/test/groovy/VertxServerTest.groovy @@ -31,7 +31,7 @@ class VertxServerTest extends AgentTestRunner { def "test server request/response"() { setup: def request = new Request.Builder() - .url("http://localhost:$port/test") + .url("http://localhost:$port/proxy") .header("x-datadog-trace-id", "123") .header("x-datadog-parent-id", "456") .get() @@ -43,14 +43,13 @@ class VertxServerTest extends AgentTestRunner { response.body().string() == "Hello World" and: - assertTraces(1) { + assertTraces(2) { trace(0, 2) { span(0) { - traceId "123" - parentId "456" serviceName "unnamed-java-app" operationName "netty.request" resourceName "GET /test" + childOf(trace(1).get(1)) spanType DDSpanTypes.HTTP_SERVER errored false tags { @@ -70,6 +69,47 @@ class VertxServerTest extends AgentTestRunner { assert span(1).operationName.endsWith('.tracedMethod') } } + trace(1, 2) { + span(0) { + serviceName "unnamed-java-app" + operationName "netty.request" + resourceName "GET /proxy" + traceId "123" + parentId "456" + spanType DDSpanTypes.HTTP_SERVER + errored false + tags { + "$Tags.COMPONENT.key" "netty" + "$Tags.HTTP_METHOD.key" "GET" + "$Tags.HTTP_STATUS.key" 200 + "$Tags.HTTP_URL.key" "http://localhost:$port/proxy" + "$Tags.PEER_HOSTNAME.key" "localhost" + "$Tags.PEER_HOST_IPV4.key" "127.0.0.1" + "$Tags.PEER_PORT.key" Integer + "$Tags.SPAN_KIND.key" Tags.SPAN_KIND_SERVER + defaultTags(true) + } + } + span(1) { + serviceName "unnamed-java-app" + operationName "netty.client.request" + resourceName "GET /test" + childOf(span(0)) + spanType DDSpanTypes.HTTP_CLIENT + errored false + tags { + "$Tags.COMPONENT.key" "netty-client" + "$Tags.HTTP_METHOD.key" "GET" + "$Tags.HTTP_STATUS.key" 200 + "$Tags.HTTP_URL.key" "http://localhost:$port/test" + "$Tags.PEER_HOSTNAME.key" "localhost" + "$Tags.PEER_HOST_IPV4.key" "127.0.0.1" + "$Tags.PEER_PORT.key" Integer + "$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT + defaultTags() + } + } + } } } diff --git a/dd-java-agent/instrumentation/vertx/src/test/java/VertxRxWebTestServer.java b/dd-java-agent/instrumentation/vertx/src/test/java/VertxRxWebTestServer.java new file mode 100644 index 00000000000..521253c2684 --- /dev/null +++ b/dd-java-agent/instrumentation/vertx/src/test/java/VertxRxWebTestServer.java @@ -0,0 +1,116 @@ +import datadog.trace.api.Trace; +import io.vertx.circuitbreaker.CircuitBreakerOptions; +import io.vertx.core.DeploymentOptions; +import io.vertx.core.Future; +import io.vertx.core.Vertx; +import io.vertx.core.VertxOptions; +import io.vertx.core.json.JsonObject; +import io.vertx.reactivex.circuitbreaker.CircuitBreaker; +import io.vertx.reactivex.core.AbstractVerticle; +import io.vertx.reactivex.core.buffer.Buffer; +import io.vertx.reactivex.ext.web.Router; +import io.vertx.reactivex.ext.web.RoutingContext; +import io.vertx.reactivex.ext.web.client.WebClient; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; + +public class VertxRxWebTestServer extends AbstractVerticle { + public static final String CONFIG_HTTP_SERVER_PORT = "http.server.port"; + + public static Vertx start(final int port) throws ExecutionException, InterruptedException { + /* This is highly against Vertx ideas, but our tests are synchronous + so we have to make sure server is up and running */ + final CompletableFuture future = new CompletableFuture<>(); + + final Vertx vertx = Vertx.vertx(new VertxOptions().setClusterPort(port)); + + vertx.deployVerticle( + VertxRxWebTestServer.class.getName(), + new DeploymentOptions() + .setConfig(new JsonObject().put(CONFIG_HTTP_SERVER_PORT, port)) + .setInstances(3), + res -> { + if (!res.succeeded()) { + throw new RuntimeException("Cannot deploy server Verticle", res.cause()); + } + future.complete(null); + }); + + future.get(); + + return vertx; + } + + @Override + public void start(final Future startFuture) { + // final io.vertx.reactivex.core.Vertx vertx = new io.vertx.reactivex.core.Vertx(this.vertx); + final WebClient client = WebClient.create(vertx); + + final int port = config().getInteger(CONFIG_HTTP_SERVER_PORT); + + final Router router = Router.router(vertx); + final CircuitBreaker breaker = + CircuitBreaker.create( + "my-circuit-breaker", + vertx, + new CircuitBreakerOptions() + .setMaxFailures(5) // number of failure before opening the circuit + .setTimeout(2000) // consider a failure if the operation does not succeed in time + // .setFallbackOnFailure(true) // do we call the fallback on failure + .setResetTimeout(10000) // time spent in open state before attempting to re-try + ); + + router + .route("/") + .handler( + routingContext -> { + routingContext.response().putHeader("content-type", "text/html").end("Hello World"); + }); + router + .route("/error") + .handler( + routingContext -> { + routingContext.response().setStatusCode(500).end(); + }); + router + .route("/proxy") + .handler( + routingContext -> { + breaker.execute( + ctx -> { + client + .get(port, "localhost", "/test") + .rxSendBuffer( + Optional.ofNullable(routingContext.getBody()).orElse(Buffer.buffer())) + .subscribe( + response -> { + routingContext + .response() + .setStatusCode(response.statusCode()) + .end(response.body()); + }); + }); + }); + router + .route("/test") + .handler( + routingContext -> { + tracedMethod(); + routingContext.next(); + }) + .blockingHandler(RoutingContext::next) + .handler( + routingContext -> { + routingContext.response().putHeader("content-type", "text/html").end("Hello World"); + }); + + vertx + .createHttpServer() + .requestHandler(router::accept) + .listen(port, h -> startFuture.complete()); + } + + @Trace + private void tracedMethod() {} +} diff --git a/dd-java-agent/instrumentation/vertx/src/test/java/VertxWebTestServer.java b/dd-java-agent/instrumentation/vertx/src/test/java/VertxWebTestServer.java index 7a1d0348afe..085dae95ca6 100644 --- a/dd-java-agent/instrumentation/vertx/src/test/java/VertxWebTestServer.java +++ b/dd-java-agent/instrumentation/vertx/src/test/java/VertxWebTestServer.java @@ -1,25 +1,36 @@ import datadog.trace.api.Trace; import io.vertx.core.AbstractVerticle; +import io.vertx.core.DeploymentOptions; import io.vertx.core.Future; import io.vertx.core.Vertx; import io.vertx.core.VertxOptions; +import io.vertx.core.buffer.Buffer; +import io.vertx.core.http.HttpClient; +import io.vertx.core.json.JsonObject; import io.vertx.ext.web.Router; +import io.vertx.ext.web.RoutingContext; +import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; public class VertxWebTestServer extends AbstractVerticle { + public static final String CONFIG_HTTP_SERVER_PORT = "http.server.port"; public static Vertx start(final int port) throws ExecutionException, InterruptedException { /* This is highly against Vertx ideas, but our tests are synchronous so we have to make sure server is up and running */ final CompletableFuture future = new CompletableFuture<>(); - final Vertx vertx = Vertx.vertx(new VertxOptions()); + final Vertx vertx = Vertx.vertx(new VertxOptions().setClusterPort(port)); + vertx.deployVerticle( - new VertxWebTestServer(port), + VertxWebTestServer.class.getName(), + new DeploymentOptions() + .setConfig(new JsonObject().put(CONFIG_HTTP_SERVER_PORT, port)) + .setInstances(3), res -> { if (!res.succeeded()) { - throw new RuntimeException("Cannot deploy server Verticle"); + throw new RuntimeException("Cannot deploy server Verticle", res.cause()); } future.complete(null); }); @@ -29,14 +40,12 @@ public static Vertx start(final int port) throws ExecutionException, Interrupted return vertx; } - private final int port; - - public VertxWebTestServer(final int port) { - this.port = port; - } - @Override public void start(final Future startFuture) { + final HttpClient client = vertx.createHttpClient(); + + final int port = config().getInteger(CONFIG_HTTP_SERVER_PORT); + final Router router = Router.router(vertx); router @@ -51,6 +60,26 @@ public void start(final Future startFuture) { routingContext -> { routingContext.response().setStatusCode(500).end(); }); + router + .route("/proxy") + .handler( + routingContext -> { + client + .get( + port, + "localhost", + "/test", + response -> { + response.bodyHandler( + buffer -> { + routingContext + .response() + .setStatusCode(response.statusCode()) + .end(buffer); + }); + }) + .end(Optional.ofNullable(routingContext.getBody()).orElse(Buffer.buffer())); + }); router .route("/test") .handler( @@ -58,10 +87,7 @@ public void start(final Future startFuture) { tracedMethod(); routingContext.next(); }) - .blockingHandler( - routingContext -> { - routingContext.next(); - }) + .blockingHandler(RoutingContext::next) .handler( routingContext -> { routingContext.response().putHeader("content-type", "text/html").end("Hello World"); @@ -74,5 +100,5 @@ public void start(final Future startFuture) { } @Trace - public void tracedMethod() {} + private void tracedMethod() {} } diff --git a/dd-java-agent/instrumentation/vertx/vertx.gradle b/dd-java-agent/instrumentation/vertx/vertx.gradle index 75c0999ba6d..a804990ea98 100644 --- a/dd-java-agent/instrumentation/vertx/vertx.gradle +++ b/dd-java-agent/instrumentation/vertx/vertx.gradle @@ -42,10 +42,18 @@ dependencies { implementation deps.autoservice testCompile project(':dd-java-agent:testing') + testCompile project(':dd-java-agent:instrumentation:netty-4.1') testCompile project(':dd-java-agent:instrumentation:java-concurrent') testCompile project(':dd-java-agent:instrumentation:trace-annotation') - testCompile project(':dd-java-agent:instrumentation:netty-4.1') + + // Tests seem to fail before 3.5... maybe a problem with some of the tests? testCompile group: 'io.vertx', name: 'vertx-web', version: '3.5.0' + testCompile group: 'io.vertx', name: 'vertx-web-client', version: '3.5.0' + testCompile group: 'io.vertx', name: 'vertx-circuit-breaker', version: '3.5.0' + testCompile group: 'io.vertx', name: 'vertx-rx-java2', version: '3.5.0' latestDepTestCompile group: 'io.vertx', name: 'vertx-web', version: '+' + latestDepTestCompile group: 'io.vertx', name: 'vertx-web-client', version: '+' + latestDepTestCompile group: 'io.vertx', name: 'vertx-circuit-breaker', version: '+' + latestDepTestCompile group: 'io.vertx', name: 'vertx-rx-java2', version: '+' } diff --git a/dd-java-agent/testing/src/main/groovy/datadog/trace/agent/test/base/HttpClientTest.groovy b/dd-java-agent/testing/src/main/groovy/datadog/trace/agent/test/base/HttpClientTest.groovy index 23c98b16633..c3b1a440f5b 100644 --- a/dd-java-agent/testing/src/main/groovy/datadog/trace/agent/test/base/HttpClientTest.groovy +++ b/dd-java-agent/testing/src/main/groovy/datadog/trace/agent/test/base/HttpClientTest.groovy @@ -176,10 +176,7 @@ abstract class HttpClientTest extends AgentTestRu assertTraces(1) { trace(0, size(3)) { basicSpan(it, 0, "parent") - span(1) { - operationName "child" - childOf span(0) - } + basicSpan(it, 1, "child", span(0)) clientSpan(it, 2, span(0), method, false) } } @@ -205,10 +202,7 @@ abstract class HttpClientTest extends AgentTestRu clientSpan(it, 0, null, method, false) } trace(1, 1) { - span(0) { - operationName "child" - parent() - } + basicSpan(it, 0, "child") } } @@ -306,7 +300,7 @@ abstract class HttpClientTest extends AgentTestRu and: assertTraces(1) { trace(0, 2) { - basicSpan(it, 0, "parent", thrownException) + basicSpan(it, 0, "parent", null, thrownException) clientSpan(it, 1, span(0), method, false, false, uri, null, thrownException) } } diff --git a/dd-java-agent/testing/src/main/groovy/datadog/trace/agent/test/utils/TraceUtils.groovy b/dd-java-agent/testing/src/main/groovy/datadog/trace/agent/test/utils/TraceUtils.groovy index c349504cb19..a05d97a90c3 100644 --- a/dd-java-agent/testing/src/main/groovy/datadog/trace/agent/test/utils/TraceUtils.groovy +++ b/dd-java-agent/testing/src/main/groovy/datadog/trace/agent/test/utils/TraceUtils.groovy @@ -1,5 +1,6 @@ package datadog.trace.agent.test.utils +import datadog.opentracing.DDSpan import datadog.trace.agent.decorator.BaseDecorator import datadog.trace.agent.test.asserts.TraceAssert import datadog.trace.context.TraceScope @@ -42,9 +43,13 @@ class TraceUtils { } } - static basicSpan(TraceAssert trace, int index, String spanName, Throwable exception = null) { + static basicSpan(TraceAssert trace, int index, String spanName, Object parentSpan = null, Throwable exception = null) { trace.span(index) { - parent() + if (parentSpan == null) { + parent() + } else { + childOf((DDSpan) parentSpan) + } serviceName "unnamed-java-app" operationName spanName resourceName spanName