Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Full mode for SQL Server #7186

Merged
merged 64 commits into from
Aug 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
7bff59f
Full mode APM/DBM for SQL Server
nenadnoveljic Jun 13, 2024
27f8857
Full mode APM/DBM for SQL Server
nenadnoveljic Jun 13, 2024
b2698c8
Add support for prepared queries
nenadnoveljic Jun 14, 2024
5540a39
githooks
nenadnoveljic Jun 26, 2024
33fde67
instrumented the concrete class
nenadnoveljic Jun 28, 2024
d1cb730
if
nenadnoveljic Jun 29, 2024
d283d0a
trigger only for oracle
nenadnoveljic Jul 1, 2024
fce6234
reformat context_info
nenadnoveljic Jul 1, 2024
bb29e39
reformat contect_info
nenadnoveljic Jul 1, 2024
e200b53
instrument the instrumentation
nenadnoveljic Jul 1, 2024
cf4e7b2
sleep in context_info
nenadnoveljic Jul 1, 2024
df4fc90
adjust span start time
nenadnoveljic Jul 1, 2024
20fb17f
bug fix for time manipulation
nenadnoveljic Jul 2, 2024
c4aa15e
no nested spans
nenadnoveljic Jul 2, 2024
1953540
instrumented span as sqlserver
nenadnoveljic Jul 2, 2024
98d4bfa
prevent injecting trace parent in service mode
nenadnoveljic Jul 2, 2024
33dc8b0
concurrent durations
nenadnoveljic Jul 3, 2024
b7b2ba7
return from prepared statement
nenadnoveljic Jul 3, 2024
55d621b
inject main span
nenadnoveljic Jul 3, 2024
e040d58
invert the spans
nenadnoveljic Jul 3, 2024
c4ae679
cleanup
vandonr Jul 4, 2024
81e0c1a
factorize set context code
vandonr Jul 4, 2024
baa05a9
remove todo and commented code
vandonr Jul 4, 2024
5d816f8
also generate scope for null spans
nenadnoveljic Jul 4, 2024
9a9ec00
fix test full mode supported
nenadnoveljic Jul 5, 2024
32d383b
overlapping spans for prepared statements
nenadnoveljic Jul 5, 2024
129af61
precreated span_id (sibling spans)
nenadnoveljic Jul 9, 2024
f459700
reformat
nenadnoveljic Jul 9, 2024
79551b3
factorize isSqlServer
nenadnoveljic Jul 9, 2024
20c90db
updated setContextInfo doc
nenadnoveljic Jul 9, 2024
98d657c
localize spanID
nenadnoveljic Jul 9, 2024
88b7221
format
nenadnoveljic Jul 9, 2024
694fe60
tag instrumentation
nenadnoveljic Jul 9, 2024
b170a00
removing underscore before dd.instrumentation tag
nenadnoveljic Jul 10, 2024
d9a1fed
format
nenadnoveljic Jul 10, 2024
6bdb1cf
fixed prepared statement test
nenadnoveljic Jul 10, 2024
fbe2b8a
format
nenadnoveljic Jul 10, 2024
40386f7
empty commit
nenadnoveljic Jul 10, 2024
7c0e3d1
Test classes
nenadnoveljic Jul 13, 2024
0d57bc1
added sqlserver test
nenadnoveljic Jul 14, 2024
10d5fd6
added sqlserver container
nenadnoveljic Jul 14, 2024
6b01b4d
removed SQL Server test file
nenadnoveljic Jul 14, 2024
69715da
refactoring: use constants
nenadnoveljic Jul 14, 2024
0b19e31
added test cases
nenadnoveljic Jul 15, 2024
c69b2a2
fix formatting
nenadnoveljic Jul 15, 2024
4a0c3b9
Added test cases
nenadnoveljic Jul 15, 2024
6706a0d
boolean instrumentation tag
nenadnoveljic Jul 16, 2024
6bd51da
added instrumentation span
nenadnoveljic Jul 16, 2024
ed1b0d4
fixed tests
nenadnoveljic Jul 16, 2024
31ae1cf
normalized db type
nenadnoveljic Jul 17, 2024
933e813
Update dd-java-agent/instrumentation/jdbc/src/main/java/datadog/trace…
nenadnoveljic Jul 19, 2024
ce8069a
Update dd-java-agent/instrumentation/jdbc/src/main/java/datadog/trace…
nenadnoveljic Jul 19, 2024
bffed95
activate span on return
nenadnoveljic Jul 19, 2024
414f924
statement.close in finally
nenadnoveljic Jul 24, 2024
7b47f62
prepared instrumentation statement
nenadnoveljic Jul 24, 2024
d580d01
prepare call with literal value
nenadnoveljic Jul 26, 2024
c253b32
prepared statements
nenadnoveljic Aug 6, 2024
ba74ddf
delete commented lines
nenadnoveljic Aug 6, 2024
70721bc
without DatatypeConverter
nenadnoveljic Aug 6, 2024
7c8a954
version as constant
nenadnoveljic Aug 7, 2024
fcb763c
context_info as bytes array
nenadnoveljic Aug 7, 2024
b5217d1
eliminate string conversions completely
nenadnoveljic Aug 7, 2024
5de92f3
use bytebuffer
nenadnoveljic Aug 8, 2024
c3e9fa9
improved exception handling
nenadnoveljic Aug 8, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down
2 changes: 2 additions & 0 deletions dd-java-agent/instrumentation/jdbc/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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')

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,23 +1,30 @@
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;
import datadog.trace.bootstrap.instrumentation.decorator.DatabaseClientDecorator;
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;
Expand Down Expand Up @@ -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 <a
* href="https://learn.microsoft.com/fr-fr/sql/t-sql/functions/context-info-transact-sql">MSSQL
* doc</a>. This is to be used where injecting trace and span in the comments with {@link
* SQLCommenter#inject} is not possible or convenient.
*
* <p>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) {
nenadnoveljic marked this conversation as resolved.
Show resolved Hide resolved
final byte VERSION = 0;
final long spanID = Config.get().getIdGenerationStrategy().generateSpanId();
nenadnoveljic marked this conversation as resolved.
Show resolved Hide resolved
AgentSpan instrumentationSpan =
nenadnoveljic marked this conversation as resolved.
Show resolved Hide resolved
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) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you create a smaller try / finally just around instrumentationStatement, then you won't need the null handling.
But that's a bit of a matter of preference

