Skip to content

Commit bec85d9

Browse files
Merge pull request #7186 from DataDog/nenanoveljic/sqlserver-full
Full mode for SQL Server
2 parents 39a68b3 + c3e9fa9 commit bec85d9

File tree

12 files changed

+713
-223
lines changed

12 files changed

+713
-223
lines changed

dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/jdbc/DBInfo.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ public Builder type(String type) {
8787
this.type = type;
8888
// Those DBs use the full text of the query including the comments as a cache key,
8989
// so we disable full propagation support for them to avoid destroying the cache.
90-
if (type.equals("oracle") || type.equals("sqlserver")) this.fullPropagationSupport = false;
90+
if (type.equals("oracle")) this.fullPropagationSupport = false;
9191
return this;
9292
}
9393

dd-java-agent/instrumentation/jdbc/build.gradle

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,11 @@ dependencies {
3434

3535
testImplementation group: 'mysql', name: 'mysql-connector-java', version: '8.0.23'
3636
testImplementation group: 'org.postgresql', name: 'postgresql', version: '[9.4,42.2.18]'
37+
testImplementation group: 'com.microsoft.sqlserver', name: 'mssql-jdbc', version: '10.2.0.jre8'
3738

3839
testImplementation group: 'org.testcontainers', name:'mysql', version: libs.versions.testcontainers.get()
3940
testImplementation group: 'org.testcontainers', name:'postgresql', version: libs.versions.testcontainers.get()
41+
testImplementation group: 'org.testcontainers', name:'mssqlserver', version: '1.19.8'
4042

4143
testRuntimeOnly project(':dd-java-agent:instrumentation:iast-instrumenter')
4244

dd-java-agent/instrumentation/jdbc/src/main/java/datadog/trace/instrumentation/jdbc/AbstractPreparedStatementInstrumentation.java

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.startSpan;
66
import static datadog.trace.instrumentation.jdbc.JDBCDecorator.DATABASE_QUERY;
77
import static datadog.trace.instrumentation.jdbc.JDBCDecorator.DECORATE;
8+
import static datadog.trace.instrumentation.jdbc.JDBCDecorator.INJECT_COMMENT;
89
import static datadog.trace.instrumentation.jdbc.JDBCDecorator.logMissingQueryInfo;
910
import static datadog.trace.instrumentation.jdbc.JDBCDecorator.logSQLException;
1011
import static net.bytebuddy.matcher.ElementMatchers.isPublic;
@@ -16,6 +17,7 @@
1617
import datadog.trace.bootstrap.InstrumentationContext;
1718
import datadog.trace.bootstrap.instrumentation.api.AgentScope;
1819
import datadog.trace.bootstrap.instrumentation.api.AgentSpan;
20+
import datadog.trace.bootstrap.instrumentation.api.AgentTracer;
1921
import datadog.trace.bootstrap.instrumentation.jdbc.DBInfo;
2022
import datadog.trace.bootstrap.instrumentation.jdbc.DBQueryInfo;
2123
import java.sql.Connection;
@@ -64,21 +66,31 @@ public static AgentScope onEnter(@Advice.This final Statement statement) {
6466
return null;
6567
}
6668
try {
67-
Connection connection = statement.getConnection();
68-
DBQueryInfo queryInfo =
69+
final Connection connection = statement.getConnection();
70+
final DBQueryInfo queryInfo =
6971
InstrumentationContext.get(Statement.class, DBQueryInfo.class).get(statement);
7072
if (null == queryInfo) {
7173
logMissingQueryInfo(statement);
7274
return null;
7375
}
74-
75-
final AgentSpan span = startSpan(DATABASE_QUERY);
76-
DECORATE.afterStart(span);
77-
DBInfo dbInfo =
76+
final AgentSpan span;
77+
final DBInfo dbInfo =
7878
JDBCDecorator.parseDBInfo(
7979
connection, InstrumentationContext.get(Connection.class, DBInfo.class));
80+
final boolean injectTraceContext = DECORATE.shouldInjectTraceContext(dbInfo);
81+
82+
if (INJECT_COMMENT && injectTraceContext && DECORATE.isSqlServer(dbInfo)) {
83+
// The span ID is pre-determined so that we can reference it when setting the context
84+
final long spanID = DECORATE.setContextInfo(connection, dbInfo);
85+
// we then force that pre-determined span ID for the span covering the actual query
86+
span = AgentTracer.get().buildSpan(DATABASE_QUERY).withSpanId(spanID).start();
87+
} else {
88+
span = startSpan(DATABASE_QUERY);
89+
}
90+
DECORATE.afterStart(span);
8091
DECORATE.onConnection(span, dbInfo);
8192
DECORATE.onPreparedStatement(span, queryInfo);
93+
8294
return activateSpan(span);
8395
} catch (SQLException e) {
8496
logSQLException(e);

dd-java-agent/instrumentation/jdbc/src/main/java/datadog/trace/instrumentation/jdbc/JDBCDecorator.java

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,30 @@
11
package datadog.trace.instrumentation.jdbc;
22

3+
import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.activateSpan;
34
import static datadog.trace.bootstrap.instrumentation.api.Tags.DB_OPERATION;
45
import static datadog.trace.bootstrap.instrumentation.api.Tags.DB_SCHEMA;
56
import static datadog.trace.bootstrap.instrumentation.api.Tags.DB_WAREHOUSE;
67

78
import datadog.trace.api.Config;
89
import datadog.trace.api.DDSpanId;
10+
import datadog.trace.api.DDTraceId;
911
import datadog.trace.api.naming.SpanNaming;
1012
import datadog.trace.bootstrap.ContextStore;
13+
import datadog.trace.bootstrap.instrumentation.api.AgentScope;
1114
import datadog.trace.bootstrap.instrumentation.api.AgentSpan;
15+
import datadog.trace.bootstrap.instrumentation.api.AgentTracer;
1216
import datadog.trace.bootstrap.instrumentation.api.InternalSpanTypes;
1317
import datadog.trace.bootstrap.instrumentation.api.Tags;
1418
import datadog.trace.bootstrap.instrumentation.api.UTF8BytesString;
1519
import datadog.trace.bootstrap.instrumentation.decorator.DatabaseClientDecorator;
1620
import datadog.trace.bootstrap.instrumentation.jdbc.DBInfo;
1721
import datadog.trace.bootstrap.instrumentation.jdbc.DBQueryInfo;
1822
import datadog.trace.bootstrap.instrumentation.jdbc.JDBCConnectionUrlParser;
23+
import java.nio.ByteBuffer;
24+
import java.nio.ByteOrder;
1925
import java.sql.Connection;
2026
import java.sql.DatabaseMetaData;
27+
import java.sql.PreparedStatement;
2128
import java.sql.SQLException;
2229
import java.sql.Statement;
2330
import java.util.HashSet;
@@ -235,6 +242,73 @@ public String traceParent(AgentSpan span, int samplingPriority) {
235242
return sb.toString();
236243
}
237244

245+
public boolean isSqlServer(final DBInfo dbInfo) {
246+
return "sqlserver".equals(dbInfo.getType());
247+
}
248+
249+
/**
250+
* Executes a `SET CONTEXT_INFO` statement on the DB with the active trace ID and the given span
251+
* ID. This context will be "attached" to future queries on the same connection. See <a
252+
* href="https://learn.microsoft.com/fr-fr/sql/t-sql/functions/context-info-transact-sql">MSSQL
253+
* doc</a>. This is to be used where injecting trace and span in the comments with {@link
254+
* SQLCommenter#inject} is not possible or convenient.
255+
*
256+
* <p>Upsides: still "visible" in sub-queries, does not bust caches based on full query text
257+
* Downsides: takes time.
258+
*
259+
* @param connection The same connection as the one that will be used for the actual statement
260+
* @param dbInfo dbInfo of the instrumented database
261+
* @return spanID pre-created spanID
262+
*/
263+
public long setContextInfo(Connection connection, DBInfo dbInfo) {
264+
final byte VERSION = 0;
265+
final long spanID = Config.get().getIdGenerationStrategy().generateSpanId();
266+
AgentSpan instrumentationSpan =
267+
AgentTracer.get().buildSpan("set context_info").withTag("dd.instrumentation", true).start();
268+
DECORATE.afterStart(instrumentationSpan);
269+
DECORATE.onConnection(instrumentationSpan, dbInfo);
270+
PreparedStatement instrumentationStatement = null;
271+
try (AgentScope scope = activateSpan(instrumentationSpan)) {
272+
final byte samplingDecision =
273+
(byte) (instrumentationSpan.forceSamplingDecision() > 0 ? 1 : 0);
274+
final byte versionAndSamplingDecision =
275+
(byte) ((VERSION << 4) & 0b11110000 | samplingDecision & 0b00000001);
276+
277+
ByteBuffer byteBuffer = ByteBuffer.allocate(1 + 3 * Long.BYTES);
278+
byteBuffer.order(ByteOrder.BIG_ENDIAN);
279+
280+
byteBuffer.put(versionAndSamplingDecision);
281+
byteBuffer.putLong(spanID);
282+
final DDTraceId traceId = instrumentationSpan.getTraceId();
283+
byteBuffer.putLong(traceId.toHighOrderLong());
284+
byteBuffer.putLong(traceId.toLong());
285+
final byte[] contextInfo = byteBuffer.array();
286+
287+
String instrumentationSql = "set context_info ?";
288+
instrumentationStatement = connection.prepareStatement(instrumentationSql);
289+
instrumentationStatement.setBytes(1, contextInfo);
290+
DECORATE.onStatement(instrumentationSpan, instrumentationSql);
291+
instrumentationStatement.execute();
292+
} catch (Exception e) {
293+
log.debug(
294+
"Failed to set extra DBM data in context info for trace {}. "
295+
+ "To disable this behavior, set DBM_PROPAGATION_MODE to 'service' mode. "
296+
+ "See https://docs.datadoghq.com/database_monitoring/connect_dbm_and_apm/ for more info.{}",
297+
instrumentationSpan.getTraceId().toHexString(),
298+
e);
299+
DECORATE.onError(instrumentationSpan, e);
300+
} finally {
301+
if (instrumentationStatement != null) {
302+
try {
303+
instrumentationStatement.close();
304+
} catch (Exception e) {
305+
}
306+
}
307+
instrumentationSpan.finish();
308+
}
309+
return spanID;
310+
}
311+
238312
@Override
239313
protected void postProcessServiceAndOperationName(
240314
AgentSpan span, DatabaseClientDecorator.NamingEntry namingEntry) {

dd-java-agent/instrumentation/jdbc/src/main/java/datadog/trace/instrumentation/jdbc/StatementInstrumentation.java

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import datadog.trace.bootstrap.InstrumentationContext;
2222
import datadog.trace.bootstrap.instrumentation.api.AgentScope;
2323
import datadog.trace.bootstrap.instrumentation.api.AgentSpan;
24+
import datadog.trace.bootstrap.instrumentation.api.AgentTracer;
2425
import datadog.trace.bootstrap.instrumentation.jdbc.DBInfo;
2526
import java.sql.Connection;
2627
import java.sql.SQLException;
@@ -84,18 +85,29 @@ public static AgentScope onEnter(
8485
}
8586
try {
8687
final Connection connection = statement.getConnection();
87-
final AgentSpan span = startSpan(DATABASE_QUERY);
88-
DECORATE.afterStart(span);
8988
final DBInfo dbInfo =
9089
JDBCDecorator.parseDBInfo(
9190
connection, InstrumentationContext.get(Connection.class, DBInfo.class));
91+
boolean injectTraceContext = DECORATE.shouldInjectTraceContext(dbInfo);
92+
final AgentSpan span;
93+
final boolean isSqlServer = DECORATE.isSqlServer(dbInfo);
94+
95+
if (isSqlServer && INJECT_COMMENT && injectTraceContext) {
96+
// The span ID is pre-determined so that we can reference it when setting the context
97+
final long spanID = DECORATE.setContextInfo(connection, dbInfo);
98+
// we then force that pre-determined span ID for the span covering the actual query
99+
span = AgentTracer.get().buildSpan(DATABASE_QUERY).withSpanId(spanID).start();
100+
} else {
101+
span = startSpan(DATABASE_QUERY);
102+
}
103+
104+
DECORATE.afterStart(span);
92105
DECORATE.onConnection(span, dbInfo);
93106
final String copy = sql;
94107
if (span != null && INJECT_COMMENT) {
95108
String traceParent = null;
96109

97-
boolean injectTraceContext = DECORATE.shouldInjectTraceContext(dbInfo);
98-
if (injectTraceContext) {
110+
if (injectTraceContext && !isSqlServer) {
99111
Integer priority = span.forceSamplingDecision();
100112
if (priority != null) {
101113
traceParent = DECORATE.traceParent(span, priority);

dd-java-agent/instrumentation/jdbc/src/test/groovy/JDBCDecoratorTest.groovy

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ abstract class JDBCDecoratorTest extends AgentTestRunner {
2121
where:
2222
dbType | expectedByType
2323
"oracle" | false
24-
"sqlserver" | false
24+
"sqlserver" | true
2525
"mysql" | true
2626
"postgresql" | true
2727
}

0 commit comments

Comments
 (0)