Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ dependencies {
library("org.springframework.boot:spring-boot-starter-webflux:$springBootVersion")
library("org.springframework.boot:spring-boot-starter-data-mongodb:$springBootVersion")
library("org.springframework.boot:spring-boot-starter-data-r2dbc:$springBootVersion")
library("org.springframework.boot:spring-boot-starter-data-jdbc:$springBootVersion")

implementation("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure")
implementation(project(":sdk-autoconfigure-support"))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,14 @@
import io.opentelemetry.instrumentation.jdbc.datasource.JdbcTelemetry;
import io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties.InstrumentationConfigUtil;
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.logging.Logger;
import javax.sql.DataSource;
import org.springframework.aop.SpringProxy;
import org.springframework.aop.framework.AdvisedSupport;
import org.springframework.aop.scope.ScopedProxyUtils;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.config.BeanPostProcessor;
Expand Down Expand Up @@ -50,22 +57,28 @@ public Object postProcessAfterInitialization(Object bean, String beanName) {
&& !isRoutingDatasource(bean)
&& !ScopedProxyUtils.isScopedTarget(beanName)) {
DataSource dataSource = (DataSource) bean;
return JdbcTelemetry.builder(openTelemetryProvider.getObject())
.setStatementSanitizationEnabled(
InstrumentationConfigUtil.isStatementSanitizationEnabled(
configPropertiesProvider.getObject(),
"otel.instrumentation.jdbc.statement-sanitizer.enabled"))
.setCaptureQueryParameters(
configPropertiesProvider
.getObject()
.getBoolean(
"otel.instrumentation.jdbc.experimental.capture-query-parameters", false))
.setTransactionInstrumenterEnabled(
configPropertiesProvider
.getObject()
.getBoolean("otel.instrumentation.jdbc.experimental.transaction.enabled", false))
.build()
.wrap(dataSource);
DataSource otelDataSource =
JdbcTelemetry.builder(openTelemetryProvider.getObject())
.setStatementSanitizationEnabled(
InstrumentationConfigUtil.isStatementSanitizationEnabled(
configPropertiesProvider.getObject(),
"otel.instrumentation.jdbc.statement-sanitizer.enabled"))
.setCaptureQueryParameters(
configPropertiesProvider
.getObject()
.getBoolean(
"otel.instrumentation.jdbc.experimental.capture-query-parameters", false))
.setTransactionInstrumenterEnabled(
configPropertiesProvider
.getObject()
.getBoolean(
"otel.instrumentation.jdbc.experimental.transaction.enabled", false))
.build()
.wrap(dataSource);

// wrap instrumented data source into a proxy that unwraps to the original data source
// see https://github.com/open-telemetry/opentelemetry-java-instrumentation/issues/13512
return new DataSource$$Wrapper(otelDataSource, dataSource);
}
return bean;
}
Expand All @@ -75,4 +88,65 @@ public Object postProcessAfterInitialization(Object bean, String beanName) {
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE - 20;
}

// Wrapper for DataSource that pretends to be a spring aop proxy. $$ in class name is commonly
// used by bytecode proxies and is tested by
// org.springframework.aop.support.AopUtils.isAopProxy(). This proxy can be unwrapped with
// ((Advised) dataSource).getTargetSource().getTarget() and it unwraps to the original data
// source.
@SuppressWarnings("checkstyle:TypeName")
private static class DataSource$$Wrapper extends AdvisedSupport
implements SpringProxy, DataSource {
private final DataSource delegate;

DataSource$$Wrapper(DataSource delegate, DataSource original) {
this.delegate = delegate;
setTarget(original);
}

@Override
public Connection getConnection() throws SQLException {
return delegate.getConnection();
}

@Override
public Connection getConnection(String username, String password) throws SQLException {
return delegate.getConnection(username, password);
}

@Override
public PrintWriter getLogWriter() throws SQLException {
return delegate.getLogWriter();
}

@Override
public void setLogWriter(PrintWriter out) throws SQLException {
delegate.setLogWriter(out);
}

@Override
public void setLoginTimeout(int seconds) throws SQLException {
delegate.setLoginTimeout(seconds);
}

@Override
public int getLoginTimeout() throws SQLException {
return delegate.getLoginTimeout();
}

@Override
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
return delegate.getParentLogger();
}

@Override
public <T> T unwrap(Class<T> iface) throws SQLException {
return delegate.unwrap(iface);
}

@Override
public boolean isWrapperFor(Class<?> iface) throws SQLException {
return delegate.isWrapperFor(iface);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.jdbc;

import static io.opentelemetry.instrumentation.testing.junit.db.SemconvStabilityUtil.maybeStable;
import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_STATEMENT;
import static org.assertj.core.api.Assertions.assertThat;

import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension;
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties;
import java.sql.Connection;
import java.sql.Statement;
import java.util.Collections;
import javax.sql.DataSource;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.springframework.aop.framework.Advised;
import org.springframework.aop.support.AopUtils;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;

class JdbcInstrumentationAutoConfigurationTest {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this test failing without the fix?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

assertThat(AopUtils.isAopProxy(dataSource)).isTrue(); should fail


@RegisterExtension
static final LibraryInstrumentationExtension testing = LibraryInstrumentationExtension.create();

private final ApplicationContextRunner runner =
new ApplicationContextRunner()
.withBean(
ConfigProperties.class,
() -> DefaultConfigProperties.createFromMap(Collections.emptyMap()))
.withConfiguration(
AutoConfigurations.of(
JdbcInstrumentationAutoConfiguration.class, DataSourceAutoConfiguration.class))
.withBean("openTelemetry", OpenTelemetry.class, testing::getOpenTelemetry);

@SuppressWarnings("deprecation") // using deprecated semconv
@Test
void statementSanitizerEnabledByDefault() {
runner.run(
context -> {
DataSource dataSource = context.getBean(DataSource.class);

assertThat(AopUtils.isAopProxy(dataSource)).isTrue();
assertThat(dataSource.getClass().getSimpleName()).isNotEqualTo("HikariDataSource");
// unwrap the instrumented data source to get the original data source
Object original = ((Advised) dataSource).getTargetSource().getTarget();
assertThat(AopUtils.isAopProxy(original)).isFalse();
assertThat(original.getClass().getSimpleName()).isEqualTo("HikariDataSource");

try (Connection connection = dataSource.getConnection()) {
try (Statement statement = connection.createStatement()) {
statement.execute("SELECT 1");
}
}

testing.waitAndAssertTraces(
trace ->
trace.hasSpansSatisfyingExactly(
span -> span.hasAttribute(maybeStable(DB_STATEMENT), "SELECT ?")));
});
}
}
Loading