diff --git a/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/jdbc/DBInfo.java b/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/jdbc/DBInfo.java
index 086f5369f09..766ff355671 100644
--- a/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/jdbc/DBInfo.java
+++ b/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/jdbc/DBInfo.java
@@ -87,7 +87,7 @@ public Builder type(String type) {
this.type = type;
// Those DBs use the full text of the query including the comments as a cache key,
// so we disable full propagation support for them to avoid destroying the cache.
- if (type.equals("oracle") || type.equals("sqlserver")) this.fullPropagationSupport = false;
+ if (type.equals("oracle")) this.fullPropagationSupport = false;
return this;
}
diff --git a/dd-java-agent/instrumentation/jdbc/build.gradle b/dd-java-agent/instrumentation/jdbc/build.gradle
index c0743218e02..5113b1e87bb 100644
--- a/dd-java-agent/instrumentation/jdbc/build.gradle
+++ b/dd-java-agent/instrumentation/jdbc/build.gradle
@@ -34,9 +34,11 @@ dependencies {
testImplementation group: 'mysql', name: 'mysql-connector-java', version: '8.0.23'
testImplementation group: 'org.postgresql', name: 'postgresql', version: '[9.4,42.2.18]'
+ testImplementation group: 'com.microsoft.sqlserver', name: 'mssql-jdbc', version: '10.2.0.jre8'
testImplementation group: 'org.testcontainers', name:'mysql', version: libs.versions.testcontainers.get()
testImplementation group: 'org.testcontainers', name:'postgresql', version: libs.versions.testcontainers.get()
+ testImplementation group: 'org.testcontainers', name:'mssqlserver', version: '1.19.8'
testRuntimeOnly project(':dd-java-agent:instrumentation:iast-instrumenter')
diff --git a/dd-java-agent/instrumentation/jdbc/src/main/java/datadog/trace/instrumentation/jdbc/AbstractPreparedStatementInstrumentation.java b/dd-java-agent/instrumentation/jdbc/src/main/java/datadog/trace/instrumentation/jdbc/AbstractPreparedStatementInstrumentation.java
index e215cce37ea..ba0ecdece55 100644
--- a/dd-java-agent/instrumentation/jdbc/src/main/java/datadog/trace/instrumentation/jdbc/AbstractPreparedStatementInstrumentation.java
+++ b/dd-java-agent/instrumentation/jdbc/src/main/java/datadog/trace/instrumentation/jdbc/AbstractPreparedStatementInstrumentation.java
@@ -5,6 +5,7 @@
import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.startSpan;
import static datadog.trace.instrumentation.jdbc.JDBCDecorator.DATABASE_QUERY;
import static datadog.trace.instrumentation.jdbc.JDBCDecorator.DECORATE;
+import static datadog.trace.instrumentation.jdbc.JDBCDecorator.INJECT_COMMENT;
import static datadog.trace.instrumentation.jdbc.JDBCDecorator.logMissingQueryInfo;
import static datadog.trace.instrumentation.jdbc.JDBCDecorator.logSQLException;
import static net.bytebuddy.matcher.ElementMatchers.isPublic;
@@ -16,6 +17,7 @@
import datadog.trace.bootstrap.InstrumentationContext;
import datadog.trace.bootstrap.instrumentation.api.AgentScope;
import datadog.trace.bootstrap.instrumentation.api.AgentSpan;
+import datadog.trace.bootstrap.instrumentation.api.AgentTracer;
import datadog.trace.bootstrap.instrumentation.jdbc.DBInfo;
import datadog.trace.bootstrap.instrumentation.jdbc.DBQueryInfo;
import java.sql.Connection;
@@ -64,21 +66,31 @@ public static AgentScope onEnter(@Advice.This final Statement statement) {
return null;
}
try {
- Connection connection = statement.getConnection();
- DBQueryInfo queryInfo =
+ final Connection connection = statement.getConnection();
+ final DBQueryInfo queryInfo =
InstrumentationContext.get(Statement.class, DBQueryInfo.class).get(statement);
if (null == queryInfo) {
logMissingQueryInfo(statement);
return null;
}
-
- final AgentSpan span = startSpan(DATABASE_QUERY);
- DECORATE.afterStart(span);
- DBInfo dbInfo =
+ final AgentSpan span;
+ final DBInfo dbInfo =
JDBCDecorator.parseDBInfo(
connection, InstrumentationContext.get(Connection.class, DBInfo.class));
+ final boolean injectTraceContext = DECORATE.shouldInjectTraceContext(dbInfo);
+
+ if (INJECT_COMMENT && injectTraceContext && DECORATE.isSqlServer(dbInfo)) {
+ // The span ID is pre-determined so that we can reference it when setting the context
+ final long spanID = DECORATE.setContextInfo(connection, dbInfo);
+ // we then force that pre-determined span ID for the span covering the actual query
+ span = AgentTracer.get().buildSpan(DATABASE_QUERY).withSpanId(spanID).start();
+ } else {
+ span = startSpan(DATABASE_QUERY);
+ }
+ DECORATE.afterStart(span);
DECORATE.onConnection(span, dbInfo);
DECORATE.onPreparedStatement(span, queryInfo);
+
return activateSpan(span);
} catch (SQLException e) {
logSQLException(e);
diff --git a/dd-java-agent/instrumentation/jdbc/src/main/java/datadog/trace/instrumentation/jdbc/JDBCDecorator.java b/dd-java-agent/instrumentation/jdbc/src/main/java/datadog/trace/instrumentation/jdbc/JDBCDecorator.java
index cec448e20c9..62ff7ce3dac 100644
--- a/dd-java-agent/instrumentation/jdbc/src/main/java/datadog/trace/instrumentation/jdbc/JDBCDecorator.java
+++ b/dd-java-agent/instrumentation/jdbc/src/main/java/datadog/trace/instrumentation/jdbc/JDBCDecorator.java
@@ -1,14 +1,18 @@
package datadog.trace.instrumentation.jdbc;
+import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.activateSpan;
import static datadog.trace.bootstrap.instrumentation.api.Tags.DB_OPERATION;
import static datadog.trace.bootstrap.instrumentation.api.Tags.DB_SCHEMA;
import static datadog.trace.bootstrap.instrumentation.api.Tags.DB_WAREHOUSE;
import datadog.trace.api.Config;
import datadog.trace.api.DDSpanId;
+import datadog.trace.api.DDTraceId;
import datadog.trace.api.naming.SpanNaming;
import datadog.trace.bootstrap.ContextStore;
+import datadog.trace.bootstrap.instrumentation.api.AgentScope;
import datadog.trace.bootstrap.instrumentation.api.AgentSpan;
+import datadog.trace.bootstrap.instrumentation.api.AgentTracer;
import datadog.trace.bootstrap.instrumentation.api.InternalSpanTypes;
import datadog.trace.bootstrap.instrumentation.api.Tags;
import datadog.trace.bootstrap.instrumentation.api.UTF8BytesString;
@@ -16,8 +20,11 @@
import datadog.trace.bootstrap.instrumentation.jdbc.DBInfo;
import datadog.trace.bootstrap.instrumentation.jdbc.DBQueryInfo;
import datadog.trace.bootstrap.instrumentation.jdbc.JDBCConnectionUrlParser;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
+import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.HashSet;
@@ -235,6 +242,73 @@ public String traceParent(AgentSpan span, int samplingPriority) {
return sb.toString();
}
+ public boolean isSqlServer(final DBInfo dbInfo) {
+ return "sqlserver".equals(dbInfo.getType());
+ }
+
+ /**
+ * Executes a `SET CONTEXT_INFO` statement on the DB with the active trace ID and the given span
+ * ID. This context will be "attached" to future queries on the same connection. See MSSQL
+ * doc. This is to be used where injecting trace and span in the comments with {@link
+ * SQLCommenter#inject} is not possible or convenient.
+ *
+ *
Upsides: still "visible" in sub-queries, does not bust caches based on full query text
+ * Downsides: takes time.
+ *
+ * @param connection The same connection as the one that will be used for the actual statement
+ * @param dbInfo dbInfo of the instrumented database
+ * @return spanID pre-created spanID
+ */
+ public long setContextInfo(Connection connection, DBInfo dbInfo) {
+ final byte VERSION = 0;
+ final long spanID = Config.get().getIdGenerationStrategy().generateSpanId();
+ AgentSpan instrumentationSpan =
+ AgentTracer.get().buildSpan("set context_info").withTag("dd.instrumentation", true).start();
+ DECORATE.afterStart(instrumentationSpan);
+ DECORATE.onConnection(instrumentationSpan, dbInfo);
+ PreparedStatement instrumentationStatement = null;
+ try (AgentScope scope = activateSpan(instrumentationSpan)) {
+ final byte samplingDecision =
+ (byte) (instrumentationSpan.forceSamplingDecision() > 0 ? 1 : 0);
+ final byte versionAndSamplingDecision =
+ (byte) ((VERSION << 4) & 0b11110000 | samplingDecision & 0b00000001);
+
+ ByteBuffer byteBuffer = ByteBuffer.allocate(1 + 3 * Long.BYTES);
+ byteBuffer.order(ByteOrder.BIG_ENDIAN);
+
+ byteBuffer.put(versionAndSamplingDecision);
+ byteBuffer.putLong(spanID);
+ final DDTraceId traceId = instrumentationSpan.getTraceId();
+ byteBuffer.putLong(traceId.toHighOrderLong());
+ byteBuffer.putLong(traceId.toLong());
+ final byte[] contextInfo = byteBuffer.array();
+
+ String instrumentationSql = "set context_info ?";
+ instrumentationStatement = connection.prepareStatement(instrumentationSql);
+ instrumentationStatement.setBytes(1, contextInfo);
+ DECORATE.onStatement(instrumentationSpan, instrumentationSql);
+ instrumentationStatement.execute();
+ } catch (Exception e) {
+ log.debug(
+ "Failed to set extra DBM data in context info for trace {}. "
+ + "To disable this behavior, set DBM_PROPAGATION_MODE to 'service' mode. "
+ + "See https://docs.datadoghq.com/database_monitoring/connect_dbm_and_apm/ for more info.{}",
+ instrumentationSpan.getTraceId().toHexString(),
+ e);
+ DECORATE.onError(instrumentationSpan, e);
+ } finally {
+ if (instrumentationStatement != null) {
+ try {
+ instrumentationStatement.close();
+ } catch (Exception e) {
+ }
+ }
+ instrumentationSpan.finish();
+ }
+ return spanID;
+ }
+
@Override
protected void postProcessServiceAndOperationName(
AgentSpan span, DatabaseClientDecorator.NamingEntry namingEntry) {
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 aee513deb7f..b684aaae783 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
@@ -21,6 +21,7 @@
import datadog.trace.bootstrap.InstrumentationContext;
import datadog.trace.bootstrap.instrumentation.api.AgentScope;
import datadog.trace.bootstrap.instrumentation.api.AgentSpan;
+import datadog.trace.bootstrap.instrumentation.api.AgentTracer;
import datadog.trace.bootstrap.instrumentation.jdbc.DBInfo;
import java.sql.Connection;
import java.sql.SQLException;
@@ -84,18 +85,29 @@ public static AgentScope onEnter(
}
try {
final Connection connection = statement.getConnection();
- final AgentSpan span = startSpan(DATABASE_QUERY);
- DECORATE.afterStart(span);
final DBInfo dbInfo =
JDBCDecorator.parseDBInfo(
connection, InstrumentationContext.get(Connection.class, DBInfo.class));
+ boolean injectTraceContext = DECORATE.shouldInjectTraceContext(dbInfo);
+ final AgentSpan span;
+ final boolean isSqlServer = DECORATE.isSqlServer(dbInfo);
+
+ if (isSqlServer && INJECT_COMMENT && injectTraceContext) {
+ // The span ID is pre-determined so that we can reference it when setting the context
+ final long spanID = DECORATE.setContextInfo(connection, dbInfo);
+ // we then force that pre-determined span ID for the span covering the actual query
+ span = AgentTracer.get().buildSpan(DATABASE_QUERY).withSpanId(spanID).start();
+ } else {
+ span = startSpan(DATABASE_QUERY);
+ }
+
+ DECORATE.afterStart(span);
DECORATE.onConnection(span, dbInfo);
final String copy = sql;
if (span != null && INJECT_COMMENT) {
String traceParent = null;
- boolean injectTraceContext = DECORATE.shouldInjectTraceContext(dbInfo);
- if (injectTraceContext) {
+ if (injectTraceContext && !isSqlServer) {
Integer priority = span.forceSamplingDecision();
if (priority != null) {
traceParent = DECORATE.traceParent(span, priority);
diff --git a/dd-java-agent/instrumentation/jdbc/src/test/groovy/JDBCDecoratorTest.groovy b/dd-java-agent/instrumentation/jdbc/src/test/groovy/JDBCDecoratorTest.groovy
index 6128e0bd5ed..69de0441f8a 100644
--- a/dd-java-agent/instrumentation/jdbc/src/test/groovy/JDBCDecoratorTest.groovy
+++ b/dd-java-agent/instrumentation/jdbc/src/test/groovy/JDBCDecoratorTest.groovy
@@ -21,7 +21,7 @@ abstract class JDBCDecoratorTest extends AgentTestRunner {
where:
dbType | expectedByType
"oracle" | false
- "sqlserver" | false
+ "sqlserver" | true
"mysql" | true
"postgresql" | true
}
diff --git a/dd-java-agent/instrumentation/jdbc/src/test/groovy/RemoteJDBCInstrumentationTest.groovy b/dd-java-agent/instrumentation/jdbc/src/test/groovy/RemoteJDBCInstrumentationTest.groovy
index b5a798b5536..e69dc8f9121 100644
--- a/dd-java-agent/instrumentation/jdbc/src/test/groovy/RemoteJDBCInstrumentationTest.groovy
+++ b/dd-java-agent/instrumentation/jdbc/src/test/groovy/RemoteJDBCInstrumentationTest.groovy
@@ -1,12 +1,15 @@
import com.mchange.v2.c3p0.ComboPooledDataSource
+import com.microsoft.sqlserver.jdbc.SQLServerException
import com.zaxxer.hikari.HikariConfig
import com.zaxxer.hikari.HikariDataSource
import datadog.trace.agent.test.naming.VersionedNamingTestBase
import datadog.trace.agent.test.utils.PortUtils
import datadog.trace.api.Config
import datadog.trace.api.DDSpanTypes
+import datadog.trace.api.naming.v1.DatabaseNamingV1
import datadog.trace.bootstrap.instrumentation.api.InstrumentationTags
import datadog.trace.bootstrap.instrumentation.api.Tags
+import org.testcontainers.containers.MSSQLServerContainer
import org.testcontainers.containers.MySQLContainer
import org.testcontainers.containers.PostgreSQLContainer
import spock.lang.Requires
@@ -29,50 +32,67 @@ import static datadog.trace.api.config.TraceInstrumentationConfig.DB_CLIENT_HOST
// workaround for SSLHandShakeException on J9 only with Hikari/MySQL
@Requires({ !System.getProperty("java.vendor").contains("IBM") })
abstract class RemoteJDBCInstrumentationTest extends VersionedNamingTestBase {
+ static final String POSTGRESQL = "postgresql"
+ static final String MYSQL = "mysql"
+ static final String SQLSERVER = "sqlserver"
+
@Shared
- def dbName = "jdbcUnitTest"
+ private Map dbName = [
+ (POSTGRESQL): "jdbcUnitTest",
+ (MYSQL) : "jdbcUnitTest",
+ (SQLSERVER) : "master"
+ ]
@Shared
private Map jdbcUrls = [
- "postgresql": "jdbc:postgresql://localhost:5432/$dbName",
- "mysql" : "jdbc:mysql://localhost:3306/$dbName"
+ "postgresql" : "jdbc:postgresql://localhost:5432/" + dbName.get("postgresql"),
+ "mysql" : "jdbc:mysql://localhost:3306/" + dbName.get("mysql"),
+ "sqlserver" : "jdbc:sqlserver://localhost:1433/" + dbName.get("sqlserver"),
]
@Shared
private Map jdbcDriverClassNames = [
"postgresql": "org.postgresql.Driver",
- "mysql" : "com.mysql.jdbc.Driver"
+ "mysql" : "com.mysql.jdbc.Driver",
+ "sqlserver" : "com.microsoft.sqlserver.jdbc.SQLServerDriver",
]
@Shared
private Map jdbcUserNames = [
"postgresql": "sa",
- "mysql" : "sa"
+ "mysql" : "sa",
+ "sqlserver" : "sa",
]
@Shared
private Map jdbcPasswords = [
"mysql" : "sa",
- "postgresql": "sa"
+ "postgresql": "sa",
+ "sqlserver" : "Datad0g_",
]
@Shared
def postgres
@Shared
def mysql
-
@Shared
- private Properties peerConnectionProps = {
- def props = new Properties()
- props.setProperty("user", "sa")
- props.setProperty("password", "sa")
- return props
- }()
+ def sqlserver
// JDBC Connection pool name (i.e. HikariCP) -> Map
@Shared
private Map> cpDatasources = new HashMap<>()
+ def peerConnectionProps(String db){
+ def props = new Properties()
+ props.setProperty("user", jdbcUserNames.get(db))
+ props.setProperty("password", jdbcPasswords.get(db))
+ return props
+ }
+
+ protected getDbType(String dbType){
+ return dbType
+ }
+
def prepareConnectionPoolDatasources() {
String[] connectionPoolNames = ["tomcat", "hikari", "c3p0",]
connectionPoolNames.each { cpName ->
@@ -152,17 +172,21 @@ abstract class RemoteJDBCInstrumentationTest extends VersionedNamingTestBase {
def setupSpec() {
postgres = new PostgreSQLContainer("postgres:11.1")
- .withDatabaseName(dbName).withUsername("sa").withPassword("sa")
+ .withDatabaseName(dbName.get(POSTGRESQL)).withUsername(jdbcUserNames.get(POSTGRESQL)).withPassword(jdbcPasswords.get(POSTGRESQL))
postgres.start()
PortUtils.waitForPortToOpen(postgres.getHost(), postgres.getMappedPort(PostgreSQLContainer.POSTGRESQL_PORT), 5, TimeUnit.SECONDS)
- jdbcUrls.put("postgresql", "${postgres.getJdbcUrl()}")
+ jdbcUrls.put(POSTGRESQL, "${postgres.getJdbcUrl()}")
mysql = new MySQLContainer("mysql:8.0")
- .withDatabaseName(dbName).withUsername("sa").withPassword("sa")
+ .withDatabaseName(dbName.get(MYSQL)).withUsername(jdbcUserNames.get(MYSQL)).withPassword(jdbcPasswords.get(MYSQL))
// https://github.com/testcontainers/testcontainers-java/issues/914
mysql.addParameter("TC_MY_CNF", null)
mysql.start()
PortUtils.waitForPortToOpen(mysql.getHost(), mysql.getMappedPort(MySQLContainer.MYSQL_PORT), 5, TimeUnit.SECONDS)
- jdbcUrls.put("mysql", "${mysql.getJdbcUrl()}")
+ jdbcUrls.put(MYSQL, "${mysql.getJdbcUrl()}")
+ sqlserver = new MSSQLServerContainer().acceptLicense().withPassword(jdbcPasswords.get(SQLSERVER))
+ sqlserver.start()
+ PortUtils.waitForPortToOpen(sqlserver.getHost(), sqlserver.getMappedPort(MSSQLServerContainer.MS_SQL_SERVER_PORT), 5, TimeUnit.SECONDS)
+ jdbcUrls.put(SQLSERVER, "${sqlserver.getJdbcUrl()};DatabaseName=${dbName.get(SQLSERVER)}")
prepareConnectionPoolDatasources()
}
@@ -177,6 +201,7 @@ abstract class RemoteJDBCInstrumentationTest extends VersionedNamingTestBase {
}
postgres?.close()
mysql?.close()
+ sqlserver?.close()
}
def "basic statement with #connection.getClass().getCanonicalName() on #driver generates spans"() {
@@ -194,33 +219,82 @@ abstract class RemoteJDBCInstrumentationTest extends VersionedNamingTestBase {
def addDbmTag = dbmTraceInjected()
resultSet.next()
resultSet.getInt(1) == 3
- assertTraces(1) {
- trace(2) {
- basicSpan(it, "parent")
- span {
- serviceName renameService ? dbName.toLowerCase() : service(driver)
- operationName this.operation(driver)
- resourceName obfuscatedQuery
- spanType DDSpanTypes.SQL
- childOf span(0)
- errored false
- measured true
- tags {
- "$Tags.COMPONENT" "java-jdbc-statement"
- "$Tags.SPAN_KIND" Tags.SPAN_KIND_CLIENT
- "$Tags.DB_TYPE" driver
- "$Tags.DB_INSTANCE" dbName.toLowerCase()
- "$Tags.PEER_HOSTNAME" String
- // currently there is a bug in the instrumentation with
- // postgresql and mysql if the connection event is missed
- // since Connection.getClientInfo will not provide the username
- "$Tags.DB_USER" { it == null || it == jdbcUserNames.get(driver) }
- "$Tags.DB_OPERATION" operation
- if (addDbmTag) {
- "$InstrumentationTags.DBM_TRACE_INJECTED" true
+ if (driver == POSTGRESQL || driver == MYSQL || !addDbmTag) {
+ assertTraces(1) {
+ trace(2) {
+ basicSpan(it, "parent")
+ span {
+ serviceName renameService ? dbName.get(driver).toLowerCase() : service(driver)
+ operationName this.operation(this.getDbType(driver))
+ resourceName obfuscatedQuery
+ spanType DDSpanTypes.SQL
+ childOf span(0)
+ errored false
+ measured true
+ tags {
+ "$Tags.COMPONENT" "java-jdbc-statement"
+ "$Tags.SPAN_KIND" Tags.SPAN_KIND_CLIENT
+ "$Tags.DB_TYPE" this.getDbType(driver)
+ "$Tags.DB_INSTANCE" dbName.get(driver).toLowerCase()
+ "$Tags.PEER_HOSTNAME" String
+ // currently there is a bug in the instrumentation with
+ // postgresql and mysql if the connection event is missed
+ // since Connection.getClientInfo will not provide the username
+ "$Tags.DB_USER" { it == null || it == jdbcUserNames.get(driver) }
+ "$Tags.DB_OPERATION" operation
+ if (addDbmTag) {
+ "$InstrumentationTags.DBM_TRACE_INJECTED" true
+ }
+ peerServiceFrom(Tags.DB_INSTANCE)
+ defaultTags()
+ }
+ }
+ }
+ }
+ } else {
+ assertTraces(1) {
+ trace(3) {
+ basicSpan(it, "parent")
+ span {
+ serviceName renameService ? dbName.get(driver).toLowerCase() : service(driver)
+ operationName this.operation(this.getDbType(driver))
+ resourceName obfuscatedQuery
+ spanType DDSpanTypes.SQL
+ childOf span(0)
+ errored false
+ measured true
+ tags {
+ "$Tags.COMPONENT" "java-jdbc-statement"
+ "$Tags.SPAN_KIND" Tags.SPAN_KIND_CLIENT
+ "$Tags.DB_TYPE" this.getDbType(driver)
+ "$Tags.DB_INSTANCE" dbName.get(driver).toLowerCase()
+ "$Tags.PEER_HOSTNAME" String
+ "$Tags.DB_USER" { it == null || it == jdbcUserNames.get(driver) }
+ "$Tags.DB_OPERATION" operation
+ peerServiceFrom(Tags.DB_INSTANCE)
+ defaultTags()
+ }
+ }
+ span {
+ serviceName renameService ? dbName.get(driver).toLowerCase() : service(driver)
+ operationName this.operation(this.getDbType(driver))
+ resourceName "set context_info ?"
+ spanType DDSpanTypes.SQL
+ childOf span(0)
+ errored false
+ measured true
+ tags {
+ "$Tags.COMPONENT" "java-jdbc-statement"
+ "$Tags.SPAN_KIND" Tags.SPAN_KIND_CLIENT
+ "$Tags.DB_TYPE" this.getDbType(driver)
+ "$Tags.DB_INSTANCE" dbName.get(driver).toLowerCase()
+ "$Tags.PEER_HOSTNAME" String
+ "$Tags.DB_USER" { it == null || it == jdbcUserNames.get(driver) }
+ "$Tags.DB_OPERATION" "set"
+ "dd.instrumentation" true
+ peerServiceFrom(Tags.DB_INSTANCE)
+ defaultTags()
}
- peerServiceFrom(Tags.DB_INSTANCE)
- defaultTags()
}
}
}
@@ -231,15 +305,19 @@ abstract class RemoteJDBCInstrumentationTest extends VersionedNamingTestBase {
connection.close()
where:
- driver | connection | renameService | query | operation | obfuscatedQuery
- "mysql" | connectTo(driver, peerConnectionProps) | false | "SELECT 3" | "SELECT" | "SELECT ?"
- "postgresql" | connectTo(driver, peerConnectionProps) | false | "SELECT 3 FROM pg_user" | "SELECT" | "SELECT ? FROM pg_user"
- "mysql" | cpDatasources.get("tomcat").get(driver).getConnection() | false | "SELECT 3" | "SELECT" | "SELECT ?"
- "postgresql" | cpDatasources.get("tomcat").get(driver).getConnection() | false | "SELECT 3 FROM pg_user" | "SELECT" | "SELECT ? FROM pg_user"
- "mysql" | cpDatasources.get("hikari").get(driver).getConnection() | false | "SELECT 3" | "SELECT" | "SELECT ?"
- "postgresql" | cpDatasources.get("hikari").get(driver).getConnection() | false | "SELECT 3 FROM pg_user" | "SELECT" | "SELECT ? FROM pg_user"
- "mysql" | cpDatasources.get("c3p0").get(driver).getConnection() | false | "SELECT 3" | "SELECT" | "SELECT ?"
- "postgresql" | cpDatasources.get("c3p0").get(driver).getConnection() | false | "SELECT 3 FROM pg_user" | "SELECT" | "SELECT ? FROM pg_user"
+ driver | connection | renameService | query | operation | obfuscatedQuery
+ MYSQL | connectTo(driver, peerConnectionProps(driver)) | false | "SELECT 3" | "SELECT" | "SELECT ?"
+ POSTGRESQL | connectTo(driver, peerConnectionProps(driver)) | false | "SELECT 3 FROM pg_user" | "SELECT" | "SELECT ? FROM pg_user"
+ SQLSERVER | connectTo(driver, peerConnectionProps(driver)) | false | "SELECT 3" | "SELECT" | "SELECT ?"
+ MYSQL | cpDatasources.get("tomcat").get(driver).getConnection() | false | "SELECT 3" | "SELECT" | "SELECT ?"
+ POSTGRESQL | cpDatasources.get("tomcat").get(driver).getConnection() | false | "SELECT 3 FROM pg_user" | "SELECT" | "SELECT ? FROM pg_user"
+ SQLSERVER | cpDatasources.get("tomcat").get(driver).getConnection() | false | "SELECT 3" | "SELECT" | "SELECT ?"
+ MYSQL | cpDatasources.get("hikari").get(driver).getConnection() | false | "SELECT 3" | "SELECT" | "SELECT ?"
+ POSTGRESQL | cpDatasources.get("hikari").get(driver).getConnection() | false | "SELECT 3 FROM pg_user" | "SELECT" | "SELECT ? FROM pg_user"
+ SQLSERVER | cpDatasources.get("hikari").get(driver).getConnection() | false | "SELECT 3" | "SELECT" | "SELECT ?"
+ MYSQL | cpDatasources.get("c3p0").get(driver).getConnection() | false | "SELECT 3" | "SELECT" | "SELECT ?"
+ POSTGRESQL | cpDatasources.get("c3p0").get(driver).getConnection() | false | "SELECT 3 FROM pg_user" | "SELECT" | "SELECT ? FROM pg_user"
+ SQLSERVER | cpDatasources.get("c3p0").get(driver).getConnection() | false | "SELECT 3" | "SELECT" | "SELECT ?"
}
def "prepared statement execute on #driver with #connection.getClass().getCanonicalName() generates a span"() {
@@ -256,31 +334,80 @@ abstract class RemoteJDBCInstrumentationTest extends VersionedNamingTestBase {
then:
resultSet.next()
resultSet.getInt(1) == 3
- assertTraces(1) {
- trace(2) {
- basicSpan(it, "parent")
- span {
- operationName this.operation(driver)
- serviceName service(driver)
- resourceName obfuscatedQuery
- spanType DDSpanTypes.SQL
- childOf span(0)
- errored false
- measured true
- tags {
- "$Tags.COMPONENT" "java-jdbc-prepared_statement"
- "$Tags.SPAN_KIND" Tags.SPAN_KIND_CLIENT
- "$Tags.DB_TYPE" driver
- "$Tags.DB_INSTANCE" dbName.toLowerCase()
- // only set when there is an out of proc instance (postgresql, mysql)
- "$Tags.PEER_HOSTNAME" String
- // currently there is a bug in the instrumentation with
- // postgresql and mysql if the connection event is missed
- // since Connection.getClientInfo will not provide the username
- "$Tags.DB_USER" { it == null || it == jdbcUserNames.get(driver) }
- "$Tags.DB_OPERATION" operation
- peerServiceFrom(Tags.DB_INSTANCE)
- defaultTags()
+ if (driver == POSTGRESQL || driver == MYSQL || !dbmTraceInjected()) {
+ assertTraces(1) {
+ trace(2) {
+ basicSpan(it, "parent")
+ span {
+ operationName this.operation(this.getDbType(driver))
+ serviceName service(driver)
+ resourceName obfuscatedQuery
+ spanType DDSpanTypes.SQL
+ childOf span(0)
+ errored false
+ measured true
+ tags {
+ "$Tags.COMPONENT" "java-jdbc-prepared_statement"
+ "$Tags.SPAN_KIND" Tags.SPAN_KIND_CLIENT
+ "$Tags.DB_TYPE" this.getDbType(driver)
+ "$Tags.DB_INSTANCE" dbName.get(driver).toLowerCase()
+ // only set when there is an out of proc instance (postgresql, mysql)
+ "$Tags.PEER_HOSTNAME" String
+ // currently there is a bug in the instrumentation with
+ // postgresql and mysql if the connection event is missed
+ // since Connection.getClientInfo will not provide the username
+ "$Tags.DB_USER" { it == null || it == jdbcUserNames.get(driver) }
+ "$Tags.DB_OPERATION" operation
+ peerServiceFrom(Tags.DB_INSTANCE)
+ defaultTags()
+ }
+ }
+ }
+ }
+ } else {
+ assertTraces(1) {
+ trace(3) {
+ basicSpan(it, "parent")
+ span {
+ operationName this.operation(this.getDbType(driver))
+ serviceName service(driver)
+ resourceName obfuscatedQuery
+ spanType DDSpanTypes.SQL
+ childOf span(0)
+ errored false
+ measured true
+ tags {
+ "$Tags.COMPONENT" "java-jdbc-prepared_statement"
+ "$Tags.SPAN_KIND" Tags.SPAN_KIND_CLIENT
+ "$Tags.DB_TYPE" this.getDbType(driver)
+ "$Tags.DB_INSTANCE" dbName.get(driver).toLowerCase()
+ "$Tags.PEER_HOSTNAME" String
+ "$Tags.DB_USER" { it == null || it == jdbcUserNames.get(driver) }
+ "$Tags.DB_OPERATION" operation
+ peerServiceFrom(Tags.DB_INSTANCE)
+ defaultTags()
+ }
+ }
+ span {
+ serviceName service(driver)
+ operationName this.operation(this.getDbType(driver))
+ resourceName "set context_info ?"
+ spanType DDSpanTypes.SQL
+ childOf span(0)
+ errored false
+ measured true
+ tags {
+ "$Tags.COMPONENT" "java-jdbc-statement"
+ "$Tags.SPAN_KIND" Tags.SPAN_KIND_CLIENT
+ "$Tags.DB_TYPE" this.getDbType(driver)
+ "$Tags.DB_INSTANCE" dbName.get(driver).toLowerCase()
+ "$Tags.PEER_HOSTNAME" String
+ "$Tags.DB_USER" { it == null || it == jdbcUserNames.get(driver) }
+ "$Tags.DB_OPERATION" "set"
+ "dd.instrumentation" true
+ peerServiceFrom(Tags.DB_INSTANCE)
+ defaultTags()
+ }
}
}
}
@@ -291,15 +418,19 @@ abstract class RemoteJDBCInstrumentationTest extends VersionedNamingTestBase {
connection.close()
where:
- driver | connection | query | operation | obfuscatedQuery
- "mysql" | connectTo(driver, peerConnectionProps) | "SELECT 3" | "SELECT" | "SELECT ?"
- "postgresql" | connectTo(driver, peerConnectionProps) | "SELECT 3 from pg_user" | "SELECT" | "SELECT ? from pg_user"
- "mysql" | cpDatasources.get("tomcat").get(driver).getConnection() | "SELECT 3" | "SELECT" | "SELECT ?"
- "postgresql" | cpDatasources.get("tomcat").get(driver).getConnection() | "SELECT 3 from pg_user" | "SELECT" | "SELECT ? from pg_user"
- "mysql" | cpDatasources.get("hikari").get(driver).getConnection() | "SELECT 3" | "SELECT" | "SELECT ?"
- "postgresql" | cpDatasources.get("hikari").get(driver).getConnection() | "SELECT 3 from pg_user" | "SELECT" | "SELECT ? from pg_user"
- "mysql" | cpDatasources.get("c3p0").get(driver).getConnection() | "SELECT 3" | "SELECT" | "SELECT ?"
- "postgresql" | cpDatasources.get("c3p0").get(driver).getConnection() | "SELECT 3 from pg_user" | "SELECT" | "SELECT ? from pg_user"
+ driver | connection | query | operation | obfuscatedQuery
+ MYSQL | connectTo(driver, peerConnectionProps(driver)) | "SELECT 3" | "SELECT" | "SELECT ?"
+ POSTGRESQL | connectTo(driver, peerConnectionProps(driver)) | "SELECT 3 from pg_user" | "SELECT" | "SELECT ? from pg_user"
+ SQLSERVER | connectTo(driver, peerConnectionProps(driver)) | "SELECT 3" | "SELECT" | "SELECT ?"
+ MYSQL | cpDatasources.get("tomcat").get(driver).getConnection() | "SELECT 3" | "SELECT" | "SELECT ?"
+ POSTGRESQL | cpDatasources.get("tomcat").get(driver).getConnection() | "SELECT 3 from pg_user" | "SELECT" | "SELECT ? from pg_user"
+ SQLSERVER | cpDatasources.get("tomcat").get(driver).getConnection() | "SELECT 3" | "SELECT" | "SELECT ?"
+ MYSQL | cpDatasources.get("hikari").get(driver).getConnection() | "SELECT 3" | "SELECT" | "SELECT ?"
+ POSTGRESQL | cpDatasources.get("hikari").get(driver).getConnection() | "SELECT 3 from pg_user" | "SELECT" | "SELECT ? from pg_user"
+ SQLSERVER | cpDatasources.get("hikari").get(driver).getConnection() | "SELECT 3" | "SELECT" | "SELECT ?"
+ MYSQL | cpDatasources.get("c3p0").get(driver).getConnection() | "SELECT 3" | "SELECT" | "SELECT ?"
+ POSTGRESQL | cpDatasources.get("c3p0").get(driver).getConnection() | "SELECT 3 from pg_user" | "SELECT" | "SELECT ? from pg_user"
+ SQLSERVER | cpDatasources.get("c3p0").get(driver).getConnection() | "SELECT 3" | "SELECT" | "SELECT ?"
}
def "prepared statement query on #driver with #connection.getClass().getCanonicalName() generates a span"() {
@@ -314,31 +445,84 @@ abstract class RemoteJDBCInstrumentationTest extends VersionedNamingTestBase {
then:
resultSet.next()
resultSet.getInt(1) == 3
- assertTraces(1) {
- trace(2) {
- basicSpan(it, "parent")
- span {
- operationName this.operation(driver)
- serviceName service(driver)
- resourceName obfuscatedQuery
- spanType DDSpanTypes.SQL
- childOf span(0)
- errored false
- measured true
- tags {
- "$Tags.COMPONENT" "java-jdbc-prepared_statement"
- "$Tags.SPAN_KIND" Tags.SPAN_KIND_CLIENT
- "$Tags.DB_TYPE" driver
- "$Tags.DB_INSTANCE" dbName.toLowerCase()
- // only set when there is an out of proc instance (postgresql, mysql)
- "$Tags.PEER_HOSTNAME" String
- // currently there is a bug in the instrumentation with
- // postgresql and mysql if the connection event is missed
- // since Connection.getClientInfo will not provide the username
- "$Tags.DB_USER" { it == null || it == jdbcUserNames.get(driver) }
- "$Tags.DB_OPERATION" operation
- peerServiceFrom(Tags.DB_INSTANCE)
- defaultTags()
+ if (driver == POSTGRESQL || driver == MYSQL || !dbmTraceInjected()) {
+ assertTraces(1) {
+ trace(2) {
+ basicSpan(it, "parent")
+ span {
+ operationName this.operation(this.getDbType(driver))
+ serviceName service(driver)
+ resourceName obfuscatedQuery
+ spanType DDSpanTypes.SQL
+ childOf span(0)
+ errored false
+ measured true
+ tags {
+ "$Tags.COMPONENT" "java-jdbc-prepared_statement"
+ "$Tags.SPAN_KIND" Tags.SPAN_KIND_CLIENT
+ "$Tags.DB_TYPE" this.getDbType(driver)
+ "$Tags.DB_INSTANCE" dbName.get(driver).toLowerCase()
+ // only set when there is an out of proc instance (postgresql, mysql)
+ "$Tags.PEER_HOSTNAME" String
+ // currently there is a bug in the instrumentation with
+ // postgresql and mysql if the connection event is missed
+ // since Connection.getClientInfo will not provide the username
+ "$Tags.DB_USER" { it == null || it == jdbcUserNames.get(driver) }
+ "$Tags.DB_OPERATION" operation
+ peerServiceFrom(Tags.DB_INSTANCE)
+ defaultTags()
+ }
+ }
+ }
+ }
+ } else {
+ assertTraces(1) {
+ trace(3) {
+ basicSpan(it, "parent")
+ span {
+ operationName this.operation(this.getDbType(driver))
+ serviceName service(driver)
+ resourceName obfuscatedQuery
+ spanType DDSpanTypes.SQL
+ childOf span(0)
+ errored false
+ measured true
+ tags {
+ "$Tags.COMPONENT" "java-jdbc-prepared_statement"
+ "$Tags.SPAN_KIND" Tags.SPAN_KIND_CLIENT
+ "$Tags.DB_TYPE" this.getDbType(driver)
+ "$Tags.DB_INSTANCE" dbName.get(driver).toLowerCase()
+ // only set when there is an out of proc instance (postgresql, mysql)
+ "$Tags.PEER_HOSTNAME" String
+ // currently there is a bug in the instrumentation with
+ // postgresql and mysql if the connection event is missed
+ // since Connection.getClientInfo will not provide the username
+ "$Tags.DB_USER" { it == null || it == jdbcUserNames.get(driver) }
+ "$Tags.DB_OPERATION" operation
+ peerServiceFrom(Tags.DB_INSTANCE)
+ defaultTags()
+ }
+ }
+ span {
+ serviceName service(driver)
+ operationName this.operation(this.getDbType(driver))
+ resourceName "set context_info ?"
+ spanType DDSpanTypes.SQL
+ childOf span(0)
+ errored false
+ measured true
+ tags {
+ "$Tags.COMPONENT" "java-jdbc-statement"
+ "$Tags.SPAN_KIND" Tags.SPAN_KIND_CLIENT
+ "$Tags.DB_TYPE" this.getDbType(driver)
+ "$Tags.DB_INSTANCE" dbName.get(driver).toLowerCase()
+ "$Tags.PEER_HOSTNAME" String
+ "$Tags.DB_USER" { it == null || it == jdbcUserNames.get(driver) }
+ "$Tags.DB_OPERATION" "set"
+ "dd.instrumentation" true
+ peerServiceFrom(Tags.DB_INSTANCE)
+ defaultTags()
+ }
}
}
}
@@ -349,15 +533,19 @@ abstract class RemoteJDBCInstrumentationTest extends VersionedNamingTestBase {
connection.close()
where:
- driver | connection | query | operation | obfuscatedQuery
- "mysql" | connectTo(driver, peerConnectionProps) | "SELECT 3" | "SELECT" | "SELECT ?"
- "postgresql" | connectTo(driver, peerConnectionProps) | "SELECT 3 from pg_user" | "SELECT" | "SELECT ? from pg_user"
- "mysql" | cpDatasources.get("tomcat").get(driver).getConnection() | "SELECT 3" | "SELECT" | "SELECT ?"
- "postgresql" | cpDatasources.get("tomcat").get(driver).getConnection() | "SELECT 3 from pg_user" | "SELECT" | "SELECT ? from pg_user"
- "mysql" | cpDatasources.get("hikari").get(driver).getConnection() | "SELECT 3" | "SELECT" | "SELECT ?"
- "postgresql" | cpDatasources.get("hikari").get(driver).getConnection() | "SELECT 3 from pg_user" | "SELECT" | "SELECT ? from pg_user"
- "mysql" | cpDatasources.get("c3p0").get(driver).getConnection() | "SELECT 3" | "SELECT" | "SELECT ?"
- "postgresql" | cpDatasources.get("c3p0").get(driver).getConnection() | "SELECT 3 from pg_user" | "SELECT" | "SELECT ? from pg_user"
+ driver | connection | query | operation | obfuscatedQuery
+ MYSQL | connectTo(driver, peerConnectionProps(driver)) | "SELECT 3" | "SELECT" | "SELECT ?"
+ POSTGRESQL | connectTo(driver, peerConnectionProps(driver)) | "SELECT 3 from pg_user" | "SELECT" | "SELECT ? from pg_user"
+ SQLSERVER | connectTo(driver, peerConnectionProps(driver)) | "SELECT 3" | "SELECT" | "SELECT ?"
+ MYSQL | cpDatasources.get("tomcat").get(driver).getConnection() | "SELECT 3" | "SELECT" | "SELECT ?"
+ POSTGRESQL | cpDatasources.get("tomcat").get(driver).getConnection() | "SELECT 3 from pg_user" | "SELECT" | "SELECT ? from pg_user"
+ SQLSERVER | cpDatasources.get("tomcat").get(driver).getConnection() | "SELECT 3" | "SELECT" | "SELECT ?"
+ MYSQL | cpDatasources.get("hikari").get(driver).getConnection() | "SELECT 3" | "SELECT" | "SELECT ?"
+ POSTGRESQL | cpDatasources.get("hikari").get(driver).getConnection() | "SELECT 3 from pg_user" | "SELECT" | "SELECT ? from pg_user"
+ SQLSERVER | cpDatasources.get("hikari").get(driver).getConnection() | "SELECT 3" | "SELECT" | "SELECT ?"
+ MYSQL | cpDatasources.get("c3p0").get(driver).getConnection() | "SELECT 3" | "SELECT" | "SELECT ?"
+ POSTGRESQL | cpDatasources.get("c3p0").get(driver).getConnection() | "SELECT 3 from pg_user" | "SELECT" | "SELECT ? from pg_user"
+ SQLSERVER | cpDatasources.get("c3p0").get(driver).getConnection() | "SELECT 3" | "SELECT" | "SELECT ?"
}
def "prepared call on #driver with #connection.getClass().getCanonicalName() generates a span"() {
@@ -372,30 +560,78 @@ abstract class RemoteJDBCInstrumentationTest extends VersionedNamingTestBase {
then:
resultSet.next()
resultSet.getInt(1) == 3
- assertTraces(1) {
- trace(2) {
- basicSpan(it, "parent")
- span {
- operationName this.operation(driver)
- serviceName service(driver)
- resourceName obfuscatedQuery
- spanType DDSpanTypes.SQL
- childOf span(0)
- errored false
- measured true
- tags {
- "$Tags.COMPONENT" "java-jdbc-prepared_statement"
- "$Tags.SPAN_KIND" Tags.SPAN_KIND_CLIENT
- "$Tags.DB_TYPE" driver
- "$Tags.DB_INSTANCE" dbName.toLowerCase()
- // only set when there is an out of proc instance (postgresql, mysql)
- "$Tags.PEER_HOSTNAME" String
- // currently there is a bug in the instrumentation with
- // postgresql and mysql if the connection event is missed
- // since Connection.getClientInfo will not provide the username
- "$Tags.DB_USER" { it == null || it == jdbcUserNames.get(driver) }
- "${Tags.DB_OPERATION}" operation
- defaultTags()
+ if (driver == POSTGRESQL || driver == MYSQL || !dbmTraceInjected()) {
+ assertTraces(1) {
+ trace(2) {
+ basicSpan(it, "parent")
+ span {
+ operationName this.operation(this.getDbType(driver))
+ serviceName service(driver)
+ resourceName obfuscatedQuery
+ spanType DDSpanTypes.SQL
+ childOf span(0)
+ errored false
+ measured true
+ tags {
+ "$Tags.COMPONENT" "java-jdbc-prepared_statement"
+ "$Tags.SPAN_KIND" Tags.SPAN_KIND_CLIENT
+ "$Tags.DB_TYPE" this.getDbType(driver)
+ "$Tags.DB_INSTANCE" dbName.get(driver).toLowerCase()
+ // only set when there is an out of proc instance (postgresql, mysql)
+ "$Tags.PEER_HOSTNAME" String
+ // currently there is a bug in the instrumentation with
+ // postgresql and mysql if the connection event is missed
+ // since Connection.getClientInfo will not provide the username
+ "$Tags.DB_USER" { it == null || it == jdbcUserNames.get(driver) }
+ "${Tags.DB_OPERATION}" operation
+ defaultTags()
+ }
+ }
+ }
+ }
+ } else {
+ assertTraces(1) {
+ trace(3) {
+ basicSpan(it, "parent")
+ span {
+ operationName this.operation(this.getDbType(driver))
+ serviceName service(driver)
+ resourceName obfuscatedQuery
+ spanType DDSpanTypes.SQL
+ childOf span(0)
+ errored false
+ measured true
+ tags {
+ "$Tags.COMPONENT" "java-jdbc-prepared_statement"
+ "$Tags.SPAN_KIND" Tags.SPAN_KIND_CLIENT
+ "$Tags.DB_TYPE" this.getDbType(driver)
+ "$Tags.DB_INSTANCE" dbName.get(driver).toLowerCase()
+ "$Tags.PEER_HOSTNAME" String
+ "$Tags.DB_USER" { it == null || it == jdbcUserNames.get(driver) }
+ "${Tags.DB_OPERATION}" operation
+ defaultTags()
+ }
+ }
+ span {
+ serviceName service(driver)
+ operationName this.operation(this.getDbType(driver))
+ resourceName "set context_info ?"
+ spanType DDSpanTypes.SQL
+ childOf span(0)
+ errored false
+ measured true
+ tags {
+ "$Tags.COMPONENT" "java-jdbc-statement"
+ "$Tags.SPAN_KIND" Tags.SPAN_KIND_CLIENT
+ "$Tags.DB_TYPE" this.getDbType(driver)
+ "$Tags.DB_INSTANCE" dbName.get(driver).toLowerCase()
+ "$Tags.PEER_HOSTNAME" String
+ "$Tags.DB_USER" { it == null || it == jdbcUserNames.get(driver) }
+ "$Tags.DB_OPERATION" "set"
+ "dd.instrumentation" true
+ peerServiceFrom(Tags.DB_INSTANCE)
+ defaultTags()
+ }
}
}
}
@@ -406,15 +642,19 @@ abstract class RemoteJDBCInstrumentationTest extends VersionedNamingTestBase {
connection.close()
where:
- driver | connection | query | operation | obfuscatedQuery
- "mysql" | connectTo(driver, peerConnectionProps) | "SELECT 3" | "SELECT" | "SELECT ?"
- "postgresql" | connectTo(driver, peerConnectionProps) | "SELECT 3 from pg_user" | "SELECT" | "SELECT ? from pg_user"
- "mysql" | cpDatasources.get("tomcat").get(driver).getConnection() | "SELECT 3" | "SELECT" | "SELECT ?"
- "postgresql" | cpDatasources.get("tomcat").get(driver).getConnection() | "SELECT 3 from pg_user" | "SELECT" | "SELECT ? from pg_user"
- "mysql" | cpDatasources.get("hikari").get(driver).getConnection() | "SELECT 3" | "SELECT" | "SELECT ?"
- "postgresql" | cpDatasources.get("hikari").get(driver).getConnection() | "SELECT 3 from pg_user" | "SELECT" | "SELECT ? from pg_user"
- "mysql" | cpDatasources.get("c3p0").get(driver).getConnection() | "SELECT 3" | "SELECT" | "SELECT ?"
- "postgresql" | cpDatasources.get("c3p0").get(driver).getConnection() | "SELECT 3 from pg_user" | "SELECT" | "SELECT ? from pg_user"
+ driver | connection | query | operation | obfuscatedQuery
+ MYSQL | connectTo(driver, peerConnectionProps(driver)) | "SELECT 3" | "SELECT" | "SELECT ?"
+ POSTGRESQL | connectTo(driver, peerConnectionProps(driver)) | "SELECT 3 from pg_user" | "SELECT" | "SELECT ? from pg_user"
+ SQLSERVER | connectTo(driver, peerConnectionProps(driver)) | "SELECT 3" | "SELECT" | "SELECT ?"
+ MYSQL | cpDatasources.get("tomcat").get(driver).getConnection() | "SELECT 3" | "SELECT" | "SELECT ?"
+ POSTGRESQL | cpDatasources.get("tomcat").get(driver).getConnection() | "SELECT 3 from pg_user" | "SELECT" | "SELECT ? from pg_user"
+ SQLSERVER | cpDatasources.get("tomcat").get(driver).getConnection() | "SELECT 3" | "SELECT" | "SELECT ?"
+ MYSQL | cpDatasources.get("hikari").get(driver).getConnection() | "SELECT 3" | "SELECT" | "SELECT ?"
+ POSTGRESQL | cpDatasources.get("hikari").get(driver).getConnection() | "SELECT 3 from pg_user" | "SELECT" | "SELECT ? from pg_user"
+ SQLSERVER | cpDatasources.get("hikari").get(driver).getConnection() | "SELECT 3" | "SELECT" | "SELECT ?"
+ MYSQL | cpDatasources.get("c3p0").get(driver).getConnection() | "SELECT 3" | "SELECT" | "SELECT ?"
+ POSTGRESQL | cpDatasources.get("c3p0").get(driver).getConnection() | "SELECT 3 from pg_user" | "SELECT" | "SELECT ? from pg_user"
+ SQLSERVER | cpDatasources.get("c3p0").get(driver).getConnection() | "SELECT 3" | "SELECT" | "SELECT ?"
}
def "statement update on #driver with #connection.getClass().getCanonicalName() generates a span"() {
@@ -431,33 +671,85 @@ abstract class RemoteJDBCInstrumentationTest extends VersionedNamingTestBase {
then:
def addDbmTag = dbmTraceInjected()
statement.updateCount == 0
- assertTraces(1) {
- trace(2) {
- basicSpan(it, "parent")
- span {
- operationName this.operation(driver)
- serviceName service(driver)
- resourceName query
- spanType DDSpanTypes.SQL
- childOf span(0)
- errored false
- tags {
- "$Tags.COMPONENT" "java-jdbc-statement"
- "$Tags.SPAN_KIND" Tags.SPAN_KIND_CLIENT
- "$Tags.DB_TYPE" driver
- "$Tags.DB_INSTANCE" dbName.toLowerCase()
- // only set when there is an out of proc instance (postgresql, mysql)
- "$Tags.PEER_HOSTNAME" String
- // currently there is a bug in the instrumentation with
- // postgresql and mysql if the connection event is missed
- // since Connection.getClientInfo will not provide the username
- "$Tags.DB_USER" { it == null || it == jdbcUserNames.get(driver) }
- "${Tags.DB_OPERATION}" operation
- if (addDbmTag) {
- "$InstrumentationTags.DBM_TRACE_INJECTED" true
+ if (driver == POSTGRESQL || driver == MYSQL || !dbmTraceInjected()) {
+ assertTraces(1) {
+ trace(2) {
+ basicSpan(it, "parent")
+ span {
+ operationName this.operation(this.getDbType(driver))
+ serviceName service(driver)
+ resourceName query
+ spanType DDSpanTypes.SQL
+ childOf span(0)
+ errored false
+ tags {
+ "$Tags.COMPONENT" "java-jdbc-statement"
+ "$Tags.SPAN_KIND" Tags.SPAN_KIND_CLIENT
+ "$Tags.DB_TYPE" this.getDbType(driver)
+ "$Tags.DB_INSTANCE" dbName.get(driver).toLowerCase()
+ // only set when there is an out of proc instance (postgresql, mysql)
+ "$Tags.PEER_HOSTNAME" String
+ // currently there is a bug in the instrumentation with
+ // postgresql and mysql if the connection event is missed
+ // since Connection.getClientInfo will not provide the username
+ "$Tags.DB_USER" { it == null || it == jdbcUserNames.get(driver) }
+ "${Tags.DB_OPERATION}" operation
+ if (addDbmTag) {
+ "$InstrumentationTags.DBM_TRACE_INJECTED" true
+ }
+ peerServiceFrom(Tags.DB_INSTANCE)
+ defaultTags()
+ }
+ }
+ }
+ }
+ } else {
+ assertTraces(1) {
+ trace(3) {
+ basicSpan(it, "parent")
+ span {
+ operationName this.operation(this.getDbType(driver))
+ serviceName service(driver)
+ resourceName query
+ spanType DDSpanTypes.SQL
+ childOf span(0)
+ errored false
+ tags {
+ "$Tags.COMPONENT" "java-jdbc-statement"
+ "$Tags.SPAN_KIND" Tags.SPAN_KIND_CLIENT
+ "$Tags.DB_TYPE" this.getDbType(driver)
+ "$Tags.DB_INSTANCE" dbName.get(driver).toLowerCase()
+ // only set when there is an out of proc instance (postgresql, mysql)
+ "$Tags.PEER_HOSTNAME" String
+ // currently there is a bug in the instrumentation with
+ // postgresql and mysql if the connection event is missed
+ // since Connection.getClientInfo will not provide the username
+ "$Tags.DB_USER" { it == null || it == jdbcUserNames.get(driver) }
+ "${Tags.DB_OPERATION}" operation
+ peerServiceFrom(Tags.DB_INSTANCE)
+ defaultTags()
+ }
+ }
+ span {
+ serviceName service(driver)
+ operationName this.operation(this.getDbType(driver))
+ resourceName "set context_info ?"
+ spanType DDSpanTypes.SQL
+ childOf span(0)
+ errored false
+ measured true
+ tags {
+ "$Tags.COMPONENT" "java-jdbc-statement"
+ "$Tags.SPAN_KIND" Tags.SPAN_KIND_CLIENT
+ "$Tags.DB_TYPE" this.getDbType(driver)
+ "$Tags.DB_INSTANCE" dbName.get(driver).toLowerCase()
+ "$Tags.PEER_HOSTNAME" String
+ "$Tags.DB_USER" { it == null || it == jdbcUserNames.get(driver) }
+ "$Tags.DB_OPERATION" "set"
+ "dd.instrumentation" true
+ peerServiceFrom(Tags.DB_INSTANCE)
+ defaultTags()
}
- peerServiceFrom(Tags.DB_INSTANCE)
- defaultTags()
}
}
}
@@ -469,15 +761,19 @@ abstract class RemoteJDBCInstrumentationTest extends VersionedNamingTestBase {
connection.close()
where:
- driver | connection | query | operation
- "mysql" | connectTo(driver, peerConnectionProps) | "CREATE TEMPORARY TABLE s_test_ (id INTEGER not NULL, PRIMARY KEY ( id ))" | "CREATE"
- "postgresql" | connectTo(driver, peerConnectionProps) | "CREATE TEMPORARY TABLE s_test (id INTEGER not NULL, PRIMARY KEY ( id ))" | "CREATE"
- "mysql" | cpDatasources.get("tomcat").get(driver).getConnection() | "CREATE TEMPORARY TABLE s_tomcat_test (id INTEGER not NULL, PRIMARY KEY ( id ))" | "CREATE"
- "postgresql" | cpDatasources.get("tomcat").get(driver).getConnection() | "CREATE TEMPORARY TABLE s_tomcat_test (id INTEGER not NULL, PRIMARY KEY ( id ))" | "CREATE"
- "mysql" | cpDatasources.get("hikari").get(driver).getConnection() | "CREATE TEMPORARY TABLE s_hikari_test (id INTEGER not NULL, PRIMARY KEY ( id ))" | "CREATE"
- "postgresql" | cpDatasources.get("hikari").get(driver).getConnection() | "CREATE TEMPORARY TABLE s_hikari_test (id INTEGER not NULL, PRIMARY KEY ( id ))" | "CREATE"
- "mysql" | cpDatasources.get("c3p0").get(driver).getConnection() | "CREATE TEMPORARY TABLE s_c3p0_test (id INTEGER not NULL, PRIMARY KEY ( id ))" | "CREATE"
- "postgresql" | cpDatasources.get("c3p0").get(driver).getConnection() | "CREATE TEMPORARY TABLE s_c3p0_test (id INTEGER not NULL, PRIMARY KEY ( id ))" | "CREATE"
+ driver | connection | query | operation
+ MYSQL | connectTo(driver, peerConnectionProps(driver)) | "CREATE TEMPORARY TABLE s_test_ (id INTEGER not NULL, PRIMARY KEY ( id ))" | "CREATE"
+ POSTGRESQL | connectTo(driver, peerConnectionProps(driver)) | "CREATE TEMPORARY TABLE s_test (id INTEGER not NULL, PRIMARY KEY ( id ))" | "CREATE"
+ SQLSERVER | connectTo(driver, peerConnectionProps(driver)) | "CREATE TABLE #s_test_ (id INTEGER not NULL, PRIMARY KEY ( id ))" | "CREATE"
+ MYSQL | cpDatasources.get("tomcat").get(driver).getConnection() | "CREATE TEMPORARY TABLE s_tomcat_test (id INTEGER not NULL, PRIMARY KEY ( id ))" | "CREATE"
+ POSTGRESQL | cpDatasources.get("tomcat").get(driver).getConnection() | "CREATE TEMPORARY TABLE s_tomcat_test (id INTEGER not NULL, PRIMARY KEY ( id ))" | "CREATE"
+ SQLSERVER | cpDatasources.get("tomcat").get(driver).getConnection() | "CREATE TABLE #s_tomcat_test (id INTEGER not NULL, PRIMARY KEY ( id ))" | "CREATE"
+ MYSQL | cpDatasources.get("hikari").get(driver).getConnection() | "CREATE TEMPORARY TABLE s_hikari_test (id INTEGER not NULL, PRIMARY KEY ( id ))" | "CREATE"
+ POSTGRESQL | cpDatasources.get("hikari").get(driver).getConnection() | "CREATE TEMPORARY TABLE s_hikari_test (id INTEGER not NULL, PRIMARY KEY ( id ))" | "CREATE"
+ SQLSERVER | cpDatasources.get("hikari").get(driver).getConnection() | "CREATE TABLE #s_hikari_test (id INTEGER not NULL, PRIMARY KEY ( id ))" | "CREATE"
+ MYSQL | cpDatasources.get("c3p0").get(driver).getConnection() | "CREATE TEMPORARY TABLE s_c3p0_test (id INTEGER not NULL, PRIMARY KEY ( id ))" | "CREATE"
+ POSTGRESQL | cpDatasources.get("c3p0").get(driver).getConnection() | "CREATE TEMPORARY TABLE s_c3p0_test (id INTEGER not NULL, PRIMARY KEY ( id ))" | "CREATE"
+ SQLSERVER | cpDatasources.get("c3p0").get(driver).getConnection() | "CREATE TABLE #s_c3p0_test (id INTEGER not NULL, PRIMARY KEY ( id ))" | "CREATE"
}
@@ -501,15 +797,15 @@ abstract class RemoteJDBCInstrumentationTest extends VersionedNamingTestBase {
connection.close()
where:
- driver | connection | query
- "postgresql" | cpDatasources.get("hikari").get(driver).getConnection() | "{ ? = call upper( ? ) }"
- "mysql" | cpDatasources.get("hikari").get(driver).getConnection() | "{ ? = call upper( ? ) }"
- "postgresql" | cpDatasources.get("tomcat").get(driver).getConnection() | " { ? = call upper( ? ) }"
- "mysql" | cpDatasources.get("tomcat").get(driver).getConnection() | "{ ? = call upper( ? ) }"
- "postgresql" | cpDatasources.get("c3p0").get(driver).getConnection() | " { ? = call upper( ? ) }"
- "mysql" | cpDatasources.get("c3p0").get(driver).getConnection() | "{ ? = call upper( ? ) }"
- "postgresql" | connectTo(driver, peerConnectionProps) | " { ? = call upper( ? ) }"
- "mysql" | connectTo(driver, peerConnectionProps) | " { ? = call upper( ? ) }"
+ driver | connection | query
+ POSTGRESQL | cpDatasources.get("hikari").get(driver).getConnection() | "{ ? = call upper( ? ) }"
+ MYSQL | cpDatasources.get("hikari").get(driver).getConnection() | "{ ? = call upper( ? ) }"
+ POSTGRESQL | cpDatasources.get("tomcat").get(driver).getConnection() | "{ ? = call upper( ? ) }"
+ MYSQL | cpDatasources.get("tomcat").get(driver).getConnection() | "{ ? = call upper( ? ) }"
+ POSTGRESQL | cpDatasources.get("c3p0").get(driver).getConnection() | "{ ? = call upper( ? ) }"
+ MYSQL | cpDatasources.get("c3p0").get(driver).getConnection() | "{ ? = call upper( ? ) }"
+ POSTGRESQL | connectTo(driver, peerConnectionProps(driver)) | "{ ? = call upper( ? ) }"
+ MYSQL | connectTo(driver, peerConnectionProps(driver)) | "{ ? = call upper( ? ) }"
}
def "prepared procedure call on #driver with #connection.getClass().getCanonicalName() does not hang"() {
@@ -533,6 +829,15 @@ abstract class RemoteJDBCInstrumentationTest extends VersionedNamingTestBase {
SELECT 1;
END
"""
+ } else if (driver == "sqlserver") {
+ createSql =
+ """
+ CREATE PROCEDURE dummy @res integer output
+ AS
+ BEGIN
+ SELECT 1;
+ END
+ """
} else {
assert false
}
@@ -542,8 +847,10 @@ abstract class RemoteJDBCInstrumentationTest extends VersionedNamingTestBase {
return
}
-
- connection.prepareCall(createSql).execute()
+ // object already exists (no IF NOT EXISTS in SQL Server)
+ try {
+ connection.prepareCall(createSql).execute()
+ } catch (SQLServerException ex) {}
injectSysConfig("dd.dbm.propagation.mode", "full")
CallableStatement proc = connection.prepareCall(query)
@@ -565,15 +872,19 @@ abstract class RemoteJDBCInstrumentationTest extends VersionedNamingTestBase {
connection.close()
where:
- driver | connection | query
- "postgresql" | cpDatasources.get("hikari").get(driver).getConnection() | "CALL dummy(?)"
- "mysql" | cpDatasources.get("hikari").get(driver).getConnection() | "CALL dummy(?)"
- "postgresql" | cpDatasources.get("tomcat").get(driver).getConnection() | " CALL dummy(?)"
- "mysql" | cpDatasources.get("tomcat").get(driver).getConnection() | "CALL dummy(?)"
- "postgresql" | cpDatasources.get("c3p0").get(driver).getConnection() | " CALL dummy(?)"
- "mysql" | cpDatasources.get("c3p0").get(driver).getConnection() | "CALL dummy(?)"
- "postgresql" | connectTo(driver, peerConnectionProps) | " CALL dummy(?)"
- "mysql" | connectTo(driver, peerConnectionProps) | "CALL dummy(?)"
+ driver | connection | query
+ POSTGRESQL | cpDatasources.get("hikari").get(driver).getConnection() | "CALL dummy(?)"
+ MYSQL | cpDatasources.get("hikari").get(driver).getConnection() | "CALL dummy(?)"
+ SQLSERVER | cpDatasources.get("hikari").get(driver).getConnection() | "{CALL dummy(?)}"
+ POSTGRESQL | cpDatasources.get("tomcat").get(driver).getConnection() | "CALL dummy(?)"
+ MYSQL | cpDatasources.get("tomcat").get(driver).getConnection() | "CALL dummy(?)"
+ SQLSERVER | cpDatasources.get("tomcat").get(driver).getConnection() | "{CALL dummy(?)}"
+ POSTGRESQL | cpDatasources.get("c3p0").get(driver).getConnection() | "CALL dummy(?)"
+ MYSQL | cpDatasources.get("c3p0").get(driver).getConnection() | "CALL dummy(?)"
+ SQLSERVER | cpDatasources.get("c3p0").get(driver).getConnection() | "{CALL dummy(?)}"
+ POSTGRESQL | connectTo(driver, peerConnectionProps(driver)) | "CALL dummy(?)"
+ MYSQL | connectTo(driver, peerConnectionProps(driver)) | "CALL dummy(?)"
+ SQLSERVER | connectTo(driver, peerConnectionProps(driver)) | "{CALL dummy(?)}"
}
@@ -656,6 +967,12 @@ class RemoteJDBCInstrumentationV1ForkedTest extends RemoteJDBCInstrumentationTes
protected boolean dbmTraceInjected() {
return false
}
+
+ @Override
+ protected String getDbType(String dbType) {
+ final databaseNaming = new DatabaseNamingV1()
+ return databaseNaming.normalizedName(dbType)
+ }
}
class RemoteDBMTraceInjectedForkedTest extends RemoteJDBCInstrumentationTest {
@@ -685,4 +1002,10 @@ class RemoteDBMTraceInjectedForkedTest extends RemoteJDBCInstrumentationTest {
protected String operation(String dbType) {
return "${dbType}.query"
}
+
+ @Override
+ protected String getDbType(String dbType) {
+ final databaseNaming = new DatabaseNamingV1()
+ return databaseNaming.normalizedName(dbType)
+ }
}
diff --git a/dd-java-agent/instrumentation/jdbc/src/test/groovy/SQLServerInjectionForkedTest.groovy b/dd-java-agent/instrumentation/jdbc/src/test/groovy/SQLServerInjectionForkedTest.groovy
new file mode 100644
index 00000000000..2e958be9a3b
--- /dev/null
+++ b/dd-java-agent/instrumentation/jdbc/src/test/groovy/SQLServerInjectionForkedTest.groovy
@@ -0,0 +1,36 @@
+
+
+import datadog.trace.agent.test.AgentTestRunner
+import datadog.trace.api.config.TraceInstrumentationConfig
+import test.TestConnection
+import test.TestDatabaseMetaData
+import test.TestStatement
+
+class SQLServerInjectionForkedTest extends AgentTestRunner {
+
+ @Override
+ void configurePreAgent() {
+ super.configurePreAgent()
+
+ injectSysConfig(TraceInstrumentationConfig.DB_DBM_PROPAGATION_MODE_MODE, "full")
+ injectSysConfig("service.name", "my_service_name")
+ }
+
+ static query = "SELECT 1"
+ static serviceInjection = "ddps='my_service_name',dddbs='sqlserver',ddh='localhost',dddb='testdb'"
+
+ def "SQL Server no trace injection with full"() {
+ setup:
+ def connection = new TestConnection(false)
+ def metadata = new TestDatabaseMetaData()
+ metadata.setURL("jdbc:microsoft:sqlserver://localhost:1433;DatabaseName=testdb;")
+ connection.setMetaData(metadata)
+
+ when:
+ def statement = connection.createStatement() as TestStatement
+ statement.executeQuery(query)
+
+ then:
+ assert statement.sql == "/*${serviceInjection}*/ ${query}"
+ }
+}
diff --git a/dd-java-agent/instrumentation/jdbc/src/test/groovy/test/TestConnection.groovy b/dd-java-agent/instrumentation/jdbc/src/test/groovy/test/TestConnection.groovy
index defb43d9792..9e75ef23d26 100644
--- a/dd-java-agent/instrumentation/jdbc/src/test/groovy/test/TestConnection.groovy
+++ b/dd-java-agent/instrumentation/jdbc/src/test/groovy/test/TestConnection.groovy
@@ -28,6 +28,7 @@ class TestConnection implements Connection {
}
}
+ private DatabaseMetaData metadata
@Override
Statement createStatement() throws SQLException {
@@ -77,7 +78,14 @@ class TestConnection implements Connection {
@Override
DatabaseMetaData getMetaData() throws SQLException {
- return new TestDatabaseMetaData()
+ if (metadata == null) {
+ return new TestDatabaseMetaData()
+ }
+ return metadata
+ }
+
+ void setMetaData(DatabaseMetaData metadata) {
+ this.metadata = metadata
}
@Override
diff --git a/dd-java-agent/instrumentation/jdbc/src/test/groovy/test/TestDatabaseMetaData.groovy b/dd-java-agent/instrumentation/jdbc/src/test/groovy/test/TestDatabaseMetaData.groovy
index 198f1832dd2..ce5b3e783c7 100644
--- a/dd-java-agent/instrumentation/jdbc/src/test/groovy/test/TestDatabaseMetaData.groovy
+++ b/dd-java-agent/instrumentation/jdbc/src/test/groovy/test/TestDatabaseMetaData.groovy
@@ -12,6 +12,8 @@ class TestDatabaseMetaData implements DatabaseMetaData {
return false
}
+ private String url
+
@Override
boolean allTablesAreSelectable() throws SQLException {
return false
@@ -19,7 +21,14 @@ class TestDatabaseMetaData implements DatabaseMetaData {
@Override
String getURL() throws SQLException {
- return "jdbc:testdb://localhost"
+ if (url == null) {
+ return "jdbc:testdb://localhost"
+ }
+ return url
+ }
+
+ void setURL(String url) {
+ this.url = url
}
@Override
diff --git a/dd-trace-core/src/main/java/datadog/trace/core/CoreTracer.java b/dd-trace-core/src/main/java/datadog/trace/core/CoreTracer.java
index 8a2343522d8..8da026f224f 100644
--- a/dd-trace-core/src/main/java/datadog/trace/core/CoreTracer.java
+++ b/dd-trace-core/src/main/java/datadog/trace/core/CoreTracer.java
@@ -1276,6 +1276,7 @@ public class CoreSpanBuilder implements AgentTracer.SpanBuilder {
private Object builderRequestContextDataIast;
private Object builderCiVisibilityContextData;
private List links;
+ private long spanId;
CoreSpanBuilder(
final String instrumentationName, final CharSequence operationName, CoreTracer tracer) {
@@ -1431,6 +1432,12 @@ public AgentTracer.SpanBuilder withLink(AgentSpanLink link) {
return this;
}
+ @Override
+ public CoreSpanBuilder withSpanId(final long spanId) {
+ this.spanId = spanId;
+ return this;
+ }
+
/**
* Build the SpanContext, if the actual span has a parent, the following attributes must be
* propagated: - ServiceName - Baggage - Trace (a list of all spans related) - SpanType
@@ -1439,7 +1446,7 @@ public AgentTracer.SpanBuilder withLink(AgentSpanLink link) {
*/
private DDSpanContext buildSpanContext() {
final DDTraceId traceId;
- final long spanId = idGenerationStrategy.generateSpanId();
+ final long spanId;
final long parentSpanId;
final Map baggage;
final TraceCollector parentTraceCollector;
@@ -1455,6 +1462,11 @@ private DDSpanContext buildSpanContext() {
final PathwayContext pathwayContext;
final PropagationTags propagationTags;
+ if (this.spanId == 0) {
+ spanId = idGenerationStrategy.generateSpanId();
+ } else {
+ spanId = this.spanId;
+ }
// FIXME [API] parentContext should be an interface implemented by ExtractedContext,
// TagContext, DDSpanContext, AgentSpan.Context
AgentSpan.Context parentContext = parent;
diff --git a/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/AgentTracer.java b/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/AgentTracer.java
index af9299df6e4..203eb664bd0 100644
--- a/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/AgentTracer.java
+++ b/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/AgentTracer.java
@@ -321,6 +321,8 @@ public interface SpanBuilder {
SpanBuilder withRequestContextData(RequestContextSlot slot, T data);
SpanBuilder withLink(AgentSpanLink link);
+
+ SpanBuilder withSpanId(long spanId);
}
static class NoopTracerAPI implements TracerAPI {