Skip to content

Commit

Permalink
Add instrumentation for vert.x redis client (open-telemetry#9838)
Browse files Browse the repository at this point in the history
  • Loading branch information
laurit authored and zeitlinger committed Dec 12, 2023
1 parent 6b2f018 commit 458066f
Show file tree
Hide file tree
Showing 17 changed files with 849 additions and 9 deletions.
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);
}
}

@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);
}

@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

0 comments on commit 458066f

Please sign in to comment.