Improve Ratpack context propagation and migrate tests

This commit is contained in:
Tyler Benson 2019-08-09 16:22:32 -07:00
parent 473aca7c08
commit 8c490a42a2
21 changed files with 898 additions and 1532 deletions

View File

@ -26,6 +26,9 @@ public class HttpServerRequestTracingHandler extends ChannelInboundHandlerAdapte
ctx.fireChannelRead(msg); // superclass does not throw
} else {
try (final Scope scope = tracer.scopeManager().activate(span, false)) {
if (scope instanceof TraceScope) {
((TraceScope) scope).setAsyncPropagation(true);
}
ctx.fireChannelRead(msg); // superclass does not throw
}
}

View File

@ -103,6 +103,7 @@ class Netty40ServerTest extends HttpServerTest<EventLoopGroup, NettyHttpServerDe
NettyHttpServerDecorator.DECORATE
}
@Override
String expectedOperationName() {
"netty.request"
}

View File

@ -26,6 +26,9 @@ public class HttpServerRequestTracingHandler extends ChannelInboundHandlerAdapte
ctx.fireChannelRead(msg); // superclass does not throw
} else {
try (final Scope scope = tracer.scopeManager().activate(span, false)) {
if (scope instanceof TraceScope) {
((TraceScope) scope).setAsyncPropagation(true);
}
ctx.fireChannelRead(msg); // superclass does not throw
}
}

View File

@ -102,6 +102,7 @@ class Netty41ServerTest extends HttpServerTest<EventLoopGroup, NettyHttpServerDe
NettyHttpServerDecorator.DECORATE
}
@Override
String expectedOperationName() {
"netty.request"
}

View File

