diff --git a/instrumentation/vertx-http-client-3.0/javaagent/build.gradle.kts b/instrumentation/vertx-http-client/vertx-http-client-3.0/javaagent/build.gradle.kts similarity index 89% rename from instrumentation/vertx-http-client-3.0/javaagent/build.gradle.kts rename to instrumentation/vertx-http-client/vertx-http-client-3.0/javaagent/build.gradle.kts index 3c58305d3887..b3f4de95a354 100644 --- a/instrumentation/vertx-http-client-3.0/javaagent/build.gradle.kts +++ b/instrumentation/vertx-http-client/vertx-http-client-3.0/javaagent/build.gradle.kts @@ -14,10 +14,11 @@ muzzle { dependencies { library("io.vertx:vertx-core:3.0.0") + implementation(project(":instrumentation:vertx-http-client:vertx-http-client-common:javaagent")) + //We need both version as different versions of Vert.x use different versions of Netty testInstrumentation(project(":instrumentation:netty:netty-4.0:javaagent")) testInstrumentation(project(":instrumentation:netty:netty-4.1:javaagent")) - testInstrumentation(project(":instrumentation:jdbc:javaagent")) // Vert.x 4.0 is incompatible with our tests. // 3.9.7 Requires Netty 4.1.60, no other version works with it. @@ -29,4 +30,4 @@ tasks { named("test") { systemProperty("testLatestDeps", findProperty("testLatestDeps")) } -} +} \ No newline at end of file diff --git a/instrumentation/vertx-http-client-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/client/HttpRequestInstrumentation.java b/instrumentation/vertx-http-client/vertx-http-client-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/v3_0/client/HttpRequestInstrumentation.java similarity index 94% rename from instrumentation/vertx-http-client-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/client/HttpRequestInstrumentation.java rename to instrumentation/vertx-http-client/vertx-http-client-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/v3_0/client/HttpRequestInstrumentation.java index 98a21f020314..52d1f743eff1 100644 --- a/instrumentation/vertx-http-client-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/client/HttpRequestInstrumentation.java +++ b/instrumentation/vertx-http-client/vertx-http-client-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/v3_0/client/HttpRequestInstrumentation.java @@ -3,11 +3,11 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.javaagent.instrumentation.vertx.client; +package io.opentelemetry.javaagent.instrumentation.vertx.v3_0.client; import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed; import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.implementsInterface; -import static io.opentelemetry.javaagent.instrumentation.vertx.client.VertxClientTracer.tracer; +import static io.opentelemetry.javaagent.instrumentation.vertx.v3_0.client.VertxClientTracer.tracer; import static net.bytebuddy.matcher.ElementMatchers.isMethod; import static net.bytebuddy.matcher.ElementMatchers.isPrivate; import static net.bytebuddy.matcher.ElementMatchers.nameStartsWith; @@ -21,6 +21,8 @@ import io.opentelemetry.javaagent.instrumentation.api.ContextStore; import io.opentelemetry.javaagent.instrumentation.api.InstrumentationContext; import io.opentelemetry.javaagent.instrumentation.api.Java8BytecodeBridge; +import io.opentelemetry.javaagent.instrumentation.vertx.client.Contexts; +import io.opentelemetry.javaagent.instrumentation.vertx.client.ExceptionHandlerWrapper; import io.vertx.core.Handler; import io.vertx.core.http.HttpClientRequest; import io.vertx.core.http.HttpClientResponse; @@ -205,7 +207,7 @@ public static void wrapExceptionHandler( if (handler != null) { ContextStore contextStore = InstrumentationContext.get(HttpClientRequest.class, Contexts.class); - handler = new ExceptionHandlerWrapper(request, contextStore, handler); + handler = new ExceptionHandlerWrapper(tracer(), request, contextStore, handler); } } } diff --git a/instrumentation/vertx-http-client-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/client/VertxClientInstrumentationModule.java b/instrumentation/vertx-http-client/vertx-http-client-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/v3_0/client/VertxClientInstrumentationModule.java similarity index 61% rename from instrumentation/vertx-http-client-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/client/VertxClientInstrumentationModule.java rename to instrumentation/vertx-http-client/vertx-http-client-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/v3_0/client/VertxClientInstrumentationModule.java index f3c73f82a805..ff80cb603b41 100644 --- a/instrumentation/vertx-http-client-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/client/VertxClientInstrumentationModule.java +++ b/instrumentation/vertx-http-client/vertx-http-client-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/v3_0/client/VertxClientInstrumentationModule.java @@ -3,20 +3,28 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.javaagent.instrumentation.vertx.client; +package io.opentelemetry.javaagent.instrumentation.vertx.v3_0.client; +import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed; import static java.util.Collections.singletonList; import com.google.auto.service.AutoService; import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule; import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; import java.util.List; +import net.bytebuddy.matcher.ElementMatcher; @AutoService(InstrumentationModule.class) public class VertxClientInstrumentationModule extends InstrumentationModule { public VertxClientInstrumentationModule() { - super("vertx-client", "vertx"); + super("vertx-client", "vertx-client-3.0", "vertx"); + } + + @Override + public ElementMatcher.Junction classLoaderMatcher() { + // class removed in 4.0 + return hasClassesNamed("io.vertx.core.Starter"); } @Override diff --git a/instrumentation/vertx-http-client/vertx-http-client-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/v3_0/client/VertxClientTracer.java b/instrumentation/vertx-http-client/vertx-http-client-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/v3_0/client/VertxClientTracer.java new file mode 100644 index 000000000000..d217fa63ac22 --- /dev/null +++ b/instrumentation/vertx-http-client/vertx-http-client-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/v3_0/client/VertxClientTracer.java @@ -0,0 +1,36 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.vertx.v3_0.client; + +import io.opentelemetry.javaagent.instrumentation.vertx.client.AbstractVertxClientTracer; +import io.vertx.core.http.HttpClientRequest; +import java.net.URI; +import java.net.URISyntaxException; +import org.checkerframework.checker.nullness.qual.Nullable; + +public class VertxClientTracer extends AbstractVertxClientTracer { + private static final VertxClientTracer TRACER = new VertxClientTracer(); + + public static VertxClientTracer tracer() { + return TRACER; + } + + @Override + protected String getInstrumentationName() { + return "io.opentelemetry.vertx-http-client-3.0"; + } + + @Override + protected String method(HttpClientRequest request) { + return request.method().name(); + } + + @Override + @Nullable + protected URI url(HttpClientRequest request) throws URISyntaxException { + return new URI(request.uri()); + } +} diff --git a/instrumentation/vertx-http-client-3.0/javaagent/src/test/groovy/client/VertxHttpClientTest.groovy b/instrumentation/vertx-http-client/vertx-http-client-3.0/javaagent/src/test/groovy/client/VertxHttpClientTest.groovy similarity index 100% rename from instrumentation/vertx-http-client-3.0/javaagent/src/test/groovy/client/VertxHttpClientTest.groovy rename to instrumentation/vertx-http-client/vertx-http-client-3.0/javaagent/src/test/groovy/client/VertxHttpClientTest.groovy diff --git a/instrumentation/vertx-http-client-3.0/javaagent/src/test/groovy/client/VertxSingleConnection.java b/instrumentation/vertx-http-client/vertx-http-client-3.0/javaagent/src/test/java/client/VertxSingleConnection.java similarity index 100% rename from instrumentation/vertx-http-client-3.0/javaagent/src/test/groovy/client/VertxSingleConnection.java rename to instrumentation/vertx-http-client/vertx-http-client-3.0/javaagent/src/test/java/client/VertxSingleConnection.java diff --git a/instrumentation/vertx-http-client/vertx-http-client-4.0/javaagent/build.gradle.kts b/instrumentation/vertx-http-client/vertx-http-client-4.0/javaagent/build.gradle.kts new file mode 100644 index 000000000000..699c8426ed95 --- /dev/null +++ b/instrumentation/vertx-http-client/vertx-http-client-4.0/javaagent/build.gradle.kts @@ -0,0 +1,20 @@ +plugins { + id("otel.javaagent-instrumentation") +} + +muzzle { + pass { + group.set("io.vertx") + module.set("vertx-core") + versions.set("[4.0.0,)") + assertInverse.set(true) + } +} + +dependencies { + library("io.vertx:vertx-core:4.0.0") + + implementation(project(":instrumentation:vertx-http-client:vertx-http-client-common:javaagent")) + + testInstrumentation(project(":instrumentation:netty:netty-4.1:javaagent")) +} \ No newline at end of file diff --git a/instrumentation/vertx-http-client/vertx-http-client-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/v4_0/client/ConnectionManagerInstrumentation.java b/instrumentation/vertx-http-client/vertx-http-client-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/v4_0/client/ConnectionManagerInstrumentation.java new file mode 100644 index 000000000000..b92954a2e882 --- /dev/null +++ b/instrumentation/vertx-http-client/vertx-http-client-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/v4_0/client/ConnectionManagerInstrumentation.java @@ -0,0 +1,54 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.vertx.v4_0.client; + +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.takesArgument; + +import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; +import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; +import io.vertx.core.Handler; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; + +/** Propagate context to connection established callback. */ +public class ConnectionManagerInstrumentation implements TypeInstrumentation { + + @Override + public ElementMatcher typeMatcher() { + return named("io.vertx.core.net.impl.clientconnection.ConnectionManager") // 4.0.0 + .or(named("io.vertx.core.net.impl.pool.ConnectionManager")); // 4.1.0 + } + + @Override + public void transform(TypeTransformer transformer) { + transformer.applyAdviceToMethod( + named("getConnection").and(takesArgument(2, named("io.vertx.core.Handler"))), + ConnectionManagerInstrumentation.class.getName() + "$GetConnectionArg2Advice"); + transformer.applyAdviceToMethod( + named("getConnection").and(takesArgument(3, named("io.vertx.core.Handler"))), + ConnectionManagerInstrumentation.class.getName() + "$GetConnectionArg3Advice"); + } + + @SuppressWarnings("unused") + public static class GetConnectionArg2Advice { + @Advice.OnMethodEnter(suppress = Throwable.class) + public static void wrapHandler( + @Advice.Argument(value = 2, readOnly = false) Handler handler) { + handler = HandlerWrapper.wrap(handler); + } + } + + @SuppressWarnings("unused") + public static class GetConnectionArg3Advice { + @Advice.OnMethodEnter(suppress = Throwable.class) + public static void wrapHandler( + @Advice.Argument(value = 3, readOnly = false) Handler handler) { + handler = HandlerWrapper.wrap(handler); + } + } +} diff --git a/instrumentation/vertx-http-client/vertx-http-client-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/v4_0/client/HandlerWrapper.java b/instrumentation/vertx-http-client/vertx-http-client-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/v4_0/client/HandlerWrapper.java new file mode 100644 index 000000000000..a2938d0e8e68 --- /dev/null +++ b/instrumentation/vertx-http-client/vertx-http-client-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/v4_0/client/HandlerWrapper.java @@ -0,0 +1,35 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.vertx.v4_0.client; + +import io.opentelemetry.context.Context; +import io.opentelemetry.context.Scope; +import io.vertx.core.Handler; + +public class HandlerWrapper implements Handler { + private final Handler delegate; + private final Context context; + + private HandlerWrapper(Handler delegate, Context context) { + this.delegate = delegate; + this.context = context; + } + + public static Handler wrap(Handler handler) { + Context current = Context.current(); + if (handler != null && !(handler instanceof HandlerWrapper) && current != Context.root()) { + handler = new HandlerWrapper<>(handler, current); + } + return handler; + } + + @Override + public void handle(T t) { + try (Scope ignore = context.makeCurrent()) { + delegate.handle(t); + } + } +} diff --git a/instrumentation/vertx-http-client/vertx-http-client-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/v4_0/client/HttpClientConnectionInstrumentation.java b/instrumentation/vertx-http-client/vertx-http-client-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/v4_0/client/HttpClientConnectionInstrumentation.java new file mode 100644 index 000000000000..b310f747f38d --- /dev/null +++ b/instrumentation/vertx-http-client/vertx-http-client-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/v4_0/client/HttpClientConnectionInstrumentation.java @@ -0,0 +1,48 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.vertx.v4_0.client; + +import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed; +import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.implementsInterface; +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.takesArgument; + +import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; +import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; +import io.vertx.core.Handler; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; + +/** Propagate context to stream opened callback. */ +public class HttpClientConnectionInstrumentation implements TypeInstrumentation { + + @Override + public ElementMatcher typeMatcher() { + return implementsInterface(named("io.vertx.core.http.impl.HttpClientConnection")); + } + + @Override + public ElementMatcher classLoaderOptimization() { + return hasClassesNamed("io.vertx.core.http.impl.HttpClientConnection"); + } + + @Override + public void transform(TypeTransformer transformer) { + transformer.applyAdviceToMethod( + named("createStream").and(takesArgument(1, named("io.vertx.core.Handler"))), + HttpClientConnectionInstrumentation.class.getName() + "$CreateStreamAdvice"); + } + + @SuppressWarnings("unused") + public static class CreateStreamAdvice { + @Advice.OnMethodEnter(suppress = Throwable.class) + public static void wrapHandler( + @Advice.Argument(value = 1, readOnly = false) Handler handler) { + handler = HandlerWrapper.wrap(handler); + } + } +} diff --git a/instrumentation/vertx-http-client/vertx-http-client-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/v4_0/client/HttpRequestInstrumentation.java b/instrumentation/vertx-http-client/vertx-http-client-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/v4_0/client/HttpRequestInstrumentation.java new file mode 100644 index 000000000000..9f7ccf7bc6f8 --- /dev/null +++ b/instrumentation/vertx-http-client/vertx-http-client-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/v4_0/client/HttpRequestInstrumentation.java @@ -0,0 +1,216 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.vertx.v4_0.client; + +import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed; +import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.implementsInterface; +import static io.opentelemetry.javaagent.instrumentation.vertx.v4_0.client.VertxClientTracer.tracer; +import static net.bytebuddy.matcher.ElementMatchers.isMethod; +import static net.bytebuddy.matcher.ElementMatchers.isPrivate; +import static net.bytebuddy.matcher.ElementMatchers.nameStartsWith; +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.takesArgument; + +import io.opentelemetry.context.Context; +import io.opentelemetry.context.Scope; +import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; +import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; +import io.opentelemetry.javaagent.instrumentation.api.ContextStore; +import io.opentelemetry.javaagent.instrumentation.api.InstrumentationContext; +import io.opentelemetry.javaagent.instrumentation.api.Java8BytecodeBridge; +import io.opentelemetry.javaagent.instrumentation.vertx.client.Contexts; +import io.opentelemetry.javaagent.instrumentation.vertx.client.ExceptionHandlerWrapper; +import io.vertx.core.Handler; +import io.vertx.core.http.HttpClientRequest; +import io.vertx.core.http.HttpClientResponse; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; + +/** + * Two things happen in this instrumentation. + * + *

First, {@link EndRequestAdvice}, {@link HandleExceptionAdvice} and {@link + * HandleResponseAdvice} deal with the common start span/end span functionality. As Vert.x is async + * framework, calls to the instrumented methods may happen from different threads. Thus, correct + * context is stored in {@code HttpClientRequest} itself. + * + *

Second, when HttpClientRequest calls any method that actually performs write on the underlying + * Netty channel, {@link MountContextAdvice} scopes that method call into the context captured on + * the first step. This ensures proper context transfer between the client who actually initiated + * the http call and the Netty Channel that will perform that operation. The main result of this + * transfer is a suppression of Netty CLIENT span. + */ +public class HttpRequestInstrumentation implements TypeInstrumentation { + + @Override + public ElementMatcher classLoaderOptimization() { + return hasClassesNamed("io.vertx.core.http.HttpClientRequest"); + } + + @Override + public ElementMatcher typeMatcher() { + return implementsInterface(named("io.vertx.core.http.HttpClientRequest")); + } + + @Override + public void transform(TypeTransformer transformer) { + transformer.applyAdviceToMethod( + isMethod().and(nameStartsWith("end").or(named("sendHead"))), + HttpRequestInstrumentation.class.getName() + "$EndRequestAdvice"); + + transformer.applyAdviceToMethod( + isMethod().and(named("handleException")), + HttpRequestInstrumentation.class.getName() + "$HandleExceptionAdvice"); + + transformer.applyAdviceToMethod( + isMethod() + .and(named("handleResponse")) + .and(takesArgument(1, named("io.vertx.core.http.HttpClientResponse"))), + HttpRequestInstrumentation.class.getName() + "$HandleResponseAdvice"); + + transformer.applyAdviceToMethod( + isMethod().and(isPrivate()).and(nameStartsWith("write").or(nameStartsWith("connected"))), + HttpRequestInstrumentation.class.getName() + "$MountContextAdvice"); + + transformer.applyAdviceToMethod( + isMethod() + .and(named("exceptionHandler")) + .and(takesArgument(0, named("io.vertx.core.Handler"))), + HttpRequestInstrumentation.class.getName() + "$ExceptionHandlerAdvice"); + } + + @SuppressWarnings("unused") + public static class EndRequestAdvice { + + @Advice.OnMethodEnter(suppress = Throwable.class) + public static void attachContext( + @Advice.This HttpClientRequest request, + @Advice.Local("otelContext") Context context, + @Advice.Local("otelScope") Scope scope) { + Context parentContext = Java8BytecodeBridge.currentContext(); + + if (!tracer().shouldStartSpan(parentContext)) { + return; + } + + context = tracer().startSpan(parentContext, request, request); + Contexts contexts = new Contexts(parentContext, context); + InstrumentationContext.get(HttpClientRequest.class, Contexts.class).put(request, contexts); + + scope = context.makeCurrent(); + } + + @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) + public static void endScope( + @Advice.Local("otelContext") Context context, + @Advice.Local("otelScope") Scope scope, + @Advice.Thrown Throwable throwable) { + if (scope != null) { + scope.close(); + } + if (throwable != null) { + tracer().endExceptionally(context, throwable); + } + } + } + + @SuppressWarnings("unused") + public static class HandleExceptionAdvice { + + @Advice.OnMethodEnter(suppress = Throwable.class) + public static void handleException( + @Advice.This HttpClientRequest request, + @Advice.Argument(0) Throwable t, + @Advice.Local("otelScope") Scope scope) { + Contexts contexts = + InstrumentationContext.get(HttpClientRequest.class, Contexts.class).get(request); + + if (contexts == null) { + return; + } + + tracer().endExceptionally(contexts.context, t); + + // Scoping all potential callbacks etc to the parent context + scope = contexts.parentContext.makeCurrent(); + } + + @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) + public static void handleResponseExit(@Advice.Local("otelScope") Scope scope) { + if (scope != null) { + scope.close(); + } + } + } + + @SuppressWarnings("unused") + public static class HandleResponseAdvice { + + @Advice.OnMethodEnter(suppress = Throwable.class) + public static void handleResponseEnter( + @Advice.This HttpClientRequest request, + @Advice.Argument(1) HttpClientResponse response, + @Advice.Local("otelScope") Scope scope) { + Contexts contexts = + InstrumentationContext.get(HttpClientRequest.class, Contexts.class).get(request); + + if (contexts == null) { + return; + } + + tracer().end(contexts.context, response); + + // Scoping all potential callbacks etc to the parent context + scope = contexts.parentContext.makeCurrent(); + } + + @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) + public static void handleResponseExit(@Advice.Local("otelScope") Scope scope) { + if (scope != null) { + scope.close(); + } + } + } + + @SuppressWarnings("unused") + public static class MountContextAdvice { + + @Advice.OnMethodEnter(suppress = Throwable.class) + public static void mountContext( + @Advice.This HttpClientRequest request, @Advice.Local("otelScope") Scope scope) { + Contexts contexts = + InstrumentationContext.get(HttpClientRequest.class, Contexts.class).get(request); + if (contexts == null) { + return; + } + + scope = contexts.context.makeCurrent(); + } + + @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) + public static void unmountContext(@Advice.Local("otelScope") Scope scope) { + if (scope != null) { + scope.close(); + } + } + } + + @SuppressWarnings("unused") + public static class ExceptionHandlerAdvice { + + @Advice.OnMethodEnter(suppress = Throwable.class) + public static void wrapExceptionHandler( + @Advice.This HttpClientRequest request, + @Advice.Argument(value = 0, readOnly = false) Handler handler) { + if (handler != null) { + ContextStore contextStore = + InstrumentationContext.get(HttpClientRequest.class, Contexts.class); + handler = new ExceptionHandlerWrapper(tracer(), request, contextStore, handler); + } + } + } +} diff --git a/instrumentation/vertx-http-client/vertx-http-client-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/v4_0/client/VertxClientInstrumentationModule.java b/instrumentation/vertx-http-client/vertx-http-client-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/v4_0/client/VertxClientInstrumentationModule.java new file mode 100644 index 000000000000..82e2b39385fb --- /dev/null +++ b/instrumentation/vertx-http-client/vertx-http-client-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/v4_0/client/VertxClientInstrumentationModule.java @@ -0,0 +1,38 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.vertx.v4_0.client; + +import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed; +import static java.util.Arrays.asList; +import static net.bytebuddy.matcher.ElementMatchers.not; + +import com.google.auto.service.AutoService; +import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule; +import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; +import java.util.List; +import net.bytebuddy.matcher.ElementMatcher; + +@AutoService(InstrumentationModule.class) +public class VertxClientInstrumentationModule extends InstrumentationModule { + + public VertxClientInstrumentationModule() { + super("vertx-client", "vertx-client-4.0", "vertx"); + } + + @Override + public ElementMatcher.Junction classLoaderMatcher() { + // class removed in 4.0 + return not(hasClassesNamed("io.vertx.core.Starter")); + } + + @Override + public List typeInstrumentations() { + return asList( + new ConnectionManagerInstrumentation(), + new HttpClientConnectionInstrumentation(), + new HttpRequestInstrumentation()); + } +} diff --git a/instrumentation/vertx-http-client/vertx-http-client-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/v4_0/client/VertxClientTracer.java b/instrumentation/vertx-http-client/vertx-http-client-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/v4_0/client/VertxClientTracer.java new file mode 100644 index 000000000000..aa9085e6e895 --- /dev/null +++ b/instrumentation/vertx-http-client/vertx-http-client-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/v4_0/client/VertxClientTracer.java @@ -0,0 +1,39 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.vertx.v4_0.client; + +import io.opentelemetry.javaagent.instrumentation.vertx.client.AbstractVertxClientTracer; +import io.vertx.core.http.HttpClientRequest; +import java.net.URI; +import java.net.URISyntaxException; +import org.checkerframework.checker.nullness.qual.Nullable; + +public class VertxClientTracer extends AbstractVertxClientTracer { + private static final VertxClientTracer TRACER = new VertxClientTracer(); + + public static VertxClientTracer tracer() { + return TRACER; + } + + @Override + protected String getInstrumentationName() { + return "io.opentelemetry.vertx-http-client-4.0"; + } + + @Override + protected String method(HttpClientRequest request) { + return request.getMethod().name(); + } + + @Override + @Nullable + protected URI url(HttpClientRequest request) throws URISyntaxException { + if (request.absoluteURI().startsWith(request.getURI())) { + return new URI(request.getURI()); + } + return new URI(request.absoluteURI()); + } +} diff --git a/instrumentation/vertx-http-client/vertx-http-client-4.0/javaagent/src/test/groovy/client/VertxHttpClientTest.groovy b/instrumentation/vertx-http-client/vertx-http-client-4.0/javaagent/src/test/groovy/client/VertxHttpClientTest.groovy new file mode 100644 index 000000000000..8bae7de7c9e7 --- /dev/null +++ b/instrumentation/vertx-http-client/vertx-http-client-4.0/javaagent/src/test/groovy/client/VertxHttpClientTest.groovy @@ -0,0 +1,111 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package client + +import io.opentelemetry.api.common.AttributeKey +import io.opentelemetry.instrumentation.test.AgentTestTrait +import io.opentelemetry.instrumentation.test.base.HttpClientTest +import io.opentelemetry.instrumentation.testing.junit.http.AbstractHttpClientTest +import io.opentelemetry.instrumentation.testing.junit.http.SingleConnection +import io.vertx.core.Future +import io.vertx.core.Vertx +import io.vertx.core.VertxOptions +import io.vertx.core.http.HttpClientOptions +import io.vertx.core.http.HttpClientRequest +import io.vertx.core.http.HttpMethod +import io.vertx.core.http.RequestOptions +import java.util.concurrent.CompletableFuture +import spock.lang.Shared + +class VertxHttpClientTest extends HttpClientTest> implements AgentTestTrait { + + @Shared + def vertx = Vertx.vertx(new VertxOptions()) + @Shared + def clientOptions = new HttpClientOptions().setConnectTimeout(CONNECT_TIMEOUT_MS) + @Shared + def httpClient = vertx.createHttpClient(clientOptions) + + @Override + Future buildRequest(String method, URI uri, Map headers) { + RequestOptions requestOptions = new RequestOptions() + .setMethod(HttpMethod.valueOf(method)) + .setAbsoluteURI(uri.toString()) + headers.each { requestOptions.putHeader(it.key, it.value) } + return httpClient.request(requestOptions) + } + + CompletableFuture sendRequest(Future request) { + CompletableFuture future = new CompletableFuture<>() + + request.compose {req -> req.send().onComplete {asyncResult -> + if (asyncResult.succeeded()) { + future.complete(asyncResult.result().statusCode()) + } else { + future.completeExceptionally(asyncResult.cause()) + } + }}.onFailure {throwable -> + future.completeExceptionally(throwable) + } + + return future + } + + @Override + int sendRequest(Future request, String method, URI uri, Map headers) { + // Vertx doesn't seem to provide any synchronous API so bridge through a callback + return sendRequest(request).get() + } + + @Override + void sendRequestWithCallback(Future request, String method, URI uri, Map headers, AbstractHttpClientTest.RequestResult requestResult) { + sendRequest(request).whenComplete { status, throwable -> + requestResult.complete({ status }, throwable) + } + } + + @Override + boolean testRedirects() { + false + } + + @Override + boolean testReusedRequest() { + // vertx requests can't be reused + false + } + + @Override + boolean testHttps() { + false + } + + @Override + String expectedClientSpanName(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 super.expectedClientSpanName(uri, method) + } + } + + @Override + Set> httpAttributes(URI uri) { + switch (uri.toString()) { + case "http://localhost:61/": // unopened port + case "https://192.0.2.1/": // non routable address + return [] + } + return super.httpAttributes(uri) + } + + @Override + SingleConnection createSingleConnection(String host, int port) { + new VertxSingleConnection(host, port) + } +} diff --git a/instrumentation/vertx-http-client/vertx-http-client-4.0/javaagent/src/test/java/client/VertxSingleConnection.java b/instrumentation/vertx-http-client/vertx-http-client-4.0/javaagent/src/test/java/client/VertxSingleConnection.java new file mode 100644 index 000000000000..562da8066a91 --- /dev/null +++ b/instrumentation/vertx-http-client/vertx-http-client-4.0/javaagent/src/test/java/client/VertxSingleConnection.java @@ -0,0 +1,53 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package client; + +import io.opentelemetry.instrumentation.testing.junit.http.SingleConnection; +import io.vertx.core.Future; +import io.vertx.core.Vertx; +import io.vertx.core.VertxOptions; +import io.vertx.core.http.HttpClient; +import io.vertx.core.http.HttpClientOptions; +import io.vertx.core.http.HttpClientRequest; +import io.vertx.core.http.HttpClientResponse; +import io.vertx.core.http.RequestOptions; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ExecutionException; + +public class VertxSingleConnection implements SingleConnection { + + private final HttpClient httpClient; + private final String host; + private final int port; + + public VertxSingleConnection(String host, int port) { + this.host = host; + this.port = port; + HttpClientOptions clientOptions = + new HttpClientOptions().setMaxPoolSize(1).setKeepAlive(true).setPipelining(true); + httpClient = Vertx.vertx(new VertxOptions()).createHttpClient(clientOptions); + } + + @Override + public int doRequest(String path, Map headers) + throws ExecutionException, InterruptedException { + String requestId = Objects.requireNonNull(headers.get(REQUEST_ID_HEADER)); + RequestOptions requestOptions = new RequestOptions().setHost(host).setPort(port).setURI(path); + headers.forEach(requestOptions::putHeader); + Future request = httpClient.request(requestOptions); + + HttpClientResponse response = + request.compose(req -> req.send()).toCompletionStage().toCompletableFuture().get(); + + String responseId = response.getHeader(REQUEST_ID_HEADER); + if (!requestId.equals(responseId)) { + throw new IllegalStateException( + String.format("Received response with id %s, expected %s", responseId, requestId)); + } + return response.statusCode(); + } +} diff --git a/instrumentation/vertx-http-client/vertx-http-client-common/javaagent/build.gradle.kts b/instrumentation/vertx-http-client/vertx-http-client-common/javaagent/build.gradle.kts new file mode 100644 index 000000000000..3a26a87f7977 --- /dev/null +++ b/instrumentation/vertx-http-client/vertx-http-client-common/javaagent/build.gradle.kts @@ -0,0 +1,7 @@ +plugins { + id("otel.javaagent-instrumentation") +} + +dependencies { + compileOnly("io.vertx:vertx-core:3.0.0") +} \ No newline at end of file diff --git a/instrumentation/vertx-http-client-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/client/VertxClientTracer.java b/instrumentation/vertx-http-client/vertx-http-client-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/client/AbstractVertxClientTracer.java similarity index 70% rename from instrumentation/vertx-http-client-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/client/VertxClientTracer.java rename to instrumentation/vertx-http-client/vertx-http-client-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/client/AbstractVertxClientTracer.java index dcfc4249e3f3..ac3ca54b93d9 100644 --- a/instrumentation/vertx-http-client-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/client/VertxClientTracer.java +++ b/instrumentation/vertx-http-client/vertx-http-client-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/client/AbstractVertxClientTracer.java @@ -10,38 +10,15 @@ import io.opentelemetry.instrumentation.api.tracer.net.NetPeerAttributes; import io.vertx.core.http.HttpClientRequest; import io.vertx.core.http.HttpClientResponse; -import java.net.URI; -import java.net.URISyntaxException; import org.checkerframework.checker.nullness.qual.Nullable; -public class VertxClientTracer +public abstract class AbstractVertxClientTracer extends HttpClientTracer { - private static final VertxClientTracer TRACER = new VertxClientTracer(); - public static VertxClientTracer tracer() { - return TRACER; - } - - public VertxClientTracer() { + protected AbstractVertxClientTracer() { super(NetPeerAttributes.INSTANCE); } - @Override - protected String getInstrumentationName() { - return "io.opentelemetry.vertx-web-3.0"; - } - - @Override - protected String method(HttpClientRequest request) { - return request.method().name(); - } - - @Override - @Nullable - protected URI url(HttpClientRequest request) throws URISyntaxException { - return new URI(request.uri()); - } - @Override @Nullable protected Integer status(HttpClientResponse response) { diff --git a/instrumentation/vertx-http-client-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/client/Contexts.java b/instrumentation/vertx-http-client/vertx-http-client-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/client/Contexts.java similarity index 100% rename from instrumentation/vertx-http-client-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/client/Contexts.java rename to instrumentation/vertx-http-client/vertx-http-client-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/client/Contexts.java diff --git a/instrumentation/vertx-http-client-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/client/ExceptionHandlerWrapper.java b/instrumentation/vertx-http-client/vertx-http-client-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/client/ExceptionHandlerWrapper.java similarity index 87% rename from instrumentation/vertx-http-client-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/client/ExceptionHandlerWrapper.java rename to instrumentation/vertx-http-client/vertx-http-client-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/client/ExceptionHandlerWrapper.java index e56146efc5cb..1becc4630271 100644 --- a/instrumentation/vertx-http-client-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/client/ExceptionHandlerWrapper.java +++ b/instrumentation/vertx-http-client/vertx-http-client-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/client/ExceptionHandlerWrapper.java @@ -5,22 +5,23 @@ package io.opentelemetry.javaagent.instrumentation.vertx.client; -import static io.opentelemetry.javaagent.instrumentation.vertx.client.VertxClientTracer.tracer; - import io.opentelemetry.context.Scope; import io.opentelemetry.javaagent.instrumentation.api.ContextStore; import io.vertx.core.Handler; import io.vertx.core.http.HttpClientRequest; public class ExceptionHandlerWrapper implements Handler { + private final AbstractVertxClientTracer tracer; private final HttpClientRequest request; private final ContextStore contextStore; private final Handler handler; public ExceptionHandlerWrapper( + AbstractVertxClientTracer tracer, HttpClientRequest request, ContextStore contextStore, Handler handler) { + this.tracer = tracer; this.request = request; this.contextStore = contextStore; this.handler = handler; @@ -34,7 +35,7 @@ public void handle(Throwable throwable) { return; } - tracer().endExceptionally(contexts.context, throwable); + tracer.endExceptionally(contexts.context, throwable); try (Scope ignored = contexts.parentContext.makeCurrent()) { callHandler(throwable); diff --git a/instrumentation/vertx-reactive-3.5/javaagent/build.gradle.kts b/instrumentation/vertx-reactive-3.5/javaagent/build.gradle.kts index 2ca51b3daa12..bd33710be4fe 100644 --- a/instrumentation/vertx-reactive-3.5/javaagent/build.gradle.kts +++ b/instrumentation/vertx-reactive-3.5/javaagent/build.gradle.kts @@ -20,7 +20,8 @@ dependencies { testInstrumentation(project(":instrumentation:jdbc:javaagent")) testInstrumentation(project(":instrumentation:netty:netty-4.1:javaagent")) testInstrumentation(project(":instrumentation:rxjava:rxjava-2.0:javaagent")) - testInstrumentation(project(":instrumentation:vertx-http-client-3.0:javaagent")) + testInstrumentation(project(":instrumentation:vertx-http-client:vertx-http-client-3.0:javaagent")) + testInstrumentation(project(":instrumentation:vertx-http-client:vertx-http-client-4.0:javaagent")) testInstrumentation(project(":instrumentation:vertx-web-3.0:javaagent")) testLibrary("io.vertx:vertx-web-client:${vertxVersion}") diff --git a/settings.gradle.kts b/settings.gradle.kts index edc6eec89915..f3c67abd7441 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -323,7 +323,9 @@ include(":instrumentation:undertow-1.4:bootstrap") include(":instrumentation:undertow-1.4:javaagent") include(":instrumentation:vaadin-14.2:javaagent") include(":instrumentation:vaadin-14.2:testing") -include(":instrumentation:vertx-http-client-3.0:javaagent") +include(":instrumentation:vertx-http-client:vertx-http-client-3.0:javaagent") +include(":instrumentation:vertx-http-client:vertx-http-client-4.0:javaagent") +include(":instrumentation:vertx-http-client:vertx-http-client-common:javaagent") include(":instrumentation:vertx-reactive-3.5:javaagent") include(":instrumentation:vertx-web-3.0:javaagent") include(":instrumentation:vertx-web-3.0:testing")