Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions dd-java-agent/instrumentation/ratpack-1.4/ratpack-1.4.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
apply plugin: 'version-scan'

versionScan {
group = "io.ratpack"
module = 'ratpack'
versions = "[1.4.0,)"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm ok restricting it to 1.4 as long as it doesn't try to inadvertently instrument any unsupported classes. We ensure this by including the below verifyPresent classes in the classLoaderHasClasses list in the instrumentation.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So this is where it gets complex! The reason 1.3 isn't supported is because the interfaces have different methods, they are the same classes but method signatures changed. Specifically it is the RequestSpec that has the changes. I could rely on a class that is only in 1.4 to force this, but one that isn't instrumented

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that is ok. The classes in that list aren't the ones we're explicitly instrumenting. They're intended as a "fingerprint" to identify the supported versions.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've sorted out the fingerprint now. It is pretty much just that method on that class that identifies 1.4 from 1.3

verifyPresent = [
"ratpack.path.PathBinding": "getDescription",
]
}

apply from: "${rootDir}/gradle/java.gradle"

/*
Here we introduce a sourceSet for the java 8 code which needs to be compiled with a source and target of 1.8
The instrumentation classes must be compiled with java 7 and do nothing when ratpack is not on the classpath. The
java 8 classes are used lazily so there is no direct linking between the 1.7 and 1.8 bytecode.
*/
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the nice comment.

sourceSets {
main_java8 {
java.srcDirs "${project.projectDir}/src/main/java8"
}
}

compileMain_java8Java {
sourceCompatibility = 1.8
targetCompatibility = 1.8
}

dependencies {
main_java8CompileOnly group: 'io.ratpack', name: 'ratpack-core', version: '1.4.0'

main_java8Compile project(':dd-trace-ot')
main_java8Compile project(':dd-java-agent:agent-tooling')

main_java8Compile deps.bytebuddy
main_java8Compile deps.opentracing
main_java8Compile deps.autoservice

compileOnly sourceSets.main_java8.compileClasspath

compile sourceSets.main_java8.output

testCompile project(':dd-java-agent:testing')
testCompile group: 'io.ratpack', name: 'ratpack-test', version: '1.4.0'

testCompile group: 'com.squareup.okhttp3', name: 'okhttp', version: '3.6.0'
}

testJava8Only += '**/RatpackTest.class'
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package datadog.trace.instrumentation.ratpack;

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 datadog.trace.instrumentation.ratpack.impl.RatpackHttpClientAdvice;
import java.net.URI;
import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.description.type.TypeDescription;

