Skip to content

Commit bc5398c

Browse files
authored
Add instrumentation for vert.x redis client (#9838)
1 parent e8b06c0 commit bc5398c

File tree

17 files changed

+849
-9
lines changed

17 files changed

+849
-9
lines changed

docs/supported-libraries.md

+1
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ These are the supported libraries and frameworks:
133133
| [Vert.x Web](https://vertx.io/docs/vertx-web/java/) | 3.0+ | N/A | Provides `http.route` [2] |
134134
| [Vert.x HttpClient](https://vertx.io/docs/apidocs/io/vertx/core/http/HttpClient.html) | 3.0+ | N/A | [HTTP Client Spans], [HTTP Client Metrics] |
135135
| [Vert.x Kafka Client](https://vertx.io/docs/vertx-kafka-client/java/) | 3.6+ | N/A | [Messaging Spans] |
136+
| [Vert.x Redis Client](https://vertx.io/docs/vertx-redis-client/java/) | 4.0+ | N/A | [Database Client Spans] |
136137
| [Vert.x RxJava2](https://vertx.io/docs/vertx-rx/java2/) | 3.5+ | N/A | context propagation only |
137138
| [Vert.x SQL Client](https://github.com/eclipse-vertx/vertx-sql-client/) | 4.0+ | N/A | [Database Client Spans] |
138139
| [Vibur DBCP](https://www.vibur.org/) | 11.0+ | [opentelemetry-vibur-dbcp-11.0](../instrumentation/vibur-dbcp-11.0/library) | [Database Pool Metrics] |
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
plugins {
2+
id("otel.javaagent-instrumentation")
3+
}
4+
5+
muzzle {
6+
pass {
7+
group.set("io.vertx")
8+
module.set("vertx-redis-client")
9+
versions.set("[4.0.0,)")
10+
assertInverse.set(true)
11+
}
12+
}
13+
14+
dependencies {
15+
library("io.vertx:vertx-redis-client:4.0.0")
16+
compileOnly("io.vertx:vertx-codegen:4.0.0")
17+
18+
testInstrumentation(project(":instrumentation:netty:netty-4.1:javaagent"))
19+
20+
testLibrary("io.vertx:vertx-codegen:4.0.0")
21+
}
22+
23+
tasks {
24+
withType<Test>().configureEach {
25+
usesService(gradle.sharedServices.registrations["testcontainersBuildService"].service)
26+
}
27+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.instrumentation.vertx.v4_0.redis;
7+
8+
import static net.bytebuddy.matcher.ElementMatchers.isConstructor;
9+
import static net.bytebuddy.matcher.ElementMatchers.named;
10+
11+
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
12+
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
13+
import io.vertx.redis.client.impl.CommandImpl;
14+
import net.bytebuddy.asm.Advice;
15+
import net.bytebuddy.description.type.TypeDescription;
16+
import net.bytebuddy.matcher.ElementMatcher;
17+
18+
public class CommandImplInstrumentation implements TypeInstrumentation {
19+
@Override
20+
public ElementMatcher<TypeDescription> typeMatcher() {
21+
return named("io.vertx.redis.client.impl.CommandImpl");
22+
}
23+
24+
@Override
25+
public void transform(TypeTransformer transformer) {
26+
transformer.applyAdviceToMethod(
27+
isConstructor(), this.getClass().getName() + "$ConstructorAdvice");
28+
}
29+
30+
@SuppressWarnings("unused")
31+
public static class ConstructorAdvice {
32+
@Advice.OnMethodExit(suppress = Throwable.class)
33+
public static void onExit(
34+
@Advice.This CommandImpl command, @Advice.Argument(0) String commandName) {
35+
VertxRedisClientSingletons.setCommandName(command, commandName);
36+
}
37+
}
38+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.instrumentation.vertx.v4_0.redis;
7+
8+
import static net.bytebuddy.matcher.ElementMatchers.named;
9+
import static net.bytebuddy.matcher.ElementMatchers.not;
10+
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
11+
12+
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
13+
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
14+
import io.vertx.redis.client.RedisConnection;
15+
import io.vertx.redis.client.impl.RedisStandaloneConnection;
16+
import io.vertx.redis.client.impl.RedisURI;
17+
import net.bytebuddy.asm.Advice;
18+
import net.bytebuddy.description.type.TypeDescription;
19+
import net.bytebuddy.matcher.ElementMatcher;
20+
21+
public class RedisConnectionProviderInstrumentation implements TypeInstrumentation {
22+
@Override
23+
public ElementMatcher<TypeDescription> typeMatcher() {
24+
return named("io.vertx.redis.client.impl.RedisConnectionManager$RedisConnectionProvider");
25+
}
26+
27+
@Override
28+
public void transform(TypeTransformer transformer) {
29+
// 4.1.0
30+
transformer.applyAdviceToMethod(
31+
named("init").and(not(takesArgument(0, named("io.vertx.redis.client.RedisConnection")))),
32+
this.getClass().getName() + "$InitAdvice");
33+
// 4.0.0
34+
transformer.applyAdviceToMethod(
35+
named("init").and(takesArgument(0, named("io.vertx.redis.client.RedisConnection"))),
36+
this.getClass().getName() + "$InitWithConnectionAdvice");
37+
}
38+
39+
@SuppressWarnings("unused")
40+
public static class InitAdvice {
41+
@Advice.OnMethodEnter(suppress = Throwable.class)
42+
public static void onEnter(@Advice.FieldValue("redisURI") RedisURI redisUri) {
43+
// for 4.1.0 and later we set RedisURI in a ThreadLocal that is used in advice added in
44+
// RedisStandaloneConnectionInstrumentation that attaches RedisURI to
45+
// RedisStandaloneConnection
46+
VertxRedisClientSingletons.setRedisUriThreadLocal(redisUri);
47+
}
48+
49+
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
50+
public static void onExit() {
51+
VertxRedisClientSingletons.setRedisUriThreadLocal(null);
52+
}
53+
}
54+
55+
@SuppressWarnings("unused")
56+
public static class InitWithConnectionAdvice {
57+
@Advice.OnMethodEnter(suppress = Throwable.class)
58+
public static void onEnter(
59+
@Advice.Argument(0) RedisConnection connection,
60+
@Advice.FieldValue("redisURI") RedisURI redisUri) {
61+
// for 4.0.x we don't need to use ThreadLocal like in 4.1.0 because in this method we have
62+
// access to both the RedisURI and RedisConnection
63+
if (connection instanceof RedisStandaloneConnection) {
64+
VertxRedisClientSingletons.setRedisUri((RedisStandaloneConnection) connection, redisUri);
65+
}
66+
}
67+
}
68+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.instrumentation.vertx.v4_0.redis;
7+
8+
import static io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge.currentContext;
9+
import static io.opentelemetry.javaagent.instrumentation.vertx.v4_0.redis.VertxRedisClientSingletons.instrumenter;
10+
import static net.bytebuddy.matcher.ElementMatchers.isConstructor;
11+
import static net.bytebuddy.matcher.ElementMatchers.named;
12+
13+
import io.opentelemetry.context.Context;
14+
import io.opentelemetry.context.Scope;
15+
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
16+
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
17+
import io.vertx.core.Future;
18+
import io.vertx.core.net.NetSocket;
19+
import io.vertx.redis.client.Request;
20+
import io.vertx.redis.client.Response;
21+
import io.vertx.redis.client.impl.RedisStandaloneConnection;
22+
import io.vertx.redis.client.impl.RedisURI;
23+
import io.vertx.redis.client.impl.RequestUtil;
24+
import net.bytebuddy.asm.Advice;
25+
import net.bytebuddy.description.type.TypeDescription;
26+
import net.bytebuddy.matcher.ElementMatcher;
27+
28+
public class RedisStandaloneConnectionInstrumentation implements TypeInstrumentation {
29+
@Override
30+
public ElementMatcher<TypeDescription> typeMatcher() {
31+
return named("io.vertx.redis.client.impl.RedisStandaloneConnection");
32+
}
33+
34+
@Override
35+
public void transform(TypeTransformer transformer) {
36+
transformer.applyAdviceToMethod(named("send"), this.getClass().getName() + "$SendAdvice");
37+
transformer.applyAdviceToMethod(
38+
isConstructor(), this.getClass().getName() + "$ConstructorAdvice");
39+
}
40+
41+
@SuppressWarnings("unused")
42+
public static class SendAdvice {
43+
@Advice.OnMethodEnter(suppress = Throwable.class)
44+
public static void onEnter(
45+
@Advice.This RedisStandaloneConnection connection,
46+
@Advice.Argument(0) Request request,
47+
@Advice.FieldValue("netSocket") NetSocket netSocket,
48+
@Advice.Local("otelRequest") VertxRedisClientRequest otelRequest,
49+
@Advice.Local("otelContext") Context context,
50+
@Advice.Local("otelScope") Scope scope) {
51+
if (request == null) {
52+
return;
53+
}
54+
55+
String commandName = VertxRedisClientSingletons.getCommandName(request.command());
56+
RedisURI redisUri = VertxRedisClientSingletons.getRedisUri(connection);
57+
if (commandName == null || redisUri == null) {
58+
return;
59+
}
60+
61+
otelRequest =
62+
new VertxRedisClientRequest(
63+
commandName, RequestUtil.getArgs(request), redisUri, netSocket);
64+
Context parentContext = currentContext();
65+
if (!instrumenter().shouldStart(parentContext, otelRequest)) {
66+
return;
67+
}
68+
69+
context = instrumenter().start(parentContext, otelRequest);
70+
scope = context.makeCurrent();
71+
}
72+
73+
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
74+
public static void onExit(
75+
@Advice.Thrown Throwable throwable,
76+
@Advice.Return(readOnly = false) Future<Response> responseFuture,
77+
@Advice.Local("otelRequest") VertxRedisClientRequest otelRequest,
78+
@Advice.Local("otelContext") Context context,
79+
@Advice.Local("otelScope") Scope scope) {
80+
if (scope == null) {
81+
return;
82+
}
83+
84+
scope.close();
85+
if (throwable != null) {
86+
instrumenter().end(context, otelRequest, null, throwable);
87+
} else {
88+
responseFuture =
89+
VertxRedisClientSingletons.wrapEndSpan(responseFuture, context, otelRequest);
90+
}
91+
}
92+
}
93+
94+
@SuppressWarnings("unused")
95+
public static class ConstructorAdvice {
96+
@Advice.OnMethodExit(suppress = Throwable.class)
97+
public static void onExit(@Advice.This RedisStandaloneConnection connection) {
98+
// used in 4.1.0, for 4.0.0 it is set in RedisConnectionProviderInstrumentation
99+
VertxRedisClientSingletons.setRedisUri(
100+
connection, VertxRedisClientSingletons.getRedisUriThreadLocal());
101+
}
102+
}
103+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.instrumentation.vertx.v4_0.redis;
7+
8+
import static io.opentelemetry.instrumentation.api.internal.AttributesExtractorUtil.internalSet;
9+
10+
import io.opentelemetry.api.common.AttributesBuilder;
11+
import io.opentelemetry.context.Context;
12+
import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor;
13+
import io.opentelemetry.semconv.SemanticAttributes;
14+
import javax.annotation.Nullable;
15+
16+
enum VertxRedisClientAttributesExtractor
17+
implements AttributesExtractor<VertxRedisClientRequest, Void> {
18+
INSTANCE;
19+
20+
@Override
21+
public void onStart(
22+
AttributesBuilder attributes, Context parentContext, VertxRedisClientRequest request) {
23+
internalSet(attributes, SemanticAttributes.DB_REDIS_DATABASE_INDEX, request.getDatabaseIndex());
24+
}
25+
26+
@Override
27+
public void onEnd(
28+
AttributesBuilder attributes,
29+
Context context,
30+
VertxRedisClientRequest request,
31+
@Nullable Void unused,
32+
@Nullable Throwable error) {}
33+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.instrumentation.vertx.v4_0.redis;
7+
8+
import io.opentelemetry.instrumentation.api.incubator.semconv.db.DbClientAttributesGetter;
9+
import io.opentelemetry.instrumentation.api.incubator.semconv.db.RedisCommandSanitizer;
10+
import io.opentelemetry.javaagent.bootstrap.internal.CommonConfig;
11+
import io.opentelemetry.semconv.SemanticAttributes;
12+
import javax.annotation.Nullable;
13+
14+
public enum VertxRedisClientAttributesGetter
15+
implements DbClientAttributesGetter<VertxRedisClientRequest> {
16+
INSTANCE;
17+
18+
private static final RedisCommandSanitizer sanitizer =
19+
RedisCommandSanitizer.create(CommonConfig.get().isStatementSanitizationEnabled());
20+
21+
@Override
22+
public String getSystem(VertxRedisClientRequest request) {
23+
return SemanticAttributes.DbSystemValues.REDIS;
24+
}
25+
26+
@Override
27+
@Nullable
28+
public String getUser(VertxRedisClientRequest request) {
29+
return request.getUser();
30+
}
31+
32+
@Override
33+
@Nullable
34+
public String getName(VertxRedisClientRequest request) {
35+
return null;
36+
}
37+
38+
@Override
39+
@Nullable
40+
public String getConnectionString(VertxRedisClientRequest request) {
41+
return request.getConnectionString();
42+
}
43+
44+
@Override
45+
public String getStatement(VertxRedisClientRequest request) {
46+
return sanitizer.sanitize(request.getCommand(), request.getArgs());
47+
}
48+
49+
@Nullable
50+
@Override
51+
public String getOperation(VertxRedisClientRequest request) {
52+
return request.getCommand();
53+
}
54+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.instrumentation.vertx.v4_0.redis;
7+
8+
import static java.util.Arrays.asList;
9+
import static java.util.Collections.singletonList;
10+
11+
import com.google.auto.service.AutoService;
12+
import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule;
13+
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
14+
import io.opentelemetry.javaagent.extension.instrumentation.internal.ExperimentalInstrumentationModule;
15+
import java.util.List;
16+
17+
@AutoService(InstrumentationModule.class)
18+
public class VertxRedisClientInstrumentationModule extends InstrumentationModule
19+
implements ExperimentalInstrumentationModule {
20+
21+
public VertxRedisClientInstrumentationModule() {
22+
super("vertx-redis-client", "vertx-redis-client-4.0", "vertx");
23+
}
24+
25+
@Override
26+
public boolean isHelperClass(String className) {
27+
return "io.vertx.redis.client.impl.RequestUtil".equals(className);
28+
}
29+
30+
@Override
31+
public List<String> injectedClassNames() {
32+
return singletonList("io.vertx.redis.client.impl.RequestUtil");
33+
}
34+
35+
@Override
36+
public List<TypeInstrumentation> typeInstrumentations() {
37+
return asList(
38+
new RedisStandaloneConnectionInstrumentation(),
39+
new RedisConnectionProviderInstrumentation(),
40+
new CommandImplInstrumentation());
41+
}
42+
}

0 commit comments

Comments
 (0)