@ -45,7 +45,9 @@ dependencies {
apply plugin: 'org.unbroken-dome.test-sets'
testSets {
latestDepTest
latestDepTest {
dirName = 'test'
}
}
dependencies {

View File

@ -1,695 +0,0 @@
import datadog.trace.agent.test.AgentTestRunner
import datadog.trace.agent.test.utils.OkHttpUtils
import datadog.trace.api.DDSpanTypes
import datadog.trace.api.DDTags
import datadog.trace.context.TraceScope
import io.netty.channel.AbstractChannel
import io.opentracing.Scope
import io.opentracing.Span
import io.opentracing.tag.Tags
import io.opentracing.util.GlobalTracer
import okhttp3.HttpUrl
import okhttp3.OkHttpClient
import okhttp3.Request
import ratpack.exec.Promise
import ratpack.exec.util.ParallelBatch
import ratpack.groovy.test.embed.GroovyEmbeddedApp
import ratpack.handling.internal.HandlerException
import ratpack.http.HttpUrlBuilder
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 bindings for #path"() {
setup:
def app = GroovyEmbeddedApp.ratpack {
handlers {
prefix("a") {
all {
context.render(context.get(PathBinding).description)
}
}
prefix("b/::\\d+") {
all {
context.render(context.get(PathBinding).description)
}
}
prefix("c/:val?") {
all {
context.render(context.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(path).build())
.get()
.build()
when:
def resp = client.newCall(request).execute()
then:
resp.code() == 200
resp.body.string() == route
assertTraces(1) {
trace(0, 2) {
span(0) {
resourceName "GET /$route"
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.resolve(path)}"
"$Tags.PEER_HOSTNAME.key" "$app.address.host"
"$Tags.PEER_HOST_IPV4.key" "127.0.0.1"
"$Tags.PEER_PORT.key" Integer
defaultTags()
}
}
span(1) {
resourceName "GET /$route"
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.resolve(path)}"
"$Tags.PEER_HOSTNAME.key" "$app.address.host"
"$Tags.PEER_PORT.key" Integer
defaultTags()
}
}
}
}
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 {
prefix("handler-error") {
all {
0 / 0
}
}
}
}
def request = new Request.Builder()
.url(app.address.resolve("/handler-error?query=param").toURL())
.get()
.build()
when:
def resp = client.newCall(request).execute()
then:
resp.code() == 500
assertTraces(1) {
trace(0, 2) {
span(0) {
resourceName "GET /handler-error"
serviceName "unnamed-java-app"
operationName "netty.request"
spanType DDSpanTypes.HTTP_SERVER
parent()
errored true
tags {
"$Tags.COMPONENT.key" "netty"
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_SERVER
"$Tags.HTTP_METHOD.key" "GET"
"$Tags.HTTP_STATUS.key" 500
"$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
errorTags(ArithmeticException, Pattern.compile("Division( is)? undefined"))
defaultTags()
}
}
span(1) {
resourceName "GET /handler-error"
serviceName "unnamed-java-app"
operationName "ratpack.handler"
spanType DDSpanTypes.HTTP_SERVER
childOf(span(0))
errored true
tags {
"$Tags.COMPONENT.key" "ratpack"
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_SERVER
"$Tags.HTTP_METHOD.key" "GET"
"$Tags.HTTP_STATUS.key" 500
"$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"))
defaultTags()
}
}
}
}
}
def "test promise error response"() {
setup:
def app = GroovyEmbeddedApp.ratpack {
handlers {
get("promise-error") {
Promise.async {
0 / 0
}.then {
context.render(it)
}
}
}
}
def request = new Request.Builder()
.url(app.address.resolve("promise-error?query=param").toURL())
.get()
.build()
when:
def resp = client.newCall(request).execute()
then:
resp.code() == 500
assertTraces(1) {
trace(0, 2) {
span(0) {
resourceName "GET /promise-error"
serviceName "unnamed-java-app"
operationName "netty.request"
spanType DDSpanTypes.HTTP_SERVER
parent()
errored true
tags {
"$Tags.COMPONENT.key" "netty"
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_SERVER
"$Tags.HTTP_METHOD.key" "GET"
"$Tags.HTTP_STATUS.key" 500
"$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
errorTags(ArithmeticException, Pattern.compile("Division( is)? undefined"))
defaultTags()
}
}
span(1) {
resourceName "GET /promise-error"
serviceName "unnamed-java-app"
operationName "ratpack.handler"
spanType DDSpanTypes.HTTP_SERVER
childOf(span(0))
errored true
tags {
"$Tags.COMPONENT.key" "ratpack"
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_SERVER
"$Tags.HTTP_METHOD.key" "GET"
"$Tags.HTTP_STATUS.key" 500
"$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
defaultTags()
}
}
}
}
}
def "test render error response"() {
setup:
def app = GroovyEmbeddedApp.ratpack {
handlers {
all {
context.render(Promise.sync {
return "fail " + 0 / 0
})
}
}
}
def request = new Request.Builder()
.url(app.address.resolve("?query=param").toURL())
.get()
.build()
when:
def resp = client.newCall(request).execute()
then:
resp.code() == 500
assertTraces(1) {
trace(0, 2) {
span(0) {
resourceName "GET /"
serviceName "unnamed-java-app"
operationName "netty.request"
spanType DDSpanTypes.HTTP_SERVER
parent()
errored true
tags {
"$Tags.COMPONENT.key" "netty"
"$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.PEER_HOSTNAME.key" "$app.address.host"
"$Tags.PEER_HOST_IPV4.key" "127.0.0.1"
"$Tags.PEER_PORT.key" Integer
errorTags(ArithmeticException, Pattern.compile("Division( is)? undefined"))
defaultTags()
}
}
span(1) {
resourceName "GET /"
serviceName "unnamed-java-app"
operationName "ratpack.handler"
spanType DDSpanTypes.HTTP_SERVER
childOf(span(0))
errored true
tags {
"$Tags.COMPONENT.key" "ratpack"
"$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.PEER_HOSTNAME.key" "$app.address.host"
"$Tags.PEER_PORT.key" Integer
"$Tags.ERROR.key" true
defaultTags()
}
}
}
}
}
def "test path call using ratpack http client"() {
setup:
// Use jetty based server to avoid confusion.
def external = httpServer {
handlers {
get("nested") {
handleDistributedRequest()
response.send("succ")
}
get("nested2") {
handleDistributedRequest()
response.send("ess")
}
}
}
def app = GroovyEmbeddedApp.ratpack {
handlers {
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 ->
// 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 }
}
.then {
context.render(it)
}
}
}
}
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"
// 3rd is the three traces, ratpack, http client 2 and http client 1
// 2nd is nested2 from the external server (the result of the 2nd internal http client call)
// 1st is nested from the external server (the result of the 1st internal http client call)
assertTraces(3) {
distributedRequestTrace(it, 0, trace(2).get(3))
distributedRequestTrace(it, 1, trace(2).get(2))
trace(2, 4) {
// main app span that processed the request from OKHTTP request
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()
}
}
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()
}
}
// Second http client call that receives the 'ess' of Success
span(2) {
resourceName "GET /?"
serviceName "unnamed-java-app"
operationName "netty.client.request"
spanType DDSpanTypes.HTTP_CLIENT
childOf(span(1))
errored false
tags {
"$Tags.COMPONENT.key" "netty-client"
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT
"$Tags.HTTP_METHOD.key" "GET"
"$Tags.HTTP_STATUS.key" 200
"$Tags.HTTP_URL.key" "${external.address}/nested2"
"$Tags.PEER_HOSTNAME.key" "$app.address.host"
"$Tags.PEER_HOST_IPV4.key" "127.0.0.1"
"$Tags.PEER_PORT.key" Integer
defaultTags()
}
}
// First http client call that receives the 'Succ' of Success
span(3) {
resourceName "GET /nested"
serviceName "unnamed-java-app"
operationName "netty.client.request"
spanType DDSpanTypes.HTTP_CLIENT
childOf(span(1))
errored false
tags {
"$Tags.COMPONENT.key" "netty-client"
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT
"$Tags.HTTP_METHOD.key" "GET"
"$Tags.HTTP_STATUS.key" 200
"$Tags.HTTP_URL.key" "${external.address}/nested"
"$Tags.PEER_HOSTNAME.key" "$app.address.host"
"$Tags.PEER_HOST_IPV4.key" "127.0.0.1"
"$Tags.PEER_PORT.key" Integer
defaultTags()
}
}
}
}
}
def "test ratpack http client error handling"() {
setup:
def badAddress = new URI("http://localhost:$UNUSABLE_PORT")
def app = GroovyEmbeddedApp.ratpack {
handlers {
get { HttpClient httpClient ->
httpClient.get(badAddress)
.map { it.body.text }
.then {
context.render(it)
}
}
}
}
def request = new Request.Builder()
.url(app.address.toURL())
.get()
.build()
when:
def resp = client.newCall(request).execute()
then:
resp.code() == 500
assertTraces(1) {
trace(0, 3) {
span(0) {
resourceName "GET /"
serviceName "unnamed-java-app"
operationName "netty.request"
spanType DDSpanTypes.HTTP_SERVER
parent()
errored true
tags {
"$Tags.COMPONENT.key" "netty"
"$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.PEER_HOSTNAME.key" "$app.address.host"
"$Tags.PEER_HOST_IPV4.key" "127.0.0.1"
"$Tags.PEER_PORT.key" Integer
"$Tags.ERROR.key" true
defaultTags()
}
}
span(1) {
resourceName "GET /"
serviceName "unnamed-java-app"
operationName "ratpack.handler"
spanType DDSpanTypes.HTTP_SERVER
childOf(span(0))
errored true
tags {
"$Tags.COMPONENT.key" "ratpack"
"$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.PEER_HOSTNAME.key" "$app.address.host"
"$Tags.PEER_PORT.key" Integer
"$Tags.ERROR.key" true
defaultTags()
}
}
span(2) {
operationName "netty.connect"
resourceName "netty.connect"
childOf(span(1))
errored true
tags {
"$Tags.COMPONENT.key" "netty"
errorTags(AbstractChannel.AnnotatedConnectException, String)
defaultTags()
}
}
}
}
}
def "test forked path call and start span in handler (#startSpanInHandler)"() {
setup:
def app = GroovyEmbeddedApp.ratpack {
handlers {
get {
TraceScope scope
if (startSpanInHandler) {
Span childSpan = GlobalTracer.get()
.buildSpan("ratpack.exec-test")
.withTag(DDTags.RESOURCE_NAME, "INSIDE-TEST")
.start()
scope = GlobalTracer.get().scopeManager().activate(childSpan, true)
}
def latch = new CountDownLatch(1)
try {
scope?.setAsyncPropagation(true)
GlobalTracer.get().activeSpan().setBaggageItem("test-baggage", "foo")
context.render(testPromise(latch).fork())
} finally {
scope?.close()
latch.countDown()
}
}
}
}
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() == "foo"
assertTraces(1) {
trace(0, (startSpanInHandler ? 3 : 2)) {
if (startSpanInHandler) {
span(0) {
resourceName "INSIDE-TEST"
serviceName "unnamed-java-app"
operationName "ratpack.exec-test"
childOf(span(2))
errored false
tags {
defaultTags()
}
}
}
span(startSpanInHandler ? 1 : 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()
}
}
span(startSpanInHandler ? 2 : 1) {
resourceName "GET /"
serviceName "unnamed-java-app"
operationName "ratpack.handler"
spanType DDSpanTypes.HTTP_SERVER
childOf(span(startSpanInHandler ? 1 : 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()
}
}
}
}
where:
startSpanInHandler << [true, false]
}
def "forked executions inherit parent scope"() {
when:
def result = ExecHarness.yieldSingle({}, {
final Scope scope =
GlobalTracer.get()
.buildSpan("ratpack.exec-test")
.withTag(DDTags.RESOURCE_NAME, "INSIDE-TEST")
.startActive(true)
((TraceScope) scope).setAsyncPropagation(true)
scope.span().setBaggageItem("test-baggage", "foo")
ParallelBatch.of(testPromise(), testPromise())
.yield()
.map({ now ->
// close the scope now that we got the baggage inside the promises
scope.close()
return now
})
})
then:
result.valueOrThrow == ["foo", "foo"]
assertTraces(1) {
trace(0, 1) {
span(0) {
resourceName "INSIDE-TEST"
serviceName "unnamed-java-app"
operationName "ratpack.exec-test"
parent()
errored false
tags {
defaultTags()
}
}
}
}
}
// returns a promise that contains the active scope's "test-baggage" baggage
Promise<String> testPromise(CountDownLatch latch = null) {
Promise.sync {
latch?.await()
Scope tracerScope = GlobalTracer.get().scopeManager().active()
return tracerScope?.span()?.getBaggageItem("test-baggage")
}
}
}

