Change the scope for the netty client callback
Previously the scope was the http client span, which could result in deep nesting. Now it is the parent span. Before [——————Parent—————] [ ^ ———Client—————] [ ^—Child—] Now: [——————Parent—————] [ ^ —Client—] [ ^—Child—] Also improve the tests.
This commit is contained in:
parent
13b794c504
commit
6ccb0d71d8
|
@ -16,4 +16,7 @@ public class AttributeKeys {
|
||||||
|
|
||||||
public static final AttributeKey<Span> CLIENT_ATTRIBUTE_KEY =
|
public static final AttributeKey<Span> CLIENT_ATTRIBUTE_KEY =
|
||||||
new AttributeKey<>(HttpClientTracingHandler.class.getName() + ".span");
|
new AttributeKey<>(HttpClientTracingHandler.class.getName() + ".span");
|
||||||
|
|
||||||
|
public static final AttributeKey<Span> CLIENT_PARENT_ATTRIBUTE_KEY =
|
||||||
|
new AttributeKey<>(HttpClientTracingHandler.class.getName() + ".parent");
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ import io.netty.channel.ChannelPromise;
|
||||||
import io.netty.handler.codec.http.HttpRequest;
|
import io.netty.handler.codec.http.HttpRequest;
|
||||||
import io.opentracing.Scope;
|
import io.opentracing.Scope;
|
||||||
import io.opentracing.Span;
|
import io.opentracing.Span;
|
||||||
|
import io.opentracing.Tracer;
|
||||||
import io.opentracing.propagation.Format;
|
import io.opentracing.propagation.Format;
|
||||||
import io.opentracing.util.GlobalTracer;
|
import io.opentracing.util.GlobalTracer;
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
|
@ -34,19 +35,19 @@ public class HttpClientRequestTracingHandler extends ChannelOutboundHandlerAdapt
|
||||||
|
|
||||||
final HttpRequest request = (HttpRequest) msg;
|
final HttpRequest request = (HttpRequest) msg;
|
||||||
|
|
||||||
final Span span = GlobalTracer.get().buildSpan("netty.client.request").start();
|
final Tracer tracer = GlobalTracer.get();
|
||||||
try (final Scope scope = GlobalTracer.get().scopeManager().activate(span, false)) {
|
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.afterStart(span);
|
||||||
DECORATE.onRequest(span, request);
|
DECORATE.onRequest(span, request);
|
||||||
DECORATE.onPeerConnection(span, (InetSocketAddress) ctx.channel().remoteAddress());
|
DECORATE.onPeerConnection(span, (InetSocketAddress) ctx.channel().remoteAddress());
|
||||||
|
|
||||||
// AWS calls are often signed, so we can't add headers without breaking the signature.
|
// AWS calls are often signed, so we can't add headers without breaking the signature.
|
||||||
if (!request.headers().contains("amz-sdk-invocation-id")) {
|
if (!request.headers().contains("amz-sdk-invocation-id")) {
|
||||||
GlobalTracer.get()
|
tracer.inject(
|
||||||
.inject(
|
span.context(), Format.Builtin.HTTP_HEADERS, new NettyResponseInjectAdapter(request));
|
||||||
span.context(),
|
|
||||||
Format.Builtin.HTTP_HEADERS,
|
|
||||||
new NettyResponseInjectAdapter(request));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.channel().attr(AttributeKeys.CLIENT_ATTRIBUTE_KEY).set(span);
|
ctx.channel().attr(AttributeKeys.CLIENT_ATTRIBUTE_KEY).set(span);
|
||||||
|
|
|
@ -9,42 +9,34 @@ import io.netty.channel.ChannelInboundHandlerAdapter;
|
||||||
import io.netty.handler.codec.http.HttpResponse;
|
import io.netty.handler.codec.http.HttpResponse;
|
||||||
import io.opentracing.Scope;
|
import io.opentracing.Scope;
|
||||||
import io.opentracing.Span;
|
import io.opentracing.Span;
|
||||||
import io.opentracing.tag.Tags;
|
|
||||||
import io.opentracing.util.GlobalTracer;
|
import io.opentracing.util.GlobalTracer;
|
||||||
|
|
||||||
public class HttpClientResponseTracingHandler extends ChannelInboundHandlerAdapter {
|
public class HttpClientResponseTracingHandler extends ChannelInboundHandlerAdapter {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void channelRead(final ChannelHandlerContext ctx, final Object msg) {
|
public void channelRead(final ChannelHandlerContext ctx, final Object msg) {
|
||||||
|
final Span parent = ctx.channel().attr(AttributeKeys.CLIENT_PARENT_ATTRIBUTE_KEY).get();
|
||||||
final Span span = ctx.channel().attr(AttributeKeys.CLIENT_ATTRIBUTE_KEY).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;
|
final boolean finishSpan = msg instanceof HttpResponse;
|
||||||
|
|
||||||
|
if (span != null && finishSpan) {
|
||||||
|
try (final Scope scope = GlobalTracer.get().scopeManager().activate(span, true)) {
|
||||||
|
DECORATE.onResponse(span, (HttpResponse) msg);
|
||||||
|
DECORATE.beforeFinish(span);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We want the callback in the scope of the parent, not the client span
|
||||||
|
if (parent != null) {
|
||||||
|
try (final Scope scope = GlobalTracer.get().scopeManager().activate(parent, false)) {
|
||||||
if (scope instanceof TraceScope) {
|
if (scope instanceof TraceScope) {
|
||||||
((TraceScope) scope).setAsyncPropagation(true);
|
((TraceScope) scope).setAsyncPropagation(true);
|
||||||
}
|
}
|
||||||
try {
|
|
||||||
ctx.fireChannelRead(msg);
|
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (finishSpan) {
|
|
||||||
DECORATE.onResponse(span, (HttpResponse) msg);
|
|
||||||
DECORATE.beforeFinish(span);
|
|
||||||
span.finish(); // Finish the span manually since finishSpanOnClose was false
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
ctx.fireChannelRead(msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
import datadog.trace.agent.test.base.HttpClientTest
|
import datadog.trace.agent.test.base.HttpClientTest
|
||||||
import datadog.trace.instrumentation.netty40.client.NettyHttpClientDecorator
|
import datadog.trace.instrumentation.netty40.client.NettyHttpClientDecorator
|
||||||
import io.opentracing.tag.Tags
|
import io.opentracing.tag.Tags
|
||||||
|
import org.asynchttpclient.AsyncCompletionHandler
|
||||||
import org.asynchttpclient.AsyncHttpClient
|
import org.asynchttpclient.AsyncHttpClient
|
||||||
import org.asynchttpclient.DefaultAsyncHttpClientConfig
|
import org.asynchttpclient.DefaultAsyncHttpClientConfig
|
||||||
|
import org.asynchttpclient.Response
|
||||||
import spock.lang.Shared
|
import spock.lang.Shared
|
||||||
|
|
||||||
import java.util.concurrent.ExecutionException
|
import java.util.concurrent.ExecutionException
|
||||||
|
@ -25,8 +27,13 @@ class Netty40ClientTest extends HttpClientTest<NettyHttpClientDecorator> {
|
||||||
def methodName = "prepare" + method.toLowerCase().capitalize()
|
def methodName = "prepare" + method.toLowerCase().capitalize()
|
||||||
def requestBuilder = asyncHttpClient."$methodName"(uri.toString())
|
def requestBuilder = asyncHttpClient."$methodName"(uri.toString())
|
||||||
headers.each { requestBuilder.setHeader(it.key, it.value) }
|
headers.each { requestBuilder.setHeader(it.key, it.value) }
|
||||||
def response = requestBuilder.execute().get()
|
def response = requestBuilder.execute(new AsyncCompletionHandler() {
|
||||||
|
@Override
|
||||||
|
Object onCompleted(Response response) throws Exception {
|
||||||
callback?.call()
|
callback?.call()
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
}).get()
|
||||||
return response.statusCode
|
return response.statusCode
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -66,7 +73,7 @@ class Netty40ClientTest extends HttpClientTest<NettyHttpClientDecorator> {
|
||||||
and:
|
and:
|
||||||
assertTraces(1) {
|
assertTraces(1) {
|
||||||
trace(0, 2) {
|
trace(0, 2) {
|
||||||
basicSpan(it, 0, "parent", thrownException)
|
basicSpan(it, 0, "parent", null, thrownException)
|
||||||
|
|
||||||
span(1) {
|
span(1) {
|
||||||
operationName "netty.connect"
|
operationName "netty.connect"
|
||||||
|
|
|
@ -20,4 +20,7 @@ public class AttributeKeys {
|
||||||
|
|
||||||
public static final AttributeKey<Span> CLIENT_ATTRIBUTE_KEY =
|
public static final AttributeKey<Span> CLIENT_ATTRIBUTE_KEY =
|
||||||
AttributeKey.valueOf(HttpClientTracingHandler.class.getName() + ".span");
|
AttributeKey.valueOf(HttpClientTracingHandler.class.getName() + ".span");
|
||||||
|
|
||||||
|
public static final AttributeKey<Span> CLIENT_PARENT_ATTRIBUTE_KEY =
|
||||||
|
AttributeKey.valueOf(HttpClientTracingHandler.class.getName() + ".parent");
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ import io.netty.channel.ChannelPromise;
|
||||||
import io.netty.handler.codec.http.HttpRequest;
|
import io.netty.handler.codec.http.HttpRequest;
|
||||||
import io.opentracing.Scope;
|
import io.opentracing.Scope;
|
||||||
import io.opentracing.Span;
|
import io.opentracing.Span;
|
||||||
|
import io.opentracing.Tracer;
|
||||||
import io.opentracing.propagation.Format;
|
import io.opentracing.propagation.Format;
|
||||||
import io.opentracing.util.GlobalTracer;
|
import io.opentracing.util.GlobalTracer;
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
|
@ -34,19 +35,19 @@ public class HttpClientRequestTracingHandler extends ChannelOutboundHandlerAdapt
|
||||||
|
|
||||||
final HttpRequest request = (HttpRequest) msg;
|
final HttpRequest request = (HttpRequest) msg;
|
||||||
|
|
||||||
final Span span = GlobalTracer.get().buildSpan("netty.client.request").start();
|
final Tracer tracer = GlobalTracer.get();
|
||||||
try (final Scope scope = GlobalTracer.get().scopeManager().activate(span, false)) {
|
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.afterStart(span);
|
||||||
DECORATE.onRequest(span, request);
|
DECORATE.onRequest(span, request);
|
||||||
DECORATE.onPeerConnection(span, (InetSocketAddress) ctx.channel().remoteAddress());
|
DECORATE.onPeerConnection(span, (InetSocketAddress) ctx.channel().remoteAddress());
|
||||||
|
|
||||||
// AWS calls are often signed, so we can't add headers without breaking the signature.
|
// AWS calls are often signed, so we can't add headers without breaking the signature.
|
||||||
if (!request.headers().contains("amz-sdk-invocation-id")) {
|
if (!request.headers().contains("amz-sdk-invocation-id")) {
|
||||||
GlobalTracer.get()
|
tracer.inject(
|
||||||
.inject(
|
span.context(), Format.Builtin.HTTP_HEADERS, new NettyResponseInjectAdapter(request));
|
||||||
span.context(),
|
|
||||||
Format.Builtin.HTTP_HEADERS,
|
|
||||||
new NettyResponseInjectAdapter(request));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.channel().attr(AttributeKeys.CLIENT_ATTRIBUTE_KEY).set(span);
|
ctx.channel().attr(AttributeKeys.CLIENT_ATTRIBUTE_KEY).set(span);
|
||||||
|
|
|
@ -9,42 +9,34 @@ import io.netty.channel.ChannelInboundHandlerAdapter;
|
||||||
import io.netty.handler.codec.http.HttpResponse;
|
import io.netty.handler.codec.http.HttpResponse;
|
||||||
import io.opentracing.Scope;
|
import io.opentracing.Scope;
|
||||||
import io.opentracing.Span;
|
import io.opentracing.Span;
|
||||||
import io.opentracing.tag.Tags;
|
|
||||||
import io.opentracing.util.GlobalTracer;
|
import io.opentracing.util.GlobalTracer;
|
||||||
|
|
||||||
public class HttpClientResponseTracingHandler extends ChannelInboundHandlerAdapter {
|
public class HttpClientResponseTracingHandler extends ChannelInboundHandlerAdapter {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void channelRead(final ChannelHandlerContext ctx, final Object msg) {
|
public void channelRead(final ChannelHandlerContext ctx, final Object msg) {
|
||||||
|
final Span parent = ctx.channel().attr(AttributeKeys.CLIENT_PARENT_ATTRIBUTE_KEY).get();
|
||||||
final Span span = ctx.channel().attr(AttributeKeys.CLIENT_ATTRIBUTE_KEY).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;
|
final boolean finishSpan = msg instanceof HttpResponse;
|
||||||
|
|
||||||
|
if (span != null && finishSpan) {
|
||||||
|
try (final Scope scope = GlobalTracer.get().scopeManager().activate(span, true)) {
|
||||||
|
DECORATE.onResponse(span, (HttpResponse) msg);
|
||||||
|
DECORATE.beforeFinish(span);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We want the callback in the scope of the parent, not the client span
|
||||||
|
if (parent != null) {
|
||||||
|
try (final Scope scope = GlobalTracer.get().scopeManager().activate(parent, false)) {
|
||||||
if (scope instanceof TraceScope) {
|
if (scope instanceof TraceScope) {
|
||||||
((TraceScope) scope).setAsyncPropagation(true);
|
((TraceScope) scope).setAsyncPropagation(true);
|
||||||
}
|
}
|
||||||
try {
|
|
||||||
ctx.fireChannelRead(msg);
|
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (finishSpan) {
|
|
||||||
DECORATE.onResponse(span, (HttpResponse) msg);
|
|
||||||
DECORATE.beforeFinish(span);
|
|
||||||
span.finish(); // Finish the span manually since finishSpanOnClose was false
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
ctx.fireChannelRead(msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,8 +9,10 @@ import io.netty.channel.ChannelInitializer
|
||||||
import io.netty.channel.embedded.EmbeddedChannel
|
import io.netty.channel.embedded.EmbeddedChannel
|
||||||
import io.netty.handler.codec.http.HttpClientCodec
|
import io.netty.handler.codec.http.HttpClientCodec
|
||||||
import io.opentracing.tag.Tags
|
import io.opentracing.tag.Tags
|
||||||
|
import org.asynchttpclient.AsyncCompletionHandler
|
||||||
import org.asynchttpclient.AsyncHttpClient
|
import org.asynchttpclient.AsyncHttpClient
|
||||||
import org.asynchttpclient.DefaultAsyncHttpClientConfig
|
import org.asynchttpclient.DefaultAsyncHttpClientConfig
|
||||||
|
import org.asynchttpclient.Response
|
||||||
import spock.lang.Shared
|
import spock.lang.Shared
|
||||||
|
|
||||||
import java.util.concurrent.ExecutionException
|
import java.util.concurrent.ExecutionException
|
||||||
|
@ -33,8 +35,13 @@ class Netty41ClientTest extends HttpClientTest<NettyHttpClientDecorator> {
|
||||||
def methodName = "prepare" + method.toLowerCase().capitalize()
|
def methodName = "prepare" + method.toLowerCase().capitalize()
|
||||||
def requestBuilder = asyncHttpClient."$methodName"(uri.toString())
|
def requestBuilder = asyncHttpClient."$methodName"(uri.toString())
|
||||||
headers.each { requestBuilder.setHeader(it.key, it.value) }
|
headers.each { requestBuilder.setHeader(it.key, it.value) }
|
||||||
def response = requestBuilder.execute().get()
|
def response = requestBuilder.execute(new AsyncCompletionHandler() {
|
||||||
|
@Override
|
||||||
|
Object onCompleted(Response response) throws Exception {
|
||||||
callback?.call()
|
callback?.call()
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
}).get()
|
||||||
return response.statusCode
|
return response.statusCode
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,7 +82,7 @@ class Netty41ClientTest extends HttpClientTest<NettyHttpClientDecorator> {
|
||||||
and:
|
and:
|
||||||
assertTraces(1) {
|
assertTraces(1) {
|
||||||
trace(0, 2) {
|
trace(0, 2) {
|
||||||
basicSpan(it, 0, "parent", thrownException)
|
basicSpan(it, 0, "parent", null, thrownException)
|
||||||
|
|
||||||
span(1) {
|
span(1) {
|
||||||
operationName "netty.connect"
|
operationName "netty.connect"
|
||||||
|
|
|
@ -429,7 +429,7 @@ class RatpackTest extends AgentTestRunner {
|
||||||
serviceName "unnamed-java-app"
|
serviceName "unnamed-java-app"
|
||||||
operationName "netty.client.request"
|
operationName "netty.client.request"
|
||||||
spanType DDSpanTypes.HTTP_CLIENT
|
spanType DDSpanTypes.HTTP_CLIENT
|
||||||
childOf(span(3))
|
childOf(span(1))
|
||||||
errored false
|
errored false
|
||||||
tags {
|
tags {
|
||||||
"$Tags.COMPONENT.key" "netty-client"
|
"$Tags.COMPONENT.key" "netty-client"
|
||||||
|
|
|
@ -1,151 +1,54 @@
|
||||||
import datadog.trace.agent.test.AgentTestRunner
|
import datadog.trace.agent.test.base.HttpClientTest
|
||||||
import datadog.trace.agent.test.utils.PortUtils
|
import datadog.trace.instrumentation.netty41.client.NettyHttpClientDecorator
|
||||||
import datadog.trace.api.DDSpanTypes
|
|
||||||
import io.netty.channel.AbstractChannel
|
|
||||||
import io.opentracing.tag.Tags
|
|
||||||
import io.vertx.core.Vertx
|
import io.vertx.core.Vertx
|
||||||
import io.vertx.core.VertxOptions
|
import io.vertx.core.VertxOptions
|
||||||
import io.vertx.core.http.HttpClient
|
import io.vertx.core.http.HttpClient
|
||||||
import io.vertx.core.http.HttpClientRequest
|
|
||||||
import io.vertx.core.http.HttpClientResponse
|
import io.vertx.core.http.HttpClientResponse
|
||||||
import io.vertx.core.http.HttpMethod
|
import io.vertx.core.http.HttpMethod
|
||||||
import spock.lang.AutoCleanup
|
|
||||||
import spock.lang.Shared
|
import spock.lang.Shared
|
||||||
|
import spock.lang.Timeout
|
||||||
|
|
||||||
import java.util.concurrent.CompletableFuture
|
import java.util.concurrent.CompletableFuture
|
||||||
|
|
||||||
import static datadog.trace.agent.test.server.http.TestHttpServer.httpServer
|
@Timeout(10)
|
||||||
import static datadog.trace.agent.test.utils.TraceUtils.runUnderTrace
|
class VertxHttpClientTest extends HttpClientTest<NettyHttpClientDecorator> {
|
||||||
|
|
||||||
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")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Shared
|
@Shared
|
||||||
Vertx vertx = Vertx.vertx(new VertxOptions())
|
Vertx vertx = Vertx.vertx(new VertxOptions())
|
||||||
@Shared
|
@Shared
|
||||||
HttpClient httpClient = vertx.createHttpClient()
|
HttpClient httpClient = vertx.createHttpClient()
|
||||||
|
|
||||||
def "#route request trace"() {
|
@Override
|
||||||
setup:
|
int doRequest(String method, URI uri, Map<String, String> headers, Closure callback) {
|
||||||
def responseFuture = new CompletableFuture<HttpClientResponse>()
|
CompletableFuture<HttpClientResponse> future = new CompletableFuture<>()
|
||||||
def messageFuture = new CompletableFuture<String>()
|
def request = httpClient.request(HttpMethod.valueOf(method), uri.port, uri.host, "$uri")
|
||||||
httpClient.getNow(server.address.port, server.address.host, "/" + route, { response ->
|
headers.each { request.putHeader(it.key, it.value) }
|
||||||
responseFuture.complete(response)
|
request.handler { response ->
|
||||||
response.bodyHandler({ buffer ->
|
callback?.call()
|
||||||
messageFuture.complete(buffer.toString())
|
future.complete(response)
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
where:
|
|
||||||
route | expectedStatus | expectedError | expectedMessage
|
|
||||||
"success" | 200 | false | MESSAGE
|
|
||||||
"error" | 500 | true | null
|
|
||||||
}
|
|
||||||
|
|
||||||
def "test connection failure"() {
|
|
||||||
setup:
|
|
||||||
def invalidPort = PortUtils.randomOpenPort()
|
|
||||||
|
|
||||||
def errorFuture = new CompletableFuture<Throwable>()
|
|
||||||
|
|
||||||
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()
|
request.end()
|
||||||
|
|
||||||
|
return future.get().statusCode()
|
||||||
}
|
}
|
||||||
|
|
||||||
when:
|
@Override
|
||||||
def throwable = errorFuture.get()
|
NettyHttpClientDecorator decorator() {
|
||||||
|
return NettyHttpClientDecorator.DECORATE
|
||||||
|
}
|
||||||
|
|
||||||
then:
|
@Override
|
||||||
throwable.cause instanceof ConnectException
|
String expectedOperationName() {
|
||||||
|
return "netty.client.request"
|
||||||
|
}
|
||||||
|
|
||||||
and:
|
@Override
|
||||||
assertTraces(1) {
|
boolean testRedirects() {
|
||||||
trace(0, 2) {
|
false
|
||||||
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,7 +31,7 @@ class VertxServerTest extends AgentTestRunner {
|
||||||
def "test server request/response"() {
|
def "test server request/response"() {
|
||||||
setup:
|
setup:
|
||||||
def request = new Request.Builder()
|
def request = new Request.Builder()
|
||||||
.url("http://localhost:$port/test")
|
.url("http://localhost:$port/proxy")
|
||||||
.header("x-datadog-trace-id", "123")
|
.header("x-datadog-trace-id", "123")
|
||||||
.header("x-datadog-parent-id", "456")
|
.header("x-datadog-parent-id", "456")
|
||||||
.get()
|
.get()
|
||||||
|
@ -43,14 +43,13 @@ class VertxServerTest extends AgentTestRunner {
|
||||||
response.body().string() == "Hello World"
|
response.body().string() == "Hello World"
|
||||||
|
|
||||||
and:
|
and:
|
||||||
assertTraces(1) {
|
assertTraces(2) {
|
||||||
trace(0, 2) {
|
trace(0, 2) {
|
||||||
span(0) {
|
span(0) {
|
||||||
traceId "123"
|
|
||||||
parentId "456"
|
|
||||||
serviceName "unnamed-java-app"
|
serviceName "unnamed-java-app"
|
||||||
operationName "netty.request"
|
operationName "netty.request"
|
||||||
resourceName "GET /test"
|
resourceName "GET /test"
|
||||||
|
childOf(trace(1).get(1))
|
||||||
spanType DDSpanTypes.HTTP_SERVER
|
spanType DDSpanTypes.HTTP_SERVER
|
||||||
errored false
|
errored false
|
||||||
tags {
|
tags {
|
||||||
|
@ -70,6 +69,47 @@ class VertxServerTest extends AgentTestRunner {
|
||||||
assert span(1).operationName.endsWith('.tracedMethod')
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,25 +1,36 @@
|
||||||
import datadog.trace.api.Trace;
|
import datadog.trace.api.Trace;
|
||||||
import io.vertx.core.AbstractVerticle;
|
import io.vertx.core.AbstractVerticle;
|
||||||
|
import io.vertx.core.DeploymentOptions;
|
||||||
import io.vertx.core.Future;
|
import io.vertx.core.Future;
|
||||||
import io.vertx.core.Vertx;
|
import io.vertx.core.Vertx;
|
||||||
import io.vertx.core.VertxOptions;
|
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.Router;
|
||||||
|
import io.vertx.ext.web.RoutingContext;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
|
|
||||||
public class VertxWebTestServer extends AbstractVerticle {
|
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 {
|
public static Vertx start(final int port) throws ExecutionException, InterruptedException {
|
||||||
/* This is highly against Vertx ideas, but our tests are synchronous
|
/* This is highly against Vertx ideas, but our tests are synchronous
|
||||||
so we have to make sure server is up and running */
|
so we have to make sure server is up and running */
|
||||||
final CompletableFuture<Void> future = new CompletableFuture<>();
|
final CompletableFuture<Void> future = new CompletableFuture<>();
|
||||||
|
|
||||||
final Vertx vertx = Vertx.vertx(new VertxOptions());
|
final Vertx vertx = Vertx.vertx(new VertxOptions().setClusterPort(port));
|
||||||
|
|
||||||
vertx.deployVerticle(
|
vertx.deployVerticle(
|
||||||
new VertxWebTestServer(port),
|
VertxWebTestServer.class.getName(),
|
||||||
|
new DeploymentOptions()
|
||||||
|
.setConfig(new JsonObject().put(CONFIG_HTTP_SERVER_PORT, port))
|
||||||
|
.setInstances(3),
|
||||||
res -> {
|
res -> {
|
||||||
if (!res.succeeded()) {
|
if (!res.succeeded()) {
|
||||||
throw new RuntimeException("Cannot deploy server Verticle");
|
throw new RuntimeException("Cannot deploy server Verticle", res.cause());
|
||||||
}
|
}
|
||||||
future.complete(null);
|
future.complete(null);
|
||||||
});
|
});
|
||||||
|
@ -29,14 +40,12 @@ public class VertxWebTestServer extends AbstractVerticle {
|
||||||
return vertx;
|
return vertx;
|
||||||
}
|
}
|
||||||
|
|
||||||
private final int port;
|
|
||||||
|
|
||||||
public VertxWebTestServer(final int port) {
|
|
||||||
this.port = port;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void start(final Future<Void> startFuture) {
|
public void start(final Future<Void> startFuture) {
|
||||||
|
final HttpClient client = vertx.createHttpClient();
|
||||||
|
|
||||||
|
final int port = config().getInteger(CONFIG_HTTP_SERVER_PORT);
|
||||||
|
|
||||||
final Router router = Router.router(vertx);
|
final Router router = Router.router(vertx);
|
||||||
|
|
||||||
router
|
router
|
||||||
|
@ -51,6 +60,26 @@ public class VertxWebTestServer extends AbstractVerticle {
|
||||||
routingContext -> {
|
routingContext -> {
|
||||||
routingContext.response().setStatusCode(500).end();
|
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
|
router
|
||||||
.route("/test")
|
.route("/test")
|
||||||
.handler(
|
.handler(
|
||||||
|
@ -58,10 +87,7 @@ public class VertxWebTestServer extends AbstractVerticle {
|
||||||
tracedMethod();
|
tracedMethod();
|
||||||
routingContext.next();
|
routingContext.next();
|
||||||
})
|
})
|
||||||
.blockingHandler(
|
.blockingHandler(RoutingContext::next)
|
||||||
routingContext -> {
|
|
||||||
routingContext.next();
|
|
||||||
})
|
|
||||||
.handler(
|
.handler(
|
||||||
routingContext -> {
|
routingContext -> {
|
||||||
routingContext.response().putHeader("content-type", "text/html").end("Hello World");
|
routingContext.response().putHeader("content-type", "text/html").end("Hello World");
|
||||||
|
@ -74,5 +100,5 @@ public class VertxWebTestServer extends AbstractVerticle {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Trace
|
@Trace
|
||||||
public void tracedMethod() {}
|
private void tracedMethod() {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,9 +42,11 @@ dependencies {
|
||||||
implementation deps.autoservice
|
implementation deps.autoservice
|
||||||
|
|
||||||
testCompile project(':dd-java-agent:testing')
|
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:java-concurrent')
|
||||||
testCompile project(':dd-java-agent:instrumentation:trace-annotation')
|
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', version: '3.5.0'
|
||||||
|
|
||||||
latestDepTestCompile group: 'io.vertx', name: 'vertx-web', version: '+'
|
latestDepTestCompile group: 'io.vertx', name: 'vertx-web', version: '+'
|
||||||
|
|
|
@ -176,10 +176,7 @@ abstract class HttpClientTest<T extends HttpClientDecorator> extends AgentTestRu
|
||||||
assertTraces(1) {
|
assertTraces(1) {
|
||||||
trace(0, size(3)) {
|
trace(0, size(3)) {
|
||||||
basicSpan(it, 0, "parent")
|
basicSpan(it, 0, "parent")
|
||||||
span(1) {
|
basicSpan(it, 1, "child", span(0))
|
||||||
operationName "child"
|
|
||||||
childOf span(0)
|
|
||||||
}
|
|
||||||
clientSpan(it, 2, span(0), method, false)
|
clientSpan(it, 2, span(0), method, false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -205,10 +202,7 @@ abstract class HttpClientTest<T extends HttpClientDecorator> extends AgentTestRu
|
||||||
clientSpan(it, 0, null, method, false)
|
clientSpan(it, 0, null, method, false)
|
||||||
}
|
}
|
||||||
trace(1, 1) {
|
trace(1, 1) {
|
||||||
span(0) {
|
basicSpan(it, 0, "child")
|
||||||
operationName "child"
|
|
||||||
parent()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -306,7 +300,7 @@ abstract class HttpClientTest<T extends HttpClientDecorator> extends AgentTestRu
|
||||||
and:
|
and:
|
||||||
assertTraces(1) {
|
assertTraces(1) {
|
||||||
trace(0, 2) {
|
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)
|
clientSpan(it, 1, span(0), method, false, false, uri, null, thrownException)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package datadog.trace.agent.test.utils
|
package datadog.trace.agent.test.utils
|
||||||
|
|
||||||
|
import datadog.opentracing.DDSpan
|
||||||
import datadog.trace.agent.decorator.BaseDecorator
|
import datadog.trace.agent.decorator.BaseDecorator
|
||||||
import datadog.trace.agent.test.asserts.TraceAssert
|
import datadog.trace.agent.test.asserts.TraceAssert
|
||||||
import datadog.trace.context.TraceScope
|
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) {
|
trace.span(index) {
|
||||||
|
if (parentSpan == null) {
|
||||||
parent()
|
parent()
|
||||||
|
} else {
|
||||||
|
childOf((DDSpan) parentSpan)
|
||||||
|
}
|
||||||
serviceName "unnamed-java-app"
|
serviceName "unnamed-java-app"
|
||||||
operationName spanName
|
operationName spanName
|
||||||
resourceName spanName
|
resourceName spanName
|
||||||
|
|
Loading…
Reference in New Issue