Skip to content

Commit 04711ca

Browse files
committed
Add agent instrumentation for Ratpack 1.7
1 parent 3b0e7b8 commit 04711ca

File tree

21 files changed

+683
-24
lines changed

21 files changed

+683
-24
lines changed

instrumentation/ratpack/ratpack-1.4/testing/src/main/java/io/opentelemetry/instrumentation/ratpack/client/AbstractRatpackHttpClientTest.java

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,13 @@
2929

3030
public abstract class AbstractRatpackHttpClientTest extends AbstractHttpClientTest<Void> {
3131

32-
private final ExecHarness exec = ExecHarness.harness();
32+
protected final ExecHarness exec = ExecHarness.harness();
3333

34-
private HttpClient client;
35-
private HttpClient singleConnectionClient;
34+
protected HttpClient client;
35+
protected HttpClient singleConnectionClient;
3636

3737
@BeforeAll
38-
void setUpClient() throws Exception {
38+
protected void setUpClient() throws Exception {
3939
exec.run(
4040
unused -> {
4141
client = buildHttpClient();
@@ -66,7 +66,8 @@ public Void buildRequest(String method, URI uri, Map<String, String> headers) {
6666
@Override
6767
public int sendRequest(Void request, String method, URI uri, Map<String, String> headers)
6868
throws Exception {
69-
return exec.yield(unused -> internalSendRequest(client, method, uri, headers))
69+
return exec.yield(
70+
execution -> internalSendRequest(client, method, uri, headers))
7071
.getValueOrThrow();
7172
}
7273

@@ -78,13 +79,17 @@ public final void sendRequestWithCallback(
7879
Map<String, String> headers,
7980
HttpClientResult httpClientResult)
8081
throws Exception {
81-
exec.execute(
82-
Operation.of(
83-
() ->
84-
internalSendRequest(client, method, uri, headers)
85-
.result(
86-
result ->
87-
httpClientResult.complete(result::getValue, result.getThrowable()))));
82+
exec.yield(
83+
(e) ->
84+
Operation.of(
85+
() ->
86+
internalSendRequest(client, method, uri, headers)
87+
.result(
88+
result ->
89+
httpClientResult.complete(
90+
result::getValue, result.getThrowable())))
91+
.promise())
92+
.getValueOrThrow();
8893
}
8994

9095
// overridden in RatpackForkedHttpClientTest
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
plugins {
2+
id("otel.javaagent-instrumentation")
3+
}
4+
5+
muzzle {
6+
pass {
7+
group.set("io.ratpack")
8+
module.set("ratpack-core")
9+
versions.set("[1.7.0,)")
10+
}
11+
}
12+
13+
dependencies {
14+
library("io.ratpack:ratpack-core:1.7.0")
15+
16+
implementation(project(":instrumentation:netty:netty-4.1:library"))
17+
implementation(project(":instrumentation:ratpack:ratpack-1.4:javaagent"))
18+
implementation(project(":instrumentation:ratpack:ratpack-1.7:library"))
19+
20+
testImplementation(project(":instrumentation:ratpack:ratpack-1.4:testing"))
21+
22+
testLibrary("io.ratpack:ratpack-test:1.7.0")
23+
24+
if (JavaVersion.current().isCompatibleWith(JavaVersion.VERSION_11)) {
25+
testImplementation("com.sun.activation:jakarta.activation:1.2.2")
26+
}
27+
}
28+
29+
// to allow all tests to pass we need to choose a specific netty version
30+
if (!(findProperty("testLatestDeps") as Boolean)) {
31+
configurations.configureEach {
32+
if (!name.contains("muzzle")) {
33+
resolutionStrategy {
34+
eachDependency {
35+
// specifying a fixed version for all libraries with io.netty group
36+
if (requested.group == "io.netty" && requested.name != "netty-tcnative") {
37+
useVersion("4.1.37.Final")
38+
}
39+
}
40+
}
41+
}
42+
}
43+
}
44+
45+
tasks {
46+
withType<Test>().configureEach {
47+
systemProperty("testLatestDeps", findProperty("testLatestDeps") as Boolean)
48+
}
49+
}
50+
51+
tasks.withType<Test>().configureEach {
52+
jvmArgs("-Dotel.instrumentation.common.experimental.controller-telemetry.enabled=true")
53+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package io.opentelemetry.javaagent.instrumentation.ratpack.v1_7;
2+
3+
import io.opentelemetry.context.Context;
4+
import io.opentelemetry.context.Scope;
5+
import ratpack.exec.Downstream;
6+
7+
public class DownstreamWrapper<T> implements Downstream<T> {
8+
9+
private final Downstream<T> delegate;
10+
private final Context parentContext;
11+
12+
private DownstreamWrapper(Downstream<T> delegate, Context parentContext) {
13+
assert parentContext != null;
14+
this.delegate = delegate;
15+
this.parentContext = parentContext;
16+
}
17+
18+
@Override
19+
public void success(T value) {
20+
try (Scope ignored = parentContext.makeCurrent()) {
21+
delegate.success(value);
22+
}
23+
}
24+
25+
@Override
26+
public void error(Throwable throwable) {
27+
try (Scope ignored = parentContext.makeCurrent()) {
28+
delegate.error(throwable);
29+
}
30+
}
31+
32+
@Override
33+
public void complete() {
34+
try (Scope ignored = parentContext.makeCurrent()) {
35+
delegate.complete();
36+
}
37+
}
38+
39+
public static <T> Downstream<T> wrapIfNeeded(Downstream<T> delegate) {
40+
if (delegate instanceof DownstreamWrapper) {
41+
return delegate;
42+
}
43+
Context context = Context.current();
44+
if (context == Context.root()) {
45+
// Skip wrapping, there is no need to propagate root context.
46+
return delegate;
47+
}
48+
return new DownstreamWrapper<>(delegate, context);
49+
}
50+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package io.opentelemetry.javaagent.instrumentation.ratpack.v1_7;
2+
3+
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
4+
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
5+
import net.bytebuddy.asm.Advice;
6+
import net.bytebuddy.description.type.TypeDescription;
7+
import net.bytebuddy.matcher.ElementMatcher;
8+
import ratpack.http.client.HttpClient;
9+
10+
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
11+
import static net.bytebuddy.matcher.ElementMatchers.isStatic;
12+
import static net.bytebuddy.matcher.ElementMatchers.named;
13+
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
14+
15+
public class HttpClientInstrumentation implements TypeInstrumentation {
16+
17+
@Override
18+
public ElementMatcher<TypeDescription> typeMatcher() {
19+
return named("ratpack.http.client.HttpClient");
20+
}
21+
22+
@Override
23+
public void transform(TypeTransformer transformer) {
24+
transformer.applyAdviceToMethod(
25+
isMethod().and(isStatic())
26+
.and(named("of"))
27+
.and(takesArgument(0, named("ratpack.func.Action"))),
28+
HttpClientInstrumentation.class.getName() + "$OfAdvice");
29+
}
30+
31+
@SuppressWarnings("unused")
32+
public static class OfAdvice {
33+
34+
@Advice.OnMethodExit(suppress = Throwable.class)
35+
public static void injectTracing(@Advice.Return(readOnly = false) HttpClient httpClient) throws Exception {
36+
httpClient = RatpackSingletons.telemetry().instrumentHttpClient(httpClient);
37+
}
38+
}
39+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package io.opentelemetry.javaagent.instrumentation.ratpack.v1_7;
2+
3+
import com.google.auto.service.AutoService;
4+
import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule;
5+
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
6+
import io.opentelemetry.javaagent.extension.instrumentation.internal.ExperimentalInstrumentationModule;
7+
import net.bytebuddy.matcher.ElementMatcher;
8+
9+
import java.util.List;
10+
11+
import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed;
12+
import static java.util.Arrays.asList;
13+
14+
@AutoService(InstrumentationModule.class)
15+
public class RatpackInstrumentationModule extends InstrumentationModule
16+
implements ExperimentalInstrumentationModule {
17+
public RatpackInstrumentationModule() {
18+
super("ratpack", "ratpack-1.7");
19+
}
20+
21+
@Override
22+
public String getModuleGroup() {
23+
// relies on netty
24+
return "netty";
25+
}
26+
27+
@Override
28+
public ElementMatcher.Junction<ClassLoader> classLoaderMatcher() {
29+
// Only activate when running ratpack 1.7 or later
30+
return hasClassesNamed("ratpack.exec.util.retry.Delay");
31+
}
32+
33+
@Override
34+
public List<TypeInstrumentation> typeInstrumentations() {
35+
return asList(
36+
new ServerRegistryInstrumentation(),
37+
new HttpClientInstrumentation(),
38+
new RequestActionSupportInstrumentation()
39+
);
40+
41+
}
42+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package io.opentelemetry.javaagent.instrumentation.ratpack.v1_7;
2+
3+
import io.netty.channel.Channel;
4+
import io.opentelemetry.api.GlobalOpenTelemetry;
5+
import io.opentelemetry.context.Context;
6+
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
7+
import io.opentelemetry.instrumentation.netty.v4_1.internal.AttributeKeys;
8+
import io.opentelemetry.instrumentation.ratpack.v1_7.RatpackTelemetry;
9+
import io.opentelemetry.instrumentation.ratpack.v1_7.internal.ContextHolder;
10+
import io.opentelemetry.javaagent.bootstrap.internal.AgentCommonConfig;
11+
import io.opentelemetry.javaagent.bootstrap.internal.ExperimentalConfig;
12+
import ratpack.exec.Execution;
13+
14+
public final class RatpackSingletons {
15+
16+
static {
17+
TELEMETRY = RatpackTelemetry
18+
.builder(GlobalOpenTelemetry.get())
19+
.configure(AgentCommonConfig.get())
20+
.build();
21+
}
22+
23+
private static final Instrumenter<String, Void> INSTRUMENTER =
24+
Instrumenter.<String, Void>builder(
25+
GlobalOpenTelemetry.get(), "io.opentelemetry.ratpack-1.7", s -> s)
26+
.setEnabled(ExperimentalConfig.get().controllerTelemetryEnabled())
27+
.buildInstrumenter();
28+
29+
public static Instrumenter<String, Void> instrumenter() {
30+
return INSTRUMENTER;
31+
}
32+
33+
private static final RatpackTelemetry TELEMETRY;
34+
35+
public static RatpackTelemetry telemetry() {
36+
return TELEMETRY;
37+
}
38+
39+
public static void propagateContextToChannel(Execution execution, Channel channel) {
40+
Context parentContext = execution.maybeGet(ContextHolder.class).map(ContextHolder::context).orElse(Context.current());
41+
channel.attr(AttributeKeys.CLIENT_PARENT_CONTEXT).set(parentContext);
42+
}
43+
44+
private RatpackSingletons() {}
45+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
package io.opentelemetry.javaagent.instrumentation.ratpack.v1_7;
2+
3+
import io.netty.channel.Channel;
4+
import io.opentelemetry.context.Context;
5+
import io.opentelemetry.context.Scope;
6+
import io.opentelemetry.instrumentation.ratpack.v1_7.internal.ContextHolder;
7+
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
8+
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
9+
import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.extendsClass;
10+
11+
import net.bytebuddy.asm.Advice;
12+
import net.bytebuddy.description.type.TypeDescription;
13+
import net.bytebuddy.matcher.ElementMatcher;
14+
import ratpack.exec.Downstream;
15+
import ratpack.exec.Execution;
16+
17+
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
18+
import static net.bytebuddy.matcher.ElementMatchers.isPrivate;
19+
import static net.bytebuddy.matcher.ElementMatchers.named;
20+
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
21+
22+
public class RequestActionSupportInstrumentation implements TypeInstrumentation {
23+
24+
@Override
25+
public ElementMatcher<TypeDescription> typeMatcher() {
26+
return extendsClass(named("ratpack.http.client.internal.RequestActionSupport"));
27+
}
28+
29+
@Override
30+
public void transform(TypeTransformer transformer) {
31+
transformer.applyAdviceToMethod(
32+
isMethod().and(isPrivate()).and(named("send"))
33+
.and(takesArgument(0, named("ratpack.exec.Downstream")))
34+
.and(takesArgument(1, named("io.netty.channel.Channel"))),
35+
RequestActionSupportInstrumentation.class.getName() + "$SendAdvice");
36+
transformer.applyAdviceToMethod(
37+
isMethod().and(named("connect"))
38+
.and(takesArgument(0, named("ratpack.exec.Downstream"))),
39+
RequestActionSupportInstrumentation.class.getName() + "$ConnectAdvice");
40+
}
41+
42+
@SuppressWarnings("unused")
43+
public static class SendAdvice {
44+
45+
@Advice.OnMethodEnter(suppress = Throwable.class)
46+
public static void injectChannelAttribute(
47+
@Advice.FieldValue("execution") Execution execution,
48+
@Advice.Argument(value=0, readOnly = false) Downstream<?> downstream,
49+
@Advice.Argument(value=1, readOnly = false) Channel channel
50+
) {
51+
RatpackSingletons.propagateContextToChannel(execution, channel);
52+
}
53+
}
54+
55+
public static class ConnectAdvice {
56+
57+
@Advice.OnMethodEnter(suppress = Throwable.class)
58+
public static Scope injectChannelAttribute(
59+
@Advice.FieldValue("execution") Execution execution,
60+
@Advice.Argument(value=0, readOnly = false) Downstream<?> downstream
61+
) {
62+
// Propagate the current context to downstream
63+
// since that the is the subsequent
64+
downstream = DownstreamWrapper.wrapIfNeeded(downstream);
65+
66+
// Capture the CLIENT span and make it current before cally Netty layer
67+
return execution.maybeGet(ContextHolder.class)
68+
.map(ContextHolder::context)
69+
.map(Context::makeCurrent)
70+
.orElse(null);
71+
}
72+
73+
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
74+
public static void exit(@Advice.Enter Scope scope) {
75+
if (scope != null) {
76+
scope.close();
77+
}
78+
}
79+
}
80+
}

0 commit comments

Comments
 (0)