View File

@ -0,0 +1,97 @@
package datadog.trace.instrumentation.ratpack;
import static datadog.trace.agent.tooling.ByteBuddyElementMatchers.safeHasSuperType;
import static java.util.Collections.singletonMap;
import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
import com.google.auto.service.AutoService;
import datadog.trace.agent.tooling.Instrumenter;
import datadog.trace.context.TraceScope;
import io.opentracing.Scope;
import io.opentracing.Span;
import io.opentracing.util.GlobalTracer;
import java.util.Map;
import lombok.extern.slf4j.Slf4j;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;
import ratpack.func.Block;
import ratpack.path.PathBinding;
@AutoService(Instrumenter.class)
public final class ContinuationInstrumentation extends Instrumenter.Default {
public ContinuationInstrumentation() {
super("ratpack");
}
@Override
public ElementMatcher<? super TypeDescription> typeMatcher() {
return safeHasSuperType(named("ratpack.exec.internal.Continuation"));
}
@Override
public String[] helperClassNames() {
return new String[] {
getClass().getName() + "$ResumeAdvice$BlockWrapper",
};
}
@Override
public Map<? extends ElementMatcher<? super MethodDescription>, String> transformers() {
return singletonMap(
named("resume").and(takesArgument(0, named("ratpack.func.Block"))),
ResumeAdvice.class.getName());
}
public static class ResumeAdvice {
@Advice.OnMethodEnter(suppress = Throwable.class)
public static void wrap(@Advice.Argument(value = 0, readOnly = false) Block block) {
final Span span = GlobalTracer.get().activeSpan();
if (span != null) {
block = BlockWrapper.wrapIfNeeded(block, span);
}
}
public void muzzleCheck(final PathBinding binding) {
// This was added in 1.4. Added here to ensure consistency with other instrumentation.
binding.getDescription();
}
@Slf4j
public static class BlockWrapper<T> implements Block {
private final Block delegate;
private final Span span;
private BlockWrapper(final Block delegate, final Span span) {
this.delegate = delegate;
this.span = span;
}
@Override
public void execute() throws Exception {
if (span != null) {
try (final Scope scope = GlobalTracer.get().scopeManager().activate(span, false)) {
if (scope instanceof TraceScope) {
((TraceScope) scope).setAsyncPropagation(true);
}
delegate.execute();
}
} else {
delegate.execute();
}
}
public static Block wrapIfNeeded(final Block delegate, final Span span) {
if (delegate instanceof BlockWrapper || span == null) {
return delegate;
}
log.debug("Wrapping action task {}", delegate);
return new BlockWrapper(delegate, span);
}
}
}
}

View File

@ -0,0 +1,107 @@
package datadog.trace.instrumentation.ratpack;
import static java.util.Collections.singletonMap;
import static net.bytebuddy.matcher.ElementMatchers.nameStartsWith;
import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
import com.google.auto.service.AutoService;
import datadog.trace.agent.tooling.Instrumenter;
import datadog.trace.context.TraceScope;
import io.opentracing.Scope;
import io.opentracing.Span;
import io.opentracing.util.GlobalTracer;
import java.util.Map;
import lombok.extern.slf4j.Slf4j;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;
import ratpack.exec.internal.Continuation;
import ratpack.func.Action;
import ratpack.path.PathBinding;
@AutoService(Instrumenter.class)
public final class DefaultExecutionInstrumentation extends Instrumenter.Default {
public DefaultExecutionInstrumentation() {
super("ratpack");
}
@Override
public ElementMatcher<? super TypeDescription> typeMatcher() {
return named("ratpack.exec.internal.DefaultExecution");
}
@Override
public String[] helperClassNames() {
return new String[] {
getClass().getName() + "$DelimitAdvice$ActionWrapper",
};
}
@Override
public Map<? extends ElementMatcher<? super MethodDescription>, String> transformers() {
return singletonMap(
nameStartsWith("delimit") // include delimitStream
.and(takesArgument(0, named("ratpack.func.Action")))
.and(takesArgument(1, named("ratpack.func.Action"))),
DelimitAdvice.class.getName());
}
public static class DelimitAdvice {
@Advice.OnMethodEnter(suppress = Throwable.class)
public static void wrap(
@Advice.Argument(value = 0, readOnly = false) Action<? super Throwable> onError,
@Advice.Argument(value = 1, readOnly = false) Action<? super Continuation> segment) {
final Span span = GlobalTracer.get().activeSpan();
if (span != null) {
/**
* Here we pass along the span instead of a continuation because we aren't sure it won't be
* used for both callbacks.
*/
onError = ActionWrapper.wrapIfNeeded(onError, span);
segment = ActionWrapper.wrapIfNeeded(segment, span);
}
}
public void muzzleCheck(final PathBinding binding) {
// This was added in 1.4. Added here to ensure consistency with other instrumentation.
binding.getDescription();
}
@Slf4j
public static class ActionWrapper<T> implements Action<T> {
private final Action<T> delegate;
private final Span span;
private ActionWrapper(final Action<T> delegate, final Span span) {
this.delegate = delegate;
this.span = span;
}
@Override
public void execute(final T t) throws Exception {
if (span != null) {
try (final Scope scope = GlobalTracer.get().scopeManager().activate(span, false)) {
if (scope instanceof TraceScope) {
((TraceScope) scope).setAsyncPropagation(true);
}
delegate.execute(t);
}
} else {
delegate.execute(t);
}
}
public static <T> Action<T> wrapIfNeeded(final Action<T> delegate, final Span span) {
if (delegate instanceof ActionWrapper || span == null) {
return delegate;
}
log.debug("Wrapping action task {}", delegate);
return new ActionWrapper(delegate, span);
}
}
}
}

View File

