Skip to content

Commit 1c1f93a

Browse files
wl2027laurit
andauthored
fix: datasource instrumentation to support custom connection (#14602)
Co-authored-by: Lauri Tulmin <[email protected]>
1 parent 011201c commit 1c1f93a

File tree

4 files changed

+104
-12
lines changed

4 files changed

+104
-12
lines changed

instrumentation/jdbc/javaagent/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ dependencies {
3131
testLibrary("org.apache.tomcat:tomcat-juli:7.0.19")
3232
testLibrary("com.zaxxer:HikariCP:2.4.0")
3333
testLibrary("com.mchange:c3p0:0.9.5")
34+
testLibrary("com.alibaba:druid:1.2.20")
3435

3536
// some classes in earlier versions of derby were split out into derbytools in later versions
3637
latestDepTestLibrary("org.apache.derby:derbytools:latest.release")

instrumentation/jdbc/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jdbc/datasource/DataSourceInstrumentation.java

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import io.opentelemetry.context.Context;
1414
import io.opentelemetry.context.Scope;
1515
import io.opentelemetry.instrumentation.jdbc.internal.JdbcUtils;
16+
import io.opentelemetry.javaagent.bootstrap.CallDepth;
1617
import io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge;
1718
import io.opentelemetry.javaagent.bootstrap.jdbc.DbInfo;
1819
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
@@ -33,7 +34,7 @@ public ElementMatcher<TypeDescription> typeMatcher() {
3334
@Override
3435
public void transform(TypeTransformer transformer) {
3536
transformer.applyAdviceToMethod(
36-
named("getConnection").and(returns(named("java.sql.Connection"))),
37+
named("getConnection").and(returns(implementsInterface(named("java.sql.Connection")))),
3738
DataSourceInstrumentation.class.getName() + "$GetConnectionAdvice");
3839
}
3940

@@ -44,7 +45,13 @@ public static class GetConnectionAdvice {
4445
public static void start(
4546
@Advice.This DataSource ds,
4647
@Advice.Local("otelContext") Context context,
47-
@Advice.Local("otelScope") Scope scope) {
48+
@Advice.Local("otelScope") Scope scope,
49+
@Advice.Local("otelCallDepth") CallDepth callDepth) {
50+
callDepth = CallDepth.forClass(DataSource.class);
51+
if (callDepth.getAndIncrement() > 0) {
52+
return;
53+
}
54+
4855
Context parentContext = Java8BytecodeBridge.currentContext();
4956
if (!Java8BytecodeBridge.spanFromContext(parentContext).getSpanContext().isValid()) {
5057
// this instrumentation is already very noisy, and calls to getConnection outside of an
@@ -64,7 +71,12 @@ public static void stopSpan(
6471
@Advice.Return Connection connection,
6572
@Advice.Thrown Throwable throwable,
6673
@Advice.Local("otelContext") Context context,
67-
@Advice.Local("otelScope") Scope scope) {
74+
@Advice.Local("otelScope") Scope scope,
75+
@Advice.Local("otelCallDepth") CallDepth callDepth) {
76+
if (callDepth.decrementAndGet() > 0) {
77+
return;
78+
}
79+
6880
if (scope == null) {
6981
return;
7082
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.instrumentation.jdbc.test;
7+
8+
import static io.opentelemetry.api.trace.SpanKind.INTERNAL;
9+
import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitStableDatabaseSemconv;
10+
import static io.opentelemetry.instrumentation.testing.junit.db.SemconvStabilityUtil.maybeStable;
11+
import static io.opentelemetry.instrumentation.testing.junit.db.SemconvStabilityUtil.maybeStableDbSystemName;
12+
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo;
13+
import static io.opentelemetry.semconv.incubating.CodeIncubatingAttributes.CODE_FUNCTION;
14+
import static io.opentelemetry.semconv.incubating.CodeIncubatingAttributes.CODE_NAMESPACE;
15+
import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_CONNECTION_STRING;
16+
import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_NAME;
17+
import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_SYSTEM;
18+
import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_USER;
19+
20+
import com.alibaba.druid.pool.DruidDataSource;
21+
import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension;
22+
import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension;
23+
import java.sql.Connection;
24+
import java.sql.SQLException;
25+
import javax.sql.DataSource;
26+
import org.junit.jupiter.api.AfterEach;
27+
import org.junit.jupiter.api.BeforeEach;
28+
import org.junit.jupiter.api.Test;
29+
import org.junit.jupiter.api.extension.RegisterExtension;
30+
31+
@SuppressWarnings("deprecation") // using deprecated semconv
32+
class DruicDataSourceTest {
33+
34+
@RegisterExtension
35+
static final InstrumentationExtension testing = AgentInstrumentationExtension.create();
36+
37+
private DataSource dataSource;
38+
39+
@BeforeEach
40+
void setUp() {
41+
DruidDataSource druidDataSource = new DruidDataSource();
42+
druidDataSource.setUrl("jdbc:h2:mem:test");
43+
druidDataSource.setDriverClassName("org.h2.Driver");
44+
druidDataSource.setUsername("sa");
45+
druidDataSource.setPassword("");
46+
druidDataSource.setMaxActive(1);
47+
this.dataSource = druidDataSource;
48+
}
49+
50+
@AfterEach
51+
void tearDown() {
52+
if (dataSource instanceof DruidDataSource) {
53+
((DruidDataSource) dataSource).close();
54+
}
55+
}
56+
57+
@Test
58+
void testGetConnection() throws SQLException {
59+
// In DruidDataSource we instrument both DruidPooledConnection getConnection() and the bridge
60+
// method Connection getConnection(). Here we call Connection getConnection() that delegates
61+
// to DruidPooledConnection getConnection(), and verify that only one span is created.
62+
testing.runWithSpan(
63+
"parent",
64+
() -> {
65+
try (Connection connection = dataSource.getConnection()) {
66+
return null;
67+
}
68+
});
69+
70+
testing.waitAndAssertTraces(
71+
trace ->
72+
trace.hasSpansSatisfyingExactly(
73+
span -> span.hasName("parent").hasKind(INTERNAL).hasNoParent(),
74+
span ->
75+
span.hasName("DruidDataSource.getConnection")
76+
.hasKind(INTERNAL)
77+
.hasParent(trace.getSpan(0))
78+
.hasAttributesSatisfyingExactly(
79+
equalTo(CODE_NAMESPACE, "com.alibaba.druid.pool.DruidDataSource"),
80+
equalTo(CODE_FUNCTION, "getConnection"),
81+
equalTo(
82+
DB_CONNECTION_STRING,
83+
emitStableDatabaseSemconv() ? null : "h2:mem:"),
84+
equalTo(maybeStable(DB_NAME), "test"),
85+
equalTo(maybeStable(DB_SYSTEM), maybeStableDbSystemName("h2")),
86+
equalTo(DB_USER, emitStableDatabaseSemconv() ? null : "sa"))));
87+
}
88+
}

instrumentation/jdbc/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jdbc/test/JdbcInstrumentationTest.java

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1041,7 +1041,6 @@ void testGetConnection(
10411041
throws SQLException {
10421042
// Tomcat's pool doesn't work because the getConnection method is
10431043
// implemented in a parent class that doesn't implement DataSource
1044-
boolean recursive = datasource instanceof EmbeddedDataSource;
10451044

10461045
if (init != null) {
10471046
init.accept(datasource);
@@ -1073,14 +1072,6 @@ void testGetConnection(
10731072
.hasKind(SpanKind.INTERNAL)
10741073
.hasParent(trace.getSpan(0))
10751074
.hasAttributesSatisfyingExactly(attributesAssertions)));
1076-
if (recursive) {
1077-
assertions.add(
1078-
span ->
1079-
span.hasName(datasource.getClass().getSimpleName() + ".getConnection")
1080-
.hasKind(SpanKind.INTERNAL)
1081-
.hasParent(trace.getSpan(1))
1082-
.hasAttributesSatisfyingExactly(attributesAssertions));
1083-
}
10841075
trace.hasSpansSatisfyingExactly(assertions);
10851076
});
10861077
}

0 commit comments

Comments
 (0)