Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Fix multi-layer JDBC driver call depth calculation #5004

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions instrumentation/jdbc/javaagent/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,7 @@ dependencies {

tasks.withType<Test>().configureEach {
jvmArgs("-Dotel.instrumentation.jdbc-datasource.enabled=true")
// this is needed to guarantee that if a layered JDBC driver is excluded then
// the underlying driver is still correctly instrumented
jvmArgs("-Dotel.javaagent.exclude-classes=io.opentelemetry.javaagent.instrumentation.jdbc.excluded.*")
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
import io.opentelemetry.javaagent.instrumentation.api.CallDepth;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Statement;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.type.TypeDescription;
Expand Down Expand Up @@ -62,7 +63,16 @@ public static void onEnter(
// using CallDepth prevents this, because this check happens before Connection#getMetadata()
// is called - the first recursive Statement call is just skipped and we do not create a span
// for it
callDepth = CallDepth.forClass(Statement.class);
// the call depth should be anchored to the specific connection of this statement to avoid
// suppressing spans for layered JDBC drivers (that should be performed by semantic
// suppression)
// it's possible however that getting the connection throws an SQLException hence falling back
// to the call depth of a generic Statement.
try {
callDepth = CallDepth.forClass(statement.getConnection().getClass());
} catch (SQLException e) {
callDepth = CallDepth.forClass(Statement.class);
}
if (callDepth.getAndIncrement() > 0) {
return;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
import io.opentelemetry.javaagent.instrumentation.api.CallDepth;
import java.sql.SQLException;
import java.sql.Statement;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.type.TypeDescription;
Expand Down Expand Up @@ -62,7 +63,16 @@ public static void onEnter(
// using CallDepth prevents this, because this check happens before Connection#getMetadata()
// is called - the first recursive Statement call is just skipped and we do not create a span
// for it
callDepth = CallDepth.forClass(Statement.class);
// the call depth should be anchored to the specific connection of this statement to avoid
// suppressing spans for layered JDBC drivers (that should be performed by semantic
// suppression)
// it's possible however that getting the connection throws an SQLException hence falling back
// to the call depth of a generic Statement.
try {
callDepth = CallDepth.forClass(statement.getConnection().getClass());
} catch (SQLException e) {
callDepth = CallDepth.forClass(Statement.class);
}
if (callDepth.getAndIncrement() > 0) {
return;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import io.opentelemetry.api.trace.SpanKind
import io.opentelemetry.instrumentation.jdbc.TestConnection
import io.opentelemetry.instrumentation.jdbc.TestDriver
import io.opentelemetry.instrumentation.test.AgentInstrumentationSpecification
import io.opentelemetry.javaagent.instrumentation.jdbc.excluded.TwoLayerDbCallingConnection
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes
import org.apache.derby.jdbc.EmbeddedDataSource
import org.apache.derby.jdbc.EmbeddedDriver
Expand Down Expand Up @@ -806,6 +807,53 @@ class JdbcInstrumentationTest extends AgentInstrumentationSpecification {
"getMetaData() uses PreparedStatement, test PreparedStatement" | true | { con, query -> con.prepareStatement(query).executeQuery() }
}

// regression test for https://github.com/open-telemetry/opentelemetry-java-instrumentation/issues/4974
def "call depth suppression should not hide layered drivers: #desc"() {
given:
def innerConnection = new DbCallingConnection(usePreparedStatementInConnection)
innerConnection.url = "jdbc:testdb://localhost/inner"

def outerConnection = new TwoLayerDbCallingConnection(innerConnection)
outerConnection.url = "jdbc:testdb://localhost/outer"

when:
runWithSpan("parent") {
executeQueryFunction(outerConnection, "SELECT * FROM table")
}

then:
assertTraces(1) {
trace(0, 2) {
span(0) {
name "parent"
kind SpanKind.INTERNAL
hasNoParent()
}
span(1) {
name "SELECT inner.table"
kind CLIENT
childOf span(0)
attributes {
"$SemanticAttributes.DB_SYSTEM" "testdb"
"$SemanticAttributes.DB_CONNECTION_STRING" "testdb://localhost"
"$SemanticAttributes.DB_NAME" "inner"
"$SemanticAttributes.DB_STATEMENT" "SELECT * FROM table"
"$SemanticAttributes.DB_OPERATION" "SELECT"
"$SemanticAttributes.DB_SQL_TABLE" "table"
"$SemanticAttributes.NET_PEER_NAME" "localhost"
}
}
}
}

where:
desc | usePreparedStatementInConnection | executeQueryFunction
"getMetaData() uses Statement, test Statement" | false | { con, query -> con.createStatement().executeQuery(query) }
"getMetaData() uses PreparedStatement, test Statement" | true | { con, query -> con.createStatement().executeQuery(query) }
"getMetaData() uses Statement, test PreparedStatement" | false | { con, query -> con.prepareStatement(query).executeQuery() }
"getMetaData() uses PreparedStatement, test PreparedStatement" | true | { con, query -> con.prepareStatement(query).executeQuery() }
}

class DbCallingConnection extends TestConnection {
final boolean usePreparedStatement

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.jdbc.excluded;

import io.opentelemetry.instrumentation.jdbc.TestConnection;
import io.opentelemetry.instrumentation.jdbc.TestPreparedStatement;
import io.opentelemetry.instrumentation.jdbc.TestStatement;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

class TwoLayerDbCallingConnection extends TestConnection {
final Connection inner;

TwoLayerDbCallingConnection(Connection inner) {
this.inner = inner;
}

@Override
public Statement createStatement() throws SQLException {
return new TestStatement(this) {
@Override
public ResultSet executeQuery(String sql) throws SQLException {
return inner.createStatement().executeQuery(sql);
}
};
}

@Override
public PreparedStatement prepareStatement(String sql) throws SQLException {
return new TestPreparedStatement(this) {
@Override
public ResultSet executeQuery() throws SQLException {
return inner.prepareStatement(sql).executeQuery(sql);
}
};
}
}