@ -1,108 +0,0 @@
package datadog.trace.instrumentation.ratpack;
import static datadog.trace.agent.tooling.ByteBuddyElementMatchers.safeHasSuperType;
import static net.bytebuddy.matcher.ElementMatchers.isInterface;
import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.not;
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
import com.google.auto.service.AutoService;
import datadog.trace.agent.tooling.Instrumenter;
import datadog.trace.context.TraceScope;
import io.opentracing.Scope;
import io.opentracing.util.GlobalTracer;
import java.util.Collections;
import java.util.Map;
import lombok.extern.slf4j.Slf4j;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;
import ratpack.exec.internal.Continuation;
import ratpack.func.Action;
import ratpack.path.PathBinding;
@AutoService(Instrumenter.class)
public final class ExecStreamInstrumentation extends Instrumenter.Default {
public ExecStreamInstrumentation() {
super("ratpack");
}
@Override
public ElementMatcher<? super TypeDescription> typeMatcher() {
return not(isInterface())
.and(safeHasSuperType(named("ratpack.exec.internal.DefaultExecution")));
}
// ratpack.exec.internal.DefaultExecution.delimit
@Override
public String[] helperClassNames() {
return new String[] {
packageName + ".ExecStreamInstrumentation$ActionWrapper",
};
}
@Override
public Map<? extends ElementMatcher<? super MethodDescription>, String> transformers() {
return Collections.singletonMap(
named("delimit")
.or(named("delimitStream"))
.and(takesArgument(0, named("ratpack.func.Action")))
.and(takesArgument(1, named("ratpack.func.Action"))),
WrapActionAdvice.class.getName());
}
public static class WrapActionAdvice {
@Advice.OnMethodEnter(suppress = Throwable.class)
public static void wrap(
@Advice.Argument(value = 0, readOnly = false) Action<Throwable> onError,
@Advice.Argument(value = 1, readOnly = false) Action<Continuation> segment) {
final Scope scope = GlobalTracer.get().scopeManager().active();
if (scope instanceof TraceScope) {
final TraceScope.Continuation continuation = ((TraceScope) scope).capture();
onError = ActionWrapper.wrapIfNeeded(onError, continuation);
segment = ActionWrapper.wrapIfNeeded(segment, continuation);
}
}
public void muzzleCheck(final PathBinding binding) {
// This was added in 1.4. Added here to ensure consistency with other instrumentation.
binding.getDescription();
}
}
@Slf4j
public static class ActionWrapper<T> implements Action<T> {
private final Action<T> delegate;
private final TraceScope.Continuation traceContinuation;
private ActionWrapper(
final Action<T> delegate, final TraceScope.Continuation traceContinuation) {
this.delegate = delegate;
this.traceContinuation = traceContinuation;
}
@Override
public void execute(final T subject) throws Exception {
if (traceContinuation != null) {
try (final TraceScope scope = traceContinuation.activate()) {
scope.setAsyncPropagation(true);
delegate.execute(subject);
}
} else {
delegate.execute(subject);
}
}
public static <T> Action<T> wrapIfNeeded(
final Action<T> delegate, final TraceScope.Continuation traceContinuation) {
if (delegate instanceof ActionWrapper) {
return delegate;
}
log.debug("Wrapping action task {}", delegate);
return new ActionWrapper<>(delegate, traceContinuation);
}
}
}

View File

@ -1,8 +1,8 @@
package datadog.trace.instrumentation.ratpack;
import static datadog.trace.agent.tooling.ByteBuddyElementMatchers.safeHasSuperType;
import static datadog.trace.instrumentation.ratpack.RatpackServerDecorator.DECORATE;
import static java.util.Collections.singletonMap;
import static net.bytebuddy.matcher.ElementMatchers.isAbstract;
import static net.bytebuddy.matcher.ElementMatchers.isInterface;
import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.not;
@ -10,10 +10,7 @@ import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
import com.google.auto.service.AutoService;
import datadog.trace.agent.tooling.Instrumenter;
import io.opentracing.Span;
import io.opentracing.util.GlobalTracer;
import java.util.Map;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;
@ -27,8 +24,8 @@ public class ServerErrorHandlerInstrumentation extends Instrumenter.Default {
@Override
public ElementMatcher<TypeDescription> typeMatcher() {
return named("ratpack.exec.Execution")
.or(not(isInterface()).and(safeHasSuperType(named("ratpack.error.ServerErrorHandler"))));
return not(isInterface().or(isAbstract()))
.and(safeHasSuperType(named("ratpack.error.ServerErrorHandler")));
}
@Override
@ -44,16 +41,9 @@ public class ServerErrorHandlerInstrumentation extends Instrumenter.Default {
@Override
public Map<? extends ElementMatcher<? super MethodDescription>, String> transformers() {
return singletonMap(
named("error").and(takesArgument(1, Throwable.class)), ErrorHandlerAdvice.class.getName());
}
public static class ErrorHandlerAdvice {
@Advice.OnMethodEnter(suppress = Throwable.class)
public static void captureThrowable(@Advice.Argument(1) final Throwable throwable) {
final Span span = GlobalTracer.get().activeSpan();
if (span != null) {
DECORATE.onError(span, throwable);
}
}
named("error")
.and(takesArgument(0, named("ratpack.handling.Context")))
.and(takesArgument(1, Throwable.class)),
packageName + ".ErrorHandlerAdvice");
}
}

View File

@ -0,0 +1,19 @@
package datadog.trace.instrumentation.ratpack;
import static datadog.trace.instrumentation.ratpack.RatpackServerDecorator.DECORATE;
import io.opentracing.Span;
import java.util.Optional;
import net.bytebuddy.asm.Advice;
import ratpack.handling.Context;
public class ErrorHandlerAdvice {
@Advice.OnMethodEnter(suppress = Throwable.class)
public static void captureThrowable(
@Advice.Argument(0) final Context ctx, @Advice.Argument(1) final Throwable throwable) {
final Optional<Span> span = ctx.maybeGet(Span.class);
if (span.isPresent()) {
DECORATE.onError(span.get(), throwable);
}
}
}

View File

@ -79,4 +79,13 @@ public class RatpackServerDecorator extends HttpServerDecorator<Request, Request
return span;
}
@Override
public Span onError(final Span span, final Throwable throwable) {
// Attempt to unwrap ratpack.handling.internal.HandlerException without direct reference.
if (throwable instanceof Error && throwable.getCause() != null) {
return super.onError(span, throwable.getCause());
}
return super.onError(span, throwable);
}
}

View File

