From bc5398ce3fc3a07af3b1ebdaee1cc7802026ba68 Mon Sep 17 00:00:00 2001 From: Lauri Tulmin Date: Fri, 8 Dec 2023 10:00:10 +0200 Subject: [PATCH] Add instrumentation for vert.x redis client (#9838) --- docs/supported-libraries.md | 1 + .../javaagent/build.gradle.kts | 27 +++ .../redis/CommandImplInstrumentation.java | 38 ++++ ...edisConnectionProviderInstrumentation.java | 68 ++++++ ...isStandaloneConnectionInstrumentation.java | 103 +++++++++ .../VertxRedisClientAttributesExtractor.java | 33 +++ .../VertxRedisClientAttributesGetter.java | 54 +++++ ...VertxRedisClientInstrumentationModule.java | 42 ++++ .../VertxRedisClientNetAttributesGetter.java | 41 ++++ .../v4_0/redis/VertxRedisClientRequest.java | 65 ++++++ .../redis/VertxRedisClientSingletons.java | 109 +++++++++ .../vertx/redis/client/impl/RequestUtil.java | 22 ++ .../v4_0/redis/VertxRedisClientTest.java | 207 ++++++++++++++++++ .../ReferenceCollectingClassVisitor.java | 3 + .../muzzle/ReferenceCollectorTest.java | 18 +- .../java/muzzle/other/OtherTestClasses.java | 26 +++ settings.gradle.kts | 1 + 17 files changed, 849 insertions(+), 9 deletions(-) create mode 100644 instrumentation/vertx/vertx-redis-client-4.0/javaagent/build.gradle.kts create mode 100644 instrumentation/vertx/vertx-redis-client-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/v4_0/redis/CommandImplInstrumentation.java create mode 100644 instrumentation/vertx/vertx-redis-client-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/v4_0/redis/RedisConnectionProviderInstrumentation.java create mode 100644 instrumentation/vertx/vertx-redis-client-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/v4_0/redis/RedisStandaloneConnectionInstrumentation.java create mode 100644 instrumentation/vertx/vertx-redis-client-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/v4_0/redis/VertxRedisClientAttributesExtractor.java create mode 100644 instrumentation/vertx/vertx-redis-client-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/v4_0/redis/VertxRedisClientAttributesGetter.java create mode 100644 instrumentation/vertx/vertx-redis-client-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/v4_0/redis/VertxRedisClientInstrumentationModule.java create mode 100644 instrumentation/vertx/vertx-redis-client-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/v4_0/redis/VertxRedisClientNetAttributesGetter.java create mode 100644 instrumentation/vertx/vertx-redis-client-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/v4_0/redis/VertxRedisClientRequest.java create mode 100644 instrumentation/vertx/vertx-redis-client-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/v4_0/redis/VertxRedisClientSingletons.java create mode 100644 instrumentation/vertx/vertx-redis-client-4.0/javaagent/src/main/java/io/vertx/redis/client/impl/RequestUtil.java create mode 100644 instrumentation/vertx/vertx-redis-client-4.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/vertx/v4_0/redis/VertxRedisClientTest.java create mode 100644 muzzle/src/test/java/muzzle/other/OtherTestClasses.java diff --git a/docs/supported-libraries.md b/docs/supported-libraries.md index 5afb6c2cad1b..f6b13a446941 100644 --- a/docs/supported-libraries.md +++ b/docs/supported-libraries.md @@ -133,6 +133,7 @@ These are the supported libraries and frameworks: | [Vert.x Web](https://vertx.io/docs/vertx-web/java/) | 3.0+ | N/A | Provides `http.route` [2] | | [Vert.x HttpClient](https://vertx.io/docs/apidocs/io/vertx/core/http/HttpClient.html) | 3.0+ | N/A | [HTTP Client Spans], [HTTP Client Metrics] | | [Vert.x Kafka Client](https://vertx.io/docs/vertx-kafka-client/java/) | 3.6+ | N/A | [Messaging Spans] | +| [Vert.x Redis Client](https://vertx.io/docs/vertx-redis-client/java/) | 4.0+ | N/A | [Database Client Spans] | | [Vert.x RxJava2](https://vertx.io/docs/vertx-rx/java2/) | 3.5+ | N/A | context propagation only | | [Vert.x SQL Client](https://github.com/eclipse-vertx/vertx-sql-client/) | 4.0+ | N/A | [Database Client Spans] | | [Vibur DBCP](https://www.vibur.org/) | 11.0+ | [opentelemetry-vibur-dbcp-11.0](../instrumentation/vibur-dbcp-11.0/library) | [Database Pool Metrics] | diff --git a/instrumentation/vertx/vertx-redis-client-4.0/javaagent/build.gradle.kts b/instrumentation/vertx/vertx-redis-client-4.0/javaagent/build.gradle.kts new file mode 100644 index 000000000000..748df2ab83cf --- /dev/null +++ b/instrumentation/vertx/vertx-redis-client-4.0/javaagent/build.gradle.kts @@ -0,0 +1,27 @@ +plugins { + id("otel.javaagent-instrumentation") +} + +muzzle { + pass { + group.set("io.vertx") + module.set("vertx-redis-client") + versions.set("[4.0.0,)") + assertInverse.set(true) + } +} + +dependencies { + library("io.vertx:vertx-redis-client:4.0.0") + compileOnly("io.vertx:vertx-codegen:4.0.0") + + testInstrumentation(project(":instrumentation:netty:netty-4.1:javaagent")) + + testLibrary("io.vertx:vertx-codegen:4.0.0") +} + +tasks { + withType().configureEach { + usesService(gradle.sharedServices.registrations["testcontainersBuildService"].service) + } +} diff --git a/instrumentation/vertx/vertx-redis-client-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/v4_0/redis/CommandImplInstrumentation.java b/instrumentation/vertx/vertx-redis-client-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/v4_0/redis/CommandImplInstrumentation.java new file mode 100644 index 000000000000..f62aa15d9a6a --- /dev/null +++ b/instrumentation/vertx/vertx-redis-client-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/v4_0/redis/CommandImplInstrumentation.java @@ -0,0 +1,38 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.vertx.v4_0.redis; + +import static net.bytebuddy.matcher.ElementMatchers.isConstructor; +import static net.bytebuddy.matcher.ElementMatchers.named; + +import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; +import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; +import io.vertx.redis.client.impl.CommandImpl; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; + +public class CommandImplInstrumentation implements TypeInstrumentation { + @Override + public ElementMatcher typeMatcher() { + return named("io.vertx.redis.client.impl.CommandImpl"); + } + + @Override + public void transform(TypeTransformer transformer) { + transformer.applyAdviceToMethod( + isConstructor(), this.getClass().getName() + "$ConstructorAdvice"); + } + + @SuppressWarnings("unused") + public static class ConstructorAdvice { + @Advice.OnMethodExit(suppress = Throwable.class) + public static void onExit( + @Advice.This CommandImpl command, @Advice.Argument(0) String commandName) { + VertxRedisClientSingletons.setCommandName(command, commandName); + } + } +} diff --git a/instrumentation/vertx/vertx-redis-client-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/v4_0/redis/RedisConnectionProviderInstrumentation.java b/instrumentation/vertx/vertx-redis-client-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/v4_0/redis/RedisConnectionProviderInstrumentation.java new file mode 100644 index 000000000000..664acfbeaf8a --- /dev/null +++ b/instrumentation/vertx/vertx-redis-client-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/v4_0/redis/RedisConnectionProviderInstrumentation.java @@ -0,0 +1,68 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.vertx.v4_0.redis; + +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.not; +import static net.bytebuddy.matcher.ElementMatchers.takesArgument; + +import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; +import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; +import io.vertx.redis.client.RedisConnection; +import io.vertx.redis.client.impl.RedisStandaloneConnection; +import io.vertx.redis.client.impl.RedisURI; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; + +public class RedisConnectionProviderInstrumentation implements TypeInstrumentation { + @Override + public ElementMatcher typeMatcher() { + return named("io.vertx.redis.client.impl.RedisConnectionManager$RedisConnectionProvider"); + } + + @Override + public void transform(TypeTransformer transformer) { + // 4.1.0 + transformer.applyAdviceToMethod( + named("init").and(not(takesArgument(0, named("io.vertx.redis.client.RedisConnection")))), + this.getClass().getName() + "$InitAdvice"); + // 4.0.0 + transformer.applyAdviceToMethod( + named("init").and(takesArgument(0, named("io.vertx.redis.client.RedisConnection"))), + this.getClass().getName() + "$InitWithConnectionAdvice"); + } + + @SuppressWarnings("unused") + public static class InitAdvice { + @Advice.OnMethodEnter(suppress = Throwable.class) + public static void onEnter(@Advice.FieldValue("redisURI") RedisURI redisUri) { + // for 4.1.0 and later we set RedisURI in a ThreadLocal that is used in advice added in + // RedisStandaloneConnectionInstrumentation that attaches RedisURI to + // RedisStandaloneConnection + VertxRedisClientSingletons.setRedisUriThreadLocal(redisUri); + } + + @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) + public static void onExit() { + VertxRedisClientSingletons.setRedisUriThreadLocal(null); + } + } + + @SuppressWarnings("unused") + public static class InitWithConnectionAdvice { + @Advice.OnMethodEnter(suppress = Throwable.class) + public static void onEnter( + @Advice.Argument(0) RedisConnection connection, + @Advice.FieldValue("redisURI") RedisURI redisUri) { + // for 4.0.x we don't need to use ThreadLocal like in 4.1.0 because in this method we have + // access to both the RedisURI and RedisConnection + if (connection instanceof RedisStandaloneConnection) { + VertxRedisClientSingletons.setRedisUri((RedisStandaloneConnection) connection, redisUri); + } + } + } +} diff --git a/instrumentation/vertx/vertx-redis-client-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/v4_0/redis/RedisStandaloneConnectionInstrumentation.java b/instrumentation/vertx/vertx-redis-client-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/v4_0/redis/RedisStandaloneConnectionInstrumentation.java new file mode 100644 index 000000000000..157a7b2e3677 --- /dev/null +++ b/instrumentation/vertx/vertx-redis-client-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/v4_0/redis/RedisStandaloneConnectionInstrumentation.java @@ -0,0 +1,103 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.vertx.v4_0.redis; + +import static io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge.currentContext; +import static io.opentelemetry.javaagent.instrumentation.vertx.v4_0.redis.VertxRedisClientSingletons.instrumenter; +import static net.bytebuddy.matcher.ElementMatchers.isConstructor; +import static net.bytebuddy.matcher.ElementMatchers.named; + +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.vertx.core.Future; +import io.vertx.core.net.NetSocket; +import io.vertx.redis.client.Request; +import io.vertx.redis.client.Response; +import io.vertx.redis.client.impl.RedisStandaloneConnection; +import io.vertx.redis.client.impl.RedisURI; +import io.vertx.redis.client.impl.RequestUtil; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; + +public class RedisStandaloneConnectionInstrumentation implements TypeInstrumentation { + @Override + public ElementMatcher typeMatcher() { + return named("io.vertx.redis.client.impl.RedisStandaloneConnection"); + } + + @Override + public void transform(TypeTransformer transformer) { + transformer.applyAdviceToMethod(named("send"), this.getClass().getName() + "$SendAdvice"); + transformer.applyAdviceToMethod( + isConstructor(), this.getClass().getName() + "$ConstructorAdvice"); + } + + @SuppressWarnings("unused") + public static class SendAdvice { + @Advice.OnMethodEnter(suppress = Throwable.class) + public static void onEnter( + @Advice.This RedisStandaloneConnection connection, + @Advice.Argument(0) Request request, + @Advice.FieldValue("netSocket") NetSocket netSocket, + @Advice.Local("otelRequest") VertxRedisClientRequest otelRequest, + @Advice.Local("otelContext") Context context, + @Advice.Local("otelScope") Scope scope) { + if (request == null) { + return; + } + + String commandName = VertxRedisClientSingletons.getCommandName(request.command()); + RedisURI redisUri = VertxRedisClientSingletons.getRedisUri(connection); + if (commandName == null || redisUri == null) { + return; + } + + otelRequest = + new VertxRedisClientRequest( + commandName, RequestUtil.getArgs(request), redisUri, netSocket); + Context parentContext = currentContext(); + if (!instrumenter().shouldStart(parentContext, otelRequest)) { + return; + } + + context = instrumenter().start(parentContext, otelRequest); + scope = context.makeCurrent(); + } + + @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) + public static void onExit( + @Advice.Thrown Throwable throwable, + @Advice.Return(readOnly = false) Future responseFuture, + @Advice.Local("otelRequest") VertxRedisClientRequest otelRequest, + @Advice.Local("otelContext") Context context, + @Advice.Local("otelScope") Scope scope) { + if (scope == null) { + return; + } + + scope.close(); + if (throwable != null) { + instrumenter().end(context, otelRequest, null, throwable); + } else { + responseFuture = + VertxRedisClientSingletons.wrapEndSpan(responseFuture, context, otelRequest); + } + } + } + + @SuppressWarnings("unused") + public static class ConstructorAdvice { + @Advice.OnMethodExit(suppress = Throwable.class) + public static void onExit(@Advice.This RedisStandaloneConnection connection) { + // used in 4.1.0, for 4.0.0 it is set in RedisConnectionProviderInstrumentation + VertxRedisClientSingletons.setRedisUri( + connection, VertxRedisClientSingletons.getRedisUriThreadLocal()); + } + } +} diff --git a/instrumentation/vertx/vertx-redis-client-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/v4_0/redis/VertxRedisClientAttributesExtractor.java b/instrumentation/vertx/vertx-redis-client-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/v4_0/redis/VertxRedisClientAttributesExtractor.java new file mode 100644 index 000000000000..670d8dcf0903 --- /dev/null +++ b/instrumentation/vertx/vertx-redis-client-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/v4_0/redis/VertxRedisClientAttributesExtractor.java @@ -0,0 +1,33 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.vertx.v4_0.redis; + +import static io.opentelemetry.instrumentation.api.internal.AttributesExtractorUtil.internalSet; + +import io.opentelemetry.api.common.AttributesBuilder; +import io.opentelemetry.context.Context; +import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; +import io.opentelemetry.semconv.SemanticAttributes; +import javax.annotation.Nullable; + +enum VertxRedisClientAttributesExtractor + implements AttributesExtractor { + INSTANCE; + + @Override + public void onStart( + AttributesBuilder attributes, Context parentContext, VertxRedisClientRequest request) { + internalSet(attributes, SemanticAttributes.DB_REDIS_DATABASE_INDEX, request.getDatabaseIndex()); + } + + @Override + public void onEnd( + AttributesBuilder attributes, + Context context, + VertxRedisClientRequest request, + @Nullable Void unused, + @Nullable Throwable error) {} +} diff --git a/instrumentation/vertx/vertx-redis-client-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/v4_0/redis/VertxRedisClientAttributesGetter.java b/instrumentation/vertx/vertx-redis-client-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/v4_0/redis/VertxRedisClientAttributesGetter.java new file mode 100644 index 000000000000..9251bf0ee915 --- /dev/null +++ b/instrumentation/vertx/vertx-redis-client-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/v4_0/redis/VertxRedisClientAttributesGetter.java @@ -0,0 +1,54 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.vertx.v4_0.redis; + +import io.opentelemetry.instrumentation.api.incubator.semconv.db.DbClientAttributesGetter; +import io.opentelemetry.instrumentation.api.incubator.semconv.db.RedisCommandSanitizer; +import io.opentelemetry.javaagent.bootstrap.internal.CommonConfig; +import io.opentelemetry.semconv.SemanticAttributes; +import javax.annotation.Nullable; + +public enum VertxRedisClientAttributesGetter + implements DbClientAttributesGetter { + INSTANCE; + + private static final RedisCommandSanitizer sanitizer = + RedisCommandSanitizer.create(CommonConfig.get().isStatementSanitizationEnabled()); + + @Override + public String getSystem(VertxRedisClientRequest request) { + return SemanticAttributes.DbSystemValues.REDIS; + } + + @Override + @Nullable + public String getUser(VertxRedisClientRequest request) { + return request.getUser(); + } + + @Override + @Nullable + public String getName(VertxRedisClientRequest request) { + return null; + } + + @Override + @Nullable + public String getConnectionString(VertxRedisClientRequest request) { + return request.getConnectionString(); + } + + @Override + public String getStatement(VertxRedisClientRequest request) { + return sanitizer.sanitize(request.getCommand(), request.getArgs()); + } + + @Nullable + @Override + public String getOperation(VertxRedisClientRequest request) { + return request.getCommand(); + } +} diff --git a/instrumentation/vertx/vertx-redis-client-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/v4_0/redis/VertxRedisClientInstrumentationModule.java b/instrumentation/vertx/vertx-redis-client-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/v4_0/redis/VertxRedisClientInstrumentationModule.java new file mode 100644 index 000000000000..e3cab6a83ce5 --- /dev/null +++ b/instrumentation/vertx/vertx-redis-client-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/v4_0/redis/VertxRedisClientInstrumentationModule.java @@ -0,0 +1,42 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.vertx.v4_0.redis; + +import static java.util.Arrays.asList; +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 io.opentelemetry.javaagent.extension.instrumentation.internal.ExperimentalInstrumentationModule; +import java.util.List; + +@AutoService(InstrumentationModule.class) +public class VertxRedisClientInstrumentationModule extends InstrumentationModule + implements ExperimentalInstrumentationModule { + + public VertxRedisClientInstrumentationModule() { + super("vertx-redis-client", "vertx-redis-client-4.0", "vertx"); + } + + @Override + public boolean isHelperClass(String className) { + return "io.vertx.redis.client.impl.RequestUtil".equals(className); + } + + @Override + public List injectedClassNames() { + return singletonList("io.vertx.redis.client.impl.RequestUtil"); + } + + @Override + public List typeInstrumentations() { + return asList( + new RedisStandaloneConnectionInstrumentation(), + new RedisConnectionProviderInstrumentation(), + new CommandImplInstrumentation()); + } +} diff --git a/instrumentation/vertx/vertx-redis-client-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/v4_0/redis/VertxRedisClientNetAttributesGetter.java b/instrumentation/vertx/vertx-redis-client-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/v4_0/redis/VertxRedisClientNetAttributesGetter.java new file mode 100644 index 000000000000..9b0e12581dee --- /dev/null +++ b/instrumentation/vertx/vertx-redis-client-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/v4_0/redis/VertxRedisClientNetAttributesGetter.java @@ -0,0 +1,41 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.vertx.v4_0.redis; + +import io.opentelemetry.instrumentation.api.semconv.network.NetworkAttributesGetter; +import io.opentelemetry.instrumentation.api.semconv.network.ServerAttributesGetter; +import javax.annotation.Nullable; + +enum VertxRedisClientNetAttributesGetter + implements + ServerAttributesGetter, + NetworkAttributesGetter { + INSTANCE; + + @Nullable + @Override + public String getServerAddress(VertxRedisClientRequest request) { + return request.getHost(); + } + + @Nullable + @Override + public Integer getServerPort(VertxRedisClientRequest request) { + return request.getPort(); + } + + @Override + @Nullable + public String getNetworkPeerAddress(VertxRedisClientRequest request, @Nullable Void unused) { + return request.getPeerAddress(); + } + + @Override + @Nullable + public Integer getNetworkPeerPort(VertxRedisClientRequest request, @Nullable Void unused) { + return request.getPeerPort(); + } +} diff --git a/instrumentation/vertx/vertx-redis-client-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/v4_0/redis/VertxRedisClientRequest.java b/instrumentation/vertx/vertx-redis-client-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/v4_0/redis/VertxRedisClientRequest.java new file mode 100644 index 000000000000..f202034b7849 --- /dev/null +++ b/instrumentation/vertx/vertx-redis-client-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/v4_0/redis/VertxRedisClientRequest.java @@ -0,0 +1,65 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.vertx.v4_0.redis; + +import io.vertx.core.net.NetSocket; +import io.vertx.redis.client.impl.RedisURI; +import java.util.List; +import java.util.Locale; + +public final class VertxRedisClientRequest { + private final String command; + private final List args; + private final RedisURI redisUri; + private final NetSocket netSocket; + + public VertxRedisClientRequest( + String command, List args, RedisURI redisUri, NetSocket netSocket) { + this.command = command.toUpperCase(Locale.ROOT); + this.args = args; + this.redisUri = redisUri; + this.netSocket = netSocket; + } + + public String getCommand() { + return command; + } + + public List getArgs() { + return args; + } + + public String getUser() { + return redisUri.user(); + } + + public Long getDatabaseIndex() { + Integer select = redisUri.select(); + return select != null ? select.longValue() : null; + } + + public String getConnectionString() { + return null; + } + + public String getHost() { + return redisUri.socketAddress().host(); + } + + public Integer getPort() { + int port = redisUri.socketAddress().port(); + return port != -1 ? port : null; + } + + public String getPeerAddress() { + return netSocket.remoteAddress().hostAddress(); + } + + public Integer getPeerPort() { + int port = netSocket.remoteAddress().port(); + return port != -1 ? port : null; + } +} diff --git a/instrumentation/vertx/vertx-redis-client-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/v4_0/redis/VertxRedisClientSingletons.java b/instrumentation/vertx/vertx-redis-client-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/v4_0/redis/VertxRedisClientSingletons.java new file mode 100644 index 000000000000..090966f719ad --- /dev/null +++ b/instrumentation/vertx/vertx-redis-client-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/v4_0/redis/VertxRedisClientSingletons.java @@ -0,0 +1,109 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.vertx.v4_0.redis; + +import io.opentelemetry.api.GlobalOpenTelemetry; +import io.opentelemetry.context.Context; +import io.opentelemetry.context.Scope; +import io.opentelemetry.instrumentation.api.incubator.semconv.db.DbClientAttributesExtractor; +import io.opentelemetry.instrumentation.api.incubator.semconv.db.DbClientSpanNameExtractor; +import io.opentelemetry.instrumentation.api.incubator.semconv.net.PeerServiceAttributesExtractor; +import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder; +import io.opentelemetry.instrumentation.api.instrumenter.SpanKindExtractor; +import io.opentelemetry.instrumentation.api.instrumenter.SpanNameExtractor; +import io.opentelemetry.instrumentation.api.semconv.network.NetworkAttributesExtractor; +import io.opentelemetry.instrumentation.api.semconv.network.ServerAttributesExtractor; +import io.opentelemetry.instrumentation.api.util.VirtualField; +import io.opentelemetry.javaagent.bootstrap.internal.CommonConfig; +import io.vertx.core.Future; +import io.vertx.redis.client.Command; +import io.vertx.redis.client.impl.RedisStandaloneConnection; +import io.vertx.redis.client.impl.RedisURI; +import java.util.concurrent.CompletableFuture; + +public final class VertxRedisClientSingletons { + private static final String INSTRUMENTATION_NAME = "io.opentelemetry.vertx-redis-client-4.0"; + private static final Instrumenter INSTRUMENTER; + + private static final ThreadLocal redisUriThreadLocal = new ThreadLocal<>(); + private static final VirtualField commandNameField = + VirtualField.find(Command.class, String.class); + private static final VirtualField redisUriField = + VirtualField.find(RedisStandaloneConnection.class, RedisURI.class); + + static { + SpanNameExtractor spanNameExtractor = + DbClientSpanNameExtractor.create(VertxRedisClientAttributesGetter.INSTANCE); + + InstrumenterBuilder builder = + Instrumenter.builder( + GlobalOpenTelemetry.get(), INSTRUMENTATION_NAME, spanNameExtractor) + .addAttributesExtractor( + DbClientAttributesExtractor.create(VertxRedisClientAttributesGetter.INSTANCE)) + .addAttributesExtractor(VertxRedisClientAttributesExtractor.INSTANCE) + .addAttributesExtractor( + ServerAttributesExtractor.create(VertxRedisClientNetAttributesGetter.INSTANCE)) + .addAttributesExtractor( + NetworkAttributesExtractor.create(VertxRedisClientNetAttributesGetter.INSTANCE)) + .addAttributesExtractor( + PeerServiceAttributesExtractor.create( + VertxRedisClientNetAttributesGetter.INSTANCE, + CommonConfig.get().getPeerServiceResolver())); + + INSTRUMENTER = builder.buildInstrumenter(SpanKindExtractor.alwaysClient()); + } + + public static Instrumenter instrumenter() { + return INSTRUMENTER; + } + + public static Future wrapEndSpan( + Future future, Context context, VertxRedisClientRequest request) { + Context parentContext = Context.current(); + CompletableFuture result = new CompletableFuture<>(); + future + .toCompletionStage() + .whenComplete( + (value, throwable) -> { + instrumenter().end(context, request, null, null); + try (Scope ignore = parentContext.makeCurrent()) { + if (throwable != null) { + result.completeExceptionally(throwable); + } else { + result.complete(value); + } + } + }); + return Future.fromCompletionStage(result); + } + + public static RedisURI getRedisUriThreadLocal() { + return redisUriThreadLocal.get(); + } + + public static void setRedisUriThreadLocal(RedisURI redisUri) { + redisUriThreadLocal.set(redisUri); + } + + public static void setCommandName(Command command, String commandName) { + commandNameField.set(command, commandName); + } + + public static String getCommandName(Command command) { + return commandNameField.get(command); + } + + public static void setRedisUri(RedisStandaloneConnection connection, RedisURI redisUri) { + redisUriField.set(connection, redisUri); + } + + public static RedisURI getRedisUri(RedisStandaloneConnection connection) { + return redisUriField.get(connection); + } + + private VertxRedisClientSingletons() {} +} diff --git a/instrumentation/vertx/vertx-redis-client-4.0/javaagent/src/main/java/io/vertx/redis/client/impl/RequestUtil.java b/instrumentation/vertx/vertx-redis-client-4.0/javaagent/src/main/java/io/vertx/redis/client/impl/RequestUtil.java new file mode 100644 index 000000000000..ad30deca7f5f --- /dev/null +++ b/instrumentation/vertx/vertx-redis-client-4.0/javaagent/src/main/java/io/vertx/redis/client/impl/RequestUtil.java @@ -0,0 +1,22 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.vertx.redis.client.impl; + +import io.vertx.redis.client.Request; +import java.util.Collections; +import java.util.List; + +public final class RequestUtil { + + public static List getArgs(Request request) { + if (request instanceof RequestImpl) { + return ((RequestImpl) request).getArgs(); + } + return Collections.emptyList(); + } + + private RequestUtil() {} +} diff --git a/instrumentation/vertx/vertx-redis-client-4.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/vertx/v4_0/redis/VertxRedisClientTest.java b/instrumentation/vertx/vertx-redis-client-4.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/vertx/v4_0/redis/VertxRedisClientTest.java new file mode 100644 index 000000000000..4d96efc047b4 --- /dev/null +++ b/instrumentation/vertx/vertx-redis-client-4.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/vertx/v4_0/redis/VertxRedisClientTest.java @@ -0,0 +1,207 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.vertx.v4_0.redis; + +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; +import static org.assertj.core.api.Assertions.assertThat; + +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.instrumentation.api.semconv.network.internal.NetworkAttributes; +import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.sdk.testing.assertj.AttributeAssertion; +import io.opentelemetry.semconv.SemanticAttributes; +import io.vertx.core.Vertx; +import io.vertx.redis.client.Redis; +import io.vertx.redis.client.RedisAPI; +import io.vertx.redis.client.RedisConnection; +import java.util.Arrays; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.testcontainers.containers.GenericContainer; + +class VertxRedisClientTest { + @RegisterExtension + private static final InstrumentationExtension testing = AgentInstrumentationExtension.create(); + + private static final GenericContainer redisServer = + new GenericContainer<>("redis:6.2.3-alpine").withExposedPorts(6379); + private static int port; + private static Vertx vertx; + private static Redis client; + private static RedisAPI redis; + + @BeforeAll + static void setupSpec() throws Exception { + redisServer.start(); + port = redisServer.getMappedPort(6379); + + vertx = Vertx.vertx(); + client = Redis.createClient(vertx, "redis://localhost:" + port + "/1"); + RedisConnection connection = + client.connect().toCompletionStage().toCompletableFuture().get(30, TimeUnit.SECONDS); + redis = RedisAPI.api(connection); + } + + @AfterAll + static void cleanupSpec() { + redis.close(); + client.close(); + redisServer.stop(); + } + + @Test + void setCommand() throws Exception { + redis + .set(Arrays.asList("foo", "bar")) + .toCompletionStage() + .toCompletableFuture() + .get(30, TimeUnit.SECONDS); + + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasName("SET") + .hasKind(SpanKind.CLIENT) + .hasAttributesSatisfyingExactly(redisSpanAttributes("SET", "SET foo ?")))); + } + + @Test + void getCommand() throws Exception { + redis + .set(Arrays.asList("foo", "bar")) + .toCompletionStage() + .toCompletableFuture() + .get(30, TimeUnit.SECONDS); + String value = + redis + .get("foo") + .toCompletionStage() + .toCompletableFuture() + .get(30, TimeUnit.SECONDS) + .toString(); + + assertThat(value).isEqualTo("bar"); + + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasName("SET") + .hasKind(SpanKind.CLIENT) + .hasAttributesSatisfyingExactly(redisSpanAttributes("SET", "SET foo ?"))), + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasName("GET") + .hasKind(SpanKind.CLIENT) + .hasAttributesSatisfyingExactly(redisSpanAttributes("GET", "GET foo")))); + } + + @Test + void getCommandWithParent() throws Exception { + redis + .set(Arrays.asList("foo", "bar")) + .toCompletionStage() + .toCompletableFuture() + .get(30, TimeUnit.SECONDS); + + CompletableFuture future = new CompletableFuture<>(); + CompletableFuture result = + future.whenComplete((value, throwable) -> testing.runWithSpan("callback", () -> {})); + + testing.runWithSpan( + "parent", + () -> + redis + .get("foo") + .toCompletionStage() + .toCompletableFuture() + .whenComplete( + (response, throwable) -> { + if (throwable == null) { + future.complete(response.toString()); + } else { + future.completeExceptionally(throwable); + } + })); + + String value = result.get(30, TimeUnit.SECONDS); + assertThat(value).isEqualTo("bar"); + + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasName("SET") + .hasKind(SpanKind.CLIENT) + .hasAttributesSatisfyingExactly(redisSpanAttributes("SET", "SET foo ?"))), + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("parent").hasKind(SpanKind.INTERNAL), + span -> + span.hasName("GET") + .hasKind(SpanKind.CLIENT) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly(redisSpanAttributes("GET", "GET foo")), + span -> + span.hasName("callback") + .hasKind(SpanKind.INTERNAL) + .hasParent(trace.getSpan(0)))); + } + + @Test + void commandWithNoArguments() throws Exception { + redis + .set(Arrays.asList("foo", "bar")) + .toCompletionStage() + .toCompletableFuture() + .get(30, TimeUnit.SECONDS); + + String value = + redis + .randomkey() + .toCompletionStage() + .toCompletableFuture() + .get(30, TimeUnit.SECONDS) + .toString(); + + assertThat(value).isEqualTo("foo"); + + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasName("SET") + .hasKind(SpanKind.CLIENT) + .hasAttributesSatisfyingExactly(redisSpanAttributes("SET", "SET foo ?"))), + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasName("RANDOMKEY") + .hasKind(SpanKind.CLIENT) + .hasAttributesSatisfyingExactly( + redisSpanAttributes("RANDOMKEY", "RANDOMKEY")))); + } + + private static AttributeAssertion[] redisSpanAttributes(String operation, String statement) { + return new AttributeAssertion[] { + equalTo(SemanticAttributes.DB_SYSTEM, "redis"), + equalTo(SemanticAttributes.DB_STATEMENT, statement), + equalTo(SemanticAttributes.DB_OPERATION, operation), + equalTo(SemanticAttributes.DB_REDIS_DATABASE_INDEX, 1), + equalTo(SemanticAttributes.SERVER_ADDRESS, "localhost"), + equalTo(SemanticAttributes.SERVER_PORT, port), + equalTo(NetworkAttributes.NETWORK_PEER_PORT, port), + equalTo(NetworkAttributes.NETWORK_PEER_ADDRESS, "127.0.0.1") + }; + } +} diff --git a/muzzle/src/main/java/io/opentelemetry/javaagent/tooling/muzzle/ReferenceCollectingClassVisitor.java b/muzzle/src/main/java/io/opentelemetry/javaagent/tooling/muzzle/ReferenceCollectingClassVisitor.java index 0efe16b37323..f411d12e4dc1 100644 --- a/muzzle/src/main/java/io/opentelemetry/javaagent/tooling/muzzle/ReferenceCollectingClassVisitor.java +++ b/muzzle/src/main/java/io/opentelemetry/javaagent/tooling/muzzle/ReferenceCollectingClassVisitor.java @@ -90,6 +90,9 @@ private static MinimumVisibilityFlag computeMinimumFieldAccess(Type from, Type t private static MinimumVisibilityFlag computeMinimumMethodAccess(Type from, Type to) { if (from.getInternalName().equalsIgnoreCase(to.getInternalName())) { return MinimumVisibilityFlag.PRIVATE_OR_HIGHER; + } else if (internalPackageName(from.getInternalName()) + .equals(internalPackageName(to.getInternalName()))) { + return MinimumVisibilityFlag.PACKAGE_OR_HIGHER; } else { // Additional references: check the type hierarchy of FROM to distinguish public from // protected diff --git a/muzzle/src/test/java/io/opentelemetry/javaagent/tooling/muzzle/ReferenceCollectorTest.java b/muzzle/src/test/java/io/opentelemetry/javaagent/tooling/muzzle/ReferenceCollectorTest.java index 9ad2fcfa8a97..04cd424be6f1 100644 --- a/muzzle/src/test/java/io/opentelemetry/javaagent/tooling/muzzle/ReferenceCollectorTest.java +++ b/muzzle/src/test/java/io/opentelemetry/javaagent/tooling/muzzle/ReferenceCollectorTest.java @@ -30,6 +30,7 @@ import muzzle.TestClasses.LdcAdvice; import muzzle.TestClasses.MethodBodyAdvice; import muzzle.TestClasses.Nested; +import muzzle.other.OtherTestClasses; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -71,16 +72,15 @@ public void methodBodyCreatesReferences() { refB, "method", "(Ljava/lang/String;)Ljava/lang/String;", - PROTECTED_OR_HIGHER, + PACKAGE_OR_HIGHER, OwnershipFlag.NON_STATIC); - assertMethod( - refB, "methodWithPrimitives", "(Z)V", PROTECTED_OR_HIGHER, OwnershipFlag.NON_STATIC); - assertMethod(refB, "staticMethod", "()V", PROTECTED_OR_HIGHER, OwnershipFlag.STATIC); + assertMethod(refB, "methodWithPrimitives", "(Z)V", PACKAGE_OR_HIGHER, OwnershipFlag.NON_STATIC); + assertMethod(refB, "staticMethod", "()V", PACKAGE_OR_HIGHER, OwnershipFlag.STATIC); assertMethod( refB, "methodWithArrays", "([Ljava/lang/String;)[Ljava/lang/Object;", - PROTECTED_OR_HIGHER, + PACKAGE_OR_HIGHER, OwnershipFlag.NON_STATIC); // field refs @@ -93,7 +93,7 @@ public void methodBodyCreatesReferences() { @Test public void protectedRefTest() { ReferenceCollector collector = new ReferenceCollector(s -> false); - collector.collectReferencesFromAdvice(Nested.B2.class.getName()); + collector.collectReferencesFromAdvice(OtherTestClasses.Nested.B2.class.getName()); collector.prune(); Map references = collector.getReferences(); @@ -137,21 +137,21 @@ public void invokedynamicCreatesReferences() { references.get("muzzle.TestClasses$Nested$SomeImplementation"), "someMethod", "()V", - PROTECTED_OR_HIGHER, + PACKAGE_OR_HIGHER, OwnershipFlag.NON_STATIC); assertThat(references).containsKey("muzzle.TestClasses$Nested$B"); assertMethod( references.get("muzzle.TestClasses$Nested$B"), "staticMethod", "()V", - PROTECTED_OR_HIGHER, + PACKAGE_OR_HIGHER, OwnershipFlag.STATIC); assertThat(references).containsKey("muzzle.TestClasses$Nested$A"); assertMethod( references.get("muzzle.TestClasses$Nested$A"), "", "()V", - PROTECTED_OR_HIGHER, + PACKAGE_OR_HIGHER, OwnershipFlag.NON_STATIC); } diff --git a/muzzle/src/test/java/muzzle/other/OtherTestClasses.java b/muzzle/src/test/java/muzzle/other/OtherTestClasses.java new file mode 100644 index 000000000000..7c26ebea664f --- /dev/null +++ b/muzzle/src/test/java/muzzle/other/OtherTestClasses.java @@ -0,0 +1,26 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package muzzle.other; + +import muzzle.TestClasses; + +// to testing protected methods we need to have a test classes in different packages +@SuppressWarnings("unused") +public class OtherTestClasses { + + @SuppressWarnings("ClassNamedLikeTypeParameter") + public static class Nested { + public static class B2 extends TestClasses.Nested.B { + public void stuff() { + super.protectedMethod(); + } + } + + private Nested() {} + } + + private OtherTestClasses() {} +} diff --git a/settings.gradle.kts b/settings.gradle.kts index 9e4e25511ed7..00fe4920f9cd 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -555,6 +555,7 @@ include(":instrumentation:vertx:vertx-http-client:vertx-http-client-4.0:javaagen include(":instrumentation:vertx:vertx-http-client:vertx-http-client-common:javaagent") include(":instrumentation:vertx:vertx-kafka-client-3.6:javaagent") include(":instrumentation:vertx:vertx-kafka-client-3.6:testing") +include(":instrumentation:vertx:vertx-redis-client-4.0:javaagent") include(":instrumentation:vertx:vertx-rx-java-3.5:javaagent") include(":instrumentation:vertx:vertx-sql-client-4.0:javaagent") include(":instrumentation:vertx:vertx-web-3.0:javaagent")