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 instrumentation for vert.x redis client #9838

Merged
merged 6 commits into from
Dec 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/supported-libraries.md
Original file line number Diff line number Diff line change
Expand Up @@ -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] |
Expand Down
Original file line number Diff line number Diff line change
@@ -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<Test>().configureEach {
usesService(gradle.sharedServices.registrations["testcontainersBuildService"].service)
}
}
Original file line number Diff line number Diff line change
@@ -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<TypeDescription> 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);
}
}
}
Original file line number Diff line number Diff line change
@@ -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<TypeDescription> 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);
laurit marked this conversation as resolved.
Show resolved Hide resolved
}
}

@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);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -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<TypeDescription> 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<Response> 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());
}
}
}
Original file line number Diff line number Diff line change
@@ -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<VertxRedisClientRequest, Void> {
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) {}
}
Original file line number Diff line number Diff line change
@@ -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<VertxRedisClientRequest> {
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();
}
}
Original file line number Diff line number Diff line change
@@ -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);
breedx-splk marked this conversation as resolved.
Show resolved Hide resolved
}
laurit marked this conversation as resolved.
Show resolved Hide resolved

@Override
public List<String> injectedClassNames() {
return singletonList("io.vertx.redis.client.impl.RequestUtil");
}

@Override
public List<TypeInstrumentation> typeInstrumentations() {
return asList(
new RedisStandaloneConnectionInstrumentation(),
new RedisConnectionProviderInstrumentation(),
new CommandImplInstrumentation());
}
}
Loading
Loading