@ -0,0 +1,175 @@
import datadog.trace.agent.test.AgentTestRunner
import datadog.trace.agent.test.utils.OkHttpUtils
import datadog.trace.api.DDSpanTypes
import datadog.trace.api.DDTags
import datadog.trace.context.TraceScope
import io.opentracing.Scope
import io.opentracing.tag.Tags
import io.opentracing.util.GlobalTracer
import okhttp3.HttpUrl
import okhttp3.OkHttpClient
import okhttp3.Request
import ratpack.exec.Promise
import ratpack.exec.util.ParallelBatch
import ratpack.groovy.test.embed.GroovyEmbeddedApp
import ratpack.path.PathBinding
import ratpack.test.exec.ExecHarness
import java.util.concurrent.CountDownLatch
class RatpackOtherTest extends AgentTestRunner {
OkHttpClient client = OkHttpUtils.client()
def "test bindings for #path"() {
setup:
def app = GroovyEmbeddedApp.ratpack {
handlers {
prefix("a") {
all {
context.render(context.get(PathBinding).description)
}
}
prefix("b/::\\d+") {
all {
context.render(context.get(PathBinding).description)
}
}
prefix("c/:val?") {
all {
context.render(context.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(path).build())
.get()
.build()
when:
def resp = client.newCall(request).execute()
then:
resp.code() == 200
resp.body.string() == route
assertTraces(1) {
trace(0, 2) {
span(0) {
resourceName "GET /$route"
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.resolve(path)}"
"$Tags.PEER_HOSTNAME.key" "$app.address.host"
"$Tags.PEER_HOST_IPV4.key" "127.0.0.1"
"$Tags.PEER_PORT.key" Integer
defaultTags()
}
}
span(1) {
resourceName "GET /$route"
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.resolve(path)}"
"$Tags.PEER_HOSTNAME.key" "$app.address.host"
"$Tags.PEER_PORT.key" Integer
defaultTags()
}
}
}
}
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 "forked executions inherit parent scope"() {
when:
def result = ExecHarness.yieldSingle({}, {
final Scope scope =
GlobalTracer.get()
.buildSpan("ratpack.exec-test")
.withTag(DDTags.RESOURCE_NAME, "INSIDE-TEST")
.startActive(true)
((TraceScope) scope).setAsyncPropagation(true)
scope.span().setBaggageItem("test-baggage", "foo")
ParallelBatch.of(testPromise(), testPromise())
.yield()
.map({ now ->
// close the scope now that we got the baggage inside the promises
scope.close()
return now
})
})
then:
result.valueOrThrow == ["foo", "foo"]
assertTraces(1) {
trace(0, 1) {
span(0) {
resourceName "INSIDE-TEST"
serviceName "unnamed-java-app"
operationName "ratpack.exec-test"
parent()
errored false
tags {
defaultTags()
}
}
}
}
}
// returns a promise that contains the active scope's "test-baggage" baggage
Promise<String> testPromise(CountDownLatch latch = null) {
Promise.sync {
latch?.await()
Scope tracerScope = GlobalTracer.get().scopeManager().active()
return tracerScope?.span()?.getBaggageItem("test-baggage")
}
}
}

View File

@ -1,694 +0,0 @@
import datadog.trace.agent.test.AgentTestRunner
import datadog.trace.agent.test.utils.OkHttpUtils
import datadog.trace.api.DDSpanTypes
import datadog.trace.api.DDTags
import datadog.trace.context.TraceScope
import io.opentracing.Scope
import io.opentracing.Span
import io.opentracing.tag.Tags
import io.opentracing.util.GlobalTracer
import okhttp3.HttpUrl
import okhttp3.OkHttpClient
import okhttp3.Request
import ratpack.exec.Promise
import ratpack.exec.util.ParallelBatch
import ratpack.groovy.test.embed.GroovyEmbeddedApp
import ratpack.handling.internal.HandlerException
import ratpack.http.HttpUrlBuilder
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 bindings for #path"() {
setup:
def app = GroovyEmbeddedApp.ratpack {
handlers {
prefix("a") {
all {
context.render(context.get(PathBinding).description)
}
}
prefix("b/::\\d+") {
all {
context.render(context.get(PathBinding).description)
}
}
prefix("c/:val?") {
all {
context.render(context.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(path).build())
.get()
.build()
when:
def resp = client.newCall(request).execute()
then:
resp.code() == 200
resp.body.string() == route
assertTraces(1) {
trace(0, 2) {
span(0) {
resourceName "GET /$route"
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.resolve(path)}"
"$Tags.PEER_HOSTNAME.key" "$app.address.host"
"$Tags.PEER_HOST_IPV4.key" "127.0.0.1"
"$Tags.PEER_PORT.key" Integer
defaultTags()
}
}
span(1) {
resourceName "GET /$route"
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.resolve(path)}"
"$Tags.PEER_HOSTNAME.key" "$app.address.host"
"$Tags.PEER_PORT.key" Integer
defaultTags()
}
}
}
}
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 {
prefix("handler-error") {
all {
0 / 0
}
}
}
}
def request = new Request.Builder()
.url(app.address.resolve("/handler-error?query=param").toURL())
.get()
.build()
when:
def resp = client.newCall(request).execute()
then:
resp.code() == 500
assertTraces(1) {
trace(0, 2) {
span(0) {
resourceName "GET /handler-error"
serviceName "unnamed-java-app"
operationName "netty.request"
spanType DDSpanTypes.HTTP_SERVER
parent()
errored true
tags {
"$Tags.COMPONENT.key" "netty"
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_SERVER
"$Tags.HTTP_METHOD.key" "GET"
"$Tags.HTTP_STATUS.key" 500
"$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
errorTags(ArithmeticException, Pattern.compile("Division( is)? undefined"))
defaultTags()
}
}
span(1) {
resourceName "GET /handler-error"
serviceName "unnamed-java-app"
operationName "ratpack.handler"
spanType DDSpanTypes.HTTP_SERVER
childOf(span(0))
errored true
tags {
"$Tags.COMPONENT.key" "ratpack"
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_SERVER
"$Tags.HTTP_METHOD.key" "GET"
"$Tags.HTTP_STATUS.key" 500
"$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"))
defaultTags()
}
}
}
}
}
def "test promise error response"() {
setup:
def app = GroovyEmbeddedApp.ratpack {
handlers {
get("promise-error") {
Promise.async {
0 / 0
}.then {
context.render(it)
}
}
}
}
def request = new Request.Builder()
.url(app.address.resolve("promise-error?query=param").toURL())
.get()
.build()
when:
def resp = client.newCall(request).execute()
then:
resp.code() == 500
assertTraces(1) {
trace(0, 2) {
span(0) {
resourceName "GET /promise-error"
serviceName "unnamed-java-app"
operationName "netty.request"
spanType DDSpanTypes.HTTP_SERVER
parent()
errored true
tags {
"$Tags.COMPONENT.key" "netty"
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_SERVER
"$Tags.HTTP_METHOD.key" "GET"
"$Tags.HTTP_STATUS.key" 500
"$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
errorTags(ArithmeticException, Pattern.compile("Division( is)? undefined"))
defaultTags()
}
}
span(1) {
resourceName "GET /promise-error"
serviceName "unnamed-java-app"
operationName "ratpack.handler"
spanType DDSpanTypes.HTTP_SERVER
childOf(span(0))
errored true
tags {
"$Tags.COMPONENT.key" "ratpack"
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_SERVER
"$Tags.HTTP_METHOD.key" "GET"
"$Tags.HTTP_STATUS.key" 500
"$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
defaultTags()
}
}
}
}
}
def "test render error response"() {
setup:
def app = GroovyEmbeddedApp.ratpack {
handlers {
all {
context.render(Promise.sync {
return "fail " + 0 / 0
})
}
}
}
def request = new Request.Builder()
.url(app.address.resolve("?query=param").toURL())
.get()
.build()
when:
def resp = client.newCall(request).execute()
then:
resp.code() == 500
assertTraces(1) {
trace(0, 2) {
span(0) {
resourceName "GET /"
serviceName "unnamed-java-app"
operationName "netty.request"
spanType DDSpanTypes.HTTP_SERVER
parent()
errored true
tags {
"$Tags.COMPONENT.key" "netty"
"$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.PEER_HOSTNAME.key" "$app.address.host"
"$Tags.PEER_HOST_IPV4.key" "127.0.0.1"
"$Tags.PEER_PORT.key" Integer
errorTags(ArithmeticException, Pattern.compile("Division( is)? undefined"))
defaultTags()
}
}
span(1) {
resourceName "GET /"
serviceName "unnamed-java-app"
operationName "ratpack.handler"
spanType DDSpanTypes.HTTP_SERVER
childOf(span(0))
errored true
tags {
"$Tags.COMPONENT.key" "ratpack"
"$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.PEER_HOSTNAME.key" "$app.address.host"
"$Tags.PEER_PORT.key" Integer
"$Tags.ERROR.key" true
defaultTags()
}
}
}
}
}
def "test path call using ratpack http client"() {
setup:
// Use jetty based server to avoid confusion.
def external = httpServer {
handlers {
get("nested") {
handleDistributedRequest()
response.send("succ")
}
get("nested2") {
handleDistributedRequest()
response.send("ess")
}
}
}
def app = GroovyEmbeddedApp.ratpack {
handlers {
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 ->
// 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 }
}
.then {
context.render(it)
}
}
}
}
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"
// 3rd is the three traces, ratpack, http client 2 and http client 1
// 2nd is nested2 from the external server (the result of the 2nd internal http client call)
// 1st is nested from the external server (the result of the 1st internal http client call)
assertTraces(3) {
distributedRequestTrace(it, 0, trace(2).get(3))
distributedRequestTrace(it, 1, trace(2).get(2))
trace(2, 4) {
// main app span that processed the request from OKHTTP request
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()
}
}
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()
}
}
// Second http client call that receives the 'ess' of Success
span(2) {
resourceName "GET /?"
serviceName "unnamed-java-app"
operationName "netty.client.request"
spanType DDSpanTypes.HTTP_CLIENT
childOf(span(1))
errored false
tags {
"$Tags.COMPONENT.key" "netty-client"
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT
"$Tags.HTTP_METHOD.key" "GET"
"$Tags.HTTP_STATUS.key" 200
"$Tags.HTTP_URL.key" "${external.address}/nested2"
"$Tags.PEER_HOSTNAME.key" "$app.address.host"
"$Tags.PEER_HOST_IPV4.key" "127.0.0.1"
"$Tags.PEER_PORT.key" Integer
defaultTags()
}
}
// First http client call that receives the 'Succ' of Success
span(3) {
resourceName "GET /nested"
serviceName "unnamed-java-app"
operationName "netty.client.request"
spanType DDSpanTypes.HTTP_CLIENT
childOf(span(1))
errored false
tags {
"$Tags.COMPONENT.key" "netty-client"
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT
"$Tags.HTTP_METHOD.key" "GET"
"$Tags.HTTP_STATUS.key" 200
"$Tags.HTTP_URL.key" "${external.address}/nested"
"$Tags.PEER_HOSTNAME.key" "$app.address.host"
"$Tags.PEER_HOST_IPV4.key" "127.0.0.1"
"$Tags.PEER_PORT.key" Integer
defaultTags()
}
}
}
}
}
def "test ratpack http client error handling"() {
setup:
def badAddress = new URI("http://localhost:$UNUSABLE_PORT")
def app = GroovyEmbeddedApp.ratpack {
handlers {
get { HttpClient httpClient ->
httpClient.get(badAddress)
.map { it.body.text }
.then {
context.render(it)
}
}
}
}
def request = new Request.Builder()
.url(app.address.toURL())
.get()
.build()
when:
def resp = client.newCall(request).execute()
then:
resp.code() == 500
assertTraces(1) {
trace(0, 3) {
span(0) {
resourceName "GET /"
serviceName "unnamed-java-app"
operationName "netty.request"
spanType DDSpanTypes.HTTP_SERVER
parent()
errored true
tags {
"$Tags.COMPONENT.key" "netty"
"$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.PEER_HOSTNAME.key" "$app.address.host"
"$Tags.PEER_HOST_IPV4.key" "127.0.0.1"
"$Tags.PEER_PORT.key" Integer
"$Tags.ERROR.key" true
defaultTags()
}
}
span(1) {
resourceName "GET /"
serviceName "unnamed-java-app"
operationName "ratpack.handler"
spanType DDSpanTypes.HTTP_SERVER
childOf(span(0))
errored true
tags {
"$Tags.COMPONENT.key" "ratpack"
"$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.PEER_HOSTNAME.key" "$app.address.host"
"$Tags.PEER_PORT.key" Integer
errorTags(ConnectException, String)
defaultTags()
}
}
span(2) {
operationName "netty.connect"
resourceName "netty.connect"
childOf(span(1))
errored true
tags {
"$Tags.COMPONENT.key" "netty"
errorTags(ConnectException, String)
defaultTags()
}
}
}
}
}
def "test forked path call and start span in handler (#startSpanInHandler)"() {
setup:
def app = GroovyEmbeddedApp.ratpack {
handlers {
get {
TraceScope scope
if (startSpanInHandler) {
Span childSpan = GlobalTracer.get()
.buildSpan("ratpack.exec-test")
.withTag(DDTags.RESOURCE_NAME, "INSIDE-TEST")
.start()
scope = GlobalTracer.get().scopeManager().activate(childSpan, true)
}
def latch = new CountDownLatch(1)
try {
scope?.setAsyncPropagation(true)
GlobalTracer.get().activeSpan().setBaggageItem("test-baggage", "foo")
context.render(testPromise(latch).fork())
} finally {
scope?.close()
latch.countDown()
}
}
}
}
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() == "foo"
assertTraces(1) {
trace(0, (startSpanInHandler ? 3 : 2)) {
if (startSpanInHandler) {
span(0) {
resourceName "INSIDE-TEST"
serviceName "unnamed-java-app"
operationName "ratpack.exec-test"
childOf(span(2))
errored false
tags {
defaultTags()
}
}
}
span(startSpanInHandler ? 1 : 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()
}
}
span(startSpanInHandler ? 2 : 1) {
resourceName "GET /"
serviceName "unnamed-java-app"
operationName "ratpack.handler"
spanType DDSpanTypes.HTTP_SERVER
childOf(span(startSpanInHandler ? 1 : 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()
}
}
}
}
where:
startSpanInHandler << [true, false]
}
def "forked executions inherit parent scope"() {
when:
def result = ExecHarness.yieldSingle({}, {
final Scope scope =
GlobalTracer.get()
.buildSpan("ratpack.exec-test")
.withTag(DDTags.RESOURCE_NAME, "INSIDE-TEST")
.startActive(true)
((TraceScope) scope).setAsyncPropagation(true)
scope.span().setBaggageItem("test-baggage", "foo")
ParallelBatch.of(testPromise(), testPromise())
.yield()
.map({ now ->
// close the scope now that we got the baggage inside the promises
scope.close()
return now
})
})
then:
result.valueOrThrow == ["foo", "foo"]
assertTraces(1) {
trace(0, 1) {
span(0) {
resourceName "INSIDE-TEST"
serviceName "unnamed-java-app"
operationName "ratpack.exec-test"
parent()
errored false
tags {
defaultTags()
}
}
}
}
}
// returns a promise that contains the active scope's "test-baggage" baggage
Promise<String> testPromise(CountDownLatch latch = null) {
Promise.sync {
latch?.await()
Scope tracerScope = GlobalTracer.get().scopeManager().active()
return tracerScope?.span()?.getBaggageItem("test-baggage")
}
}
}

