Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add agent instrumentation for Ratpack 1.7+ #12572

Merged
merged 10 commits into from
Nov 20, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -5,29 +5,13 @@

package io.opentelemetry.javaagent.instrumentation.ratpack;

import static io.opentelemetry.semconv.ServerAttributes.SERVER_ADDRESS;
import static io.opentelemetry.semconv.ServerAttributes.SERVER_PORT;

import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.instrumentation.ratpack.client.AbstractRatpackForkedHttpClientTest;
import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension;
import io.opentelemetry.instrumentation.testing.junit.http.HttpClientInstrumentationExtension;
import java.net.URI;
import java.util.HashSet;
import java.util.Set;
import org.junit.jupiter.api.extension.RegisterExtension;

class RatpackForkedHttpClientTest extends AbstractRatpackForkedHttpClientTest {

@RegisterExtension
static final InstrumentationExtension testing = HttpClientInstrumentationExtension.forAgent();

@Override
protected Set<AttributeKey<?>> computeHttpAttributes(URI uri) {
Set<AttributeKey<?>> attributes = new HashSet<>(super.computeHttpAttributes(uri));
// underlying netty instrumentation does not provide these
attributes.remove(SERVER_ADDRESS);
attributes.remove(SERVER_PORT);
return attributes;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,29 +5,13 @@

package io.opentelemetry.javaagent.instrumentation.ratpack;

import static io.opentelemetry.semconv.ServerAttributes.SERVER_ADDRESS;
import static io.opentelemetry.semconv.ServerAttributes.SERVER_PORT;

import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.instrumentation.ratpack.client.AbstractRatpackHttpClientTest;
import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension;
import io.opentelemetry.instrumentation.testing.junit.http.HttpClientInstrumentationExtension;
import java.net.URI;
import java.util.HashSet;
import java.util.Set;
import org.junit.jupiter.api.extension.RegisterExtension;

class RatpackHttpClientTest extends AbstractRatpackHttpClientTest {

@RegisterExtension
static final InstrumentationExtension testing = HttpClientInstrumentationExtension.forAgent();

@Override
protected Set<AttributeKey<?>> computeHttpAttributes(URI uri) {
Set<AttributeKey<?>> attributes = new HashSet<>(super.computeHttpAttributes(uri));
// underlying netty instrumentation does not provide these
attributes.remove(SERVER_ADDRESS);
attributes.remove(SERVER_PORT);
return attributes;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,29 +5,13 @@

package io.opentelemetry.javaagent.instrumentation.ratpack;

import static io.opentelemetry.semconv.ServerAttributes.SERVER_ADDRESS;
import static io.opentelemetry.semconv.ServerAttributes.SERVER_PORT;

import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.instrumentation.ratpack.client.AbstractRatpackPooledHttpClientTest;
import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension;
import io.opentelemetry.instrumentation.testing.junit.http.HttpClientInstrumentationExtension;
import java.net.URI;
import java.util.HashSet;
import java.util.Set;
import org.junit.jupiter.api.extension.RegisterExtension;

class RatpackPooledHttpClientTest extends AbstractRatpackPooledHttpClientTest {

@RegisterExtension
static final InstrumentationExtension testing = HttpClientInstrumentationExtension.forAgent();

@Override
protected Set<AttributeKey<?>> computeHttpAttributes(URI uri) {
Set<AttributeKey<?>> attributes = new HashSet<>(super.computeHttpAttributes(uri));
// underlying netty instrumentation does not provide these
attributes.remove(SERVER_ADDRESS);
attributes.remove(SERVER_PORT);
return attributes;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,20 @@

package io.opentelemetry.instrumentation.ratpack.client;

import static io.opentelemetry.semconv.ServerAttributes.SERVER_ADDRESS;
import static io.opentelemetry.semconv.ServerAttributes.SERVER_PORT;

import io.netty.channel.ConnectTimeoutException;
import io.netty.handler.timeout.ReadTimeoutException;
import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.instrumentation.testing.junit.http.AbstractHttpClientTest;
import io.opentelemetry.instrumentation.testing.junit.http.HttpClientResult;
import io.opentelemetry.instrumentation.testing.junit.http.HttpClientTestOptions;
import io.opentelemetry.semconv.NetworkAttributes;
import java.net.URI;
import java.time.Duration;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.junit.jupiter.api.AfterAll;
Expand All @@ -29,13 +34,13 @@

public abstract class AbstractRatpackHttpClientTest extends AbstractHttpClientTest<Void> {

private final ExecHarness exec = ExecHarness.harness();
protected final ExecHarness exec = ExecHarness.harness();

private HttpClient client;
private HttpClient singleConnectionClient;
protected HttpClient client;
protected HttpClient singleConnectionClient;

@BeforeAll
void setUpClient() throws Exception {
protected void setUpClient() throws Exception {
exec.run(
unused -> {
client = buildHttpClient();
Expand Down Expand Up @@ -66,7 +71,7 @@ public Void buildRequest(String method, URI uri, Map<String, String> headers) {
@Override
public int sendRequest(Void request, String method, URI uri, Map<String, String> headers)
throws Exception {
return exec.yield(unused -> internalSendRequest(client, method, uri, headers))
return exec.yield(execution -> internalSendRequest(client, method, uri, headers))
.getValueOrThrow();
}

Expand All @@ -78,13 +83,17 @@ public final void sendRequestWithCallback(
Map<String, String> headers,
HttpClientResult httpClientResult)
throws Exception {
exec.execute(
Operation.of(
() ->
internalSendRequest(client, method, uri, headers)
.result(
result ->
httpClientResult.complete(result::getValue, result.getThrowable()))));
exec.yield(
(e) ->
Operation.of(
() ->
internalSendRequest(client, method, uri, headers)
.result(
result ->
httpClientResult.complete(
result::getValue, result.getThrowable())))
.promise())
.getValueOrThrow();
}

// overridden in RatpackForkedHttpClientTest
Expand Down Expand Up @@ -118,32 +127,13 @@ protected void configure(HttpClientTestOptions.Builder optionsBuilder) {
.getValueOrThrow();
});

optionsBuilder.setExpectedClientSpanNameMapper(
(uri, method) -> {
switch (uri.toString()) {
case "http://localhost:61/": // unopened port
case "https://192.0.2.1/": // non routable address
return "CONNECT";
default:
return HttpClientTestOptions.DEFAULT_EXPECTED_CLIENT_SPAN_NAME_MAPPER.apply(
uri, method);
}
});
if (useNettyClientAttributes()) {
optionsBuilder.setExpectedClientSpanNameMapper(
AbstractRatpackHttpClientTest::nettyExpectedClientSpanNameMapper);
}

optionsBuilder.setClientSpanErrorMapper(
(uri, exception) -> {
if (uri.toString().equals("https://192.0.2.1/")) {
return new ConnectTimeoutException(
"connection timed out"
+ (Boolean.getBoolean("testLatestDeps") ? " after 2000 ms" : "")
+ ": /192.0.2.1:443");
} else if (OS.WINDOWS.isCurrentOs() && uri.toString().equals("http://localhost:61/")) {
return new ConnectTimeoutException("connection timed out: localhost/127.0.0.1:61");
} else if (uri.getPath().equals("/read-timeout")) {
return ReadTimeoutException.INSTANCE;
}
return exception;
});
AbstractRatpackHttpClientTest::nettyClientSpanErrorMapper);

optionsBuilder.setHttpAttributes(this::computeHttpAttributes);

Expand All @@ -160,7 +150,45 @@ protected Set<AttributeKey<?>> computeHttpAttributes(URI uri) {
case "https://192.0.2.1/": // non routable address
return Collections.emptySet();
default:
return HttpClientTestOptions.DEFAULT_HTTP_ATTRIBUTES;
HashSet<AttributeKey<?>> attributes =
new HashSet<>(HttpClientTestOptions.DEFAULT_HTTP_ATTRIBUTES);
if (useNettyClientAttributes()) {
// underlying netty instrumentation does not provide these
attributes.remove(SERVER_ADDRESS);
attributes.remove(SERVER_PORT);
} else {
// ratpack client instrumentation does not provide this
attributes.remove(NetworkAttributes.NETWORK_PROTOCOL_VERSION);
}
return attributes;
}
}

protected boolean useNettyClientAttributes() {
return true;
}

private static Throwable nettyClientSpanErrorMapper(URI uri, Throwable exception) {
if (uri.toString().equals("https://192.0.2.1/")) {
return new ConnectTimeoutException(
"connection timed out"
+ (Boolean.getBoolean("testLatestDeps") ? " after 2000 ms" : "")
+ ": /192.0.2.1:443");
} else if (OS.WINDOWS.isCurrentOs() && uri.toString().equals("http://localhost:61/")) {
return new ConnectTimeoutException("connection timed out: localhost/127.0.0.1:61");
} else if (uri.getPath().equals("/read-timeout")) {
return ReadTimeoutException.INSTANCE;
}
return exception;
}

private static String nettyExpectedClientSpanNameMapper(URI uri, String method) {
switch (uri.toString()) {
case "http://localhost:61/": // unopened port
case "https://192.0.2.1/": // non routable address
return "CONNECT";
default:
return HttpClientTestOptions.DEFAULT_EXPECTED_CLIENT_SPAN_NAME_MAPPER.apply(uri, method);
}
}
}
33 changes: 33 additions & 0 deletions instrumentation/ratpack/ratpack-1.7/javaagent/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
plugins {
id("otel.javaagent-instrumentation")
}

muzzle {
pass {
group.set("io.ratpack")
module.set("ratpack-core")
versions.set("[1.7.0,)")
}
fail {
group.set("io.ratpack")
module.set("ratpack-core")
versions.set("[1.0,1.7)")
}
}
johnrengelman marked this conversation as resolved.
Show resolved Hide resolved

dependencies {
library("io.ratpack:ratpack-core:1.7.0")

implementation(project(":instrumentation:netty:netty-4.1:library"))
implementation(project(":instrumentation:ratpack:ratpack-1.7:library"))

testImplementation(project(":instrumentation:ratpack:ratpack-1.4:testing"))
testInstrumentation(project(":instrumentation:ratpack:ratpack-1.4:javaagent"))
}

tasks {
withType<Test>().configureEach {
systemProperty("testLatestDeps", findProperty("testLatestDeps") as Boolean)
jvmArgs("-Dotel.instrumentation.common.experimental.controller-telemetry.enabled=true")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.ratpack.v1_7;

import static net.bytebuddy.matcher.ElementMatchers.isConstructor;
import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;

import com.google.common.collect.ImmutableList;
import io.opentelemetry.instrumentation.ratpack.v1_7.internal.OpenTelemetryExecInitializer;
import io.opentelemetry.instrumentation.ratpack.v1_7.internal.OpenTelemetryExecInterceptor;
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.asm.Advice.AssignReturned.ToArguments.ToArgument;
import net.bytebuddy.asm.Advice.AssignReturned.ToFields.ToField;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;
import ratpack.exec.ExecInitializer;
import ratpack.exec.ExecInterceptor;

public class DefaultExecControllerInstrumentation implements TypeInstrumentation {

@Override
public ElementMatcher<TypeDescription> typeMatcher() {
return named("ratpack.exec.internal.DefaultExecController");
}

@Override
public void transform(TypeTransformer transformer) {
transformer.applyAdviceToMethod(
named("setInitializers")
.and(takesArgument(0, named("com.google.common.collect.ImmutableList"))),
DefaultExecControllerInstrumentation.class.getName() + "$SetInitializersAdvice");

transformer.applyAdviceToMethod(
named("setInterceptors")
.and(takesArgument(0, named("com.google.common.collect.ImmutableList"))),
DefaultExecControllerInstrumentation.class.getName() + "$SetInterceptorsAdvice");

transformer.applyAdviceToMethod(
isConstructor(),
DefaultExecControllerInstrumentation.class.getName() + "$ConstructorAdvice");
}

@SuppressWarnings("unused")
public static class SetInitializersAdvice {
@Advice.OnMethodEnter(suppress = Throwable.class)
@Advice.AssignReturned.ToArguments(@ToArgument(0))
public static ImmutableList<? extends ExecInitializer> enter(
@Advice.Argument(0) ImmutableList<? extends ExecInitializer> initializers) {
return ImmutableList.<ExecInitializer>builder()
.addAll(initializers)
.add(OpenTelemetryExecInitializer.INSTANCE)
.build();
}
}

@SuppressWarnings("unused")
public static class SetInterceptorsAdvice {
@Advice.OnMethodEnter(suppress = Throwable.class)
@Advice.AssignReturned.ToArguments(@ToArgument(0))
public static ImmutableList<? extends ExecInterceptor> enter(
@Advice.Argument(0) ImmutableList<? extends ExecInterceptor> interceptors) {
return ImmutableList.<ExecInterceptor>builder()
.addAll(interceptors)
.add(OpenTelemetryExecInterceptor.INSTANCE)
.build();
}
}

@SuppressWarnings("unused")
public static class ConstructorAdvice {

@SuppressWarnings("UnusedVariable")
@Advice.OnMethodExit(suppress = Throwable.class)
@Advice.AssignReturned.ToFields({
@ToField(value = "initializers", index = 0),
@ToField(value = "interceptors", index = 1)
})
public static Object[] exit(
@Advice.FieldValue("initializers") ImmutableList<? extends ExecInitializer> initializers,
@Advice.FieldValue("interceptors") ImmutableList<? extends ExecInterceptor> interceptors) {
return new Object[] {
ImmutableList.of(OpenTelemetryExecInitializer.INSTANCE),
ImmutableList.of(OpenTelemetryExecInterceptor.INSTANCE)
};
}
}
}
Loading
Loading