Wrap instrumented data source in spring starter (#14255)
This commit is contained in:
parent
e147999616
commit
b02219c206
|
|
@ -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"))
|
||||
|
|
|
|||
|
|
@ -10,7 +10,14 @@ import io.opentelemetry.api.OpenTelemetry;
|
|||
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;
|
||||
|
|
@ -50,7 +57,8 @@ final class DataSourcePostProcessor implements BeanPostProcessor, Ordered {
|
|||
&& !isRoutingDatasource(bean)
|
||||
&& !ScopedProxyUtils.isScopedTarget(beanName)) {
|
||||
DataSource dataSource = (DataSource) bean;
|
||||
return JdbcTelemetry.builder(openTelemetryProvider.getObject())
|
||||
DataSource otelDataSource =
|
||||
JdbcTelemetry.builder(openTelemetryProvider.getObject())
|
||||
.setStatementSanitizationEnabled(
|
||||
InstrumentationConfigUtil.isStatementSanitizationEnabled(
|
||||
configPropertiesProvider.getObject(),
|
||||
|
|
@ -63,9 +71,14 @@ final class DataSourcePostProcessor implements BeanPostProcessor, Ordered {
|
|||
.setTransactionInstrumenterEnabled(
|
||||
configPropertiesProvider
|
||||
.getObject()
|
||||
.getBoolean("otel.instrumentation.jdbc.experimental.transaction.enabled", false))
|
||||
.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;
|
||||
}
|
||||
|
|
@ -75,4 +88,65 @@ final class DataSourcePostProcessor implements BeanPostProcessor, Ordered {
|
|||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
||||
@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…
Reference in New Issue