View File

@ -0,0 +1,26 @@
package client
import ratpack.exec.ExecResult
class RatpackForkedHttpClientTest extends RatpackHttpClientTest {
@Override
int doRequest(String method, URI uri, Map<String, String> headers, Closure callback) {
ExecResult<Integer> result = exec.yield {
def resp = client.request(uri) { spec ->
spec.method(method)
spec.headers { headersSpec ->
headers.entrySet().each {
headersSpec.add(it.key, it.value)
}
}
}
return resp.fork().map {
callback?.call()
it.status.code
}
}
return result.value
}
}

View File

@ -0,0 +1,58 @@
package client
import datadog.trace.agent.test.base.HttpClientTest
import datadog.trace.instrumentation.netty41.client.NettyHttpClientDecorator
import ratpack.exec.ExecResult
import ratpack.http.client.HttpClient
import ratpack.test.exec.ExecHarness
import spock.lang.AutoCleanup
import spock.lang.Shared
class RatpackHttpClientTest extends HttpClientTest<NettyHttpClientDecorator> {
@AutoCleanup
@Shared
ExecHarness exec = ExecHarness.harness()
@Shared
def client = HttpClient.of {}
@Override
int doRequest(String method, URI uri, Map<String, String> headers, Closure callback) {
ExecResult<Integer> result = exec.yield {
def resp = client.request(uri) { spec ->
spec.method(method)
spec.headers { headersSpec ->
headers.entrySet().each {
headersSpec.add(it.key, it.value)
}
}
}
return resp.map {
callback?.call()
it.status.code
}
}
return result.value
}
@Override
NettyHttpClientDecorator decorator() {
return NettyHttpClientDecorator.DECORATE
}
@Override
String expectedOperationName() {
return "netty.client.request"
}
@Override
boolean testRedirects() {
false
}
@Override
boolean testConnectionFailure() {
false
}
}

