Ratpack instrumentaiton support
This commit is contained in:
parent
c7cf1cf36d
commit
6e92221e5a
|
@ -0,0 +1,37 @@
|
||||||
|
apply plugin: 'version-scan'
|
||||||
|
|
||||||
|
versionScan {
|
||||||
|
group = "io.ratpack"
|
||||||
|
module = 'ratpack'
|
||||||
|
versions = "[1.4.6,)"
|
||||||
|
verifyPresent = [
|
||||||
|
"ratpack.exec.ExecStarter" : null,
|
||||||
|
"ratpack.exec.Execution" : null,
|
||||||
|
"ratpack.func.Action" : null,
|
||||||
|
"ratpack.http.client.HttpClient" : null,
|
||||||
|
"ratpack.server.internal.ServerRegistry": "buildBaseRegistry",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
apply from: "${rootDir}/gradle/java.gradle"
|
||||||
|
|
||||||
|
// Ratpack only supports Java 1.8+
|
||||||
|
sourceCompatibility = 1.8
|
||||||
|
targetCompatibility = 1.8
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
compileOnly group: 'io.ratpack', name: 'ratpack-core', version: '1.4.6'
|
||||||
|
|
||||||
|
compile project(':dd-trace-ot')
|
||||||
|
compile project(':dd-java-agent:agent-tooling')
|
||||||
|
|
||||||
|
compile deps.bytebuddy
|
||||||
|
compile deps.opentracing
|
||||||
|
compile deps.autoservice
|
||||||
|
|
||||||
|
testCompile project(':dd-java-agent:testing')
|
||||||
|
testCompile group: 'io.ratpack', name: 'ratpack-test', version: '1.4.6'
|
||||||
|
|
||||||
|
testCompile project(':dd-java-agent:instrumentation:okhttp-3') // used in the tests
|
||||||
|
testCompile group: 'com.squareup.okhttp3', name: 'okhttp', version: '3.6.0'
|
||||||
|
}
|
|
@ -0,0 +1,225 @@
|
||||||
|
package datadog.trace.instrumentation.ratpack;
|
||||||
|
|
||||||
|
import static datadog.trace.agent.tooling.ClassLoaderMatcher.classLoaderHasClasses;
|
||||||
|
import static datadog.trace.instrumentation.ratpack.RatpackInstrumentation.ACTION_TYPE_DESCRIPTION;
|
||||||
|
import static datadog.trace.instrumentation.ratpack.RatpackInstrumentation.EXEC_NAME;
|
||||||
|
import static net.bytebuddy.matcher.ElementMatchers.hasSuperType;
|
||||||
|
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.takesArguments;
|
||||||
|
|
||||||
|
import com.google.auto.service.AutoService;
|
||||||
|
import datadog.trace.agent.tooling.DDAdvice;
|
||||||
|
import datadog.trace.agent.tooling.HelperInjector;
|
||||||
|
import datadog.trace.agent.tooling.Instrumenter;
|
||||||
|
import io.opentracing.Span;
|
||||||
|
import io.opentracing.tag.Tags;
|
||||||
|
import io.opentracing.util.GlobalTracer;
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
import java.io.StringWriter;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
import net.bytebuddy.agent.builder.AgentBuilder;
|
||||||
|
import net.bytebuddy.asm.Advice;
|
||||||
|
import net.bytebuddy.description.type.TypeDescription;
|
||||||
|
import ratpack.exec.Promise;
|
||||||
|
import ratpack.exec.Result;
|
||||||
|
import ratpack.func.Action;
|
||||||
|
import ratpack.http.client.ReceivedResponse;
|
||||||
|
import ratpack.http.client.RequestSpec;
|
||||||
|
import ratpack.http.client.StreamedResponse;
|
||||||
|
|
||||||
|
@AutoService(Instrumenter.class)
|
||||||
|
public final class RatpackHttpClientInstrumentation extends Instrumenter.Configurable {
|
||||||
|
|
||||||
|
private static final HelperInjector HTTP_CLIENT_HELPER_INJECTOR =
|
||||||
|
new HelperInjector(
|
||||||
|
"datadog.trace.instrumentation.ratpack.RatpackHttpClientInstrumentation$RatpackHttpClientRequestAdvice",
|
||||||
|
"datadog.trace.instrumentation.ratpack.RatpackHttpClientInstrumentation$RatpackHttpClientRequestStreamAdvice",
|
||||||
|
"datadog.trace.instrumentation.ratpack.RatpackHttpClientInstrumentation$RatpackHttpGetAdvice");
|
||||||
|
public static final TypeDescription.ForLoadedType URI_TYPE_DESCRIPTION =
|
||||||
|
new TypeDescription.ForLoadedType(URI.class);
|
||||||
|
|
||||||
|
public RatpackHttpClientInstrumentation() {
|
||||||
|
super(EXEC_NAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean defaultEnabled() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AgentBuilder apply(final AgentBuilder agentBuilder) {
|
||||||
|
|
||||||
|
return agentBuilder
|
||||||
|
.type(
|
||||||
|
not(isInterface()).and(hasSuperType(named("ratpack.http.client.HttpClient"))),
|
||||||
|
classLoaderHasClasses(
|
||||||
|
"ratpack.exec.Promise",
|
||||||
|
"ratpack.exec.Result",
|
||||||
|
"ratpack.func.Action",
|
||||||
|
"ratpack.http.client.ReceivedResponse",
|
||||||
|
"ratpack.http.client.RequestSpec",
|
||||||
|
"ratpack.http.client.StreamedResponse",
|
||||||
|
"ratpack.http.Request",
|
||||||
|
"ratpack.func.Function",
|
||||||
|
"ratpack.http.HttpMethod",
|
||||||
|
"ratpack.http.MutableHeaders",
|
||||||
|
"com.google.common.collect.ListMultimap"))
|
||||||
|
.transform(HTTP_CLIENT_HELPER_INJECTOR)
|
||||||
|
.transform(
|
||||||
|
DDAdvice.create()
|
||||||
|
.advice(
|
||||||
|
named("request")
|
||||||
|
.and(takesArguments(URI_TYPE_DESCRIPTION, ACTION_TYPE_DESCRIPTION)),
|
||||||
|
RatpackHttpClientRequestAdvice.class.getName()))
|
||||||
|
.transform(
|
||||||
|
DDAdvice.create()
|
||||||
|
.advice(
|
||||||
|
named("requestStream")
|
||||||
|
.and(takesArguments(URI_TYPE_DESCRIPTION, ACTION_TYPE_DESCRIPTION)),
|
||||||
|
RatpackHttpClientRequestStreamAdvice.class.getName()))
|
||||||
|
.transform(
|
||||||
|
DDAdvice.create()
|
||||||
|
.advice(
|
||||||
|
named("get").and(takesArguments(URI_TYPE_DESCRIPTION, ACTION_TYPE_DESCRIPTION)),
|
||||||
|
RatpackHttpGetAdvice.class.getName()))
|
||||||
|
.asDecorator();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Map<String, Object> errorLogs(final Throwable throwable) {
|
||||||
|
final Map<String, Object> errorLogs = new HashMap<>(4);
|
||||||
|
errorLogs.put("event", Tags.ERROR.getKey());
|
||||||
|
errorLogs.put("error.kind", throwable.getClass().getName());
|
||||||
|
errorLogs.put("error.object", throwable);
|
||||||
|
|
||||||
|
errorLogs.put("message", throwable.getMessage());
|
||||||
|
|
||||||
|
final StringWriter sw = new StringWriter();
|
||||||
|
throwable.printStackTrace(new PrintWriter(sw));
|
||||||
|
errorLogs.put("stack", sw.toString());
|
||||||
|
|
||||||
|
return errorLogs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class RequestAction implements Action<RequestSpec> {
|
||||||
|
|
||||||
|
private final Action<? super RequestSpec> requestAction;
|
||||||
|
private final AtomicReference<Span> span;
|
||||||
|
|
||||||
|
public RequestAction(Action<? super RequestSpec> requestAction, AtomicReference<Span> span) {
|
||||||
|
this.requestAction = requestAction;
|
||||||
|
this.span = span;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute(RequestSpec requestSpec) throws Exception {
|
||||||
|
requestAction.execute(
|
||||||
|
new WrappedRequestSpec(
|
||||||
|
requestSpec, GlobalTracer.get(), GlobalTracer.get().scopeManager().active(), span));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ResponseAction implements Action<Result<ReceivedResponse>> {
|
||||||
|
private final AtomicReference<Span> spanRef;
|
||||||
|
|
||||||
|
public ResponseAction(AtomicReference<Span> spanRef) {
|
||||||
|
this.spanRef = spanRef;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute(Result<ReceivedResponse> result) {
|
||||||
|
Span span = spanRef.get();
|
||||||
|
if (span == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
span.finish();
|
||||||
|
if (result.isError()) {
|
||||||
|
Tags.ERROR.set(span, true);
|
||||||
|
span.log(errorLogs(result.getThrowable()));
|
||||||
|
} else {
|
||||||
|
Tags.HTTP_STATUS.set(span, result.getValue().getStatusCode());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class StreamedResponseAction implements Action<Result<StreamedResponse>> {
|
||||||
|
private final Span span;
|
||||||
|
|
||||||
|
public StreamedResponseAction(Span span) {
|
||||||
|
this.span = span;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute(Result<StreamedResponse> result) {
|
||||||
|
span.finish();
|
||||||
|
if (result.isError()) {
|
||||||
|
Tags.ERROR.set(span, true);
|
||||||
|
span.log(errorLogs(result.getThrowable()));
|
||||||
|
} else {
|
||||||
|
Tags.HTTP_STATUS.set(span, result.getValue().getStatusCode());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class RatpackHttpClientRequestAdvice {
|
||||||
|
@Advice.OnMethodEnter
|
||||||
|
public static AtomicReference<Span> injectTracing(
|
||||||
|
@Advice.Argument(value = 1, readOnly = false) Action<? super RequestSpec> requestAction) {
|
||||||
|
AtomicReference<Span> span = new AtomicReference<>();
|
||||||
|
|
||||||
|
//noinspection UnusedAssignment
|
||||||
|
requestAction = new RequestAction(requestAction, span);
|
||||||
|
|
||||||
|
return span;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Advice.OnMethodExit
|
||||||
|
public static void finishTracing(
|
||||||
|
@Advice.Return(readOnly = false) Promise<ReceivedResponse> promise,
|
||||||
|
@Advice.Enter AtomicReference<Span> ref) {
|
||||||
|
|
||||||
|
//noinspection UnusedAssignment
|
||||||
|
promise = promise.wiretap(new ResponseAction(ref));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class RatpackHttpClientRequestStreamAdvice {
|
||||||
|
@Advice.OnMethodEnter
|
||||||
|
public static AtomicReference<Span> injectTracing(
|
||||||
|
@Advice.Argument(value = 1, readOnly = false) Action<? super RequestSpec> requestAction) {
|
||||||
|
AtomicReference<Span> span = new AtomicReference<>();
|
||||||
|
|
||||||
|
//noinspection UnusedAssignment
|
||||||
|
requestAction = new RequestAction(requestAction, span);
|
||||||
|
|
||||||
|
return span;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Advice.OnMethodExit
|
||||||
|
public static void finishTracing(
|
||||||
|
@Advice.Return(readOnly = false) Promise<StreamedResponse> promise,
|
||||||
|
@Advice.Enter AtomicReference<Span> ref) {
|
||||||
|
Span span = ref.get();
|
||||||
|
if (span == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//noinspection UnusedAssignment
|
||||||
|
promise = promise.wiretap(new StreamedResponseAction(span));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class RatpackHttpGetAdvice {
|
||||||
|
@Advice.OnMethodEnter
|
||||||
|
public static void ensureGetMethodSet(
|
||||||
|
@Advice.Argument(value = 1, readOnly = false) Action<? super RequestSpec> requestAction) {
|
||||||
|
//noinspection UnusedAssignment
|
||||||
|
requestAction = requestAction.prepend(RequestSpec::get);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,170 @@
|
||||||
|
package datadog.trace.instrumentation.ratpack;
|
||||||
|
|
||||||
|
import static datadog.trace.agent.tooling.ClassLoaderMatcher.classLoaderHasClasses;
|
||||||
|
import static net.bytebuddy.matcher.ElementMatchers.hasSuperType;
|
||||||
|
import static net.bytebuddy.matcher.ElementMatchers.isInterface;
|
||||||
|
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
|
||||||
|
import static net.bytebuddy.matcher.ElementMatchers.isStatic;
|
||||||
|
import static net.bytebuddy.matcher.ElementMatchers.named;
|
||||||
|
import static net.bytebuddy.matcher.ElementMatchers.not;
|
||||||
|
import static net.bytebuddy.matcher.ElementMatchers.returns;
|
||||||
|
import static net.bytebuddy.matcher.ElementMatchers.takesArguments;
|
||||||
|
|
||||||
|
import com.google.auto.service.AutoService;
|
||||||
|
import datadog.opentracing.scopemanager.ContextualScopeManager;
|
||||||
|
import datadog.trace.agent.tooling.DDAdvice;
|
||||||
|
import datadog.trace.agent.tooling.HelperInjector;
|
||||||
|
import datadog.trace.agent.tooling.Instrumenter;
|
||||||
|
import io.opentracing.Scope;
|
||||||
|
import io.opentracing.ScopeManager;
|
||||||
|
import io.opentracing.util.GlobalTracer;
|
||||||
|
import java.lang.reflect.Modifier;
|
||||||
|
import java.util.Collections;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import net.bytebuddy.agent.builder.AgentBuilder;
|
||||||
|
import net.bytebuddy.asm.Advice;
|
||||||
|
import net.bytebuddy.description.type.TypeDescription;
|
||||||
|
import ratpack.exec.ExecStarter;
|
||||||
|
import ratpack.exec.Execution;
|
||||||
|
import ratpack.func.Action;
|
||||||
|
import ratpack.handling.HandlerDecorator;
|
||||||
|
import ratpack.registry.Registry;
|
||||||
|
import ratpack.registry.RegistrySpec;
|
||||||
|
|
||||||
|
@AutoService(Instrumenter.class)
|
||||||
|
@Slf4j
|
||||||
|
public final class RatpackInstrumentation extends Instrumenter.Configurable {
|
||||||
|
|
||||||
|
static final String EXEC_NAME = "ratpack";
|
||||||
|
private static final HelperInjector SERVER_REGISTRY_HELPER_INJECTOR =
|
||||||
|
new HelperInjector(
|
||||||
|
"datadog.trace.instrumentation.ratpack.RatpackScopeManager",
|
||||||
|
"datadog.trace.instrumentation.ratpack.TracingHandler",
|
||||||
|
"datadog.trace.instrumentation.ratpack.RatpackInstrumentation$RatpackServerRegistryAdvice");
|
||||||
|
private static final HelperInjector EXEC_STARTER_HELPER_INJECTOR =
|
||||||
|
new HelperInjector(
|
||||||
|
"datadog.trace.instrumentation.ratpack.RatpackInstrumentation$ExecStarterAdvice",
|
||||||
|
"datadog.trace.instrumentation.ratpack.RatpackInstrumentation$ExecStarterAction");
|
||||||
|
|
||||||
|
public static final TypeDescription.Latent ACTION_TYPE_DESCRIPTION =
|
||||||
|
new TypeDescription.Latent(
|
||||||
|
"ratpack.func.Action", Modifier.PUBLIC, null, Collections.emptyList());
|
||||||
|
|
||||||
|
public RatpackInstrumentation() {
|
||||||
|
super(EXEC_NAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean defaultEnabled() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AgentBuilder apply(final AgentBuilder agentBuilder) {
|
||||||
|
|
||||||
|
return agentBuilder
|
||||||
|
.type(
|
||||||
|
named("ratpack.server.internal.ServerRegistry"),
|
||||||
|
classLoaderHasClasses(
|
||||||
|
"ratpack.handling.HandlerDecorator",
|
||||||
|
"ratpack.registry.Registry",
|
||||||
|
"ratpack.registry.RegistrySpec",
|
||||||
|
"ratpack.handling.Context",
|
||||||
|
"ratpack.handling.Handler",
|
||||||
|
"ratpack.http.Request",
|
||||||
|
"ratpack.http.Status"))
|
||||||
|
.transform(SERVER_REGISTRY_HELPER_INJECTOR)
|
||||||
|
.transform(
|
||||||
|
DDAdvice.create()
|
||||||
|
.advice(
|
||||||
|
isMethod().and(isStatic()).and(named("buildBaseRegistry")),
|
||||||
|
RatpackServerRegistryAdvice.class.getName()))
|
||||||
|
.asDecorator()
|
||||||
|
.type(
|
||||||
|
not(isInterface()).and(hasSuperType(named("ratpack.exec.ExecStarter"))),
|
||||||
|
classLoaderHasClasses(
|
||||||
|
"ratpack.exec.Execution", "ratpack.registry.RegistrySpec", "ratpack.func.Action"))
|
||||||
|
.transform(EXEC_STARTER_HELPER_INJECTOR)
|
||||||
|
.transform(
|
||||||
|
DDAdvice.create()
|
||||||
|
.advice(
|
||||||
|
named("register").and(takesArguments(ACTION_TYPE_DESCRIPTION)),
|
||||||
|
ExecStarterAdvice.class.getName()))
|
||||||
|
.asDecorator()
|
||||||
|
.type(
|
||||||
|
named(Execution.class.getName())
|
||||||
|
.or(not(isInterface()).and(hasSuperType(named("ratpack.exec.Execution")))),
|
||||||
|
classLoaderHasClasses(
|
||||||
|
"ratpack.exec.ExecStarter", "ratpack.registry.RegistrySpec", "ratpack.func.Action"))
|
||||||
|
.transform(EXEC_STARTER_HELPER_INJECTOR)
|
||||||
|
.transform(
|
||||||
|
DDAdvice.create()
|
||||||
|
.advice(
|
||||||
|
named("fork").and(returns(named("ratpack.exec.ExecStarter"))),
|
||||||
|
ExecutionAdvice.class.getName()))
|
||||||
|
.asDecorator();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class RatpackServerRegistryAdvice {
|
||||||
|
@Advice.OnMethodExit(suppress = Throwable.class)
|
||||||
|
public static void injectTracing(@Advice.Return(readOnly = false) Registry registry) {
|
||||||
|
RatpackScopeManager ratpackScopeManager = new RatpackScopeManager();
|
||||||
|
// the value returned from ServerRegistry.buildBaseRegistry needs to be modified to add our
|
||||||
|
// scope manager and handler decorator to the registry
|
||||||
|
//noinspection UnusedAssignment
|
||||||
|
registry =
|
||||||
|
registry.join(
|
||||||
|
Registry.builder()
|
||||||
|
.add(ScopeManager.class, ratpackScopeManager)
|
||||||
|
.add(HandlerDecorator.prepend(new TracingHandler()))
|
||||||
|
.build());
|
||||||
|
|
||||||
|
if (GlobalTracer.isRegistered()) {
|
||||||
|
if (GlobalTracer.get().scopeManager() instanceof ContextualScopeManager) {
|
||||||
|
((ContextualScopeManager) GlobalTracer.get().scopeManager())
|
||||||
|
.addScopeContext(ratpackScopeManager);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.warn("No GlobalTracer registered");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ExecStarterAdvice {
|
||||||
|
@Advice.OnMethodEnter
|
||||||
|
public static void addScopeToRegistry(
|
||||||
|
@Advice.Argument(value = 0, readOnly = false) Action<? super RegistrySpec> action) {
|
||||||
|
Scope active = GlobalTracer.get().scopeManager().active();
|
||||||
|
if (active != null) {
|
||||||
|
//noinspection UnusedAssignment
|
||||||
|
action = new ExecStarterAction(active).append(action);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ExecutionAdvice {
|
||||||
|
@Advice.OnMethodExit
|
||||||
|
public static void addScopeToRegistry(@Advice.Return ExecStarter starter) {
|
||||||
|
Scope active = GlobalTracer.get().scopeManager().active();
|
||||||
|
if (active != null) {
|
||||||
|
starter.register(new ExecStarterAction(active));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ExecStarterAction implements Action<RegistrySpec> {
|
||||||
|
private final Scope active;
|
||||||
|
|
||||||
|
@SuppressWarnings("WeakerAccess")
|
||||||
|
public ExecStarterAction(Scope active) {
|
||||||
|
this.active = active;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute(RegistrySpec spec) {
|
||||||
|
if (active != null) {
|
||||||
|
spec.add(active);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
package datadog.trace.instrumentation.ratpack;
|
||||||
|
|
||||||
|
import com.google.common.collect.ListMultimap;
|
||||||
|
import io.opentracing.propagation.TextMap;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.Map;
|
||||||
|
import ratpack.http.Request;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple request extractor in the same vein as @see
|
||||||
|
* io.opentracing.contrib.web.servlet.filter.HttpServletRequestExtractAdapter
|
||||||
|
*/
|
||||||
|
public class RatpackRequestExtractAdapter implements TextMap {
|
||||||
|
private final ListMultimap<String, String> headers;
|
||||||
|
|
||||||
|
RatpackRequestExtractAdapter(Request request) {
|
||||||
|
this.headers = request.getHeaders().asMultiValueMap().asMultimap();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Iterator<Map.Entry<String, String>> iterator() {
|
||||||
|
return headers.entries().iterator();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void put(String key, String value) {
|
||||||
|
throw new UnsupportedOperationException("This class should be used only with Tracer.inject()!");
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,78 @@
|
||||||
|
package datadog.trace.instrumentation.ratpack;
|
||||||
|
|
||||||
|
import datadog.opentracing.scopemanager.ScopeContext;
|
||||||
|
import io.opentracing.Scope;
|
||||||
|
import io.opentracing.Span;
|
||||||
|
import ratpack.exec.Execution;
|
||||||
|
import ratpack.exec.UnmanagedThreadException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This scope manager uses the Ratpack Execution to store the current Scope. This is a ratpack
|
||||||
|
* registry analogous to a ThreadLocal but for an execution that may transfer between several
|
||||||
|
* threads
|
||||||
|
*/
|
||||||
|
public final class RatpackScopeManager implements ScopeContext {
|
||||||
|
@Override
|
||||||
|
public boolean inContext() {
|
||||||
|
return Execution.isManagedThread();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Scope activate(Span span, boolean finishSpanOnClose) {
|
||||||
|
Execution execution = Execution.current();
|
||||||
|
RatpackScope ratpackScope =
|
||||||
|
new RatpackScope(
|
||||||
|
span, finishSpanOnClose, execution.maybeGet(RatpackScope.class).orElse(null));
|
||||||
|
execution.add(RatpackScope.class, ratpackScope);
|
||||||
|
execution.onComplete(
|
||||||
|
ratpackScope); // ensure that the scope is closed when the execution finishes
|
||||||
|
return ratpackScope;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Scope active() {
|
||||||
|
try {
|
||||||
|
return Execution.current().maybeGet(RatpackScope.class).orElse(null);
|
||||||
|
} catch (UnmanagedThreadException ume) {
|
||||||
|
return null; // should never happen due to inContextCheck
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class RatpackScope implements Scope {
|
||||||
|
private final Span wrapped;
|
||||||
|
private final boolean finishOnClose;
|
||||||
|
private final RatpackScope toRestore;
|
||||||
|
|
||||||
|
RatpackScope(Span wrapped, boolean finishOnClose, RatpackScope toRestore) {
|
||||||
|
this.wrapped = wrapped;
|
||||||
|
this.finishOnClose = finishOnClose;
|
||||||
|
this.toRestore = toRestore;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Span span() {
|
||||||
|
return wrapped;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
Execution execution = Execution.current();
|
||||||
|
// only close if this scope is the current scope for this Execution
|
||||||
|
// As with ThreadLocalScope this shouldn't happen if users call methods in the expected order
|
||||||
|
execution
|
||||||
|
.maybeGet(RatpackScope.class)
|
||||||
|
.filter(s -> this == s)
|
||||||
|
.ifPresent(
|
||||||
|
ignore -> {
|
||||||
|
if (finishOnClose) {
|
||||||
|
wrapped.finish();
|
||||||
|
}
|
||||||
|
// pop the execution "stack"
|
||||||
|
execution.remove(RatpackScope.class);
|
||||||
|
if (toRestore != null) {
|
||||||
|
execution.add(toRestore);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
package datadog.trace.instrumentation.ratpack;
|
||||||
|
|
||||||
|
import io.opentracing.propagation.TextMap;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.Map;
|
||||||
|
import ratpack.http.client.RequestSpec;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SimpleTextMap to add headers to an outgoing Ratpack HttpClient request
|
||||||
|
*
|
||||||
|
* @see datadog.trace.instrumentation.apachehttpclient.DDTracingClientExec.HttpHeadersInjectAdapter
|
||||||
|
*/
|
||||||
|
public class RequestSpecInjectAdapter implements TextMap {
|
||||||
|
private final RequestSpec requestSpec;
|
||||||
|
|
||||||
|
public RequestSpecInjectAdapter(RequestSpec requestSpec) {
|
||||||
|
this.requestSpec = requestSpec;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Iterator<Map.Entry<String, String>> iterator() {
|
||||||
|
throw new UnsupportedOperationException("Should be used only with tracer#inject()");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void put(String key, String value) {
|
||||||
|
requestSpec.getHeaders().add(key, value);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,57 @@
|
||||||
|
package datadog.trace.instrumentation.ratpack;
|
||||||
|
|
||||||
|
import datadog.trace.api.DDSpanTypes;
|
||||||
|
import datadog.trace.api.DDTags;
|
||||||
|
import io.opentracing.Scope;
|
||||||
|
import io.opentracing.Span;
|
||||||
|
import io.opentracing.SpanContext;
|
||||||
|
import io.opentracing.propagation.Format;
|
||||||
|
import io.opentracing.tag.Tags;
|
||||||
|
import io.opentracing.util.GlobalTracer;
|
||||||
|
import ratpack.handling.Context;
|
||||||
|
import ratpack.handling.Handler;
|
||||||
|
import ratpack.http.Request;
|
||||||
|
import ratpack.http.Status;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This Ratpack handler reads tracing headers from the incoming request, starts a scope and ensures
|
||||||
|
* that the scope is closed when the request is sent
|
||||||
|
*/
|
||||||
|
public final class TracingHandler implements Handler {
|
||||||
|
@Override
|
||||||
|
public void handle(Context ctx) {
|
||||||
|
Request request = ctx.getRequest();
|
||||||
|
|
||||||
|
final SpanContext extractedContext =
|
||||||
|
GlobalTracer.get()
|
||||||
|
.extract(Format.Builtin.HTTP_HEADERS, new RatpackRequestExtractAdapter(request));
|
||||||
|
|
||||||
|
final Scope scope =
|
||||||
|
GlobalTracer.get()
|
||||||
|
.buildSpan("ratpack")
|
||||||
|
.asChildOf(extractedContext)
|
||||||
|
.withTag(Tags.COMPONENT.getKey(), "handler")
|
||||||
|
.withTag(Tags.SPAN_KIND.getKey(), Tags.SPAN_KIND_SERVER)
|
||||||
|
.withTag(DDTags.SPAN_TYPE, DDSpanTypes.WEB_SERVLET)
|
||||||
|
.withTag(Tags.HTTP_METHOD.getKey(), request.getMethod().getName())
|
||||||
|
.withTag(Tags.HTTP_URL.getKey(), request.getUri())
|
||||||
|
.startActive(true);
|
||||||
|
|
||||||
|
ctx.getResponse()
|
||||||
|
.beforeSend(
|
||||||
|
response -> {
|
||||||
|
Span span = scope.span();
|
||||||
|
Status status = response.getStatus();
|
||||||
|
if (status != null) {
|
||||||
|
// Should a 4xx be marked as an error?
|
||||||
|
if (status.is4xx() || status.is5xx()) {
|
||||||
|
Tags.ERROR.set(span, true);
|
||||||
|
}
|
||||||
|
Tags.HTTP_STATUS.set(span, status.getCode());
|
||||||
|
}
|
||||||
|
scope.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
ctx.next();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,138 @@
|
||||||
|
package datadog.trace.instrumentation.ratpack;
|
||||||
|
|
||||||
|
import io.opentracing.Scope;
|
||||||
|
import io.opentracing.Span;
|
||||||
|
import io.opentracing.Tracer;
|
||||||
|
import io.opentracing.propagation.Format;
|
||||||
|
import io.opentracing.tag.Tags;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
import javax.net.ssl.SSLContext;
|
||||||
|
import ratpack.func.Action;
|
||||||
|
import ratpack.func.Function;
|
||||||
|
import ratpack.http.HttpMethod;
|
||||||
|
import ratpack.http.MutableHeaders;
|
||||||
|
import ratpack.http.client.ReceivedResponse;
|
||||||
|
import ratpack.http.client.RequestSpec;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RequestSpec wrapper that captures the method type, sets up redirect handling and starts new spans
|
||||||
|
* when a method type is set.
|
||||||
|
*/
|
||||||
|
public final class WrappedRequestSpec implements RequestSpec {
|
||||||
|
|
||||||
|
private final RequestSpec delegate;
|
||||||
|
private final Tracer tracer;
|
||||||
|
private final Scope scope;
|
||||||
|
private final AtomicReference<Span> span;
|
||||||
|
|
||||||
|
WrappedRequestSpec(RequestSpec spec, Tracer tracer, Scope scope, AtomicReference<Span> span) {
|
||||||
|
this.delegate = spec;
|
||||||
|
this.tracer = tracer;
|
||||||
|
this.scope = scope;
|
||||||
|
this.span = span;
|
||||||
|
this.delegate.onRedirect(this::redirectHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Default redirect handler that ensures the span is marked as received before
|
||||||
|
* a new span is created.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
private Action<? super RequestSpec> redirectHandler(ReceivedResponse response) {
|
||||||
|
//handler.handleReceive(response.getStatusCode(), null, span.get());
|
||||||
|
return (s) -> new WrappedRequestSpec(s, tracer, scope, span);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RequestSpec redirects(int maxRedirects) {
|
||||||
|
this.delegate.redirects(maxRedirects);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RequestSpec onRedirect(
|
||||||
|
Function<? super ReceivedResponse, Action<? super RequestSpec>> function) {
|
||||||
|
|
||||||
|
Function<? super ReceivedResponse, Action<? super RequestSpec>> wrapped =
|
||||||
|
(ReceivedResponse response) -> redirectHandler(response).append(function.apply(response));
|
||||||
|
|
||||||
|
this.delegate.onRedirect(wrapped);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RequestSpec sslContext(SSLContext sslContext) {
|
||||||
|
this.delegate.sslContext(sslContext);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MutableHeaders getHeaders() {
|
||||||
|
return this.delegate.getHeaders();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RequestSpec maxContentLength(int numBytes) {
|
||||||
|
this.delegate.maxContentLength(numBytes);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RequestSpec headers(Action<? super MutableHeaders> action) throws Exception {
|
||||||
|
this.delegate.headers(action);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RequestSpec method(HttpMethod method) {
|
||||||
|
Span span =
|
||||||
|
tracer
|
||||||
|
.buildSpan("ratpack")
|
||||||
|
.asChildOf(scope.span())
|
||||||
|
.withTag(Tags.COMPONENT.getKey(), "httpclient")
|
||||||
|
.withTag(Tags.SPAN_KIND.getKey(), Tags.SPAN_KIND_CLIENT)
|
||||||
|
.withTag(Tags.HTTP_URL.getKey(), getUri().toString())
|
||||||
|
.withTag(Tags.HTTP_METHOD.getKey(), method.getName())
|
||||||
|
.start();
|
||||||
|
this.span.set(span);
|
||||||
|
this.delegate.method(method);
|
||||||
|
tracer.inject(span.context(), Format.Builtin.HTTP_HEADERS, new RequestSpecInjectAdapter(this));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RequestSpec decompressResponse(boolean shouldDecompress) {
|
||||||
|
this.delegate.decompressResponse(shouldDecompress);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public URI getUri() {
|
||||||
|
return this.delegate.getUri();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RequestSpec connectTimeout(Duration duration) {
|
||||||
|
this.delegate.connectTimeout(duration);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RequestSpec readTimeout(Duration duration) {
|
||||||
|
this.delegate.readTimeout(duration);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Body getBody() {
|
||||||
|
return this.delegate.getBody();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RequestSpec body(Action<? super Body> action) throws Exception {
|
||||||
|
this.delegate.body(action);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,279 @@
|
||||||
|
import datadog.opentracing.DDTracer
|
||||||
|
import datadog.opentracing.scopemanager.ContextualScopeManager
|
||||||
|
import datadog.trace.agent.test.AgentTestRunner
|
||||||
|
import datadog.trace.api.DDSpanTypes
|
||||||
|
import datadog.trace.common.writer.ListWriter
|
||||||
|
import datadog.trace.instrumentation.ratpack.RatpackScopeManager
|
||||||
|
import io.opentracing.Scope
|
||||||
|
import io.opentracing.util.GlobalTracer
|
||||||
|
import okhttp3.OkHttpClient
|
||||||
|
import okhttp3.Request
|
||||||
|
import ratpack.exec.Promise
|
||||||
|
import ratpack.exec.util.ParallelBatch
|
||||||
|
import ratpack.groovy.test.embed.GroovyEmbeddedApp
|
||||||
|
import ratpack.http.HttpUrlBuilder
|
||||||
|
import ratpack.http.client.HttpClient
|
||||||
|
import ratpack.test.exec.ExecHarness
|
||||||
|
|
||||||
|
import java.lang.reflect.Field
|
||||||
|
import java.lang.reflect.Modifier
|
||||||
|
|
||||||
|
class RatpackTest extends AgentTestRunner {
|
||||||
|
static {
|
||||||
|
System.setProperty("dd.integration.ratpack.enabled", "true")
|
||||||
|
}
|
||||||
|
OkHttpClient client = new OkHttpClient.Builder()
|
||||||
|
// Uncomment when debugging:
|
||||||
|
// .connectTimeout(1, TimeUnit.HOURS)
|
||||||
|
// .writeTimeout(1, TimeUnit.HOURS)
|
||||||
|
// .readTimeout(1, TimeUnit.HOURS)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
|
||||||
|
ListWriter writer = new ListWriter()
|
||||||
|
|
||||||
|
def setup() {
|
||||||
|
assert GlobalTracer.isRegistered()
|
||||||
|
setWriterOnGlobalTracer()
|
||||||
|
writer.start()
|
||||||
|
assert GlobalTracer.isRegistered()
|
||||||
|
}
|
||||||
|
|
||||||
|
def setWriterOnGlobalTracer() {
|
||||||
|
// this is not safe, reflection is used to modify a private final field
|
||||||
|
DDTracer existing = (DDTracer) GlobalTracer.get().tracer
|
||||||
|
final Field field = DDTracer.getDeclaredField("writer")
|
||||||
|
field.setAccessible(true)
|
||||||
|
Field modifiersField = Field.getDeclaredField("modifiers")
|
||||||
|
modifiersField.setAccessible(true)
|
||||||
|
modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL)
|
||||||
|
field.set(existing, writer)
|
||||||
|
}
|
||||||
|
|
||||||
|
def "test path call"() {
|
||||||
|
setup:
|
||||||
|
def app = GroovyEmbeddedApp.ratpack {
|
||||||
|
handlers {
|
||||||
|
get {
|
||||||
|
context.render("success")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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"
|
||||||
|
|
||||||
|
writer.size() == 2 // second (parent) trace is the okhttp call above...
|
||||||
|
def trace = writer.firstTrace()
|
||||||
|
trace.size() == 1
|
||||||
|
def span = trace[0]
|
||||||
|
|
||||||
|
span.context().serviceName == "unnamed-java-app"
|
||||||
|
span.context().operationName == "ratpack"
|
||||||
|
span.context().tags["component"] == "handler"
|
||||||
|
span.context().spanType == DDSpanTypes.WEB_SERVLET
|
||||||
|
!span.context().getErrorFlag()
|
||||||
|
span.context().tags["http.url"] == "/"
|
||||||
|
span.context().tags["http.method"] == "GET"
|
||||||
|
span.context().tags["span.kind"] == "server"
|
||||||
|
span.context().tags["http.status_code"] == 200
|
||||||
|
span.context().tags["thread.name"] != null
|
||||||
|
span.context().tags["thread.id"] != null
|
||||||
|
}
|
||||||
|
|
||||||
|
def "test error response"() {
|
||||||
|
setup:
|
||||||
|
def app = GroovyEmbeddedApp.ratpack {
|
||||||
|
handlers {
|
||||||
|
get {
|
||||||
|
context.clientError(404)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
def request = new Request.Builder()
|
||||||
|
.url(app.address.toURL())
|
||||||
|
.get()
|
||||||
|
.build()
|
||||||
|
when:
|
||||||
|
def resp = client.newCall(request).execute()
|
||||||
|
then:
|
||||||
|
resp.code() == 404
|
||||||
|
|
||||||
|
writer.size() == 2 // second (parent) trace is the okhttp call above...
|
||||||
|
def trace = writer.firstTrace()
|
||||||
|
trace.size() == 1
|
||||||
|
def span = trace[0]
|
||||||
|
|
||||||
|
span.context().getErrorFlag()
|
||||||
|
span.context().serviceName == "unnamed-java-app"
|
||||||
|
span.context().operationName == "ratpack"
|
||||||
|
span.context().tags["component"] == "handler"
|
||||||
|
span.context().spanType == DDSpanTypes.WEB_SERVLET
|
||||||
|
span.context().tags["http.url"] == "/"
|
||||||
|
span.context().tags["http.method"] == "GET"
|
||||||
|
span.context().tags["span.kind"] == "server"
|
||||||
|
span.context().tags["http.status_code"] == 404
|
||||||
|
span.context().tags["thread.name"] != null
|
||||||
|
span.context().tags["thread.id"] != null
|
||||||
|
}
|
||||||
|
|
||||||
|
def "test path call using ratpack http client"() {
|
||||||
|
/*
|
||||||
|
This test is somewhat convoluted and it raises some questions about how this is supposed to work
|
||||||
|
*/
|
||||||
|
setup:
|
||||||
|
|
||||||
|
def external = GroovyEmbeddedApp.ratpack {
|
||||||
|
handlers {
|
||||||
|
get("nested") {
|
||||||
|
context.render("succ")
|
||||||
|
}
|
||||||
|
get("nested2") {
|
||||||
|
context.render("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"
|
||||||
|
|
||||||
|
// fourth (parent) trace is the okhttp call above...,
|
||||||
|
// 3rd is the three traces, ratpack, http client 2 and http client 1 - I would have expected client 1 and then 2
|
||||||
|
// 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)
|
||||||
|
// I am not sure if this is correct
|
||||||
|
writer.size() == 4
|
||||||
|
def trace = writer.get(2)
|
||||||
|
trace.size() == 3
|
||||||
|
def span = trace[0]
|
||||||
|
|
||||||
|
span.context().serviceName == "unnamed-java-app"
|
||||||
|
span.context().operationName == "ratpack"
|
||||||
|
span.context().tags["component"] == "handler"
|
||||||
|
span.context().spanType == DDSpanTypes.WEB_SERVLET
|
||||||
|
!span.context().getErrorFlag()
|
||||||
|
span.context().tags["http.url"] == "/"
|
||||||
|
span.context().tags["http.method"] == "GET"
|
||||||
|
span.context().tags["span.kind"] == "server"
|
||||||
|
span.context().tags["http.status_code"] == 200
|
||||||
|
span.context().tags["thread.name"] != null
|
||||||
|
span.context().tags["thread.id"] != null
|
||||||
|
|
||||||
|
//def trace2 = writer.get(1)
|
||||||
|
//trace2.size() == 1
|
||||||
|
def clientTrace1 = trace[1] // this is in reverse order - should the 2nd http call occur before the first
|
||||||
|
|
||||||
|
clientTrace1.context().serviceName == "unnamed-java-app"
|
||||||
|
clientTrace1.context().operationName == "ratpack"
|
||||||
|
clientTrace1.context().tags["component"] == "httpclient"
|
||||||
|
!clientTrace1.context().getErrorFlag()
|
||||||
|
clientTrace1.context().tags["http.url"] == "${external.address}nested2"
|
||||||
|
clientTrace1.context().tags["http.method"] == "GET"
|
||||||
|
clientTrace1.context().tags["span.kind"] == "client"
|
||||||
|
clientTrace1.context().tags["http.status_code"] == 200
|
||||||
|
clientTrace1.context().tags["thread.name"] != null
|
||||||
|
clientTrace1.context().tags["thread.id"] != null
|
||||||
|
|
||||||
|
def clientTrace2 = trace[2]
|
||||||
|
|
||||||
|
clientTrace2.context().serviceName == "unnamed-java-app"
|
||||||
|
clientTrace2.context().operationName == "ratpack"
|
||||||
|
clientTrace2.context().tags["component"] == "httpclient"
|
||||||
|
!clientTrace2.context().getErrorFlag()
|
||||||
|
clientTrace2.context().tags["http.url"] == "${external.address}nested"
|
||||||
|
clientTrace2.context().tags["http.method"] == "GET"
|
||||||
|
clientTrace2.context().tags["span.kind"] == "client"
|
||||||
|
clientTrace2.context().tags["http.status_code"] == 200
|
||||||
|
clientTrace2.context().tags["thread.name"] != null
|
||||||
|
clientTrace2.context().tags["thread.id"] != null
|
||||||
|
|
||||||
|
def nestedTrace = writer.get(1)
|
||||||
|
nestedTrace.size() == 1
|
||||||
|
def nestedSpan = nestedTrace[0]
|
||||||
|
|
||||||
|
nestedSpan.context().serviceName == "unnamed-java-app"
|
||||||
|
nestedSpan.context().operationName == "ratpack"
|
||||||
|
nestedSpan.context().tags["component"] == "handler"
|
||||||
|
nestedSpan.context().spanType == DDSpanTypes.WEB_SERVLET
|
||||||
|
!nestedSpan.context().getErrorFlag()
|
||||||
|
nestedSpan.context().tags["http.url"] == "/nested2"
|
||||||
|
nestedSpan.context().tags["http.method"] == "GET"
|
||||||
|
nestedSpan.context().tags["span.kind"] == "server"
|
||||||
|
nestedSpan.context().tags["http.status_code"] == 200
|
||||||
|
nestedSpan.context().tags["thread.name"] != null
|
||||||
|
nestedSpan.context().tags["thread.id"] != null
|
||||||
|
|
||||||
|
def nestedTrace2 = writer.get(0)
|
||||||
|
nestedTrace2.size() == 1
|
||||||
|
def nestedSpan2 = nestedTrace2[0]
|
||||||
|
|
||||||
|
nestedSpan2.context().serviceName == "unnamed-java-app"
|
||||||
|
nestedSpan2.context().operationName == "ratpack"
|
||||||
|
nestedSpan2.context().tags["component"] == "handler"
|
||||||
|
nestedSpan2.context().spanType == DDSpanTypes.WEB_SERVLET
|
||||||
|
!nestedSpan2.context().getErrorFlag()
|
||||||
|
nestedSpan2.context().tags["http.url"] == "/nested"
|
||||||
|
nestedSpan2.context().tags["http.method"] == "GET"
|
||||||
|
nestedSpan2.context().tags["span.kind"] == "server"
|
||||||
|
nestedSpan2.context().tags["http.status_code"] == 200
|
||||||
|
nestedSpan2.context().tags["thread.name"] != null
|
||||||
|
nestedSpan2.context().tags["thread.id"] != null
|
||||||
|
}
|
||||||
|
|
||||||
|
def "forked executions inherit parent scope"() {
|
||||||
|
when:
|
||||||
|
def result = ExecHarness.yieldSingle({ spec ->
|
||||||
|
// This does the work of the initial instrumentation that occurs on the server registry. Because we are using
|
||||||
|
// ExecHarness for testing this does not get executed by the instrumentation
|
||||||
|
def ratpackScopeManager = new RatpackScopeManager()
|
||||||
|
spec.add(ratpackScopeManager)
|
||||||
|
((ContextualScopeManager) GlobalTracer.get().scopeManager())
|
||||||
|
.addScopeContext(ratpackScopeManager)
|
||||||
|
}, {
|
||||||
|
final Scope scope =
|
||||||
|
GlobalTracer.get()
|
||||||
|
.buildSpan("ratpack.exec-test")
|
||||||
|
.startActive(true)
|
||||||
|
scope.span().setBaggageItem("test-baggage", "foo")
|
||||||
|
ParallelBatch.of(testPromise(), testPromise()).yield()
|
||||||
|
})
|
||||||
|
|
||||||
|
then:
|
||||||
|
result.valueOrThrow == ["foo", "foo"]
|
||||||
|
}
|
||||||
|
|
||||||
|
Promise<String> testPromise() {
|
||||||
|
Promise.sync {
|
||||||
|
GlobalTracer.get().activeSpan().getBaggageItem("test-baggage")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -34,6 +34,7 @@ include ':dd-java-agent:instrumentation:play-2.4:play-2.6-testing'
|
||||||
include ':dd-java-agent:instrumentation:servlet-2'
|
include ':dd-java-agent:instrumentation:servlet-2'
|
||||||
include ':dd-java-agent:instrumentation:servlet-3'
|
include ':dd-java-agent:instrumentation:servlet-3'
|
||||||
include ':dd-java-agent:instrumentation:spring-web'
|
include ':dd-java-agent:instrumentation:spring-web'
|
||||||
|
include ':dd-java-agent:instrumentation:ratpack'
|
||||||
include ':dd-java-agent:instrumentation:trace-annotation'
|
include ':dd-java-agent:instrumentation:trace-annotation'
|
||||||
|
|
||||||
// benchmark
|
// benchmark
|
||||||
|
|
Loading…
Reference in New Issue