Skip to content

Commit f295b73

Browse files
authored
Merge pull request #276 from jonmort/ratpack-support
Ratpack instrumentation support
2 parents 3df3b3d + 35ad8c9 commit f295b73

12 files changed

Lines changed: 1073 additions & 0 deletions

File tree

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
apply plugin: 'version-scan'
2+
3+
versionScan {
4+
group = "io.ratpack"
5+
module = 'ratpack'
6+
versions = "[1.4.0,)"
7+
verifyPresent = [
8+
"ratpack.path.PathBinding": "getDescription",
9+
]
10+
}
11+
12+
apply from: "${rootDir}/gradle/java.gradle"
13+
14+
/*
15+
Here we introduce a sourceSet for the java 8 code which needs to be compiled with a source and target of 1.8
16+
The instrumentation classes must be compiled with java 7 and do nothing when ratpack is not on the classpath. The
17+
java 8 classes are used lazily so there is no direct linking between the 1.7 and 1.8 bytecode.
18+
*/
19+
sourceSets {
20+
main_java8 {
21+
java.srcDirs "${project.projectDir}/src/main/java8"
22+
}
23+
}
24+
25+
compileMain_java8Java {
26+
sourceCompatibility = 1.8
27+
targetCompatibility = 1.8
28+
}
29+
30+
dependencies {
31+
main_java8CompileOnly group: 'io.ratpack', name: 'ratpack-core', version: '1.4.0'
32+
33+
main_java8Compile project(':dd-trace-ot')
34+
main_java8Compile project(':dd-java-agent:agent-tooling')
35+
36+
main_java8Compile deps.bytebuddy
37+
main_java8Compile deps.opentracing
38+
main_java8Compile deps.autoservice
39+
40+
compileOnly sourceSets.main_java8.compileClasspath
41+
42+
compile sourceSets.main_java8.output
43+
44+
testCompile project(':dd-java-agent:testing')
45+
testCompile group: 'io.ratpack', name: 'ratpack-test', version: '1.4.0'
46+
47+
testCompile group: 'com.squareup.okhttp3', name: 'okhttp', version: '3.6.0'
48+
}
49+
50+
testJava8Only += '**/RatpackTest.class'
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package datadog.trace.instrumentation.ratpack;
2+
3+
import static net.bytebuddy.matcher.ElementMatchers.hasSuperType;
4+
import static net.bytebuddy.matcher.ElementMatchers.isInterface;
5+
import static net.bytebuddy.matcher.ElementMatchers.named;
6+
import static net.bytebuddy.matcher.ElementMatchers.not;
7+
import static net.bytebuddy.matcher.ElementMatchers.takesArguments;
8+
9+
import com.google.auto.service.AutoService;
10+
import datadog.trace.agent.tooling.DDAdvice;
11+
import datadog.trace.agent.tooling.HelperInjector;
12+
import datadog.trace.agent.tooling.Instrumenter;
13+
import datadog.trace.instrumentation.ratpack.impl.RatpackHttpClientAdvice;
14+
import java.net.URI;
15+
import net.bytebuddy.agent.builder.AgentBuilder;
16+
import net.bytebuddy.description.type.TypeDescription;
17+
18+
@AutoService(Instrumenter.class)
19+
public final class RatpackHttpClientInstrumentation extends Instrumenter.Configurable {
20+
21+
private static final HelperInjector HTTP_CLIENT_HELPER_INJECTOR =
22+
new HelperInjector(
23+
"datadog.trace.instrumentation.ratpack.impl.RatpackHttpClientAdvice$RatpackHttpClientRequestAdvice",
24+
"datadog.trace.instrumentation.ratpack.impl.RatpackHttpClientAdvice$RatpackHttpClientRequestStreamAdvice",
25+
"datadog.trace.instrumentation.ratpack.impl.RatpackHttpClientAdvice$RatpackHttpGetAdvice");
26+
public static final TypeDescription.ForLoadedType URI_TYPE_DESCRIPTION =
27+
new TypeDescription.ForLoadedType(URI.class);
28+
29+
public RatpackHttpClientInstrumentation() {
30+
super(RatpackInstrumentation.EXEC_NAME);
31+
}
32+
33+
@Override
34+
protected boolean defaultEnabled() {
35+
return false;
36+
}
37+
38+
@Override
39+
public AgentBuilder apply(final AgentBuilder agentBuilder) {
40+
41+
return agentBuilder
42+
.type(
43+
not(isInterface()).and(hasSuperType(named("ratpack.http.client.HttpClient"))),
44+
RatpackInstrumentation.CLASSLOADER_CONTAINS_RATPACK_1_4_OR_ABOVE)
45+
.transform(HTTP_CLIENT_HELPER_INJECTOR)
46+
.transform(
47+
DDAdvice.create()
48+
.advice(
49+
named("request")
50+
.and(
51+
takesArguments(
52+
URI_TYPE_DESCRIPTION,
53+
RatpackInstrumentation.ACTION_TYPE_DESCRIPTION)),
54+
RatpackHttpClientAdvice.RatpackHttpClientRequestAdvice.class.getName()))
55+
.transform(
56+
DDAdvice.create()
57+
.advice(
58+
named("requestStream")
59+
.and(
60+
takesArguments(
61+
URI_TYPE_DESCRIPTION,
62+
RatpackInstrumentation.ACTION_TYPE_DESCRIPTION)),
63+
RatpackHttpClientAdvice.RatpackHttpClientRequestStreamAdvice.class.getName()))
64+
.transform(
65+
DDAdvice.create()
66+
.advice(
67+
named("get")
68+
.and(
69+
takesArguments(
70+
URI_TYPE_DESCRIPTION,
71+
RatpackInstrumentation.ACTION_TYPE_DESCRIPTION)),
72+
RatpackHttpClientAdvice.RatpackHttpGetAdvice.class.getName()))
73+
.asDecorator();
74+
}
75+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
package datadog.trace.instrumentation.ratpack;
2+
3+
import static datadog.trace.agent.tooling.ClassLoaderMatcher.classLoaderHasClassWithMethod;
4+
import static net.bytebuddy.matcher.ElementMatchers.*;
5+
6+
import com.google.auto.service.AutoService;
7+
import datadog.trace.agent.tooling.DDAdvice;
8+
import datadog.trace.agent.tooling.HelperInjector;
9+
import datadog.trace.agent.tooling.Instrumenter;
10+
import datadog.trace.instrumentation.ratpack.impl.RatpackServerAdvice;
11+
import java.lang.reflect.Modifier;
12+
import lombok.extern.slf4j.Slf4j;
13+
import net.bytebuddy.agent.builder.AgentBuilder;
14+
import net.bytebuddy.description.type.TypeDescription;
15+
import net.bytebuddy.matcher.ElementMatcher;
16+
17+
@AutoService(Instrumenter.class)
18+
@Slf4j
19+
public final class RatpackInstrumentation extends Instrumenter.Configurable {
20+
21+
static final String EXEC_NAME = "ratpack";
22+
private static final HelperInjector SERVER_REGISTRY_HELPER_INJECTOR =
23+
new HelperInjector(
24+
"datadog.trace.instrumentation.ratpack.impl.RatpackScopeManager",
25+
"datadog.trace.instrumentation.ratpack.impl.TracingHandler",
26+
"datadog.trace.instrumentation.ratpack.impl.RatpackServerAdvice$RatpackServerRegistryAdvice");
27+
private static final HelperInjector EXEC_STARTER_HELPER_INJECTOR =
28+
new HelperInjector(
29+
"datadog.trace.instrumentation.ratpack.impl.RatpackServerAdvice$ExecStarterAdvice",
30+
"datadog.trace.instrumentation.ratpack.impl.RatpackServerAdvice$ExecStarterAction");
31+
32+
static final TypeDescription.Latent ACTION_TYPE_DESCRIPTION =
33+
new TypeDescription.Latent("ratpack.func.Action", Modifier.PUBLIC, null);
34+
35+
static final ElementMatcher.Junction.AbstractBase<ClassLoader>
36+
CLASSLOADER_CONTAINS_RATPACK_1_4_OR_ABOVE =
37+
classLoaderHasClassWithMethod("ratpack.path.PathBinding", "getDescription");
38+
39+
public RatpackInstrumentation() {
40+
super(EXEC_NAME);
41+
}
42+
43+
@Override
44+
protected boolean defaultEnabled() {
45+
return false;
46+
}
47+
48+
@Override
49+
public AgentBuilder apply(final AgentBuilder agentBuilder) {
50+
51+
return agentBuilder
52+
.type(
53+
named("ratpack.server.internal.ServerRegistry"),
54+
CLASSLOADER_CONTAINS_RATPACK_1_4_OR_ABOVE)
55+
.transform(SERVER_REGISTRY_HELPER_INJECTOR)
56+
.transform(
57+
DDAdvice.create()
58+
.advice(
59+
isMethod().and(isStatic()).and(named("buildBaseRegistry")),
60+
RatpackServerAdvice.RatpackServerRegistryAdvice.class.getName()))
61+
.asDecorator()
62+
.type(
63+
not(isInterface()).and(hasSuperType(named("ratpack.exec.ExecStarter"))),
64+
CLASSLOADER_CONTAINS_RATPACK_1_4_OR_ABOVE)
65+
.transform(EXEC_STARTER_HELPER_INJECTOR)
66+
.transform(
67+
DDAdvice.create()
68+
.advice(
69+
named("register").and(takesArguments(ACTION_TYPE_DESCRIPTION)),
70+
RatpackServerAdvice.ExecStarterAdvice.class.getName()))
71+
.asDecorator()
72+
.type(
73+
named("ratpack.exec.Execution")
74+
.or(not(isInterface()).and(hasSuperType(named("ratpack.exec.Execution")))),
75+
CLASSLOADER_CONTAINS_RATPACK_1_4_OR_ABOVE)
76+
.transform(EXEC_STARTER_HELPER_INJECTOR)
77+
.transform(
78+
DDAdvice.create()
79+
.advice(
80+
named("fork").and(returns(named("ratpack.exec.ExecStarter"))),
81+
RatpackServerAdvice.ExecutionAdvice.class.getName()))
82+
.asDecorator();
83+
}
84+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
package datadog.trace.instrumentation.ratpack.impl;
2+
3+
import static io.opentracing.log.Fields.ERROR_OBJECT;
4+
5+
import io.opentracing.Span;
6+
import io.opentracing.tag.Tags;
7+
import io.opentracing.util.GlobalTracer;
8+
import java.util.Collections;
9+
import java.util.concurrent.atomic.AtomicReference;
10+
import net.bytebuddy.asm.Advice;
11+
import ratpack.exec.Promise;
12+
import ratpack.exec.Result;
13+
import ratpack.func.Action;
14+
import ratpack.http.client.ReceivedResponse;
15+
import ratpack.http.client.RequestSpec;
16+
import ratpack.http.client.StreamedResponse;
17+
18+
public class RatpackHttpClientAdvice {
19+
public static class RequestAction implements Action<RequestSpec> {
20+
21+
private final Action<? super RequestSpec> requestAction;
22+
private final AtomicReference<Span> spanRef;
23+
24+
public RequestAction(Action<? super RequestSpec> requestAction, AtomicReference<Span> spanRef) {
25+
this.requestAction = requestAction;
26+
this.spanRef = spanRef;
27+
}
28+
29+
@Override
30+
public void execute(RequestSpec requestSpec) throws Exception {
31+
WrappedRequestSpec wrappedRequestSpec;
32+
if (requestSpec instanceof WrappedRequestSpec) {
33+
wrappedRequestSpec = (WrappedRequestSpec) requestSpec;
34+
} else {
35+
wrappedRequestSpec =
36+
new WrappedRequestSpec(
37+
requestSpec,
38+
GlobalTracer.get(),
39+
GlobalTracer.get().scopeManager().active(),
40+
spanRef);
41+
}
42+
requestAction.execute(wrappedRequestSpec);
43+
}
44+
}
45+
46+
public static class ResponseAction implements Action<Result<ReceivedResponse>> {
47+
private final AtomicReference<Span> spanRef;
48+
49+
public ResponseAction(AtomicReference<Span> spanRef) {
50+
this.spanRef = spanRef;
51+
}
52+
53+
@Override
54+
public void execute(Result<ReceivedResponse> result) {
55+
Span span = spanRef.get();
56+
if (span == null) {
57+
return;
58+
}
59+
span.finish();
60+
if (result.isError()) {
61+
Tags.ERROR.set(span, true);
62+
span.log(Collections.singletonMap(ERROR_OBJECT, result.getThrowable()));
63+
} else {
64+
Tags.HTTP_STATUS.set(span, result.getValue().getStatusCode());
65+
}
66+
}
67+
}
68+
69+
public static class StreamedResponseAction implements Action<Result<StreamedResponse>> {
70+
private final Span span;
71+
72+
public StreamedResponseAction(Span span) {
73+
this.span = span;
74+
}
75+
76+
@Override
77+
public void execute(Result<StreamedResponse> result) {
78+
span.finish();
79+
if (result.isError()) {
80+
Tags.ERROR.set(span, true);
81+
span.log(Collections.singletonMap(ERROR_OBJECT, result.getThrowable()));
82+
} else {
83+
Tags.HTTP_STATUS.set(span, result.getValue().getStatusCode());
84+
}
85+
}
86+
}
87+
88+
public static class RatpackHttpClientRequestAdvice {
89+
@Advice.OnMethodEnter
90+
public static AtomicReference<Span> injectTracing(
91+
@Advice.Argument(value = 1, readOnly = false) Action<? super RequestSpec> requestAction) {
92+
AtomicReference<Span> span = new AtomicReference<>();
93+
94+
//noinspection UnusedAssignment
95+
requestAction = new RequestAction(requestAction, span);
96+
97+
return span;
98+
}
99+
100+
@Advice.OnMethodExit
101+
public static void finishTracing(
102+
@Advice.Return(readOnly = false) Promise<ReceivedResponse> promise,
103+
@Advice.Enter AtomicReference<Span> ref) {
104+
105+
//noinspection UnusedAssignment
106+
promise = promise.wiretap(new ResponseAction(ref));
107+
}
108+
}
109+
110+
public static class RatpackHttpClientRequestStreamAdvice {
111+
@Advice.OnMethodEnter
112+
public static AtomicReference<Span> injectTracing(
113+
@Advice.Argument(value = 1, readOnly = false) Action<? super RequestSpec> requestAction) {
114+
AtomicReference<Span> span = new AtomicReference<>();
115+
116+
//noinspection UnusedAssignment
117+
requestAction = new RequestAction(requestAction, span);
118+
119+
return span;
120+
}
121+
122+
@Advice.OnMethodExit
123+
public static void finishTracing(
124+
@Advice.Return(readOnly = false) Promise<StreamedResponse> promise,
125+
@Advice.Enter AtomicReference<Span> ref) {
126+
Span span = ref.get();
127+
if (span == null) {
128+
return;
129+
}
130+
131+
//noinspection UnusedAssignment
132+
promise = promise.wiretap(new StreamedResponseAction(span));
133+
}
134+
}
135+
136+
public static class RatpackHttpGetAdvice {
137+
@Advice.OnMethodEnter
138+
public static void ensureGetMethodSet(
139+
@Advice.Argument(value = 1, readOnly = false) Action<? super RequestSpec> requestAction) {
140+
//noinspection UnusedAssignment
141+
requestAction = requestAction.prepend(RequestSpec::get);
142+
}
143+
}
144+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package datadog.trace.instrumentation.ratpack.impl;
2+
3+
import com.google.common.collect.ListMultimap;
4+
import io.opentracing.propagation.TextMap;
5+
import java.util.Iterator;
6+
import java.util.Map;
7+
import ratpack.http.Request;
8+
9+
/**
10+
* Simple request extractor in the same vein as @see
11+
* io.opentracing.contrib.web.servlet.filter.HttpServletRequestExtractAdapter
12+
*/
13+
public class RatpackRequestExtractAdapter implements TextMap {
14+
private final ListMultimap<String, String> headers;
15+
16+
RatpackRequestExtractAdapter(Request request) {
17+
this.headers = request.getHeaders().asMultiValueMap().asMultimap();
18+
}
19+
20+
@Override
21+
public Iterator<Map.Entry<String, String>> iterator() {
22+
return headers.entries().iterator();
23+
}
24+
25+
@Override
26+
public void put(String key, String value) {
27+
throw new UnsupportedOperationException("This class should be used only with Tracer.inject()!");
28+
}
29+
}

0 commit comments

Comments
 (0)