Skip to content

Commit dccbb93

Browse files
authored
make jdbc indy-ready (#14764)
1 parent 9305d86 commit dccbb93

File tree

7 files changed

+231
-211
lines changed

7 files changed

+231
-211
lines changed

instrumentation/jdbc/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jdbc/ConnectionInstrumentation.java

Lines changed: 40 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import java.sql.Connection;
2727
import java.sql.PreparedStatement;
2828
import java.util.Locale;
29+
import javax.annotation.Nullable;
2930
import net.bytebuddy.asm.Advice;
3031
import net.bytebuddy.description.type.TypeDescription;
3132
import net.bytebuddy.matcher.ElementMatcher;
@@ -68,35 +69,52 @@ public static void addDbInfo(
6869
@SuppressWarnings("unused")
6970
public static class TransactionAdvice {
7071

71-
@Advice.OnMethodEnter(suppress = Throwable.class)
72-
public static void onEnter(
73-
@Advice.This Connection connection,
74-
@Advice.Origin("#m") String methodName,
75-
@Advice.Local("otelContext") Context context,
76-
@Advice.Local("otelScope") Scope scope) {
77-
Context parentContext = currentContext();
78-
DbRequest request =
79-
DbRequest.createTransaction(connection, methodName.toUpperCase(Locale.ROOT));
80-
81-
if (request == null || !transactionInstrumenter().shouldStart(parentContext, request)) {
82-
return;
72+
public static final class AdviceScope {
73+
private final DbRequest request;
74+
private final Context context;
75+
private final Scope scope;
76+
77+
private AdviceScope(DbRequest request, Context context, Scope scope) {
78+
this.request = request;
79+
this.context = context;
80+
this.scope = scope;
81+
}
82+
83+
@Nullable
84+
public static AdviceScope start(Connection connection, String methodName) {
85+
DbRequest request =
86+
DbRequest.createTransaction(connection, methodName.toUpperCase(Locale.ROOT));
87+
if (request == null) {
88+
return null;
89+
}
90+
Context parentContext = currentContext();
91+
if (!transactionInstrumenter().shouldStart(parentContext, request)) {
92+
return null;
93+
}
94+
95+
Context context = transactionInstrumenter().start(parentContext, request);
96+
return new AdviceScope(request, context, context.makeCurrent());
8397
}
8498

85-
context = transactionInstrumenter().start(parentContext, request);
86-
scope = context.makeCurrent();
99+
public void end(@Nullable Throwable throwable) {
100+
scope.close();
101+
transactionInstrumenter().end(context, request, null, throwable);
102+
}
103+
}
104+
105+
@Advice.OnMethodEnter(suppress = Throwable.class)
106+
public static AdviceScope onEnter(
107+
@Advice.This Connection connection, @Advice.Origin("#m") String methodName) {
108+
return AdviceScope.start(connection, methodName);
87109
}
88110

89111
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
90112
public static void stopSpan(
91-
@Advice.Thrown Throwable throwable,
92-
@Advice.Local("otelRequest") DbRequest request,
93-
@Advice.Local("otelContext") Context context,
94-
@Advice.Local("otelScope") Scope scope) {
95-
if (scope == null) {
96-
return;
113+
@Advice.Thrown @Nullable Throwable throwable,
114+
@Advice.Enter @Nullable AdviceScope adviceScope) {
115+
if (adviceScope != null) {
116+
adviceScope.end(throwable);
97117
}
98-
scope.close();
99-
transactionInstrumenter().end(context, request, null, throwable);
100118
}
101119
}
102120
}
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.instrumentation.jdbc;
7+
8+
import static io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge.currentContext;
9+
import static io.opentelemetry.javaagent.instrumentation.jdbc.JdbcSingletons.statementInstrumenter;
10+
11+
import io.opentelemetry.context.Context;
12+
import io.opentelemetry.context.Scope;
13+
import io.opentelemetry.instrumentation.jdbc.internal.DbRequest;
14+
import io.opentelemetry.instrumentation.jdbc.internal.JdbcData;
15+
import io.opentelemetry.javaagent.bootstrap.CallDepth;
16+
import java.sql.PreparedStatement;
17+
import java.sql.Statement;
18+
import java.util.Map;
19+
import java.util.function.Supplier;
20+
import javax.annotation.Nullable;
21+
22+
public class JdbcAdviceScope {
23+
private final CallDepth callDepth;
24+
private final DbRequest request;
25+
private final Context context;
26+
private final Scope scope;
27+
28+
private JdbcAdviceScope(CallDepth callDepth, DbRequest request, Context context, Scope scope) {
29+
this.callDepth = callDepth;
30+
this.request = request;
31+
this.context = context;
32+
this.scope = scope;
33+
}
34+
35+
public static JdbcAdviceScope startBatch(CallDepth callDepth, Statement statement) {
36+
return start(callDepth, () -> createBatchRequest(statement));
37+
}
38+
39+
public static JdbcAdviceScope startStatement(
40+
CallDepth callDepth, String sql, Statement statement) {
41+
return start(callDepth, () -> DbRequest.create(statement, sql));
42+
}
43+
44+
public static JdbcAdviceScope startPreparedStatement(
45+
CallDepth callDepth, PreparedStatement preparedStatement) {
46+
return start(
47+
callDepth,
48+
() -> DbRequest.create(preparedStatement, JdbcData.getParameters(preparedStatement)));
49+
}
50+
51+
private static JdbcAdviceScope start(CallDepth callDepth, Supplier<DbRequest> requestSupplier) {
52+
// Connection#getMetaData() may execute a Statement or PreparedStatement to retrieve DB info
53+
// this happens before the DB CLIENT span is started (and put in the current context), so this
54+
// instrumentation runs again and the shouldStartSpan() check always returns true - and so on
55+
// until we get a StackOverflowError
56+
// using CallDepth prevents this, because this check happens before Connection#getMetadata()
57+
// is called - the first recursive Statement call is just skipped and we do not create a span
58+
// for it
59+
if (callDepth.getAndIncrement() > 0) {
60+
return new JdbcAdviceScope(callDepth, null, null, null);
61+
}
62+
63+
Context parentContext = currentContext();
64+
DbRequest request = requestSupplier.get();
65+
if (request == null || !statementInstrumenter().shouldStart(parentContext, request)) {
66+
return new JdbcAdviceScope(callDepth, null, null, null);
67+
}
68+
69+
Context context = statementInstrumenter().start(parentContext, request);
70+
return new JdbcAdviceScope(callDepth, request, context, context.makeCurrent());
71+
}
72+
73+
private static DbRequest createBatchRequest(Statement statement) {
74+
if (statement instanceof PreparedStatement) {
75+
String sql = JdbcData.preparedStatement.get((PreparedStatement) statement);
76+
if (sql == null) {
77+
return null;
78+
}
79+
Long batchSize = JdbcData.getPreparedStatementBatchSize((PreparedStatement) statement);
80+
Map<String, String> parameters = JdbcData.getParameters((PreparedStatement) statement);
81+
return DbRequest.create(statement, sql, batchSize, parameters);
82+
} else {
83+
JdbcData.StatementBatchInfo batchInfo = JdbcData.getStatementBatchInfo(statement);
84+
if (batchInfo == null) {
85+
return DbRequest.create(statement, null);
86+
} else {
87+
return DbRequest.create(statement, batchInfo.getStatements(), batchInfo.getBatchSize());
88+
}
89+
}
90+
}
91+
92+
public void end(@Nullable Throwable throwable) {
93+
if (callDepth.decrementAndGet() > 0) {
94+
return;
95+
}
96+
if (scope == null) {
97+
return;
98+
}
99+
scope.close();
100+
statementInstrumenter().end(context, request, null, throwable);
101+
}
102+
}

instrumentation/jdbc/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jdbc/JdbcInstrumentationModule.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,12 @@
1010
import com.google.auto.service.AutoService;
1111
import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule;
1212
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
13+
import io.opentelemetry.javaagent.extension.instrumentation.internal.ExperimentalInstrumentationModule;
1314
import java.util.List;
1415

1516
@AutoService(InstrumentationModule.class)
16-
public class JdbcInstrumentationModule extends InstrumentationModule {
17+
public class JdbcInstrumentationModule extends InstrumentationModule
18+
implements ExperimentalInstrumentationModule {
1719
public JdbcInstrumentationModule() {
1820
super("jdbc");
1921
}
@@ -26,4 +28,9 @@ public List<TypeInstrumentation> typeInstrumentations() {
2628
new PreparedStatementInstrumentation(),
2729
new StatementInstrumentation());
2830
}
31+
32+
@Override
33+
public boolean isIndyReady() {
34+
return true;
35+
}
2936
}

instrumentation/jdbc/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jdbc/PreparedStatementInstrumentation.java

Lines changed: 9 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,9 @@
55

66
package io.opentelemetry.javaagent.instrumentation.jdbc;
77

8-
import static io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge.currentContext;
98
import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed;
109
import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.implementsInterface;
1110
import static io.opentelemetry.javaagent.instrumentation.jdbc.JdbcSingletons.CAPTURE_QUERY_PARAMETERS;
12-
import static io.opentelemetry.javaagent.instrumentation.jdbc.JdbcSingletons.statementInstrumenter;
1311
import static net.bytebuddy.matcher.ElementMatchers.isPublic;
1412
import static net.bytebuddy.matcher.ElementMatchers.nameStartsWith;
1513
import static net.bytebuddy.matcher.ElementMatchers.named;
@@ -19,9 +17,6 @@
1917
import static net.bytebuddy.matcher.ElementMatchers.takesArguments;
2018
import static net.bytebuddy.matcher.ElementMatchers.takesNoArguments;
2119

22-
import io.opentelemetry.context.Context;
23-
import io.opentelemetry.context.Scope;
24-
import io.opentelemetry.instrumentation.jdbc.internal.DbRequest;
2520
import io.opentelemetry.instrumentation.jdbc.internal.JdbcData;
2621
import io.opentelemetry.javaagent.bootstrap.CallDepth;
2722
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
@@ -34,7 +29,7 @@
3429
import java.sql.Time;
3530
import java.sql.Timestamp;
3631
import java.util.Calendar;
37-
import java.util.Map;
32+
import javax.annotation.Nullable;
3833
import net.bytebuddy.asm.Advice;
3934
import net.bytebuddy.description.type.TypeDescription;
4035
import net.bytebuddy.matcher.ElementMatcher;
@@ -105,57 +100,24 @@ public void transform(TypeTransformer transformer) {
105100
@SuppressWarnings("unused")
106101
public static class PreparedStatementAdvice {
107102

103+
@Nullable
108104
@Advice.OnMethodEnter(suppress = Throwable.class)
109-
public static void onEnter(
110-
@Advice.This PreparedStatement statement,
111-
@Advice.Local("otelCallDepth") CallDepth callDepth,
112-
@Advice.Local("otelRequest") DbRequest request,
113-
@Advice.Local("otelContext") Context context,
114-
@Advice.Local("otelScope") Scope scope) {
105+
public static JdbcAdviceScope onEnter(@Advice.This PreparedStatement statement) {
115106
// skip prepared statements without attached sql, probably a wrapper around the actual
116107
// prepared statement
117108
if (JdbcData.preparedStatement.get(statement) == null) {
118-
return;
119-
}
120-
121-
// Connection#getMetaData() may execute a Statement or PreparedStatement to retrieve DB info
122-
// this happens before the DB CLIENT span is started (and put in the current context), so this
123-
// instrumentation runs again and the shouldStartSpan() check always returns true - and so on
124-
// until we get a StackOverflowError
125-
// using CallDepth prevents this, because this check happens before Connection#getMetadata()
126-
// is called - the first recursive Statement call is just skipped and we do not create a span
127-
// for it
128-
callDepth = CallDepth.forClass(Statement.class);
129-
if (callDepth.getAndIncrement() > 0) {
130-
return;
131-
}
132-
133-
Context parentContext = currentContext();
134-
Map<String, String> parameters = JdbcData.getParameters(statement);
135-
request = DbRequest.create(statement, parameters);
136-
137-
if (request == null || !statementInstrumenter().shouldStart(parentContext, request)) {
138-
return;
109+
return null;
139110
}
140111

141-
context = statementInstrumenter().start(parentContext, request);
142-
scope = context.makeCurrent();
112+
return JdbcAdviceScope.startPreparedStatement(CallDepth.forClass(Statement.class), statement);
143113
}
144114

145115
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
146116
public static void stopSpan(
147-
@Advice.Thrown Throwable throwable,
148-
@Advice.Local("otelCallDepth") CallDepth callDepth,
149-
@Advice.Local("otelRequest") DbRequest request,
150-
@Advice.Local("otelContext") Context context,
151-
@Advice.Local("otelScope") Scope scope) {
152-
if (callDepth == null || callDepth.decrementAndGet() > 0) {
153-
return;
154-
}
155-
156-
if (scope != null) {
157-
scope.close();
158-
statementInstrumenter().end(context, request, null, throwable);
117+
@Advice.Thrown @Nullable Throwable throwable,
118+
@Advice.Enter @Nullable JdbcAdviceScope adviceScope) {
119+
if (adviceScope != null) {
120+
adviceScope.end(throwable);
159121
}
160122
}
161123
}

0 commit comments

Comments
 (0)