diff --git a/dd-java-agent/agent-bootstrap/src/jmh/java/datadog/trace/bootstrap/instrumentation/jdbc/JDBCConnectionContextBenchmark.java b/dd-java-agent/agent-bootstrap/src/jmh/java/datadog/trace/bootstrap/instrumentation/jdbc/JDBCConnectionContextBenchmark.java new file mode 100644 index 00000000000..5bc46035d41 --- /dev/null +++ b/dd-java-agent/agent-bootstrap/src/jmh/java/datadog/trace/bootstrap/instrumentation/jdbc/JDBCConnectionContextBenchmark.java @@ -0,0 +1,49 @@ +package datadog.trace.bootstrap.instrumentation.jdbc; + +import java.util.concurrent.TimeUnit; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Threads; +import org.openjdk.jmh.annotations.Warmup; + +/** + * Measures the stable Oracle service-hash path. + * + *
+ * ./gradlew :dd-java-agent:agent-bootstrap:jmh \
+ *   -Pjmh.includes=JDBCConnectionContextBenchmark -Pjmh.profilers=gc
+ * 
+ */ +@BenchmarkMode(Mode.Throughput) +@OutputTimeUnit(TimeUnit.SECONDS) +@Fork(5) +@Warmup(iterations = 2) +@Measurement(iterations = 5) +@Threads(8) +public class JDBCConnectionContextBenchmark { + + @State(Scope.Thread) + public static class ConnectionState { + private static final String BASE_HASH = "-6937226773133363462"; + + final JDBCConnectionContext context = + new JDBCConnectionContext(new DBInfo.Builder().type("oracle").build()); + + @Setup + public void setup() { + context.markOracleServiceHashSet(BASE_HASH); + } + } + + @Benchmark + public boolean stableHash(ConnectionState state) { + return state.context.shouldSetOracleServiceHash(ConnectionState.BASE_HASH); + } +} 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 6b802825b47..17aa109ad1e 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 @@ -17,7 +17,6 @@ public final class DBInfo { private final Integer port; private final String warehouse; private final String schema; - private volatile String poolName; DBInfo( String type, @@ -30,8 +29,7 @@ public final class DBInfo { String host, Integer port, String warehouse, - String schema, - String poolName) { + String schema) { this.type = type; this.subtype = subtype; this.fullPropagationSupport = fullPropagationSupport; @@ -43,7 +41,6 @@ public final class DBInfo { this.port = port; this.warehouse = warehouse; this.schema = schema; - this.poolName = poolName; } public static final class Builder { @@ -60,7 +57,6 @@ public static final class Builder { private String schema; private String host; private Integer port; - private String poolName; Builder() {} @@ -75,8 +71,7 @@ public static final class Builder { String host, Integer port, String warehouse, - String schema, - String poolName) { + String schema) { this.type = type; this.subtype = subtype; this.fullPropagationSupport = fullPropagationSupport; @@ -88,7 +83,6 @@ public static final class Builder { this.port = port; this.warehouse = warehouse; this.schema = schema; - this.poolName = poolName; } public Builder type(String type) { @@ -146,11 +140,6 @@ public Builder port(Integer port) { return this; } - public Builder poolName(String poolName) { - this.poolName = poolName; - return this; - } - public DBInfo build() { return new DBInfo( type, @@ -163,8 +152,7 @@ public DBInfo build() { host, port, warehouse, - schema, - poolName); + schema); } } @@ -212,14 +200,6 @@ public String getSchema() { return schema; } - public String getPoolName() { - return poolName; - } - - public void setPoolName(String poolname) { - this.poolName = poolname; - } - public Builder toBuilder() { return new Builder( type, @@ -232,8 +212,7 @@ public Builder toBuilder() { host, port, warehouse, - schema, - poolName); + schema); } @Override @@ -251,8 +230,7 @@ public boolean equals(Object o) { && Objects.equals(host, dbInfo.host) && Objects.equals(port, dbInfo.port) && Objects.equals(warehouse, dbInfo.warehouse) - && Objects.equals(schema, dbInfo.schema) - && Objects.equals(poolName, dbInfo.poolName); + && Objects.equals(schema, dbInfo.schema); } @Override diff --git a/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/jdbc/JDBCConnectionContext.java b/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/jdbc/JDBCConnectionContext.java new file mode 100644 index 00000000000..30e43725baa --- /dev/null +++ b/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/jdbc/JDBCConnectionContext.java @@ -0,0 +1,46 @@ +package datadog.trace.bootstrap.instrumentation.jdbc; + +/** Immutable database metadata and mutable state scoped to one JDBC connection. */ +public final class JDBCConnectionContext { + public static final JDBCConnectionContext DEFAULT = new JDBCConnectionContext(DBInfo.DEFAULT); + + private final DBInfo dbInfo; + private volatile String poolName; + private volatile String oracleServiceHash; + private volatile boolean oracleServiceActionUnsupported; + + public JDBCConnectionContext(DBInfo dbInfo) { + this.dbInfo = dbInfo; + } + + public DBInfo getDbInfo() { + return dbInfo; + } + + public String getPoolName() { + return poolName; + } + + public void setPoolName(String poolName) { + this.poolName = poolName; + } + + /** Returns whether this connection needs the given Oracle service hash. */ + public boolean shouldSetOracleServiceHash(String baseHash) { + return !oracleServiceActionUnsupported && !baseHash.equals(oracleServiceHash); + } + + /** Returns whether the given Oracle service hash was successfully applied to this connection. */ + public boolean isOracleServiceHashSet(String baseHash) { + return baseHash.equals(oracleServiceHash); + } + + /** Records a successfully applied Oracle service hash. */ + public void markOracleServiceHashSet(String baseHash) { + oracleServiceHash = baseHash; + } + + public void markOracleServiceActionUnsupported() { + oracleServiceActionUnsupported = true; + } +} diff --git a/dd-java-agent/agent-bootstrap/src/test/java/datadog/trace/bootstrap/instrumentation/jdbc/JDBCConnectionContextTest.java b/dd-java-agent/agent-bootstrap/src/test/java/datadog/trace/bootstrap/instrumentation/jdbc/JDBCConnectionContextTest.java new file mode 100644 index 00000000000..c5bb6d4fe44 --- /dev/null +++ b/dd-java-agent/agent-bootstrap/src/test/java/datadog/trace/bootstrap/instrumentation/jdbc/JDBCConnectionContextTest.java @@ -0,0 +1,57 @@ +package datadog.trace.bootstrap.instrumentation.jdbc; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +class JDBCConnectionContextTest { + + @Test + void keepsMutableStateSeparateFromDatabaseInfo() { + DBInfo dbInfo = new DBInfo.Builder().type("oracle").build(); + JDBCConnectionContext first = new JDBCConnectionContext(dbInfo); + JDBCConnectionContext second = new JDBCConnectionContext(dbInfo); + + assertSame(dbInfo, first.getDbInfo()); + assertSame(dbInfo, second.getDbInfo()); + assertNull(first.getPoolName()); + assertNull(second.getPoolName()); + + first.setPoolName("first-pool"); + + assertEquals("first-pool", first.getPoolName()); + assertNull(second.getPoolName()); + } + + @Test + void tracksTheLastSuccessfullySetOracleServiceHash() { + JDBCConnectionContext context = oracleContext(); + + assertTrue(context.shouldSetOracleServiceHash("123")); + assertFalse(context.isOracleServiceHashSet("123")); + + context.markOracleServiceHashSet("123"); + + assertFalse(context.shouldSetOracleServiceHash("123")); + assertTrue(context.isOracleServiceHashSet("123")); + assertFalse(context.isOracleServiceHashSet("456")); + assertTrue(context.shouldSetOracleServiceHash("456")); + } + + @Test + void skipsActionAfterDriverIsMarkedUnsupported() { + JDBCConnectionContext context = oracleContext(); + + context.markOracleServiceActionUnsupported(); + + assertFalse(context.shouldSetOracleServiceHash("123")); + } + + private static JDBCConnectionContext oracleContext() { + return new JDBCConnectionContext(new DBInfo.Builder().type("oracle").build()); + } +} 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 f24dcb8077a..ba9d7322669 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 @@ -22,6 +22,7 @@ import datadog.trace.bootstrap.instrumentation.api.AgentTracer; import datadog.trace.bootstrap.instrumentation.jdbc.DBInfo; import datadog.trace.bootstrap.instrumentation.jdbc.DBQueryInfo; +import datadog.trace.bootstrap.instrumentation.jdbc.JDBCConnectionContext; import java.sql.Connection; import java.sql.SQLException; import java.sql.Statement; @@ -48,7 +49,7 @@ public String[] helperClassNames() { public Map contextStore() { Map contextStore = new HashMap<>(4); contextStore.put("java.sql.Statement", DBQueryInfo.class.getName()); - contextStore.put("java.sql.Connection", DBInfo.class.getName()); + contextStore.put("java.sql.Connection", JDBCConnectionContext.class.getName()); return contextStore; } @@ -76,15 +77,20 @@ public static AgentScope onEnter(@Advice.This final Statement statement) { return null; } final AgentSpan span; - final DBInfo dbInfo = - JDBCDecorator.parseDBInfo( - connection, InstrumentationContext.get(Connection.class, DBInfo.class)); + final JDBCConnectionContext connectionContext = + JDBCDecorator.parseConnectionContext( + connection, + InstrumentationContext.get(Connection.class, JDBCConnectionContext.class)); + final DBInfo dbInfo = connectionContext.getDbInfo(); final boolean injectTraceContext = DECORATE.shouldInjectTraceContext(dbInfo); + final String oracleServiceHash = + DECORATE.setServiceHashAction(connection, connectionContext); + if (INJECT_COMMENT && injectTraceContext) { if (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); + final long spanID = DECORATE.setContextInfo(connection, connectionContext); // we then force that pre-determined span ID for the span covering the actual query span = AgentTracer.get() @@ -105,9 +111,9 @@ public static AgentScope onEnter(@Advice.This final Statement statement) { span = startSpan("java-jdbc-prepared_statement", DATABASE_QUERY); } DECORATE.afterStart(span); - DECORATE.onConnection(span, dbInfo); + DECORATE.onConnection(span, connectionContext); DECORATE.onPreparedStatement(span, queryInfo); - DECORATE.withBaseHash(span); + DECORATE.withBaseHash(span, dbInfo, oracleServiceHash); return activateSpan(span); } catch (SQLException e) { diff --git a/dd-java-agent/instrumentation/jdbc/src/main/java/datadog/trace/instrumentation/jdbc/DBMCompatibleConnectionInstrumentation.java b/dd-java-agent/instrumentation/jdbc/src/main/java/datadog/trace/instrumentation/jdbc/DBMCompatibleConnectionInstrumentation.java index 486a92dcbf7..1565a1af0f5 100644 --- a/dd-java-agent/instrumentation/jdbc/src/main/java/datadog/trace/instrumentation/jdbc/DBMCompatibleConnectionInstrumentation.java +++ b/dd-java-agent/instrumentation/jdbc/src/main/java/datadog/trace/instrumentation/jdbc/DBMCompatibleConnectionInstrumentation.java @@ -20,6 +20,7 @@ import datadog.trace.bootstrap.instrumentation.api.AgentSpan; import datadog.trace.bootstrap.instrumentation.jdbc.DBInfo; import datadog.trace.bootstrap.instrumentation.jdbc.DBQueryInfo; +import datadog.trace.bootstrap.instrumentation.jdbc.JDBCConnectionContext; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.Statement; @@ -102,7 +103,7 @@ public void methodAdvice(MethodTransformer transformer) { public Map contextStore() { Map contextStore = new HashMap<>(4); contextStore.put("java.sql.Statement", DBQueryInfo.class.getName()); - contextStore.put("java.sql.Connection", DBInfo.class.getName()); + contextStore.put("java.sql.Connection", JDBCConnectionContext.class.getName()); return contextStore; } @@ -121,8 +122,13 @@ public static String onEnter( final String inputSql = sql; final AgentSpan activeSpan = activeSpan(); final DBInfo dbInfo = - JDBCDecorator.parseDBInfo( - connection, InstrumentationContext.get(Connection.class, DBInfo.class)); + JDBCDecorator.parseConnectionContext( + connection, + InstrumentationContext.get(Connection.class, JDBCConnectionContext.class)) + .getDbInfo(); + if (!DECORATE.shouldInjectSqlComment(dbInfo)) { + return inputSql; + } String dbService = DECORATE.getDbService(dbInfo); if (dbService != null) { dbService = traceConfig(activeSpan).getServiceMapping().getOrDefault(dbService, dbService); diff --git a/dd-java-agent/instrumentation/jdbc/src/main/java/datadog/trace/instrumentation/jdbc/DriverInstrumentation.java b/dd-java-agent/instrumentation/jdbc/src/main/java/datadog/trace/instrumentation/jdbc/DriverInstrumentation.java index c5c266ed312..f593a8dc130 100644 --- a/dd-java-agent/instrumentation/jdbc/src/main/java/datadog/trace/instrumentation/jdbc/DriverInstrumentation.java +++ b/dd-java-agent/instrumentation/jdbc/src/main/java/datadog/trace/instrumentation/jdbc/DriverInstrumentation.java @@ -12,6 +12,7 @@ import datadog.trace.agent.tooling.InstrumenterModule; import datadog.trace.bootstrap.InstrumentationContext; import datadog.trace.bootstrap.instrumentation.jdbc.DBInfo; +import datadog.trace.bootstrap.instrumentation.jdbc.JDBCConnectionContext; import datadog.trace.bootstrap.instrumentation.jdbc.JDBCConnectionUrlParser; import java.sql.Connection; import java.sql.DatabaseMetaData; @@ -43,7 +44,7 @@ public ElementMatcher hierarchyMatcher() { @Override public Map contextStore() { - return singletonMap("java.sql.Connection", DBInfo.class.getName()); + return singletonMap("java.sql.Connection", JDBCConnectionContext.class.getName()); } @Override @@ -114,7 +115,8 @@ public static void addDBInfo( } } DBInfo dbInfo = JDBCConnectionUrlParser.extractDBInfo(connectionUrl, connectionProps); - InstrumentationContext.get(Connection.class, DBInfo.class).put(connWithContext, dbInfo); + InstrumentationContext.get(Connection.class, JDBCConnectionContext.class) + .put(connWithContext, new JDBCConnectionContext(dbInfo)); } } } diff --git a/dd-java-agent/instrumentation/jdbc/src/main/java/datadog/trace/instrumentation/jdbc/HikariDataSourceInstrumentation.java b/dd-java-agent/instrumentation/jdbc/src/main/java/datadog/trace/instrumentation/jdbc/HikariDataSourceInstrumentation.java index 05517b28c1e..ecdf80c5a88 100644 --- a/dd-java-agent/instrumentation/jdbc/src/main/java/datadog/trace/instrumentation/jdbc/HikariDataSourceInstrumentation.java +++ b/dd-java-agent/instrumentation/jdbc/src/main/java/datadog/trace/instrumentation/jdbc/HikariDataSourceInstrumentation.java @@ -8,7 +8,7 @@ import datadog.trace.agent.tooling.Instrumenter; import datadog.trace.agent.tooling.InstrumenterModule; import datadog.trace.bootstrap.InstrumentationContext; -import datadog.trace.bootstrap.instrumentation.jdbc.DBInfo; +import datadog.trace.bootstrap.instrumentation.jdbc.JDBCConnectionContext; import java.sql.Connection; import java.util.Map; import net.bytebuddy.asm.Advice; @@ -32,7 +32,7 @@ public String instrumentedType() { @Override public Map contextStore() { - return singletonMap("java.sql.Connection", DBInfo.class.getName()); + return singletonMap("java.sql.Connection", JDBCConnectionContext.class.getName()); } @Override @@ -66,11 +66,12 @@ public static void start( if (unwrapped == null) { return; } - DBInfo dbInfo = InstrumentationContext.get(Connection.class, DBInfo.class).get(unwrapped); - if (dbInfo == null) { + JDBCConnectionContext connectionContext = + InstrumentationContext.get(Connection.class, JDBCConnectionContext.class).get(unwrapped); + if (connectionContext == null) { return; } - dbInfo.setPoolName(hikariPoolname); + connectionContext.setPoolName(hikariPoolname); } } } diff --git a/dd-java-agent/instrumentation/jdbc/src/main/java/datadog/trace/instrumentation/jdbc/IastConnectionCallSite.java b/dd-java-agent/instrumentation/jdbc/src/main/java/datadog/trace/instrumentation/jdbc/IastConnectionCallSite.java index 79ccdfcf20f..40a1c839bde 100644 --- a/dd-java-agent/instrumentation/jdbc/src/main/java/datadog/trace/instrumentation/jdbc/IastConnectionCallSite.java +++ b/dd-java-agent/instrumentation/jdbc/src/main/java/datadog/trace/instrumentation/jdbc/IastConnectionCallSite.java @@ -11,6 +11,7 @@ import datadog.trace.api.iast.sink.SqlInjectionModule; import datadog.trace.bootstrap.ContextStore; import datadog.trace.bootstrap.instrumentation.jdbc.DBInfo; +import datadog.trace.bootstrap.instrumentation.jdbc.JDBCConnectionContext; import java.sql.Connection; import javax.annotation.Nonnull; @@ -20,20 +21,21 @@ helpers = {JDBCDecorator.class}) public class IastConnectionCallSite { - private static ContextStore DB_INFO_STORE = null; + private static ContextStore CONNECTION_CONTEXT_STORE = null; @SuppressWarnings("unchecked") @Nonnull public static DBInfo getDBInfo(final Connection connection) { - if (DB_INFO_STORE == null) { - final int storeId = getContextStoreId(Connection.class.getName(), DBInfo.class.getName()); + if (CONNECTION_CONTEXT_STORE == null) { + final int storeId = + getContextStoreId(Connection.class.getName(), JDBCConnectionContext.class.getName()); final ContextStore store = getContextStore(storeId); - DB_INFO_STORE = (ContextStore) store; + CONNECTION_CONTEXT_STORE = (ContextStore) store; } - if (DB_INFO_STORE == null) { + if (CONNECTION_CONTEXT_STORE == null) { return JDBCDecorator.parseDBInfoFromConnection(connection); } else { - return JDBCDecorator.parseDBInfo(connection, DB_INFO_STORE); + return JDBCDecorator.parseConnectionContext(connection, CONNECTION_CONTEXT_STORE).getDbInfo(); } } 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 7914ef8d89c..a6da8a0148c 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 @@ -24,12 +24,15 @@ 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.JDBCConnectionContext; import datadog.trace.bootstrap.instrumentation.jdbc.JDBCConnectionUrlParser; import java.nio.ByteBuffer; import java.nio.ByteOrder; +import java.sql.ClientInfoStatus; import java.sql.Connection; import java.sql.DatabaseMetaData; import java.sql.PreparedStatement; +import java.sql.SQLClientInfoException; import java.sql.SQLException; import java.sql.Statement; import java.util.HashSet; @@ -54,6 +57,8 @@ public class JDBCDecorator extends DatabaseClientDecorator { SpanNaming.instance().namingSchema().database().service("jdbc"); public static final String DD_INSTRUMENTATION_PREFIX = "_DD_"; + public static final String DD_ORACLE_SERVICE_HASH_PREFIX = "_DD_DDSH:"; + private static final String ORACLE_ACTION_CLIENT_INFO = "OCSID.ACTION"; public static final String DBM_PROPAGATION_MODE = Config.get().getDbmPropagationMode(); private static final boolean DBM_INJECT_SQL_BASE_HASH = Config.get().isDbmInjectSqlBaseHash(); @@ -65,6 +70,10 @@ public class JDBCDecorator extends DatabaseClientDecorator { || DBM_PROPAGATION_MODE.equals(DBM_PROPAGATION_MODE_DYNAMIC_SERVICE); private static final boolean INJECT_TRACE_CONTEXT = DBM_PROPAGATION_MODE.equals(DBM_PROPAGATION_MODE_FULL); + private static final boolean INJECT_ORACLE_SERVICE_HASH_ACTION = + DBM_PROPAGATION_MODE.equals(DBM_PROPAGATION_MODE_DYNAMIC_SERVICE) + && PROPAGATE_PROCESS_TAGS + && Config.get().isDbmPropagationOracleActionOnlyEnabled(); public static final boolean DBM_TRACE_PREPARED_STATEMENTS = Config.get().isDbmTracePreparedStatements(); public static final boolean DBM_ALWAYS_APPEND_SQL_COMMENT = @@ -153,23 +162,24 @@ private void setTagIfPresent(final AgentSpan span, final String key, final Strin } } - public void onConnection(final AgentSpan span, DBInfo dbInfo) { + public void onConnection(final AgentSpan span, final JDBCConnectionContext connectionContext) { + final DBInfo dbInfo = connectionContext.getDbInfo(); if (dbInfo != null) { processDatabaseType(span, dbInfo.getType()); setTagIfPresent(span, DB_WAREHOUSE, dbInfo.getWarehouse()); setTagIfPresent(span, DB_SCHEMA, dbInfo.getSchema()); - setTagIfPresent(span, DB_POOL_NAME, dbInfo.getPoolName()); + setTagIfPresent(span, DB_POOL_NAME, connectionContext.getPoolName()); } super.onConnection(span, dbInfo); } - public static DBInfo parseDBInfo( - final Connection connection, ContextStore contextStore) { + public static JDBCConnectionContext parseConnectionContext( + final Connection connection, ContextStore contextStore) { if (connection == null) { - return DBInfo.DEFAULT; + return JDBCConnectionContext.DEFAULT; } - DBInfo dbInfo = contextStore.get(connection); + JDBCConnectionContext connectionContext = contextStore.get(connection); /* * Logic to get the DBInfo from a JDBC Connection, if the connection was not created via * Driver.connect, or it has never seen before, the connectionInfo map will return null and will @@ -178,32 +188,32 @@ public static DBInfo parseDBInfo( * avoid retry overhead. */ { - if (dbInfo == null) { - // first look for injected DBInfo in wrapped delegates + if (connectionContext == null) { + // first look for injected connection context in wrapped delegates Connection conn = connection; Set connections = new HashSet<>(); connections.add(conn); try { - while (dbInfo == null) { + while (connectionContext == null) { Connection delegate = conn.unwrap(Connection.class); if (delegate == null || !connections.add(delegate)) { // cycle detected, stop looking break; } - dbInfo = contextStore.get(delegate); + connectionContext = contextStore.get(delegate); conn = delegate; } } catch (Throwable ignore) { } - if (dbInfo == null) { + if (connectionContext == null) { // couldn't find DBInfo from a previous call anywhere, so we try to fetch it from the DB - dbInfo = parseDBInfoFromConnection(connection); + connectionContext = new JDBCConnectionContext(parseDBInfoFromConnection(connection)); } - // store the DBInfo on the outermost connection instance to avoid future searches - contextStore.put(connection, dbInfo); + // store the context on the outermost connection instance to avoid future searches + contextStore.put(connection, connectionContext); } } - return dbInfo; + return connectionContext; } public String getDbService(final DBInfo dbInfo) { @@ -263,9 +273,13 @@ public void onPreparedStatement(AgentSpan span, DBQueryInfo dbQueryInfo) { * Sets the base hash tag on the span if DBM hash injection is enabled. This is necessary so that * the span (tags) and the query can be matched in the backend. */ - public void withBaseHash(AgentSpan span) { + public void withBaseHash(AgentSpan span, DBInfo dbInfo, String oracleServiceHash) { if (INJECT_COMMENT && DBM_INJECT_SQL_BASE_HASH && PROPAGATE_PROCESS_TAGS) { - span.setTag(Tags.BASE_HASH, BaseHash.getBaseHashStr()); + final String baseHash = + usesOracleServiceHashAction(dbInfo) ? oracleServiceHash : BaseHash.getBaseHashStr(); + if (baseHash != null) { + span.setTag(Tags.BASE_HASH, baseHash); + } } } @@ -292,6 +306,69 @@ public boolean isSqlServer(final DBInfo dbInfo) { return "sqlserver".equals(dbInfo.getType()); } + public boolean shouldInjectSqlComment(final DBInfo dbInfo) { + return INJECT_COMMENT && !usesOracleServiceHashAction(dbInfo); + } + + private boolean usesOracleServiceHashAction(final DBInfo dbInfo) { + return INJECT_ORACLE_SERVICE_HASH_ACTION && isOracle(dbInfo); + } + + /** + * Sets the dynamic service hash in {@code v$session.action} once per Oracle session and hash. + * + * @return the hash in ACTION, or {@code null} if ACTION propagation is disabled or failed + */ + public String setServiceHashAction( + Connection connection, JDBCConnectionContext connectionContext) { + if (!usesOracleServiceHashAction(connectionContext.getDbInfo())) { + return null; + } + + String baseHash = BaseHash.getBaseHashStr(); + if (baseHash == null) { + return null; + } + if (!connectionContext.shouldSetOracleServiceHash(baseHash)) { + return connectionContext.isOracleServiceHashSet(baseHash) ? baseHash : null; + } + synchronized (connectionContext) { + // Re-read after taking the lock so a waiting query does not restore a stale process hash. + baseHash = BaseHash.getBaseHashStr(); + if (baseHash == null) { + return null; + } + if (!connectionContext.shouldSetOracleServiceHash(baseHash)) { + return connectionContext.isOracleServiceHashSet(baseHash) ? baseHash : null; + } + try { + connection.setClientInfo( + ORACLE_ACTION_CLIENT_INFO, DD_ORACLE_SERVICE_HASH_PREFIX + baseHash); + connectionContext.markOracleServiceHashSet(baseHash); + return baseHash; + } catch (Throwable e) { + if (isUnsupportedOracleAction(e)) { + connectionContext.markOracleServiceActionUnsupported(); + } + logInjectionErrorOnce("service hash action", e); + return null; + } + } + } + + private static boolean isUnsupportedOracleAction(Throwable error) { + if (error instanceof UnsupportedOperationException || error instanceof AbstractMethodError) { + return true; + } + if (error instanceof SQLClientInfoException) { + final SQLClientInfoException clientInfoException = (SQLClientInfoException) error; + return clientInfoException.getFailedProperties() != null + && ClientInfoStatus.REASON_UNKNOWN_PROPERTY.equals( + clientInfoException.getFailedProperties().get(ORACLE_ACTION_CLIENT_INFO)); + } + return false; + } + /** * Executes `connection.setClientInfo("OCSID.ACTION", traceContext)` statement on the Oracle DB to * set the trace parent in `v$session.action`. This is used because it isn't possible to propagate @@ -329,10 +406,10 @@ public void setAction(AgentSpan span, Connection connection) { * 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 + * @param connectionContext metadata and state for the instrumented connection * @return spanID pre-created spanID */ - public long setContextInfo(Connection connection, DBInfo dbInfo) { + public long setContextInfo(Connection connection, JDBCConnectionContext connectionContext) { final byte VERSION = 0; final long spanID = Config.get().getIdGenerationStrategy().generateSpanId(); // potentially get build span like here @@ -342,7 +419,7 @@ public long setContextInfo(Connection connection, DBInfo dbInfo) { .withTag("dd.instrumentation", true) .start(); DECORATE.afterStart(instrumentationSpan); - DECORATE.onConnection(instrumentationSpan, dbInfo); + DECORATE.onConnection(instrumentationSpan, connectionContext); try (ContextScope scope = activateSpan(instrumentationSpan)) { final byte samplingDecision = (byte) (instrumentationSpan.forceSamplingDecision() > 0 ? 1 : 0); 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 5b5ec71b671..767ee6a8914 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 @@ -25,6 +25,7 @@ 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.JDBCConnectionContext; import java.sql.Connection; import java.sql.SQLException; import java.sql.Statement; @@ -55,7 +56,7 @@ public ElementMatcher hierarchyMatcher() { @Override public Map contextStore() { - return singletonMap("java.sql.Connection", DBInfo.class.getName()); + return singletonMap("java.sql.Connection", JDBCConnectionContext.class.getName()); } @Override @@ -85,18 +86,23 @@ public static AgentScope onEnter( } try { final Connection connection = statement.getConnection(); - final DBInfo dbInfo = - JDBCDecorator.parseDBInfo( - connection, InstrumentationContext.get(Connection.class, DBInfo.class)); + final JDBCConnectionContext connectionContext = + JDBCDecorator.parseConnectionContext( + connection, + InstrumentationContext.get(Connection.class, JDBCConnectionContext.class)); + final DBInfo dbInfo = connectionContext.getDbInfo(); boolean injectTraceContext = DECORATE.shouldInjectTraceContext(dbInfo); final AgentSpan span; final boolean isSqlServer = DECORATE.isSqlServer(dbInfo); final boolean isOracle = DECORATE.isOracle(dbInfo); + final String oracleServiceHash = + DECORATE.setServiceHashAction(connection, connectionContext); + if (INJECT_COMMENT && injectTraceContext) { if (isSqlServer) { // The span ID is pre-determined so that we can reference it when setting the context - final long spanID = DECORATE.setContextInfo(connection, dbInfo); + final long spanID = DECORATE.setContextInfo(connection, connectionContext); // we then force that pre-determined span ID for the span covering the actual query span = AgentTracer.get() @@ -114,9 +120,9 @@ public static AgentScope onEnter( } DECORATE.afterStart(span); - DECORATE.onConnection(span, dbInfo); + DECORATE.onConnection(span, connectionContext); final String copy = sql; - if (span != null && INJECT_COMMENT) { + if (span != null && DECORATE.shouldInjectSqlComment(dbInfo)) { String traceParent = null; if (injectTraceContext) { @@ -172,7 +178,7 @@ public static AgentScope onEnter( appendComment); } DECORATE.onStatement(span, copy); - DECORATE.withBaseHash(span); + DECORATE.withBaseHash(span, dbInfo, oracleServiceHash); return activateSpan(span); } catch (SQLException e) { // if we can't get the connection for any reason diff --git a/dd-java-agent/instrumentation/jdbc/src/test/groovy/OracleInjectionForkedTest.groovy b/dd-java-agent/instrumentation/jdbc/src/test/groovy/OracleInjectionForkedTest.groovy index e13fbcdc734..c8f7c038cce 100644 --- a/dd-java-agent/instrumentation/jdbc/src/test/groovy/OracleInjectionForkedTest.groovy +++ b/dd-java-agent/instrumentation/jdbc/src/test/groovy/OracleInjectionForkedTest.groovy @@ -1,5 +1,11 @@ +import static datadog.trace.api.config.GeneralConfig.EXPERIMENTAL_PROPAGATE_PROCESS_TAGS_ENABLED + import datadog.trace.agent.test.InstrumentationSpecification +import datadog.trace.api.BaseHash +import datadog.trace.api.DDSpanTypes +import datadog.trace.api.ProcessTags import datadog.trace.api.config.TraceInstrumentationConfig +import datadog.trace.bootstrap.instrumentation.api.Tags import test.TestConnection import test.TestDatabaseMetaData import test.TestPreparedStatement @@ -75,3 +81,214 @@ class OracleInjectionForkedTest extends OracleInjectionTestBase { serviceNameUrl | serviceNameInjection } } + +class OracleDynamicServiceActionInjectionForkedTest extends OracleInjectionTestBase { + @Override + void configurePreAgent() { + super.configurePreAgent() + + injectSysConfig(TraceInstrumentationConfig.DB_DBM_PROPAGATION_MODE_MODE, "dynamic_service") + injectSysConfig( + TraceInstrumentationConfig.DB_DBM_PROPAGATION_ORACLE_ACTION_ONLY_ENABLED, "true") + } + + def setup() { + ProcessTags.reset() + BaseHash.updateBaseHash(-6937226773133363462L) + } + + def "Oracle dynamic service mode propagates the hash in ACTION without changing statement SQL"() { + setup: + def connection = createOracleConnection(serviceNameUrl) + def statement = connection.createStatement() as TestStatement + + when: + statement.executeQuery(query) + statement.executeQuery(query) + + then: + statement.sql == query + connection.clientInfoName == "OCSID.ACTION" + connection.clientInfoValue == "_DD_DDSH:-6937226773133363462" + connection.clientInfoSetCount == 1 + assertTraces(2) { + trace(1) { + span { + spanType DDSpanTypes.SQL + tags(false) { + "$Tags.BASE_HASH" "-6937226773133363462" + } + } + } + trace(1) { + span { + spanType DDSpanTypes.SQL + tags(false) { + "$Tags.BASE_HASH" "-6937226773133363462" + } + } + } + } + } + + def "Oracle dynamic service mode preserves prepared statement SQL"() { + setup: + def connection = createOracleConnection(sidUrl) + + when: + def statement = connection.prepareStatement(query) as TestPreparedStatement + statement.execute() + + then: + statement.sql == query + connection.clientInfoValue == "_DD_DDSH:-6937226773133363462" + connection.clientInfoSetCount == 1 + } + + def "Oracle dynamic service mode refreshes ACTION only when the hash changes"() { + setup: + def connection = createOracleConnection(serviceNameUrl) + def statement = connection.createStatement() as TestStatement + + when: + statement.executeQuery(query) + BaseHash.updateBaseHash(123456789L) + statement.executeQuery(query) + + then: + connection.clientInfoValue == "_DD_DDSH:123456789" + connection.clientInfoSetCount == 2 + } + + def "Oracle #statementType uses one BaseHash value for ACTION and the span"() { + setup: + def connection = createOracleConnection(serviceNameUrl) + connection.clientInfoSetCallback = { + BaseHash.updateBaseHash(123456789L) + } + + when: + if (prepared) { + connection.prepareStatement(query).execute() + } else { + connection.createStatement().executeQuery(query) + } + + then: + connection.clientInfoValue == "_DD_DDSH:-6937226773133363462" + BaseHash.getBaseHashStr() == "123456789" + assertTraces(1) { + trace(1) { + span { + spanType DDSpanTypes.SQL + tags(false) { + "$Tags.BASE_HASH" "-6937226773133363462" + } + } + } + } + + where: + statementType | prepared + "statement" | false + "prepared statement" | true + } + + def "Oracle dynamic service mode initializes every connection with the same URL"() { + setup: + def firstConnection = createOracleConnection(serviceNameUrl) + def secondConnection = createOracleConnection(serviceNameUrl) + + when: + firstConnection.createStatement().executeQuery(query) + secondConnection.createStatement().executeQuery(query) + + then: + firstConnection.clientInfoSetCount == 1 + secondConnection.clientInfoSetCount == 1 + } + + def "Oracle dynamic service mode retries the same hash after a transient failure"() { + setup: + def connection = createOracleConnection(serviceNameUrl) + connection.clientInfoFailuresRemaining = 1 + def statement = connection.createStatement() as TestStatement + + when: + statement.executeQuery(query) + statement.executeQuery(query) + + then: + connection.clientInfoValue == "_DD_DDSH:-6937226773133363462" + connection.clientInfoSetCount == 2 + } + + def "Oracle dynamic service mode remembers unsupported client info"() { + setup: + def connection = createOracleConnection(serviceNameUrl) + connection.clientInfoUnsupported = true + def statement = connection.createStatement() as TestStatement + + when: + statement.executeQuery(query) + statement.executeQuery(query) + + then: + connection.clientInfoValue == null + connection.clientInfoSetCount == 1 + } +} + +class OracleDynamicServiceActionProcessTagsDisabledForkedTest extends OracleInjectionTestBase { + @Override + void configurePreAgent() { + super.configurePreAgent() + + injectSysConfig(TraceInstrumentationConfig.DB_DBM_PROPAGATION_MODE_MODE, "dynamic_service") + injectSysConfig( + TraceInstrumentationConfig.DB_DBM_PROPAGATION_ORACLE_ACTION_ONLY_ENABLED, "true") + injectSysConfig(EXPERIMENTAL_PROPAGATE_PROCESS_TAGS_ENABLED, "false") + } + + def "Oracle statement keeps SQL comments and skips ACTION when process tags are disabled"() { + setup: + def connection = createOracleConnection(serviceNameUrl) + def statement = connection.createStatement() as TestStatement + + when: + statement.executeQuery(query) + + then: + statement.sql == "/*${serviceNameInjection}*/ ${query}" + connection.clientInfoSetCount == 0 + assertTraces(1) { + trace(1) { + span { + spanType DDSpanTypes.SQL + assert !span.tags.containsKey(Tags.BASE_HASH) + } + } + } + } + + def "Oracle prepared statement keeps SQL comments and skips ACTION when process tags are disabled"() { + setup: + def connection = createOracleConnection(sidUrl) + + when: + def statement = connection.prepareStatement(query) as TestPreparedStatement + statement.execute() + + then: + statement.sql == "/*${sidInjection}*/ ${query}" + connection.clientInfoSetCount == 0 + assertTraces(1) { + trace(1) { + span { + spanType DDSpanTypes.SQL + assert !span.tags.containsKey(Tags.BASE_HASH) + } + } + } + } +} 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 c710a6c89bb..e526d4c9a45 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 @@ -22,6 +22,13 @@ import java.util.concurrent.Executor * A JDBC connection class that optionally throws an exception in the constructor, used to test */ class TestConnection implements Connection { + public String clientInfoName + public String clientInfoValue + public int clientInfoSetCount + public int clientInfoFailuresRemaining + public boolean clientInfoUnsupported + public Runnable clientInfoSetCallback + TestConnection(boolean throwException) { if (throwException) { throw new RuntimeException("connection exception") @@ -232,6 +239,17 @@ class TestConnection implements Connection { @Override void setClientInfo(String name, String value) throws SQLClientInfoException { + clientInfoSetCount++ + if (clientInfoUnsupported) { + throw new UnsupportedOperationException("client info is unsupported") + } + if (clientInfoFailuresRemaining > 0) { + clientInfoFailuresRemaining-- + throw new SQLClientInfoException() + } + clientInfoName = name + clientInfoValue = value + clientInfoSetCallback?.run() } @Override diff --git a/dd-trace-api/src/main/java/datadog/trace/api/ConfigDefaults.java b/dd-trace-api/src/main/java/datadog/trace/api/ConfigDefaults.java index 991c6fd6937..bafb06642ae 100644 --- a/dd-trace-api/src/main/java/datadog/trace/api/ConfigDefaults.java +++ b/dd-trace-api/src/main/java/datadog/trace/api/ConfigDefaults.java @@ -73,6 +73,7 @@ public final class ConfigDefaults { static final boolean DEFAULT_DB_CLIENT_HOST_SPLIT_BY_INSTANCE_TYPE_SUFFIX = false; static final boolean DEFAULT_DB_CLIENT_HOST_SPLIT_BY_HOST = false; static final String DEFAULT_DB_DBM_PROPAGATION_MODE_MODE = "disabled"; + static final boolean DEFAULT_DB_DBM_PROPAGATION_ORACLE_ACTION_ONLY_ENABLED = false; static final boolean DEFAULT_DB_DBM_TRACE_PREPARED_STATEMENTS = false; static final boolean DEFAULT_DB_DBM_ALWAYS_APPEND_SQL_COMMENT = false; // Default value is set to 0, it disables the latency trace interceptor diff --git a/dd-trace-api/src/main/java/datadog/trace/api/config/TraceInstrumentationConfig.java b/dd-trace-api/src/main/java/datadog/trace/api/config/TraceInstrumentationConfig.java index 855a7243caf..50c1079ffd1 100644 --- a/dd-trace-api/src/main/java/datadog/trace/api/config/TraceInstrumentationConfig.java +++ b/dd-trace-api/src/main/java/datadog/trace/api/config/TraceInstrumentationConfig.java @@ -74,6 +74,8 @@ public final class TraceInstrumentationConfig { public static final String DB_DBM_INJECT_SQL_BASEHASH = "dbm.inject.sql.basehash"; public static final String DB_DBM_PROPAGATION_MODE_MODE = "dbm.propagation.mode"; + public static final String DB_DBM_PROPAGATION_ORACLE_ACTION_ONLY_ENABLED = + "dbm.propagation.oracle.action-only.enabled"; public static final String DB_DBM_TRACE_PREPARED_STATEMENTS = "dbm.trace_prepared_statements"; public static final String DB_DBM_ALWAYS_APPEND_SQL_COMMENT = "dbm.always_append_sql_comment"; diff --git a/internal-api/src/main/java/datadog/trace/api/Config.java b/internal-api/src/main/java/datadog/trace/api/Config.java index fade2b4c417..54c550e0ff2 100644 --- a/internal-api/src/main/java/datadog/trace/api/Config.java +++ b/internal-api/src/main/java/datadog/trace/api/Config.java @@ -59,6 +59,7 @@ import static datadog.trace.api.ConfigDefaults.DEFAULT_DB_CLIENT_HOST_SPLIT_BY_INSTANCE_TYPE_SUFFIX; import static datadog.trace.api.ConfigDefaults.DEFAULT_DB_DBM_ALWAYS_APPEND_SQL_COMMENT; import static datadog.trace.api.ConfigDefaults.DEFAULT_DB_DBM_PROPAGATION_MODE_MODE; +import static datadog.trace.api.ConfigDefaults.DEFAULT_DB_DBM_PROPAGATION_ORACLE_ACTION_ONLY_ENABLED; import static datadog.trace.api.ConfigDefaults.DEFAULT_DB_DBM_TRACE_PREPARED_STATEMENTS; import static datadog.trace.api.ConfigDefaults.DEFAULT_DEBUGGER_EXCEPTION_CAPTURE_INTERMEDIATE_SPANS_ENABLED; import static datadog.trace.api.ConfigDefaults.DEFAULT_DEBUGGER_EXCEPTION_CAPTURE_INTERVAL_SECONDS; @@ -583,6 +584,7 @@ import static datadog.trace.api.config.TraceInstrumentationConfig.DB_DBM_ALWAYS_APPEND_SQL_COMMENT; import static datadog.trace.api.config.TraceInstrumentationConfig.DB_DBM_INJECT_SQL_BASEHASH; import static datadog.trace.api.config.TraceInstrumentationConfig.DB_DBM_PROPAGATION_MODE_MODE; +import static datadog.trace.api.config.TraceInstrumentationConfig.DB_DBM_PROPAGATION_ORACLE_ACTION_ONLY_ENABLED; import static datadog.trace.api.config.TraceInstrumentationConfig.DB_DBM_TRACE_PREPARED_STATEMENTS; import static datadog.trace.api.config.TraceInstrumentationConfig.DB_METADATA_FETCHING_ON_CONNECT; import static datadog.trace.api.config.TraceInstrumentationConfig.DB_METADATA_FETCHING_ON_QUERY; @@ -1246,6 +1248,7 @@ public static String getHostName() { private final boolean dbmInjectSqlBaseHash; private final String dbmPropagationMode; + private final boolean dbmPropagationOracleActionOnlyEnabled; private final boolean dbmTracePreparedStatements; private final boolean dbmAlwaysAppendSqlComment; private final boolean dbMetadataFetchingOnQuery; @@ -1840,6 +1843,11 @@ private Config(final ConfigProvider configProvider, final InstrumenterConfig ins configProvider.getString( DB_DBM_PROPAGATION_MODE_MODE, DEFAULT_DB_DBM_PROPAGATION_MODE_MODE); + dbmPropagationOracleActionOnlyEnabled = + configProvider.getBoolean( + DB_DBM_PROPAGATION_ORACLE_ACTION_ONLY_ENABLED, + DEFAULT_DB_DBM_PROPAGATION_ORACLE_ACTION_ONLY_ENABLED); + dbmTracePreparedStatements = configProvider.getBoolean( DB_DBM_TRACE_PREPARED_STATEMENTS, DEFAULT_DB_DBM_TRACE_PREPARED_STATEMENTS); @@ -6000,6 +6008,10 @@ public String getDbmPropagationMode() { return dbmPropagationMode; } + public boolean isDbmPropagationOracleActionOnlyEnabled() { + return dbmPropagationOracleActionOnlyEnabled; + } + // Database monitoring propagation mode constants public static final String DBM_PROPAGATION_MODE_STATIC = "service"; public static final String DBM_PROPAGATION_MODE_FULL = "full"; @@ -6579,6 +6591,8 @@ public String toString() { + dbmInjectSqlBaseHash + ", dbmPropagationMode=" + dbmPropagationMode + + ", dbmPropagationOracleActionOnlyEnabled=" + + dbmPropagationOracleActionOnlyEnabled + ", dbmTracePreparedStatements=" + dbmTracePreparedStatements + ", splitByTags=" diff --git a/internal-api/src/test/groovy/datadog/trace/api/ConfigTest.groovy b/internal-api/src/test/groovy/datadog/trace/api/ConfigTest.groovy index ef95d5e902c..550e3c317ab 100644 --- a/internal-api/src/test/groovy/datadog/trace/api/ConfigTest.groovy +++ b/internal-api/src/test/groovy/datadog/trace/api/ConfigTest.groovy @@ -100,6 +100,7 @@ import static datadog.trace.api.config.RemoteConfigConfig.REMOTE_CONFIG_URL import static datadog.trace.api.config.TraceInstrumentationConfig.DB_CLIENT_HOST_SPLIT_BY_HOST import static datadog.trace.api.config.TraceInstrumentationConfig.DB_CLIENT_HOST_SPLIT_BY_INSTANCE import static datadog.trace.api.config.TraceInstrumentationConfig.DB_CLIENT_HOST_SPLIT_BY_INSTANCE_TYPE_SUFFIX +import static datadog.trace.api.config.TraceInstrumentationConfig.DB_DBM_PROPAGATION_ORACLE_ACTION_ONLY_ENABLED import static datadog.trace.api.config.TraceInstrumentationConfig.HTTP_CLIENT_HOST_SPLIT_BY_DOMAIN import static datadog.trace.api.config.TraceInstrumentationConfig.RUNTIME_CONTEXT_FIELD_INJECTION import static datadog.trace.api.config.TraceInstrumentationConfig.TRACE_ENABLED @@ -3427,6 +3428,26 @@ class ConfigTest extends DDSpecification { "false" | "true" | false // sys prop takes precedence } + def "Oracle DBM action propagation enabled = #configured"() { + setup: + def properties = new Properties() + if (configured != null) { + properties.setProperty(DB_DBM_PROPAGATION_ORACLE_ACTION_ONLY_ENABLED, configured) + } + + when: + def config = new Config(ConfigProvider.withPropertiesOverride(properties)) + + then: + config.isDbmPropagationOracleActionOnlyEnabled() == expected + + where: + configured | expected + null | false + "false" | false + "true" | true + } + def "trace resource renaming activation with appsec=#appsec and explicit=#explicit"() { setup: if (appsec != null) { diff --git a/metadata/supported-configurations.json b/metadata/supported-configurations.json index a1459dc3453..304981aa9d1 100644 --- a/metadata/supported-configurations.json +++ b/metadata/supported-configurations.json @@ -1185,6 +1185,14 @@ "aliases": [] } ], + "DD_DBM_PROPAGATION_ORACLE_ACTION_ONLY_ENABLED": [ + { + "version": "A", + "type": "boolean", + "default": "false", + "aliases": [] + } + ], "DD_DBM_TRACE_PREPARED_STATEMENTS": [ { "version": "A",