View File

@ -0,0 +1,22 @@
package server;
import static net.bytebuddy.matcher.ElementMatchers.named;
import com.google.auto.service.AutoService;
import datadog.trace.agent.test.base.HttpServerTestAdvice;
import datadog.trace.agent.tooling.Instrumenter;
import net.bytebuddy.agent.builder.AgentBuilder;
@AutoService(Instrumenter.class)
public class NettyServerTestInstrumentation implements Instrumenter {
@Override
public AgentBuilder instrument(final AgentBuilder agentBuilder) {
return agentBuilder
.type(named("io.netty.handler.codec.ByteToMessageDecoder"))
.transform(
new AgentBuilder.Transformer.ForAdvice()
.advice(
named("channelRead"), HttpServerTestAdvice.ServerEntryAdvice.class.getName()));
}
}

View File

@ -0,0 +1,75 @@
package server
import ratpack.exec.Promise
import ratpack.groovy.test.embed.GroovyEmbeddedApp
import ratpack.test.embed.EmbeddedApp
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.ERROR
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.EXCEPTION
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.REDIRECT
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.SUCCESS
class RatpackAsyncHttpServerTest extends RatpackHttpServerTest {
@Override
EmbeddedApp startServer(int bindPort) {
def ratpack = GroovyEmbeddedApp.ratpack {
serverConfig {
port bindPort
}
bindings {
bind TestErrorHandler
}
handlers {
prefix(SUCCESS.rawPath()) {
all {
Promise.sync {
SUCCESS
} then { ServerEndpoint endpoint ->
controller(endpoint) {
context.response.status(endpoint.status).send(endpoint.body)
}
}
}
}
prefix(REDIRECT.rawPath()) {
all {
Promise.sync {
REDIRECT
} then { ServerEndpoint endpoint ->
controller(endpoint) {
context.redirect(endpoint.body)
}
}
}
}
prefix(ERROR.rawPath()) {
all {
Promise.sync {
ERROR
} then { ServerEndpoint endpoint ->
controller(endpoint) {
context.response.status(endpoint.status).send(endpoint.body)
}
}
}
}
prefix(EXCEPTION.rawPath()) {
all {
Promise.sync {
EXCEPTION
} then { ServerEndpoint endpoint ->
controller(endpoint) {
throw new Exception(endpoint.body)
}
}
}
}
}
}
ratpack.server.start()
assert ratpack.address.port == bindPort
return ratpack
}
}

View File

@ -0,0 +1,75 @@
package server
import ratpack.exec.Promise
import ratpack.groovy.test.embed.GroovyEmbeddedApp
import ratpack.test.embed.EmbeddedApp
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.ERROR
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.EXCEPTION
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.REDIRECT
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.SUCCESS
class RatpackForkedHttpServerTest extends RatpackHttpServerTest {
@Override
EmbeddedApp startServer(int bindPort) {
def ratpack = GroovyEmbeddedApp.ratpack {
serverConfig {
port bindPort
}
bindings {
bind TestErrorHandler
}
handlers {
prefix(SUCCESS.rawPath()) {
all {
Promise.sync {
SUCCESS
}.fork().then { ServerEndpoint endpoint ->
controller(endpoint) {
context.response.status(endpoint.status).send(endpoint.body)
}
}
}
}
prefix(REDIRECT.rawPath()) {
all {
Promise.sync {
REDIRECT
}.fork().then { ServerEndpoint endpoint ->
controller(endpoint) {
context.redirect(endpoint.body)
}
}
}
}
prefix(ERROR.rawPath()) {
all {
Promise.sync {
ERROR
}.fork().then { ServerEndpoint endpoint ->
controller(endpoint) {
context.response.status(endpoint.status).send(endpoint.body)
}
}
}
}
prefix(EXCEPTION.rawPath()) {
all {
Promise.sync {
EXCEPTION
}.fork().then { ServerEndpoint endpoint ->
controller(endpoint) {
throw new Exception(endpoint.body)
}
}
}
}
}
}
ratpack.server.start()
assert ratpack.address.port == bindPort
return ratpack
}
}

View File

