Skip to content

Commit aeb5b6b

Browse files
committed
support newer Database ex
1 parent 5dc4f51 commit aeb5b6b

File tree

4 files changed

+228
-16
lines changed

4 files changed

+228
-16
lines changed

instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/internal/SemconvStability.java

+7-2
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@
1818
*/
1919
public final class SemconvStability {
2020

21-
private static final boolean emitOldDatabaseSemconv;
22-
private static final boolean emitStableDatabaseSemconv;
21+
private static boolean emitOldDatabaseSemconv;
22+
private static boolean emitStableDatabaseSemconv;
2323

2424
static {
2525
boolean oldDatabase = true;
@@ -78,4 +78,9 @@ public static String stableDbSystemName(String oldDbSystem) {
7878
}
7979

8080
private SemconvStability() {}
81+
82+
public static void setForTesting(boolean emitOldDatabaseSemconv, boolean emitStableDatabaseSemconv) {
83+
SemconvStability.emitOldDatabaseSemconv = emitOldDatabaseSemconv;
84+
SemconvStability.emitStableDatabaseSemconv = emitStableDatabaseSemconv;
85+
}
8186
}

instrumentation/vertx/vertx-sql-client-4.0/javaagent/build.gradle.kts

+22-4
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,31 @@ muzzle {
1212
}
1313

1414
dependencies {
15-
library("io.vertx:vertx-sql-client:4.0.0")
16-
compileOnly("io.vertx:vertx-codegen:4.0.0")
15+
val version = "4.0.0"
16+
library("io.vertx:vertx-sql-client:$version")
17+
compileOnly("io.vertx:vertx-codegen:$version")
1718

1819
testInstrumentation(project(":instrumentation:netty:netty-4.1:javaagent"))
1920

20-
testLibrary("io.vertx:vertx-pg-client:4.0.0")
21-
testLibrary("io.vertx:vertx-codegen:4.0.0")
21+
testLibrary("io.vertx:vertx-pg-client:$version")
22+
testLibrary("io.vertx:vertx-codegen:$version")
23+
}
24+
25+
testing {
26+
suites {
27+
val testVertx5 by registering(JvmTestSuite::class) {
28+
val version = "4.5.13"
29+
dependencies {
30+
implementation(project())
31+
implementation("io.vertx:vertx-sql-client:$version")
32+
implementation("io.vertx:vertx-codegen:$version")
33+
implementation("io.vertx:vertx-pg-client:$version")
34+
implementation("org.testcontainers:testcontainers")
35+
36+
implementation(project(":instrumentation:netty:netty-4.1:javaagent"))
37+
}
38+
}
39+
}
2240
}
2341

2442
tasks {

instrumentation/vertx/vertx-sql-client-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/v4_0/sql/VertxSqlClientAttributesGetter.java

+40-10
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,20 @@
99

1010
import io.opentelemetry.instrumentation.api.incubator.semconv.db.SqlClientAttributesGetter;
1111
import java.lang.reflect.InvocationTargetException;
12+
import java.lang.reflect.Method;
13+
import java.util.Arrays;
1214
import java.util.Collection;
15+
import java.util.List;
16+
import java.util.function.Function;
1317
import javax.annotation.Nullable;
1418

1519
public enum VertxSqlClientAttributesGetter
1620
implements SqlClientAttributesGetter<VertxSqlClientRequest, Void> {
1721
INSTANCE;
1822

23+
private static final List<Function<Exception, String>> responseStatusExtractors =
24+
createResponseStatusExtractors();
25+
1926
@Override
2027
public String getDbSystem(VertxSqlClientRequest request) {
2128
return null;
@@ -49,18 +56,41 @@ public Collection<String> getRawQueryTexts(VertxSqlClientRequest request) {
4956
@Nullable
5057
@Override
5158
public String getResponseStatus(@Nullable Void response, @Nullable Throwable error) {
52-
try {
53-
// loaded via reflection, because this class is not available in all versions that we support
54-
Class<?> ex = Class.forName("io.vertx.pgclient.PgException");
55-
if (ex.isInstance(error)) {
56-
return (String) ex.getMethod("getCode").invoke(error);
59+
for (Function<Exception, String> extractor : responseStatusExtractors) {
60+
String status = extractor.apply((Exception) error);
61+
if (status != null) {
62+
return status;
5763
}
58-
} catch (ClassNotFoundException
59-
| NoSuchMethodException
60-
| IllegalAccessException
61-
| InvocationTargetException e) {
62-
return null;
6364
}
6465
return null;
6566
}
67+
68+
private static List<Function<Exception, String>> createResponseStatusExtractors() {
69+
return Arrays.asList(
70+
responseStatusExtractor("io.vertx.sqlclient.DatabaseException", "getSqlState"),
71+
// older version only have this method
72+
responseStatusExtractor("io.vertx.pgclient.PgException", "getCode"));
73+
}
74+
75+
private static Function<Exception, String> responseStatusExtractor(
76+
String className, String methodName) {
77+
try {
78+
// loaded via reflection, because this class is not available in all versions that we support
79+
Class<?> exClass = Class.forName(className);
80+
Method method = exClass.getDeclaredMethod(methodName);
81+
82+
return (error) -> {
83+
if (exClass.isInstance(error)) {
84+
try {
85+
return String.valueOf(method.invoke(error)); // can be String or int
86+
} catch (IllegalAccessException | InvocationTargetException e) {
87+
return null;
88+
}
89+
}
90+
return null;
91+
};
92+
} catch (ClassNotFoundException | NoSuchMethodException e) {
93+
return (error) -> null;
94+
}
95+
}
6696
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.instrumentation.vertx.v4_0.sql;
7+
8+
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo;
9+
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.satisfies;
10+
import static io.opentelemetry.semconv.ErrorAttributes.ERROR_TYPE;
11+
import static io.opentelemetry.semconv.ExceptionAttributes.EXCEPTION_MESSAGE;
12+
import static io.opentelemetry.semconv.ExceptionAttributes.EXCEPTION_STACKTRACE;
13+
import static io.opentelemetry.semconv.ExceptionAttributes.EXCEPTION_TYPE;
14+
import static io.opentelemetry.semconv.ServerAttributes.SERVER_ADDRESS;
15+
import static io.opentelemetry.semconv.ServerAttributes.SERVER_PORT;
16+
import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_NAMESPACE;
17+
import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_QUERY_TEXT;
18+
import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_RESPONSE_STATUS_CODE;
19+
20+
import io.opentelemetry.api.trace.SpanKind;
21+
import io.opentelemetry.instrumentation.api.internal.SemconvStability;
22+
import io.opentelemetry.instrumentation.testing.internal.AutoCleanupExtension;
23+
import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension;
24+
import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension;
25+
import io.opentelemetry.sdk.trace.data.StatusData;
26+
import io.vertx.core.Vertx;
27+
import io.vertx.pgclient.PgConnectOptions;
28+
import io.vertx.pgclient.PgException;
29+
import io.vertx.sqlclient.Pool;
30+
import io.vertx.sqlclient.PoolOptions;
31+
import java.time.Duration;
32+
import java.util.concurrent.CompletableFuture;
33+
import java.util.concurrent.CountDownLatch;
34+
import java.util.concurrent.TimeUnit;
35+
import org.junit.jupiter.api.AfterAll;
36+
import org.junit.jupiter.api.BeforeAll;
37+
import org.junit.jupiter.api.Test;
38+
import org.junit.jupiter.api.extension.RegisterExtension;
39+
import org.slf4j.Logger;
40+
import org.slf4j.LoggerFactory;
41+
import org.testcontainers.containers.GenericContainer;
42+
import org.testcontainers.containers.output.Slf4jLogConsumer;
43+
44+
class VertxSqlClientTest {
45+
private static final Logger logger = LoggerFactory.getLogger(VertxSqlClientTest.class);
46+
47+
private static final String USER_DB = "SA";
48+
private static final String PW_DB = "password123";
49+
private static final String DB = "tempdb";
50+
51+
@RegisterExtension
52+
private static final InstrumentationExtension testing = AgentInstrumentationExtension.create();
53+
54+
@RegisterExtension
55+
private static final AutoCleanupExtension cleanup = AutoCleanupExtension.create();
56+
57+
private static GenericContainer<?> container;
58+
private static Vertx vertx;
59+
private static Pool pool;
60+
private static String host;
61+
private static int port;
62+
63+
@BeforeAll
64+
static void setUp() throws Exception {
65+
SemconvStability.setForTesting(false, true);
66+
container =
67+
new GenericContainer<>("postgres:9.6.8")
68+
.withEnv("POSTGRES_USER", USER_DB)
69+
.withEnv("POSTGRES_PASSWORD", PW_DB)
70+
.withEnv("POSTGRES_DB", DB)
71+
.withExposedPorts(5432)
72+
.withLogConsumer(new Slf4jLogConsumer(logger))
73+
.withStartupTimeout(Duration.ofMinutes(2));
74+
container.start();
75+
vertx = Vertx.vertx();
76+
host = container.getHost();
77+
port = container.getMappedPort(5432);
78+
PgConnectOptions options =
79+
new PgConnectOptions()
80+
.setPort(port)
81+
.setHost(host)
82+
.setDatabase(DB)
83+
.setUser(USER_DB)
84+
.setPassword(PW_DB);
85+
pool = Pool.pool(vertx, options, new PoolOptions().setMaxSize(4));
86+
pool.query("create table test(id int primary key, name varchar(255))")
87+
.execute()
88+
.compose(
89+
r ->
90+
// insert some test data
91+
pool.query("insert into test values (1, 'Hello'), (2, 'World')").execute())
92+
.toCompletionStage()
93+
.toCompletableFuture()
94+
.get(30, TimeUnit.SECONDS);
95+
}
96+
97+
@AfterAll
98+
static void cleanUp() {
99+
pool.close();
100+
vertx.close();
101+
container.stop();
102+
}
103+
104+
@Test
105+
void testInvalidQuery() throws Exception {
106+
CountDownLatch latch = new CountDownLatch(1);
107+
CompletableFuture<Object> result = new CompletableFuture<>();
108+
result.whenComplete((rows, throwable) -> testing.runWithSpan("callback", latch::countDown));
109+
testing.runWithSpan(
110+
"parent",
111+
() ->
112+
pool.query("invalid")
113+
.execute(
114+
rowSetAsyncResult -> {
115+
if (rowSetAsyncResult.succeeded()) {
116+
result.complete(rowSetAsyncResult.result());
117+
} else {
118+
result.completeExceptionally(rowSetAsyncResult.cause());
119+
}
120+
}));
121+
122+
latch.await(30, TimeUnit.SECONDS);
123+
124+
testing.waitAndAssertTraces(
125+
trace ->
126+
trace.hasSpansSatisfyingExactly(
127+
span -> span.hasName("parent").hasKind(SpanKind.INTERNAL),
128+
span ->
129+
span.hasName("tempdb")
130+
.hasKind(SpanKind.CLIENT)
131+
.hasParent(trace.getSpan(0))
132+
.hasStatus(StatusData.error())
133+
.hasEventsSatisfyingExactly(
134+
event ->
135+
event
136+
.hasName("exception")
137+
.hasAttributesSatisfyingExactly(
138+
equalTo(EXCEPTION_TYPE, PgException.class.getName()),
139+
satisfies(
140+
EXCEPTION_MESSAGE,
141+
val -> val.contains("syntax error at or near")),
142+
satisfies(
143+
EXCEPTION_STACKTRACE,
144+
val -> val.isInstanceOf(String.class))))
145+
.hasAttributesSatisfyingExactly(
146+
equalTo(DB_NAMESPACE, DB),
147+
equalTo(DB_QUERY_TEXT, "invalid"),
148+
equalTo(SERVER_ADDRESS, host),
149+
equalTo(SERVER_PORT, port),
150+
equalTo(DB_RESPONSE_STATUS_CODE, "42601"),
151+
// is the same as in the older versions of vertx, but extracted from
152+
// io.vertx.sqlclient.DatabaseException
153+
equalTo(ERROR_TYPE, "io.vertx.pgclient.PgException")),
154+
span ->
155+
span.hasName("callback")
156+
.hasKind(SpanKind.INTERNAL)
157+
.hasParent(trace.getSpan(0))));
158+
}
159+
}

0 commit comments

Comments
 (0)