Add JdbcTelemetry and JdbcTelemetryBuilder (#9685)

This commit is contained in:
Hannah Chan 2023-10-18 01:11:44 +11:00 committed by GitHub
parent 3b08db75cd
commit 2a554bd153
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 264 additions and 48 deletions

View File

@ -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<DbRequest, Void> STATEMENT_INSTRUMENTER;
public static final Instrumenter<DataSource, DbInfo> DATASOURCE_INSTRUMENTER =
createDataSourceInstrumenter(GlobalOpenTelemetry.get());
createDataSourceInstrumenter(GlobalOpenTelemetry.get(), true);
static {
JdbcAttributesGetter dbAttributesGetter = new JdbcAttributesGetter();

View File

@ -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);
}
}

View File

@ -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<DataSource, DbInfo> dataSourceInstrumenter;
private final Instrumenter<DbRequest, Void> statementInstrumenter;
JdbcTelemetry(
Instrumenter<DataSource, DbInfo> dataSourceInstrumenter,
Instrumenter<DbRequest, Void> statementInstrumenter) {
this.dataSourceInstrumenter = dataSourceInstrumenter;
this.statementInstrumenter = statementInstrumenter;
}
public DataSource wrap(DataSource dataSource) {
return new OpenTelemetryDataSource(
dataSource, this.dataSourceInstrumenter, this.statementInstrumenter);
}
}

View File

@ -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));
}
}

View File

@ -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 class OpenTelemetryDataSource implements DataSource, AutoCloseable {
* @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<DataSource, DbInfo> dataSourceInstrumenter,
Instrumenter<DbRequest, Void> statementInstrumenter) {
this.delegate = delegate;
this.dataSourceInstrumenter = dataSourceInstrumenter;
this.statementInstrumenter = statementInstrumenter;
}
@Override
public Connection getConnection() throws SQLException {
Connection connection = wrapCall(delegate::getConnection);

View File

@ -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<DataSource, DbInfo> createDataSourceInstrumenter(
OpenTelemetry openTelemetry) {
DataSourceCodeAttributesGetter getter = DataSourceCodeAttributesGetter.INSTANCE;
return Instrumenter.<DataSource, DbInfo>builder(
openTelemetry, INSTRUMENTATION_NAME, CodeSpanNameExtractor.create(getter))
.addAttributesExtractor(CodeAttributesExtractor.create(getter))
.addAttributesExtractor(DataSourceDbAttributesExtractor.INSTANCE)
.buildInstrumenter();
}
private DataSourceInstrumenterFactory() {}
}

View File

@ -9,10 +9,14 @@ import io.opentelemetry.api.GlobalOpenTelemetry;
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 final class JdbcInstrumenterFactory {
public static Instrumenter<DbRequest, Void> createStatementInstrumenter(
OpenTelemetry openTelemetry) {
return createStatementInstrumenter(
openTelemetry,
true,
ConfigPropertiesUtil.getBoolean(
"otel.instrumentation.common.db-statement-sanitizer.enabled", true));
}
public static Instrumenter<DbRequest, Void> createStatementInstrumenter(
OpenTelemetry openTelemetry, boolean enabled, boolean statementSanitizationEnabled) {
return Instrumenter.<DbRequest, Void>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<DataSource, DbInfo> createDataSourceInstrumenter(
OpenTelemetry openTelemetry, boolean enabled) {
DataSourceCodeAttributesGetter getter = DataSourceCodeAttributesGetter.INSTANCE;
return Instrumenter.<DataSource, DbInfo>builder(
openTelemetry, INSTRUMENTATION_NAME, CodeSpanNameExtractor.create(getter))
.addAttributesExtractor(CodeAttributesExtractor.create(getter))
.addAttributesExtractor(DataSourceDbAttributesExtractor.INSTANCE)
.setEnabled(enabled)
.buildInstrumenter();
}
private JdbcInstrumenterFactory() {}
}

View File

@ -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;"))));
}
}

View File

@ -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 @@ class OpenTelemetryDataSourceTest {
@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);

View File

@ -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 class DatasourceConfig {
dataSource.setUrl("jdbc:h2:mem:db");
dataSource.setUsername("username");
dataSource.setPassword("pwd");
return new OpenTelemetryDataSource(dataSource, openTelemetry);
return JdbcTelemetry.create(openTelemetry).wrap(dataSource);
}
}