Copy link
Contributor Author

@nenadnoveljic nenadnoveljic Aug 8, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe we need then another try/catch/finally block, as the code before instrumentationStatement = connection.prepareStatement(instrumentationSql); also can throw exceptions we want to log. This would make the code more verbose. The current approach with the ugly (instrumentationStatement != null) check seems less problematic. What do you prefer?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My preference is usually that there's a tight try / finally with the resource acquisition just before the try and the resource disposed of by itself in the finally. This code is mixing the handling of two different resources into the one try / finally structure which can be hard to follow.

try {
instrumentationStatement.close();
} catch (Exception e) {
}
}
instrumentationSpan.finish();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally, finish needs to be in a finally block.
If close raises anything other than a SQLException, the span isn't being finished.

Copy link
Contributor Author

@nenadnoveljic nenadnoveljic Aug 7, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can I change } catch (SQLException e) { to } catch (Exception e) {, and leave instrumentationSpan.finish(); where it is, instead? A (small) advantage would be to avoid nesting.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed as described above

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, it has to be a finally block. catch ( Exception e ) doesn't catch everything, since there are other classes of Throwables.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I opened a separate PR for this.

}
return spanID;
}

@Override
protected void postProcessServiceAndOperationName(
AgentSpan span, DatabaseClientDecorator.NamingEntry namingEntry) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ abstract class JDBCDecoratorTest extends AgentTestRunner {
where:
dbType | expectedByType
"oracle" | false
"sqlserver" | false
"sqlserver" | true
"mysql" | true
"postgresql" | true
}
Expand Down
Loading
Loading