diff --git a/instrumentation/jdbc/javaagent/src/test/groovy/JdbcInstrumentationTest.groovy b/instrumentation/jdbc/javaagent/src/test/groovy/JdbcInstrumentationTest.groovy index 72692ddbe844..ec10458c7df1 100644 --- a/instrumentation/jdbc/javaagent/src/test/groovy/JdbcInstrumentationTest.groovy +++ b/instrumentation/jdbc/javaagent/src/test/groovy/JdbcInstrumentationTest.groovy @@ -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.test.ProxyStatementFactory import io.opentelemetry.semconv.trace.attributes.SemanticAttributes import org.apache.derby.jdbc.EmbeddedDataSource import org.apache.derby.jdbc.EmbeddedDriver @@ -820,4 +821,36 @@ class JdbcInstrumentationTest extends AgentInstrumentationSpecification { return super.getMetaData() } } + + // regression test for https://github.com/open-telemetry/opentelemetry-java-instrumentation/issues/6015 + def "test proxy statement"() { + def connection = new Driver().connect(jdbcUrls.get("h2"), null) + Statement statement = connection.createStatement() + Statement proxyStatement = ProxyStatementFactory.proxyStatement(statement) + ResultSet resultSet = runWithSpan("parent") { + return proxyStatement.executeQuery("SELECT 3") + } + + expect: + resultSet.next() + resultSet.getInt(1) == 3 + assertTraces(1) { + trace(0, 2) { + span(0) { + name "parent" + kind SpanKind.INTERNAL + hasNoParent() + } + span(1) { + name "SELECT $dbNameLower" + kind CLIENT + childOf span(0) + } + } + } + + cleanup: + statement.close() + connection.close() + } } diff --git a/instrumentation/jdbc/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jdbc/test/ProxyStatementFactory.java b/instrumentation/jdbc/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jdbc/test/ProxyStatementFactory.java new file mode 100644 index 000000000000..88ef3cb93a8f --- /dev/null +++ b/instrumentation/jdbc/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jdbc/test/ProxyStatementFactory.java @@ -0,0 +1,36 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.jdbc.test; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Proxy; +import java.sql.Statement; + +public class ProxyStatementFactory { + + public static Statement proxyStatement(Statement statement) throws Exception { + TestClassLoader classLoader = new TestClassLoader(ProxyStatementFactory.class.getClassLoader()); + Class testInterface = classLoader.loadClass(TestInterface.class.getName()); + if (testInterface.getClassLoader() != classLoader) { + throw new IllegalStateException("wrong class loader"); + } + InvocationHandler invocationHandler = (proxy, method, args) -> method.invoke(statement, args); + Statement proxyStatement = + (Statement) + Proxy.newProxyInstance( + classLoader, new Class[] {Statement.class, testInterface}, invocationHandler); + // adding package private interface TestInterface to jdk proxy forces defining the proxy class + // in the same package as the package private interface + if (!proxyStatement + .getClass() + .getName() + .startsWith("io.opentelemetry.javaagent.instrumentation.jdbc.test")) { + throw new IllegalStateException("proxy statement is in wrong package"); + } + + return proxyStatement; + } +} diff --git a/instrumentation/jdbc/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jdbc/test/TestClassLoader.java b/instrumentation/jdbc/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jdbc/test/TestClassLoader.java new file mode 100644 index 000000000000..a6a1704b53ad --- /dev/null +++ b/instrumentation/jdbc/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jdbc/test/TestClassLoader.java @@ -0,0 +1,35 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.jdbc.test; + +import java.net.URL; +import java.net.URLClassLoader; + +public class TestClassLoader extends URLClassLoader { + + public TestClassLoader(ClassLoader parent) { + super( + new URL[] {TestClassLoader.class.getProtectionDomain().getCodeSource().getLocation()}, + parent); + } + + @Override + protected synchronized Class loadClass(String name, boolean resolve) + throws ClassNotFoundException { + Class clazz = findLoadedClass(name); + if (clazz != null) { + return clazz; + } + if (name.startsWith("io.opentelemetry.javaagent.instrumentation.jdbc.test")) { + try { + return findClass(name); + } catch (ClassNotFoundException exception) { + // ignore + } + } + return super.loadClass(name, resolve); + } +} diff --git a/instrumentation/jdbc/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jdbc/test/TestInterface.java b/instrumentation/jdbc/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jdbc/test/TestInterface.java new file mode 100644 index 000000000000..ee246133e013 --- /dev/null +++ b/instrumentation/jdbc/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jdbc/test/TestInterface.java @@ -0,0 +1,12 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.jdbc.test; + +// Adding a package private interface to jdk proxy forces defining the proxy class in the package +// of the package private class. Usually proxy classes are defined in a package that we exclude from +// instrumentation. We use this class to force proxy into a different package so it would get +// instrumented. +interface TestInterface {}