diff --git a/dd-java-agent-ittests/dd-java-agent-ittests.gradle b/dd-java-agent-ittests/dd-java-agent-ittests.gradle index c829b08b10..783c243c90 100644 --- a/dd-java-agent-ittests/dd-java-agent-ittests.gradle +++ b/dd-java-agent-ittests/dd-java-agent-ittests.gradle @@ -23,10 +23,6 @@ dependencies { testCompile group: 'org.apache.httpcomponents', name: 'httpclient', version: '4.3' testCompile group: 'com.squareup.okhttp3', name: 'okhttp', version: '3.6.0' - // JDBC tests: - testCompile group: 'com.h2database', name: 'h2', version: '1.4.196' - testCompile group: 'org.hsqldb', name: 'hsqldb', version: '2.3.+' - testCompile group: 'org.apache.derby', name: 'derby', version: '10.12.1.1' } tasks.withType(Test) { diff --git a/dd-java-agent/instrumentation/jdbc/jdbc.gradle b/dd-java-agent/instrumentation/jdbc/jdbc.gradle index 00ec61f5fc..80dba9603d 100644 --- a/dd-java-agent/instrumentation/jdbc/jdbc.gradle +++ b/dd-java-agent/instrumentation/jdbc/jdbc.gradle @@ -7,4 +7,12 @@ dependencies { compile deps.opentracing annotationProcessor deps.autoservice implementation deps.autoservice + + testCompile project(':dd-java-agent:testing') + // jdbc unit testing + testCompile group: 'com.h2database', name: 'h2', version: '1.4.197' + testCompile group: 'org.apache.derby', name: 'derby', version: '10.12.1.1' + // not using hsqldb 2.4 because org/hsqldb/jdbc/JDBCDriver : Unsupported major.minor version 52.0 + testCompile group: 'org.hsqldb', name: 'hsqldb', version: '2.3.+' + } diff --git a/dd-java-agent/instrumentation/jdbc/src/main/java/datadog/trace/instrumentation/jdbc/ConnectionInstrumentation.java b/dd-java-agent/instrumentation/jdbc/src/main/java/datadog/trace/instrumentation/jdbc/ConnectionInstrumentation.java index 28549d06d3..f530387627 100644 --- a/dd-java-agent/instrumentation/jdbc/src/main/java/datadog/trace/instrumentation/jdbc/ConnectionInstrumentation.java +++ b/dd-java-agent/instrumentation/jdbc/src/main/java/datadog/trace/instrumentation/jdbc/ConnectionInstrumentation.java @@ -1,7 +1,6 @@ package datadog.trace.instrumentation.jdbc; import static net.bytebuddy.matcher.ElementMatchers.failSafe; -import static net.bytebuddy.matcher.ElementMatchers.isConstructor; import static net.bytebuddy.matcher.ElementMatchers.isInterface; import static net.bytebuddy.matcher.ElementMatchers.isSubTypeOf; import static net.bytebuddy.matcher.ElementMatchers.nameStartsWith; @@ -13,11 +12,9 @@ import com.google.auto.service.AutoService; import datadog.trace.agent.tooling.DDAdvice; import datadog.trace.agent.tooling.DDTransformers; import datadog.trace.agent.tooling.Instrumenter; -import datadog.trace.bootstrap.CallDepthThreadLocalMap; import datadog.trace.bootstrap.JDBCMaps; import java.sql.Connection; import java.sql.PreparedStatement; -import java.sql.SQLException; import net.bytebuddy.agent.builder.AgentBuilder; import net.bytebuddy.asm.Advice; @@ -40,8 +37,6 @@ public final class ConnectionInstrumentation extends Instrumenter.Configurable { .and(takesArgument(0, String.class)) .and(returns(PreparedStatement.class)), ConnectionPrepareAdvice.class.getName())) - .transform( - DDAdvice.create().advice(isConstructor(), ConnectionConstructorAdvice.class.getName())) .asDecorator(); } @@ -52,34 +47,4 @@ public final class ConnectionInstrumentation extends Instrumenter.Configurable { JDBCMaps.preparedStatements.put(statement, sql); } } - - public static class ConnectionConstructorAdvice { - @Advice.OnMethodEnter(suppress = Throwable.class) - public static int constructorEnter() { - // We use this to make sure we only apply the exit instrumentation - // after the constructors are done calling their super constructors. - return CallDepthThreadLocalMap.incrementCallDepth(Connection.class); - } - - // Since we're instrumenting the constructor, we can't add onThrowable. - @Advice.OnMethodExit(suppress = Throwable.class) - public static void constructorExit( - @Advice.Enter final int depth, @Advice.This final Connection connection) - throws SQLException { - if (depth == 0) { - CallDepthThreadLocalMap.reset(Connection.class); - final String url = connection.getMetaData().getURL(); - if (url != null) { - // Remove end of url to prevent passwords from leaking: - final String sanitizedURL = url.replaceAll("[?;].*", ""); - final String type = url.split(":", -1)[1]; - String user = connection.getMetaData().getUserName(); - if (user != null && user.trim().equals("")) { - user = null; - } - JDBCMaps.connectionInfo.put(connection, new JDBCMaps.DBInfo(sanitizedURL, type, user)); - } - } - } - } } diff --git a/dd-java-agent/instrumentation/jdbc/src/main/java/datadog/trace/instrumentation/jdbc/PreparedStatementInstrumentation.java b/dd-java-agent/instrumentation/jdbc/src/main/java/datadog/trace/instrumentation/jdbc/PreparedStatementInstrumentation.java index e4487e5ae7..32fd3501ad 100644 --- a/dd-java-agent/instrumentation/jdbc/src/main/java/datadog/trace/instrumentation/jdbc/PreparedStatementInstrumentation.java +++ b/dd-java-agent/instrumentation/jdbc/src/main/java/datadog/trace/instrumentation/jdbc/PreparedStatementInstrumentation.java @@ -23,13 +23,13 @@ import io.opentracing.tag.Tags; import io.opentracing.util.GlobalTracer; import java.sql.Connection; import java.sql.PreparedStatement; +import java.sql.SQLException; import java.util.Collections; import net.bytebuddy.agent.builder.AgentBuilder; import net.bytebuddy.asm.Advice; @AutoService(Instrumenter.class) public final class PreparedStatementInstrumentation extends Instrumenter.Configurable { - public PreparedStatementInstrumentation() { super("jdbc"); } @@ -65,9 +65,39 @@ public final class PreparedStatementInstrumentation extends Instrumenter.Configu } JDBCMaps.DBInfo dbInfo = JDBCMaps.connectionInfo.get(connection); - if (dbInfo == null) { - dbInfo = JDBCMaps.DBInfo.DEFAULT; + /** + * Logic to get the DBInfo from a JDBC Connection, if the connection was never seen before, + * the connectionInfo map will return null and will attempt to extract DBInfo from the + * connection. If the DBInfo can't be extracted, then the connection will be stored with the + * DEFAULT DBInfo as the value in the connectionInfo map to avoid retry overhead. + * + *
This should be a util method to be shared between PreparedStatementInstrumentation and + * StatementInstrumentation class, but java.sql.* are on the platform classloaders in Java 9, + * which prevents us from referencing them in the bootstrap utils. + */ + { + if (dbInfo == null) { + try { + final String url = connection.getMetaData().getURL(); + if (url != null) { + // Remove end of url to prevent passwords from leaking: + final String sanitizedURL = url.replaceAll("[?;].*", ""); + final String type = url.split(":", -1)[1]; + String user = connection.getMetaData().getUserName(); + if (user != null && user.trim().equals("")) { + user = null; + } + dbInfo = new JDBCMaps.DBInfo(sanitizedURL, type, user); + } else { + dbInfo = JDBCMaps.DBInfo.DEFAULT; + } + } catch (SQLException se) { + dbInfo = JDBCMaps.DBInfo.DEFAULT; + } + JDBCMaps.connectionInfo.put(connection, dbInfo); + } } + final Scope scope = GlobalTracer.get().buildSpan(dbInfo.getType() + ".query").startActive(true); diff --git a/dd-java-agent/instrumentation/jdbc/src/main/java/datadog/trace/instrumentation/jdbc/StatementInstrumentation.java b/dd-java-agent/instrumentation/jdbc/src/main/java/datadog/trace/instrumentation/jdbc/StatementInstrumentation.java index 9a20e27b8d..61790f4d83 100644 --- a/dd-java-agent/instrumentation/jdbc/src/main/java/datadog/trace/instrumentation/jdbc/StatementInstrumentation.java +++ b/dd-java-agent/instrumentation/jdbc/src/main/java/datadog/trace/instrumentation/jdbc/StatementInstrumentation.java @@ -22,6 +22,7 @@ import io.opentracing.noop.NoopScopeManager.NoopScope; import io.opentracing.tag.Tags; import io.opentracing.util.GlobalTracer; import java.sql.Connection; +import java.sql.SQLException; import java.sql.Statement; import java.util.Collections; import net.bytebuddy.agent.builder.AgentBuilder; @@ -65,15 +66,43 @@ public final class StatementInstrumentation extends Instrumenter.Configurable { } JDBCMaps.DBInfo dbInfo = JDBCMaps.connectionInfo.get(connection); - if (dbInfo == null) { - dbInfo = JDBCMaps.DBInfo.DEFAULT; + /** + * Logic to get the DBInfo from a JDBC Connection, if the connection was never seen before, + * the connectionInfo map will return null and will attempt to extract DBInfo from the + * connection. If the DBInfo can't be extracted, then the connection will be stored with the + * DEFAULT DBInfo as the value in the connectionInfo map to avoid retry overhead. + * + *
This should be a util method to be shared between PreparedStatementInstrumentation and
+ * StatementInstrumentation class, but java.sql.* are on the platform classloaders in Java 9,
+ * which prevents us from referencing them in the bootstrap utils.
+ */
+ {
+ if (dbInfo == null) {
+ try {
+ final String url = connection.getMetaData().getURL();
+ if (url != null) {
+ // Remove end of url to prevent passwords from leaking:
+ final String sanitizedURL = url.replaceAll("[?;].*", "");
+ final String type = url.split(":", -1)[1];
+ String user = connection.getMetaData().getUserName();
+ if (user != null && user.trim().equals("")) {
+ user = null;
+ }
+ dbInfo = new JDBCMaps.DBInfo(sanitizedURL, type, user);
+ } else {
+ dbInfo = JDBCMaps.DBInfo.DEFAULT;
+ }
+ } catch (SQLException se) {
+ dbInfo = JDBCMaps.DBInfo.DEFAULT;
+ }
+ JDBCMaps.connectionInfo.put(connection, dbInfo);
+ }
}
final Scope scope =
GlobalTracer.get().buildSpan(dbInfo.getType() + ".query").startActive(true);
final Span span = scope.span();
-
Tags.DB_TYPE.set(span, dbInfo.getType());
Tags.SPAN_KIND.set(span, Tags.SPAN_KIND_CLIENT);
Tags.COMPONENT.set(span, "java-jdbc-statement");
diff --git a/dd-java-agent/instrumentation/jdbc/src/test/groovy/DummyThrowingConnection.groovy b/dd-java-agent/instrumentation/jdbc/src/test/groovy/DummyThrowingConnection.groovy
new file mode 100644
index 0000000000..018c27c172
--- /dev/null
+++ b/dd-java-agent/instrumentation/jdbc/src/test/groovy/DummyThrowingConnection.groovy
@@ -0,0 +1,296 @@
+import java.sql.Array
+import java.sql.Blob
+import java.sql.CallableStatement
+import java.sql.Clob
+import java.sql.Connection
+import java.sql.DatabaseMetaData
+import java.sql.NClob
+import java.sql.PreparedStatement
+import java.sql.SQLClientInfoException
+import java.sql.SQLException
+import java.sql.SQLWarning
+import java.sql.SQLXML
+import java.sql.Savepoint
+import java.sql.Statement
+import java.sql.Struct
+import java.util.concurrent.Executor
+
+
+/**
+ * A JDBC connection class that throws an exception in the constructor, used to test
+ */
+class DummyThrowingConnection implements Connection {
+ DummyThrowingConnection() {
+ throw new RuntimeException("Dummy exception")
+ }
+
+ @Override
+ Statement createStatement() throws SQLException {
+ return null
+ }
+
+ @Override
+ PreparedStatement prepareStatement(String sql) throws SQLException {
+ return null
+ }
+
+ @Override
+ CallableStatement prepareCall(String sql) throws SQLException {
+ return null
+ }
+
+ @Override
+ String nativeSQL(String sql) throws SQLException {
+ return null
+ }
+
+ @Override
+ void setAutoCommit(boolean autoCommit) throws SQLException {
+
+ }
+
+ @Override
+ boolean getAutoCommit() throws SQLException {
+ return false
+ }
+
+ @Override
+ void commit() throws SQLException {
+
+ }
+
+ @Override
+ void rollback() throws SQLException {
+
+ }
+
+ @Override
+ void close() throws SQLException {
+
+ }
+
+ @Override
+ boolean isClosed() throws SQLException {
+ return false
+ }
+
+ @Override
+ DatabaseMetaData getMetaData() throws SQLException {
+ return null
+ }
+
+ @Override
+ void setReadOnly(boolean readOnly) throws SQLException {
+
+ }
+
+ @Override
+ boolean isReadOnly() throws SQLException {
+ return false
+ }
+
+ @Override
+ void setCatalog(String catalog) throws SQLException {
+
+ }
+
+ @Override
+ String getCatalog() throws SQLException {
+ return null
+ }
+
+ @Override
+ void setTransactionIsolation(int level) throws SQLException {
+
+ }
+
+ @Override
+ int getTransactionIsolation() throws SQLException {
+ return 0
+ }
+
+ @Override
+ SQLWarning getWarnings() throws SQLException {
+ return null
+ }
+
+ @Override
+ void clearWarnings() throws SQLException {
+
+ }
+
+ @Override
+ Statement createStatement(int resultSetType, int resultSetConcurrency) throws SQLException {
+ return null
+ }
+
+ @Override
+ PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency) throws SQLException {
+ return null
+ }
+
+ @Override
+ CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency) throws SQLException {
+ return null
+ }
+
+ @Override
+ Map