@AutoService(Instrumenter.class)
public final class RatpackHttpClientInstrumentation extends Instrumenter.Configurable {

private static final HelperInjector HTTP_CLIENT_HELPER_INJECTOR =
new HelperInjector(
"datadog.trace.instrumentation.ratpack.impl.RatpackHttpClientAdvice$RatpackHttpClientRequestAdvice",
"datadog.trace.instrumentation.ratpack.impl.RatpackHttpClientAdvice$RatpackHttpClientRequestStreamAdvice",
"datadog.trace.instrumentation.ratpack.impl.RatpackHttpClientAdvice$RatpackHttpGetAdvice");
public static final TypeDescription.ForLoadedType URI_TYPE_DESCRIPTION =
new TypeDescription.ForLoadedType(URI.class);

public RatpackHttpClientInstrumentation() {
super(RatpackInstrumentation.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"))),
RatpackInstrumentation.CLASSLOADER_CONTAINS_RATPACK_1_4_OR_ABOVE)
.transform(HTTP_CLIENT_HELPER_INJECTOR)
.transform(
DDAdvice.create()
.advice(
named("request")
.and(
takesArguments(
URI_TYPE_DESCRIPTION,
RatpackInstrumentation.ACTION_TYPE_DESCRIPTION)),
RatpackHttpClientAdvice.RatpackHttpClientRequestAdvice.class.getName()))
.transform(
DDAdvice.create()
.advice(
named("requestStream")
.and(
takesArguments(
URI_TYPE_DESCRIPTION,
RatpackInstrumentation.ACTION_TYPE_DESCRIPTION)),
RatpackHttpClientAdvice.RatpackHttpClientRequestStreamAdvice.class.getName()))
.transform(
DDAdvice.create()
.advice(
named("get")
.and(
takesArguments(
URI_TYPE_DESCRIPTION,
RatpackInstrumentation.ACTION_TYPE_DESCRIPTION)),
RatpackHttpClientAdvice.RatpackHttpGetAdvice.class.getName()))
.asDecorator();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package datadog.trace.instrumentation.ratpack;

import static datadog.trace.agent.tooling.ClassLoaderMatcher.classLoaderHasClassWithMethod;
import static net.bytebuddy.matcher.ElementMatchers.*;

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 datadog.trace.instrumentation.ratpack.impl.RatpackServerAdvice;
import java.lang.reflect.Modifier;
import lombok.extern.slf4j.Slf4j;
import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;

@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.impl.RatpackScopeManager",
"datadog.trace.instrumentation.ratpack.impl.TracingHandler",
"datadog.trace.instrumentation.ratpack.impl.RatpackServerAdvice$RatpackServerRegistryAdvice");
private static final HelperInjector EXEC_STARTER_HELPER_INJECTOR =
new HelperInjector(
"datadog.trace.instrumentation.ratpack.impl.RatpackServerAdvice$ExecStarterAdvice",
"datadog.trace.instrumentation.ratpack.impl.RatpackServerAdvice$ExecStarterAction");

static final TypeDescription.Latent ACTION_TYPE_DESCRIPTION =
new TypeDescription.Latent("ratpack.func.Action", Modifier.PUBLIC, null);

static final ElementMatcher.Junction.AbstractBase<ClassLoader>
CLASSLOADER_CONTAINS_RATPACK_1_4_OR_ABOVE =
classLoaderHasClassWithMethod("ratpack.path.PathBinding", "getDescription");

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"),
CLASSLOADER_CONTAINS_RATPACK_1_4_OR_ABOVE)
.transform(SERVER_REGISTRY_HELPER_INJECTOR)
.transform(
DDAdvice.create()
.advice(
isMethod().and(isStatic()).and(named("buildBaseRegistry")),
RatpackServerAdvice.RatpackServerRegistryAdvice.class.getName()))
.asDecorator()
.type(
not(isInterface()).and(hasSuperType(named("ratpack.exec.ExecStarter"))),
CLASSLOADER_CONTAINS_RATPACK_1_4_OR_ABOVE)
.transform(EXEC_STARTER_HELPER_INJECTOR)
.transform(
DDAdvice.create()
.advice(
named("register").and(takesArguments(ACTION_TYPE_DESCRIPTION)),
RatpackServerAdvice.ExecStarterAdvice.class.getName()))
.asDecorator()
.type(
named("ratpack.exec.Execution")
.or(not(isInterface()).and(hasSuperType(named("ratpack.exec.Execution")))),
CLASSLOADER_CONTAINS_RATPACK_1_4_OR_ABOVE)
.transform(EXEC_STARTER_HELPER_INJECTOR)
.transform(
DDAdvice.create()
.advice(
named("fork").and(returns(named("ratpack.exec.ExecStarter"))),
RatpackServerAdvice.ExecutionAdvice.class.getName()))
.asDecorator();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
package datadog.trace.instrumentation.ratpack.impl;

import static io.opentracing.log.Fields.ERROR_OBJECT;

import io.opentracing.Span;
import io.opentracing.tag.Tags;
import io.opentracing.util.GlobalTracer;
import java.util.Collections;
import java.util.concurrent.atomic.AtomicReference;
import net.bytebuddy.asm.Advice;
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;

public class RatpackHttpClientAdvice {
public static class RequestAction implements Action<RequestSpec> {

private final Action<? super RequestSpec> requestAction;
private final AtomicReference<Span> spanRef;

public RequestAction(Action<? super RequestSpec> requestAction, AtomicReference<Span> spanRef) {
this.requestAction = requestAction;
this.spanRef = spanRef;
}

@Override
public void execute(RequestSpec requestSpec) throws Exception {
WrappedRequestSpec wrappedRequestSpec;
if (requestSpec instanceof WrappedRequestSpec) {
wrappedRequestSpec = (WrappedRequestSpec) requestSpec;
} else {
wrappedRequestSpec =
new WrappedRequestSpec(
requestSpec,
GlobalTracer.get(),
GlobalTracer.get().scopeManager().active(),
spanRef);
}
requestAction.execute(wrappedRequestSpec);
}
}

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(Collections.singletonMap(ERROR_OBJECT, 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(Collections.singletonMap(ERROR_OBJECT, 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);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package datadog.trace.instrumentation.ratpack.impl;

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