fix: datasource instrumentation to support custom connection (#14602)
Co-authored-by: Lauri Tulmin <ltulmin@splunk.com>
This commit is contained in:
parent
011201cd70
commit
1c1f93a51a
|
|
@ -31,6 +31,7 @@ dependencies {
|
|||
testLibrary("org.apache.tomcat:tomcat-juli:7.0.19")
|
||||
testLibrary("com.zaxxer:HikariCP:2.4.0")
|
||||
testLibrary("com.mchange:c3p0:0.9.5")
|
||||
testLibrary("com.alibaba:druid:1.2.20")
|
||||
|
||||
// some classes in earlier versions of derby were split out into derbytools in later versions
|
||||
latestDepTestLibrary("org.apache.derby:derbytools:latest.release")
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import static net.bytebuddy.matcher.ElementMatchers.returns;
|
|||
import io.opentelemetry.context.Context;
|
||||
import io.opentelemetry.context.Scope;
|
||||
import io.opentelemetry.instrumentation.jdbc.internal.JdbcUtils;
|
||||
import io.opentelemetry.javaagent.bootstrap.CallDepth;
|
||||
import io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge;
|
||||
import io.opentelemetry.javaagent.bootstrap.jdbc.DbInfo;
|
||||
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
|
||||
|
|
@ -33,7 +34,7 @@ public class DataSourceInstrumentation implements TypeInstrumentation {
|
|||
@Override
|
||||
public void transform(TypeTransformer transformer) {
|
||||
transformer.applyAdviceToMethod(
|
||||
named("getConnection").and(returns(named("java.sql.Connection"))),
|
||||
named("getConnection").and(returns(implementsInterface(named("java.sql.Connection")))),
|
||||
DataSourceInstrumentation.class.getName() + "$GetConnectionAdvice");
|
||||
}
|
||||
|
||||
|
|
@ -44,7 +45,13 @@ public class DataSourceInstrumentation implements TypeInstrumentation {
|
|||
public static void start(
|
||||
@Advice.This DataSource ds,
|
||||
@Advice.Local("otelContext") Context context,
|
||||
@Advice.Local("otelScope") Scope scope) {
|
||||
@Advice.Local("otelScope") Scope scope,
|
||||
@Advice.Local("otelCallDepth") CallDepth callDepth) {
|
||||
callDepth = CallDepth.forClass(DataSource.class);
|
||||
if (callDepth.getAndIncrement() > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
Context parentContext = Java8BytecodeBridge.currentContext();
|
||||
if (!Java8BytecodeBridge.spanFromContext(parentContext).getSpanContext().isValid()) {
|
||||
// this instrumentation is already very noisy, and calls to getConnection outside of an
|
||||
|
|
@ -64,7 +71,12 @@ public class DataSourceInstrumentation implements TypeInstrumentation {
|
|||
@Advice.Return Connection connection,
|
||||
@Advice.Thrown Throwable throwable,
|
||||
@Advice.Local("otelContext") Context context,
|
||||
@Advice.Local("otelScope") Scope scope) {
|
||||
@Advice.Local("otelScope") Scope scope,
|
||||
@Advice.Local("otelCallDepth") CallDepth callDepth) {
|
||||
if (callDepth.decrementAndGet() > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (scope == null) {
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package io.opentelemetry.javaagent.instrumentation.jdbc.test;
|
||||
|
||||
import static io.opentelemetry.api.trace.SpanKind.INTERNAL;
|
||||
import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitStableDatabaseSemconv;
|
||||
import static io.opentelemetry.instrumentation.testing.junit.db.SemconvStabilityUtil.maybeStable;
|
||||
import static io.opentelemetry.instrumentation.testing.junit.db.SemconvStabilityUtil.maybeStableDbSystemName;
|
||||
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo;
|
||||
import static io.opentelemetry.semconv.incubating.CodeIncubatingAttributes.CODE_FUNCTION;
|
||||
import static io.opentelemetry.semconv.incubating.CodeIncubatingAttributes.CODE_NAMESPACE;
|
||||
import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_CONNECTION_STRING;
|
||||
import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_NAME;
|
||||
import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_SYSTEM;
|
||||
import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_USER;
|
||||
|
||||
import com.alibaba.druid.pool.DruidDataSource;
|
||||
import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension;
|
||||
import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension;
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
import javax.sql.DataSource;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||
|
||||
@SuppressWarnings("deprecation") // using deprecated semconv
|
||||
class DruicDataSourceTest {
|
||||
|
||||
@RegisterExtension
|
||||
static final InstrumentationExtension testing = AgentInstrumentationExtension.create();
|
||||
|
||||
private DataSource dataSource;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
DruidDataSource druidDataSource = new DruidDataSource();
|
||||
druidDataSource.setUrl("jdbc:h2:mem:test");
|
||||
druidDataSource.setDriverClassName("org.h2.Driver");
|
||||
druidDataSource.setUsername("sa");
|
||||
druidDataSource.setPassword("");
|
||||
druidDataSource.setMaxActive(1);
|
||||
this.dataSource = druidDataSource;
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() {
|
||||
if (dataSource instanceof DruidDataSource) {
|
||||
((DruidDataSource) dataSource).close();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetConnection() throws SQLException {
|
||||
// In DruidDataSource we instrument both DruidPooledConnection getConnection() and the bridge
|
||||
// method Connection getConnection(). Here we call Connection getConnection() that delegates
|
||||
// to DruidPooledConnection getConnection(), and verify that only one span is created.
|
||||
testing.runWithSpan(
|
||||
"parent",
|
||||
() -> {
|
||||
try (Connection connection = dataSource.getConnection()) {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
testing.waitAndAssertTraces(
|
||||
trace ->
|
||||
trace.hasSpansSatisfyingExactly(
|
||||
span -> span.hasName("parent").hasKind(INTERNAL).hasNoParent(),
|
||||
span ->
|
||||
span.hasName("DruidDataSource.getConnection")
|
||||
.hasKind(INTERNAL)
|
||||
.hasParent(trace.getSpan(0))
|
||||
.hasAttributesSatisfyingExactly(
|
||||
equalTo(CODE_NAMESPACE, "com.alibaba.druid.pool.DruidDataSource"),
|
||||
equalTo(CODE_FUNCTION, "getConnection"),
|
||||
equalTo(
|
||||
DB_CONNECTION_STRING,
|
||||
emitStableDatabaseSemconv() ? null : "h2:mem:"),
|
||||
equalTo(maybeStable(DB_NAME), "test"),
|
||||
equalTo(maybeStable(DB_SYSTEM), maybeStableDbSystemName("h2")),
|
||||
equalTo(DB_USER, emitStableDatabaseSemconv() ? null : "sa"))));
|
||||
}
|
||||
}
|
||||
|
|
@ -1041,7 +1041,6 @@ class JdbcInstrumentationTest {
|
|||
throws SQLException {
|
||||
// Tomcat's pool doesn't work because the getConnection method is
|
||||
// implemented in a parent class that doesn't implement DataSource
|
||||
boolean recursive = datasource instanceof EmbeddedDataSource;
|
||||
|
||||
if (init != null) {
|
||||
init.accept(datasource);
|
||||
|
|
@ -1073,14 +1072,6 @@ class JdbcInstrumentationTest {
|
|||
.hasKind(SpanKind.INTERNAL)
|
||||
.hasParent(trace.getSpan(0))
|
||||
.hasAttributesSatisfyingExactly(attributesAssertions)));
|
||||
if (recursive) {
|
||||
assertions.add(
|
||||
span ->
|
||||
span.hasName(datasource.getClass().getSimpleName() + ".getConnection")
|
||||
.hasKind(SpanKind.INTERNAL)
|
||||
.hasParent(trace.getSpan(1))
|
||||
.hasAttributesSatisfyingExactly(attributesAssertions));
|
||||
}
|
||||
trace.hasSpansSatisfyingExactly(assertions);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue