diff --git a/instrumentation/jdbc/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jdbc/JdbcSingletons.java b/instrumentation/jdbc/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jdbc/JdbcSingletons.java index cd20a638f5dc..fcc102f04602 100644 --- a/instrumentation/jdbc/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jdbc/JdbcSingletons.java +++ b/instrumentation/jdbc/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jdbc/JdbcSingletons.java @@ -5,7 +5,7 @@ package io.opentelemetry.javaagent.instrumentation.jdbc; -import static io.opentelemetry.instrumentation.jdbc.internal.DataSourceInstrumenterFactory.createDataSourceInstrumenter; +import static io.opentelemetry.instrumentation.jdbc.internal.JdbcInstrumenterFactory.createDataSourceInstrumenter; import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; @@ -27,7 +27,7 @@ public final class JdbcSingletons { private static final Instrumenter STATEMENT_INSTRUMENTER; public static final Instrumenter DATASOURCE_INSTRUMENTER = - createDataSourceInstrumenter(GlobalOpenTelemetry.get()); + createDataSourceInstrumenter(GlobalOpenTelemetry.get(), true); static { JdbcAttributesGetter dbAttributesGetter = new JdbcAttributesGetter(); diff --git a/instrumentation/jdbc/library/README.md b/instrumentation/jdbc/library/README.md index 4b940e8b4ad1..fa6a52a51485 100644 --- a/instrumentation/jdbc/library/README.md +++ b/instrumentation/jdbc/library/README.md @@ -57,7 +57,7 @@ public class DataSourceConfig { dataSource.setUrl("jdbc:postgresql://127.0.0.1:5432/example"); dataSource.setUsername("postgres"); dataSource.setPassword("root"); - return new OpenTelemetryDataSource(dataSource); + return JdbcTelemetry.create(openTelemetry).wrap(dataSource); } } diff --git a/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/datasource/JdbcTelemetry.java b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/datasource/JdbcTelemetry.java new file mode 100644 index 000000000000..cc857b823563 --- /dev/null +++ b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/datasource/JdbcTelemetry.java @@ -0,0 +1,41 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.jdbc.datasource; + +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import io.opentelemetry.instrumentation.jdbc.internal.DbRequest; +import io.opentelemetry.instrumentation.jdbc.internal.dbinfo.DbInfo; +import javax.sql.DataSource; + +/** Entrypoint for instrumenting a JDBC DataSources. */ +public final class JdbcTelemetry { + + /** Returns a new {@link JdbcTelemetry} configured with the given {@link OpenTelemetry}. */ + public static JdbcTelemetry create(OpenTelemetry openTelemetry) { + return builder(openTelemetry).build(); + } + + /** Returns a new {@link JdbcTelemetryBuilder} configured with the given {@link OpenTelemetry}. */ + public static JdbcTelemetryBuilder builder(OpenTelemetry openTelemetry) { + return new JdbcTelemetryBuilder(openTelemetry); + } + + private final Instrumenter dataSourceInstrumenter; + private final Instrumenter statementInstrumenter; + + JdbcTelemetry( + Instrumenter dataSourceInstrumenter, + Instrumenter statementInstrumenter) { + this.dataSourceInstrumenter = dataSourceInstrumenter; + this.statementInstrumenter = statementInstrumenter; + } + + public DataSource wrap(DataSource dataSource) { + return new OpenTelemetryDataSource( + dataSource, this.dataSourceInstrumenter, this.statementInstrumenter); + } +} diff --git a/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/datasource/JdbcTelemetryBuilder.java b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/datasource/JdbcTelemetryBuilder.java new file mode 100644 index 000000000000..825b29547334 --- /dev/null +++ b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/datasource/JdbcTelemetryBuilder.java @@ -0,0 +1,53 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.jdbc.datasource; + +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.instrumentation.jdbc.internal.JdbcInstrumenterFactory; + +/** A builder of {@link JdbcTelemetry}. */ +public final class JdbcTelemetryBuilder { + + private final OpenTelemetry openTelemetry; + private boolean dataSourceInstrumenterEnabled = true; + private boolean statementInstrumenterEnabled = true; + private boolean statementSanitizationEnabled = true; + + JdbcTelemetryBuilder(OpenTelemetry openTelemetry) { + this.openTelemetry = openTelemetry; + } + + /** Configures whether spans are created for JDBC Connections. Enabled by default. */ + @CanIgnoreReturnValue + public JdbcTelemetryBuilder setDataSourceInstrumenterEnabled(boolean enabled) { + this.dataSourceInstrumenterEnabled = enabled; + return this; + } + + /** Configures whether spans are created for JDBC Statements. Enabled by default. */ + @CanIgnoreReturnValue + public JdbcTelemetryBuilder setStatementInstrumenterEnabled(boolean enabled) { + this.statementInstrumenterEnabled = enabled; + return this; + } + + /** Configures whether JDBC Statements are sanitized. Enabled by default. */ + @CanIgnoreReturnValue + public JdbcTelemetryBuilder setStatementSanitizationEnabled(boolean enabled) { + this.statementSanitizationEnabled = enabled; + return this; + } + + /** Returns a new {@link JdbcTelemetry} with the settings of this {@link JdbcTelemetryBuilder}. */ + public JdbcTelemetry build() { + return new JdbcTelemetry( + JdbcInstrumenterFactory.createDataSourceInstrumenter( + openTelemetry, dataSourceInstrumenterEnabled), + JdbcInstrumenterFactory.createStatementInstrumenter( + openTelemetry, statementInstrumenterEnabled, statementSanitizationEnabled)); + } +} diff --git a/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/datasource/OpenTelemetryDataSource.java b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/datasource/OpenTelemetryDataSource.java index 174668069982..232d28b7aac8 100644 --- a/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/datasource/OpenTelemetryDataSource.java +++ b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/datasource/OpenTelemetryDataSource.java @@ -20,7 +20,7 @@ package io.opentelemetry.instrumentation.jdbc.datasource; -import static io.opentelemetry.instrumentation.jdbc.internal.DataSourceInstrumenterFactory.createDataSourceInstrumenter; +import static io.opentelemetry.instrumentation.jdbc.internal.JdbcInstrumenterFactory.createDataSourceInstrumenter; import static io.opentelemetry.instrumentation.jdbc.internal.JdbcInstrumenterFactory.createStatementInstrumenter; import static io.opentelemetry.instrumentation.jdbc.internal.JdbcUtils.computeDbInfo; @@ -66,12 +66,29 @@ public OpenTelemetryDataSource(DataSource delegate) { * @param delegate the DataSource to wrap * @param openTelemetry the OpenTelemetry instance to setup for */ + @Deprecated public OpenTelemetryDataSource(DataSource delegate, OpenTelemetry openTelemetry) { this.delegate = delegate; - this.dataSourceInstrumenter = createDataSourceInstrumenter(openTelemetry); + this.dataSourceInstrumenter = createDataSourceInstrumenter(openTelemetry, true); this.statementInstrumenter = createStatementInstrumenter(openTelemetry); } + /** + * Create a OpenTelemetry DataSource wrapping another DataSource. + * + * @param delegate the DataSource to wrap + * @param dataSourceInstrumenter the DataSource Instrumenter to use + * @param statementInstrumenter the Statement Instrumenter to use + */ + OpenTelemetryDataSource( + DataSource delegate, + Instrumenter dataSourceInstrumenter, + Instrumenter statementInstrumenter) { + this.delegate = delegate; + this.dataSourceInstrumenter = dataSourceInstrumenter; + this.statementInstrumenter = statementInstrumenter; + } + @Override public Connection getConnection() throws SQLException { Connection connection = wrapCall(delegate::getConnection); diff --git a/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/DataSourceInstrumenterFactory.java b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/DataSourceInstrumenterFactory.java deleted file mode 100644 index 4f15ca89b0fc..000000000000 --- a/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/DataSourceInstrumenterFactory.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.instrumentation.jdbc.internal; - -import io.opentelemetry.api.OpenTelemetry; -import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; -import io.opentelemetry.instrumentation.api.instrumenter.code.CodeAttributesExtractor; -import io.opentelemetry.instrumentation.api.instrumenter.code.CodeSpanNameExtractor; -import io.opentelemetry.instrumentation.jdbc.internal.dbinfo.DbInfo; -import javax.sql.DataSource; - -/** - * This class is internal and is hence not for public use. Its APIs are unstable and can change at - * any time. - */ -public final class DataSourceInstrumenterFactory { - - private static final String INSTRUMENTATION_NAME = "io.opentelemetry.jdbc"; - - public static Instrumenter createDataSourceInstrumenter( - OpenTelemetry openTelemetry) { - DataSourceCodeAttributesGetter getter = DataSourceCodeAttributesGetter.INSTANCE; - return Instrumenter.builder( - openTelemetry, INSTRUMENTATION_NAME, CodeSpanNameExtractor.create(getter)) - .addAttributesExtractor(CodeAttributesExtractor.create(getter)) - .addAttributesExtractor(DataSourceDbAttributesExtractor.INSTANCE) - .buildInstrumenter(); - } - - private DataSourceInstrumenterFactory() {} -} diff --git a/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/JdbcInstrumenterFactory.java b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/JdbcInstrumenterFactory.java index 2bb92dbf1030..f051b4e573ae 100644 --- a/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/JdbcInstrumenterFactory.java +++ b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/JdbcInstrumenterFactory.java @@ -9,10 +9,14 @@ import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; import io.opentelemetry.instrumentation.api.instrumenter.SpanKindExtractor; +import io.opentelemetry.instrumentation.api.instrumenter.code.CodeAttributesExtractor; +import io.opentelemetry.instrumentation.api.instrumenter.code.CodeSpanNameExtractor; import io.opentelemetry.instrumentation.api.instrumenter.db.DbClientSpanNameExtractor; import io.opentelemetry.instrumentation.api.instrumenter.db.SqlClientAttributesExtractor; import io.opentelemetry.instrumentation.api.instrumenter.network.ServerAttributesExtractor; import io.opentelemetry.instrumentation.api.internal.ConfigPropertiesUtil; +import io.opentelemetry.instrumentation.jdbc.internal.dbinfo.DbInfo; +import javax.sql.DataSource; /** * This class is internal and is hence not for public use. Its APIs are unstable and can change at @@ -30,19 +34,38 @@ public static Instrumenter createStatementInstrumenter() { public static Instrumenter createStatementInstrumenter( OpenTelemetry openTelemetry) { + return createStatementInstrumenter( + openTelemetry, + true, + ConfigPropertiesUtil.getBoolean( + "otel.instrumentation.common.db-statement-sanitizer.enabled", true)); + } + + public static Instrumenter createStatementInstrumenter( + OpenTelemetry openTelemetry, boolean enabled, boolean statementSanitizationEnabled) { return Instrumenter.builder( openTelemetry, INSTRUMENTATION_NAME, DbClientSpanNameExtractor.create(dbAttributesGetter)) .addAttributesExtractor( SqlClientAttributesExtractor.builder(dbAttributesGetter) - .setStatementSanitizationEnabled( - ConfigPropertiesUtil.getBoolean( - "otel.instrumentation.common.db-statement-sanitizer.enabled", true)) + .setStatementSanitizationEnabled(statementSanitizationEnabled) .build()) .addAttributesExtractor(ServerAttributesExtractor.create(netAttributesGetter)) + .setEnabled(enabled) .buildInstrumenter(SpanKindExtractor.alwaysClient()); } + public static Instrumenter createDataSourceInstrumenter( + OpenTelemetry openTelemetry, boolean enabled) { + DataSourceCodeAttributesGetter getter = DataSourceCodeAttributesGetter.INSTANCE; + return Instrumenter.builder( + openTelemetry, INSTRUMENTATION_NAME, CodeSpanNameExtractor.create(getter)) + .addAttributesExtractor(CodeAttributesExtractor.create(getter)) + .addAttributesExtractor(DataSourceDbAttributesExtractor.INSTANCE) + .setEnabled(enabled) + .buildInstrumenter(); + } + private JdbcInstrumenterFactory() {} } diff --git a/instrumentation/jdbc/library/src/test/java/io/opentelemetry/instrumentation/jdbc/datasource/JdbcTelemetryTest.java b/instrumentation/jdbc/library/src/test/java/io/opentelemetry/instrumentation/jdbc/datasource/JdbcTelemetryTest.java new file mode 100644 index 000000000000..6b442d340e4b --- /dev/null +++ b/instrumentation/jdbc/library/src/test/java/io/opentelemetry/instrumentation/jdbc/datasource/JdbcTelemetryTest.java @@ -0,0 +1,116 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.jdbc.datasource; + +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; + +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension; +import io.opentelemetry.semconv.SemanticAttributes; +import java.sql.SQLException; +import javax.sql.DataSource; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +class JdbcTelemetryTest { + + @RegisterExtension + static final InstrumentationExtension testing = LibraryInstrumentationExtension.create(); + + @Test + void buildWithDefaults() throws SQLException { + JdbcTelemetry telemetry = JdbcTelemetry.builder(testing.getOpenTelemetry()).build(); + DataSource dataSource = telemetry.wrap(new TestDataSource()); + + testing.runWithSpan( + "parent", () -> dataSource.getConnection().createStatement().execute("SELECT 1;")); + + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("parent"), + span -> span.hasName("TestDataSource.getConnection"), + span -> + span.hasName("SELECT dbname") + .hasAttribute(equalTo(SemanticAttributes.DB_STATEMENT, "SELECT ?;")))); + } + + @Test + void buildWithAllInstrumentersDisabled() throws SQLException { + JdbcTelemetry telemetry = + JdbcTelemetry.builder(testing.getOpenTelemetry()) + .setDataSourceInstrumenterEnabled(false) + .setStatementInstrumenterEnabled(false) + .build(); + + DataSource dataSource = telemetry.wrap(new TestDataSource()); + + testing.runWithSpan( + "parent", () -> dataSource.getConnection().createStatement().execute("SELECT 1;")); + + testing.waitAndAssertTraces( + trace -> trace.hasSpansSatisfyingExactly(span -> span.hasName("parent"))); + } + + @Test + void buildWithDataSourceInstrumenterDisabled() throws SQLException { + JdbcTelemetry telemetry = + JdbcTelemetry.builder(testing.getOpenTelemetry()) + .setDataSourceInstrumenterEnabled(false) + .build(); + + DataSource dataSource = telemetry.wrap(new TestDataSource()); + + testing.runWithSpan( + "parent", () -> dataSource.getConnection().createStatement().execute("SELECT 1;")); + + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("parent"), span -> span.hasName("SELECT dbname"))); + } + + @Test + void buildWithStatementInstrumenterDisabled() throws SQLException { + JdbcTelemetry telemetry = + JdbcTelemetry.builder(testing.getOpenTelemetry()) + .setStatementInstrumenterEnabled(false) + .build(); + + DataSource dataSource = telemetry.wrap(new TestDataSource()); + + testing.runWithSpan( + "parent", () -> dataSource.getConnection().createStatement().execute("SELECT 1;")); + + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("parent"), + span -> span.hasName("TestDataSource.getConnection"))); + } + + @Test + void buildWithSanitizationDisabled() throws SQLException { + JdbcTelemetry telemetry = + JdbcTelemetry.builder(testing.getOpenTelemetry()) + .setStatementSanitizationEnabled(false) + .build(); + + DataSource dataSource = telemetry.wrap(new TestDataSource()); + + testing.runWithSpan( + "parent", () -> dataSource.getConnection().createStatement().execute("SELECT 1;")); + + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("parent"), + span -> span.hasName("TestDataSource.getConnection"), + span -> + span.hasName("SELECT dbname") + .hasAttribute(equalTo(SemanticAttributes.DB_STATEMENT, "SELECT 1;")))); + } +} diff --git a/instrumentation/jdbc/library/src/test/java/io/opentelemetry/instrumentation/jdbc/datasource/OpenTelemetryDataSourceTest.java b/instrumentation/jdbc/library/src/test/java/io/opentelemetry/instrumentation/jdbc/datasource/OpenTelemetryDataSourceTest.java index 9b6410c65a9e..853ed7196b4f 100644 --- a/instrumentation/jdbc/library/src/test/java/io/opentelemetry/instrumentation/jdbc/datasource/OpenTelemetryDataSourceTest.java +++ b/instrumentation/jdbc/library/src/test/java/io/opentelemetry/instrumentation/jdbc/datasource/OpenTelemetryDataSourceTest.java @@ -35,8 +35,8 @@ class OpenTelemetryDataSourceTest { @ParameterizedTest @ArgumentsSource(GetConnectionMethods.class) void shouldEmitGetConnectionSpans(GetConnectionFunction getConnection) throws SQLException { - OpenTelemetryDataSource dataSource = - new OpenTelemetryDataSource(new TestDataSource(), testing.getOpenTelemetry()); + JdbcTelemetry telemetry = JdbcTelemetry.create(testing.getOpenTelemetry()); + DataSource dataSource = telemetry.wrap(new TestDataSource()); Connection connection = testing.runWithSpan("parent", () -> getConnection.call(dataSource)); @@ -67,8 +67,8 @@ void shouldEmitGetConnectionSpans(GetConnectionFunction getConnection) throws SQ @ArgumentsSource(GetConnectionMethods.class) void shouldNotEmitGetConnectionSpansWithoutParentSpan(GetConnectionFunction getConnection) throws SQLException { - OpenTelemetryDataSource dataSource = - new OpenTelemetryDataSource(new TestDataSource(), testing.getOpenTelemetry()); + JdbcTelemetry telemetry = JdbcTelemetry.create(testing.getOpenTelemetry()); + DataSource dataSource = telemetry.wrap(new TestDataSource()); Connection connection = getConnection.call(dataSource); diff --git a/smoke-tests-otel-starter/src/main/java/io/opentelemetry/spring/smoketest/DatasourceConfig.java b/smoke-tests-otel-starter/src/main/java/io/opentelemetry/spring/smoketest/DatasourceConfig.java index 2ac4ae8e7107..d735c27fcb98 100644 --- a/smoke-tests-otel-starter/src/main/java/io/opentelemetry/spring/smoketest/DatasourceConfig.java +++ b/smoke-tests-otel-starter/src/main/java/io/opentelemetry/spring/smoketest/DatasourceConfig.java @@ -6,7 +6,7 @@ package io.opentelemetry.spring.smoketest; import io.opentelemetry.api.OpenTelemetry; -import io.opentelemetry.instrumentation.jdbc.datasource.OpenTelemetryDataSource; +import io.opentelemetry.instrumentation.jdbc.datasource.JdbcTelemetry; import javax.sql.DataSource; import org.apache.commons.dbcp2.BasicDataSource; import org.springframework.context.annotation.Bean; @@ -22,6 +22,6 @@ public DataSource dataSource(OpenTelemetry openTelemetry) { dataSource.setUrl("jdbc:h2:mem:db"); dataSource.setUsername("username"); dataSource.setPassword("pwd"); - return new OpenTelemetryDataSource(dataSource, openTelemetry); + return JdbcTelemetry.create(openTelemetry).wrap(dataSource); } }