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",