@ -0,0 +1,145 @@
package server
import datadog.opentracing.DDSpan
import datadog.trace.agent.test.asserts.ListWriterAssert
import datadog.trace.agent.test.asserts.TraceAssert
import datadog.trace.agent.test.base.HttpServerTest
import datadog.trace.api.DDSpanTypes
import datadog.trace.instrumentation.netty41.server.NettyHttpServerDecorator
import datadog.trace.instrumentation.ratpack.RatpackServerDecorator
import groovy.transform.stc.ClosureParams
import groovy.transform.stc.SimpleType
import io.opentracing.tag.Tags
import ratpack.error.ServerErrorHandler
import ratpack.groovy.test.embed.GroovyEmbeddedApp
import ratpack.handling.Context
import ratpack.test.embed.EmbeddedApp
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.ERROR
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.EXCEPTION
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.REDIRECT
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.SUCCESS
class RatpackHttpServerTest extends HttpServerTest<EmbeddedApp, NettyHttpServerDecorator> {
@Override
EmbeddedApp startServer(int bindPort) {
def ratpack = GroovyEmbeddedApp.ratpack {
serverConfig {
port bindPort
}
bindings {
bind TestErrorHandler
}
handlers {
prefix(SUCCESS.rawPath()) {
all {
controller(SUCCESS) {
context.response.status(SUCCESS.status).send(SUCCESS.body)
}
}
}
prefix(REDIRECT.rawPath()) {
all {
controller(REDIRECT) {
context.redirect(REDIRECT.body)
}
}
}
prefix(ERROR.rawPath()) {
all {
controller(ERROR) {
context.response.status(ERROR.status).send(ERROR.body)
}
}
}
prefix(EXCEPTION.rawPath()) {
all {
controller(EXCEPTION) {
throw new Exception(EXCEPTION.body)
}
}
}
}
}
ratpack.server.start()
assert ratpack.address.port == bindPort
return ratpack
}
static class TestErrorHandler implements ServerErrorHandler {
@Override
void error(Context context, Throwable throwable) throws Exception {
context.response.status(500).send(throwable.message)
}
}
@Override
void stopServer(EmbeddedApp server) {
server.close()
}
@Override
NettyHttpServerDecorator decorator() {
return NettyHttpServerDecorator.DECORATE
}
@Override
String expectedOperationName() {
"netty.request"
}
@Override
boolean hasHandlerSpan() {
true
}
void cleanAndAssertTraces(
final int size,
@ClosureParams(value = SimpleType, options = "datadog.trace.agent.test.asserts.ListWriterAssert")
@DelegatesTo(value = ListWriterAssert, strategy = Closure.DELEGATE_FIRST)
final Closure spec) {
// If this is failing, make sure HttpServerTestAdvice is applied correctly.
TEST_WRITER.waitForTraces(size * 2)
// Ratpack closes the handler span before the controller returns, so we need to manually reorder it.
TEST_WRITER.each {
def controllerSpan = it.find {
it.operationName == "controller"
}
if (controllerSpan) {
it.remove(controllerSpan)
it.add(controllerSpan)
}
}
super.cleanAndAssertTraces(size, spec)
}
@Override
void handlerSpan(TraceAssert trace, int index, Object parent, ServerEndpoint endpoint = SUCCESS) {
trace.span(index) {
serviceName expectedServiceName()
operationName "ratpack.handler"
spanType DDSpanTypes.HTTP_SERVER
errored endpoint == ERROR || endpoint == EXCEPTION
childOf(parent as DDSpan)
tags {
"$Tags.COMPONENT.key" RatpackServerDecorator.DECORATE.component()
"$Tags.HTTP_STATUS.key" Integer
"$Tags.HTTP_URL.key" String
"$Tags.PEER_HOSTNAME.key" "localhost"
"$Tags.PEER_PORT.key" Integer
"$Tags.PEER_HOST_IPV4.key" { it == null || it == "127.0.0.1" } // Optional
"$Tags.HTTP_METHOD.key" String
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_SERVER
defaultTags()
if (endpoint == ERROR) {
"$Tags.ERROR.key" true
} else if (endpoint == EXCEPTION) {
errorTags(Exception, EXCEPTION.body)
}
}
}
}
}

View File

@ -72,6 +72,10 @@ abstract class HttpServerTest<SERVER, DECORATOR extends HttpServerDecorator> ext
abstract String expectedOperationName()
boolean hasHandlerSpan() {
false
}
boolean testNotFound() {
true
}
@ -148,12 +152,20 @@ abstract class HttpServerTest<SERVER, DECORATOR extends HttpServerDecorator> ext
and:
cleanAndAssertTraces(count) {
(1..count).eachWithIndex { val, i ->
if (hasHandlerSpan()) {
trace(i, 3) {
serverSpan(it, 0)
handlerSpan(it, 1, span(0))
controllerSpan(it, 2, span(1))
}
} else {
trace(i, 2) {
serverSpan(it, 0)
controllerSpan(it, 1, span(0))
}
}
}
}
where:
method = "GET"
@ -177,11 +189,19 @@ abstract class HttpServerTest<SERVER, DECORATOR extends HttpServerDecorator> ext
and:
cleanAndAssertTraces(1) {
if (hasHandlerSpan()) {
trace(0, 3) {
serverSpan(it, 0, traceId, parentId)
handlerSpan(it, 1, span(0))
controllerSpan(it, 2, span(1))
}
} else {
trace(0, 2) {
serverSpan(it, 0, traceId, parentId)
controllerSpan(it, 1, span(0))
}
}
}
where:
method = "GET"
@ -201,11 +221,19 @@ abstract class HttpServerTest<SERVER, DECORATOR extends HttpServerDecorator> ext
and:
cleanAndAssertTraces(1) {
if (hasHandlerSpan()) {
trace(0, 3) {
serverSpan(it, 0, null, null, method, REDIRECT)
handlerSpan(it, 1, span(0))
controllerSpan(it, 2, span(1))
}
} else {
trace(0, 2) {
serverSpan(it, 0, null, null, method, REDIRECT)
controllerSpan(it, 1, span(0))
}
}
}
where:
method = "GET"
@ -223,11 +251,19 @@ abstract class HttpServerTest<SERVER, DECORATOR extends HttpServerDecorator> ext
and:
cleanAndAssertTraces(1) {
if (hasHandlerSpan()) {
trace(0, 3) {
serverSpan(it, 0, null, null, method, ERROR)
handlerSpan(it, 1, span(0), ERROR)
controllerSpan(it, 2, span(1))
}
} else {
trace(0, 2) {
serverSpan(it, 0, null, null, method, ERROR)
controllerSpan(it, 1, span(0))
}
}
}
where:
method = "GET"
@ -247,11 +283,19 @@ abstract class HttpServerTest<SERVER, DECORATOR extends HttpServerDecorator> ext
and:
cleanAndAssertTraces(1) {
if (hasHandlerSpan()) {
trace(0, 3) {
serverSpan(it, 0, null, null, method, EXCEPTION)
handlerSpan(it, 1, span(0), EXCEPTION)
controllerSpan(it, 2, span(1), EXCEPTION.body)
}
} else {
trace(0, 2) {
serverSpan(it, 0, null, null, method, EXCEPTION)
controllerSpan(it, 1, span(0), EXCEPTION.body)
}
}
}
where:
method = "GET"
@ -269,10 +313,17 @@ abstract class HttpServerTest<SERVER, DECORATOR extends HttpServerDecorator> ext
and:
cleanAndAssertTraces(1) {
if (hasHandlerSpan()) {
trace(0, 2) {
serverSpan(it, 0, null, null, method, NOT_FOUND)
handlerSpan(it, 1, span(0), NOT_FOUND)
}
} else {
trace(0, 1) {
serverSpan(it, 0, null, null, method, NOT_FOUND)
}
}
}
where:
method = "GET"
@ -320,6 +371,10 @@ abstract class HttpServerTest<SERVER, DECORATOR extends HttpServerDecorator> ext
}
}
void handlerSpan(TraceAssert trace, int index, Object parent, ServerEndpoint endpoint = SUCCESS) {
throw new UnsupportedOperationException("handlerSpan not implemented in " + getClass().name)
}
// parent span must be cast otherwise it breaks debugging classloading (junit loads it early)
void serverSpan(TraceAssert trace, int index, String traceID = null, String parentID = null, String method = "GET", ServerEndpoint endpoint = SUCCESS) {
trace.span(index) {