|
1 | 1 | package datadog.trace.instrumentation.jdbc; |
2 | 2 |
|
| 3 | +import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.activateSpan; |
3 | 4 | import static datadog.trace.bootstrap.instrumentation.api.Tags.DB_OPERATION; |
4 | 5 | import static datadog.trace.bootstrap.instrumentation.api.Tags.DB_SCHEMA; |
5 | 6 | import static datadog.trace.bootstrap.instrumentation.api.Tags.DB_WAREHOUSE; |
6 | 7 |
|
7 | 8 | import datadog.trace.api.Config; |
8 | 9 | import datadog.trace.api.DDSpanId; |
| 10 | +import datadog.trace.api.DDTraceId; |
9 | 11 | import datadog.trace.api.naming.SpanNaming; |
10 | 12 | import datadog.trace.bootstrap.ContextStore; |
| 13 | +import datadog.trace.bootstrap.instrumentation.api.AgentScope; |
11 | 14 | import datadog.trace.bootstrap.instrumentation.api.AgentSpan; |
| 15 | +import datadog.trace.bootstrap.instrumentation.api.AgentTracer; |
12 | 16 | import datadog.trace.bootstrap.instrumentation.api.InternalSpanTypes; |
13 | 17 | import datadog.trace.bootstrap.instrumentation.api.Tags; |
14 | 18 | import datadog.trace.bootstrap.instrumentation.api.UTF8BytesString; |
15 | 19 | import datadog.trace.bootstrap.instrumentation.decorator.DatabaseClientDecorator; |
16 | 20 | import datadog.trace.bootstrap.instrumentation.jdbc.DBInfo; |
17 | 21 | import datadog.trace.bootstrap.instrumentation.jdbc.DBQueryInfo; |
18 | 22 | import datadog.trace.bootstrap.instrumentation.jdbc.JDBCConnectionUrlParser; |
| 23 | +import java.nio.ByteBuffer; |
| 24 | +import java.nio.ByteOrder; |
19 | 25 | import java.sql.Connection; |
20 | 26 | import java.sql.DatabaseMetaData; |
| 27 | +import java.sql.PreparedStatement; |
21 | 28 | import java.sql.SQLException; |
22 | 29 | import java.sql.Statement; |
23 | 30 | import java.util.HashSet; |
@@ -235,6 +242,73 @@ public String traceParent(AgentSpan span, int samplingPriority) { |
235 | 242 | return sb.toString(); |
236 | 243 | } |
237 | 244 |
|
| 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 | + |
238 | 312 | @Override |
239 | 313 | protected void postProcessServiceAndOperationName( |
240 | 314 | AgentSpan span, DatabaseClientDecorator.NamingEntry namingEntry) { |
|
0 commit comments