Skip to content

Commit c51637a

Browse files
Fix Oracle DBM trace correlation (#10829)
Fix Oracle DBM trace correlation by injecting the database instance name into dddbs and dddb SQL comment tags instead of the generic type string and null linter scope changes to oracle only and add service-name URL format test test fix simplify implementation, getDb returns instance if db not found Co-authored-by: allen.zhou <allen.zhou@datadoghq.com>
1 parent 1eea182 commit c51637a

File tree

5 files changed

+139
-6
lines changed

5 files changed

+139
-6
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ public String getInstance() {
193193
}
194194

195195
public String getDb() {
196-
return db;
196+
return db != null ? db : instance;
197197
}
198198

199199
public String getHost() {

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

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -205,11 +205,18 @@ public static DBInfo parseDBInfo(
205205
}
206206

207207
public String getDbService(final DBInfo dbInfo) {
208-
String dbService = null;
209-
if (null != dbInfo) {
210-
dbService = dbService(dbInfo.getType(), dbInstance(dbInfo));
208+
if (null == dbInfo) {
209+
return null;
211210
}
212-
return dbService;
211+
// For Oracle, the URL parser sets instance (SID/service name) but never db.
212+
// Without this, dddbs defaults to the generic type string "oracle" which breaks
213+
// DBM trace correlation. Other databases (e.g. SQL Server) rely on the type-based
214+
// service name for DBM correlation and must not be changed here.
215+
if ("oracle".equals(dbInfo.getType()) && dbInfo.getInstance() != null) {
216+
String service = dbClientService(dbInfo.getInstance());
217+
return service != null ? service : dbInfo.getInstance();
218+
}
219+
return dbService(dbInfo.getType(), dbInstance(dbInfo));
213220
}
214221

215222
public static DBInfo parseDBInfoFromConnection(final Connection connection) {

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

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named;
66
import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.activateSpan;
77
import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.startSpan;
8+
import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.traceConfig;
89
import static datadog.trace.bootstrap.instrumentation.api.InstrumentationTags.DBM_TRACE_INJECTED;
910
import static datadog.trace.instrumentation.jdbc.JDBCDecorator.DATABASE_QUERY;
1011
import static datadog.trace.instrumentation.jdbc.JDBCDecorator.DECORATE;
@@ -145,10 +146,21 @@ public static AgentScope onEnter(
145146
appendComment = true;
146147
}
147148

149+
final String dbService;
150+
if (isOracle) {
151+
String oracleService = DECORATE.getDbService(dbInfo);
152+
if (oracleService != null) {
153+
oracleService =
154+
traceConfig(span).getServiceMapping().getOrDefault(oracleService, oracleService);
155+
}
156+
dbService = oracleService;
157+
} else {
158+
dbService = span.getServiceName();
159+
}
148160
sql =
149161
SQLCommenter.inject(
150162
sql,
151-
span.getServiceName(),
163+
dbService,
152164
dbInfo.getType(),
153165
dbInfo.getHost(),
154166
dbInfo.getDb(),
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import datadog.trace.agent.test.InstrumentationSpecification
2+
import datadog.trace.api.config.TraceInstrumentationConfig
3+
import test.TestConnection
4+
import test.TestDatabaseMetaData
5+
import test.TestPreparedStatement
6+
import test.TestStatement
7+
8+
/**
9+
* Tests that Oracle DBM SQL comment injection produces the correct dddbs and dddb tags.
10+
*
11+
* Bug 1: dddbs was populated with the generic type string "oracle" instead of the SID/service name.
12+
* Bug 2: dddb was never injected because the Oracle URL parser sets instance, not db.
13+
*/
14+
abstract class OracleInjectionTestBase extends InstrumentationSpecification {
15+
@Override
16+
void configurePreAgent() {
17+
super.configurePreAgent()
18+
19+
injectSysConfig(TraceInstrumentationConfig.DB_DBM_PROPAGATION_MODE_MODE, "full")
20+
injectSysConfig("service.name", "my_service_name")
21+
}
22+
23+
static query = "SELECT 1"
24+
25+
// Note: the URL parser lowercases the full URL before extraction, so identifiers are lowercase.
26+
static sidUrl = "jdbc:oracle:thin:@localhost:1521:BENEDB"
27+
static serviceNameUrl = "jdbc:oracle:thin:@//localhost:1521/MYSERVICE"
28+
29+
static sidInjection = "ddps='my_service_name',dddbs='benedb',ddh='localhost',dddb='benedb'"
30+
static serviceNameInjection = "ddps='my_service_name',dddbs='myservice',ddh='localhost',dddb='myservice'"
31+
32+
TestConnection createOracleConnection(String url) {
33+
def connection = new TestConnection(false)
34+
def metadata = new TestDatabaseMetaData()
35+
metadata.setURL(url)
36+
connection.setMetaData(metadata)
37+
return connection
38+
}
39+
}
40+
41+
class OracleInjectionForkedTest extends OracleInjectionTestBase {
42+
43+
def "Oracle prepared statement injects instance name in dddbs and dddb"() {
44+
setup:
45+
def connection = createOracleConnection(url)
46+
47+
when:
48+
def statement = connection.prepareStatement(query) as TestPreparedStatement
49+
statement.execute()
50+
51+
then:
52+
statement.sql == "/*${expected}*/ ${query}"
53+
54+
where:
55+
url | expected
56+
sidUrl | sidInjection
57+
serviceNameUrl | serviceNameInjection
58+
}
59+
60+
def "Oracle single statement injects instance name in dddbs and dddb"() {
61+
setup:
62+
def connection = createOracleConnection(url)
63+
64+
when:
65+
def statement = connection.createStatement() as TestStatement
66+
statement.executeQuery(query)
67+
68+
then:
69+
// Oracle uses v$session.action for trace context, so no traceparent in comment
70+
statement.sql == "/*${expected}*/ ${query}"
71+
72+
where:
73+
url | expected
74+
sidUrl | sidInjection
75+
serviceNameUrl | serviceNameInjection
76+
}
77+
}

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

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1146,6 +1146,43 @@ class RemoteDBMTraceInjectedForkedTest extends RemoteJDBCInstrumentationTest {
11461146
final databaseNaming = new DatabaseNamingV1()
11471147
return databaseNaming.normalizedName(dbType)
11481148
}
1149+
1150+
def "Oracle DBM comment contains instance name in dddbs and dddb, not generic type string"() {
1151+
setup:
1152+
// Use a query text unlikely to already be in v$sql cursor cache
1153+
def markerQuery = "SELECT 1729 /* oracle-dbm-fix-verify */ FROM dual"
1154+
def conn = connectTo(ORACLE, peerConnectionProps(ORACLE))
1155+
1156+
when:
1157+
def stmt = conn.createStatement()
1158+
runUnderTrace("parent") {
1159+
stmt.execute(markerQuery)
1160+
}
1161+
TEST_WRITER.waitForTraces(1)
1162+
1163+
then:
1164+
// Connect as system to read v$sql — system shares the same password as the test user
1165+
// in the gvenzl/oracle-free image (both are set via ORACLE_PASSWORD).
1166+
def adminUrl = "jdbc:oracle:thin:@//${oracle.getHost()}:${oracle.getMappedPort(1521)}/freepdb1"
1167+
def adminConn = java.sql.DriverManager.getConnection(adminUrl, "system", oracle.getPassword())
1168+
def rs = adminConn.createStatement().executeQuery(
1169+
"SELECT sql_fulltext FROM v\$sql " +
1170+
"WHERE sql_fulltext LIKE '%1729%' AND sql_fulltext LIKE '%dddbs%' " +
1171+
"AND sql_fulltext LIKE '%oracle-dbm-fix-verify%' " +
1172+
"AND ROWNUM = 1"
1173+
)
1174+
assert rs.next() : "Instrumented Oracle query not found in v\$sql — DBM comment may be missing"
1175+
def sqlText = rs.getString(1)
1176+
// dddbs and dddb should both carry the PDB/service name, not the generic "oracle" type string
1177+
assert sqlText.contains("dddbs='freepdb1'") : "Expected dddbs='freepdb1' in SQL comment, got: ${sqlText}"
1178+
assert sqlText.contains("dddb='freepdb1'") : "Expected dddb='freepdb1' in SQL comment, got: ${sqlText}"
1179+
assert !sqlText.contains("dddbs='oracle'") : "dddbs must not be the generic type string 'oracle': ${sqlText}"
1180+
1181+
cleanup:
1182+
adminConn?.close()
1183+
stmt?.close()
1184+
conn?.close()
1185+
}
11491186
}
11501187

11511188
class RemoteDBMTraceInjectedForkedTestTracePreparedStatements extends RemoteJDBCInstrumentationTest {

0 commit comments

Comments
 (0)