-
Notifications
You must be signed in to change notification settings - Fork 1k
Added instrumentation for transaction commit/rollback in jdbc #13709
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 8 commits
83795a3
140583e
54fee2e
fb74aa2
fb22764
2de16b2
12b544b
510c1b0
ac5ea54
114ccac
1336ab2
5a4a0d2
7bccecd
a928021
542f4b5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,6 @@ | ||
| # Settings for the JDBC instrumentation | ||
|
|
||
| | System property | Type | Default | Description | | ||
| |---------------------------------------------------------|---------|---------|----------------------------------------| | ||
| | `otel.instrumentation.jdbc.statement-sanitizer.enabled` | Boolean | `true` | Enables the DB statement sanitization. | | ||
| | System property | Type | Default | Description | | ||
| |--------------------------------------------------------------|---------|---------|------------------------------------------------------------------------------------------| | ||
| | `otel.instrumentation.jdbc.statement-sanitizer.enabled` | Boolean | `true` | Enables the DB statement sanitization. | | ||
| | `otel.instrumentation.jdbc.experimental.transaction.enabled` | Boolean | `false` | Enables experimental instrumentation to create spans for COMMIT and ROLLBACK operations. | |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -16,8 +16,11 @@ | |
| import io.opentelemetry.instrumentation.api.instrumenter.SpanKindExtractor; | ||
| import io.opentelemetry.instrumentation.api.semconv.network.ServerAttributesExtractor; | ||
| import io.opentelemetry.instrumentation.jdbc.internal.DbRequest; | ||
| import io.opentelemetry.instrumentation.jdbc.internal.JdbcAttributesGetter; | ||
| import io.opentelemetry.instrumentation.jdbc.internal.JdbcNetworkAttributesGetter; | ||
| import io.opentelemetry.instrumentation.jdbc.internal.JdbcStatementAttributesGetter; | ||
| import io.opentelemetry.instrumentation.jdbc.internal.JdbcTransactionAttributesGetter; | ||
| import io.opentelemetry.instrumentation.jdbc.internal.StatementNetworkAttributesGetter; | ||
| import io.opentelemetry.instrumentation.jdbc.internal.TransactionNetworkAttributesGetter; | ||
| import io.opentelemetry.instrumentation.jdbc.internal.TransactionRequest; | ||
| import io.opentelemetry.javaagent.bootstrap.internal.AgentCommonConfig; | ||
| import io.opentelemetry.javaagent.bootstrap.internal.AgentInstrumentationConfig; | ||
| import io.opentelemetry.javaagent.bootstrap.jdbc.DbInfo; | ||
|
|
@@ -27,32 +30,60 @@ public final class JdbcSingletons { | |
| private static final String INSTRUMENTATION_NAME = "io.opentelemetry.jdbc"; | ||
|
|
||
| private static final Instrumenter<DbRequest, Void> STATEMENT_INSTRUMENTER; | ||
| private static final Instrumenter<TransactionRequest, Void> TRANSACTION_INSTRUMENTER; | ||
| public static final Instrumenter<DataSource, DbInfo> DATASOURCE_INSTRUMENTER = | ||
| createDataSourceInstrumenter(GlobalOpenTelemetry.get(), true); | ||
|
|
||
| static { | ||
| JdbcAttributesGetter dbAttributesGetter = new JdbcAttributesGetter(); | ||
| JdbcNetworkAttributesGetter netAttributesGetter = new JdbcNetworkAttributesGetter(); | ||
| JdbcStatementAttributesGetter statementAttributesGetter = new JdbcStatementAttributesGetter(); | ||
| JdbcTransactionAttributesGetter transactionAttributesGetter = | ||
| new JdbcTransactionAttributesGetter(); | ||
| StatementNetworkAttributesGetter statementNetAttributesGetter = | ||
| new StatementNetworkAttributesGetter(); | ||
| TransactionNetworkAttributesGetter transactionNetAttributesGetter = | ||
| new TransactionNetworkAttributesGetter(); | ||
|
|
||
| STATEMENT_INSTRUMENTER = | ||
| Instrumenter.<DbRequest, Void>builder( | ||
| GlobalOpenTelemetry.get(), | ||
| INSTRUMENTATION_NAME, | ||
| DbClientSpanNameExtractor.create(dbAttributesGetter)) | ||
| DbClientSpanNameExtractor.create(statementAttributesGetter)) | ||
| .addAttributesExtractor( | ||
| SqlClientAttributesExtractor.builder(dbAttributesGetter) | ||
| SqlClientAttributesExtractor.builder(statementAttributesGetter) | ||
| .setStatementSanitizationEnabled( | ||
| AgentInstrumentationConfig.get() | ||
| .getBoolean( | ||
| "otel.instrumentation.jdbc.statement-sanitizer.enabled", | ||
| AgentCommonConfig.get().isStatementSanitizationEnabled())) | ||
| .build()) | ||
| .addAttributesExtractor(ServerAttributesExtractor.create(netAttributesGetter)) | ||
| .addAttributesExtractor(ServerAttributesExtractor.create(statementNetAttributesGetter)) | ||
| .addAttributesExtractor( | ||
| PeerServiceAttributesExtractor.create( | ||
| netAttributesGetter, AgentCommonConfig.get().getPeerServiceResolver())) | ||
| statementNetAttributesGetter, AgentCommonConfig.get().getPeerServiceResolver())) | ||
| .addOperationMetrics(DbClientMetrics.get()) | ||
| .buildInstrumenter(SpanKindExtractor.alwaysClient()); | ||
|
|
||
| TRANSACTION_INSTRUMENTER = | ||
| Instrumenter.<TransactionRequest, Void>builder( | ||
|
||
| GlobalOpenTelemetry.get(), INSTRUMENTATION_NAME, TransactionRequest::spanName) | ||
| .addAttributesExtractor( | ||
| SqlClientAttributesExtractor.builder(transactionAttributesGetter).build()) | ||
| .addAttributesExtractor( | ||
| ServerAttributesExtractor.create(transactionNetAttributesGetter)) | ||
| .addAttributesExtractor( | ||
| PeerServiceAttributesExtractor.create( | ||
| transactionNetAttributesGetter, | ||
| AgentCommonConfig.get().getPeerServiceResolver())) | ||
| .addOperationMetrics(DbClientMetrics.get()) | ||
|
||
| .setEnabled( | ||
| AgentInstrumentationConfig.get() | ||
| .getBoolean( | ||
| "otel.instrumentation.jdbc.experimental.transaction.enabled", false)) | ||
| .buildInstrumenter(SpanKindExtractor.alwaysClient()); | ||
| } | ||
|
|
||
| public static Instrumenter<TransactionRequest, Void> transactionInstrumenter() { | ||
| return TRANSACTION_INSTRUMENTER; | ||
| } | ||
|
|
||
| public static Instrumenter<DbRequest, Void> statementInstrumenter() { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1632,4 +1632,146 @@ void testPreparedBatch(String system, Connection connection, String username, St | |
| DB_OPERATION_BATCH_SIZE, | ||
| emitStableDatabaseSemconv() ? 2L : null)))); | ||
| } | ||
|
|
||
| @ParameterizedTest | ||
| @MethodSource("transactionOperationsStream") | ||
| void testCommitTransaction(String system, Connection connection, String username, String url) | ||
| throws SQLException { | ||
|
|
||
| String tableName = "TXN_COMMIT_TEST_" + system.toUpperCase(Locale.ROOT); | ||
| Statement createTable = connection.createStatement(); | ||
| createTable.execute("CREATE TABLE " + tableName + " (id INTEGER not NULL, PRIMARY KEY ( id ))"); | ||
| cleanup.deferCleanup(createTable); | ||
|
|
||
| boolean originalAutoCommit = connection.getAutoCommit(); | ||
| connection.setAutoCommit(false); | ||
|
|
||
| testing.waitForTraces(1); | ||
| testing.clearData(); | ||
|
|
||
| try { | ||
| Statement insertStatement = connection.createStatement(); | ||
| cleanup.deferCleanup(insertStatement); | ||
|
|
||
| testing.runWithSpan( | ||
| "parent", | ||
| () -> { | ||
| insertStatement.executeUpdate("INSERT INTO " + tableName + " VALUES(1)"); | ||
| connection.commit(); | ||
| }); | ||
|
|
||
| testing.waitAndAssertTraces( | ||
| trace -> | ||
| trace.hasSpansSatisfyingExactly( | ||
| span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), | ||
| span -> | ||
| span.hasName("INSERT jdbcunittest." + tableName) | ||
| .hasKind(SpanKind.CLIENT) | ||
| .hasParent(trace.getSpan(0)) | ||
| .hasAttributesSatisfyingExactly( | ||
| equalTo(maybeStable(DB_SYSTEM), maybeStableDbSystemName(system)), | ||
| equalTo(maybeStable(DB_NAME), dbNameLower), | ||
| equalTo(DB_USER, emitStableDatabaseSemconv() ? null : username), | ||
| equalTo( | ||
| DB_CONNECTION_STRING, emitStableDatabaseSemconv() ? null : url), | ||
| equalTo( | ||
| maybeStable(DB_STATEMENT), | ||
| "INSERT INTO " + tableName + " VALUES(?)"), | ||
| equalTo(maybeStable(DB_OPERATION), "INSERT"), | ||
| equalTo(maybeStable(DB_SQL_TABLE), tableName)), | ||
| span -> | ||
| span.hasName("COMMIT") | ||
| .hasKind(SpanKind.CLIENT) | ||
| .hasParent(trace.getSpan(0)) | ||
| .hasAttributesSatisfyingExactly( | ||
| equalTo(maybeStable(DB_SYSTEM), maybeStableDbSystemName(system)), | ||
| equalTo(maybeStable(DB_NAME), dbNameLower), | ||
| equalTo(DB_USER, emitStableDatabaseSemconv() ? null : username), | ||
| equalTo( | ||
| DB_CONNECTION_STRING, | ||
| emitStableDatabaseSemconv() ? null : url)))); | ||
| } finally { | ||
| connection.setAutoCommit(originalAutoCommit); | ||
stillya marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
| } | ||
|
|
||
| @ParameterizedTest | ||
| @MethodSource("transactionOperationsStream") | ||
| void testRollbackTransaction(String system, Connection connection, String username, String url) | ||
| throws SQLException { | ||
|
|
||
| String tableName = "TXN_ROLLBACK_TEST_" + system.toUpperCase(Locale.ROOT); | ||
| Statement createTable = connection.createStatement(); | ||
| createTable.execute("CREATE TABLE " + tableName + " (id INTEGER not NULL, PRIMARY KEY ( id ))"); | ||
| cleanup.deferCleanup(createTable); | ||
|
|
||
| boolean originalAutoCommit = connection.getAutoCommit(); | ||
| connection.setAutoCommit(false); | ||
|
|
||
| testing.waitForTraces(1); | ||
| testing.clearData(); | ||
|
|
||
| try { | ||
| Statement insertStatement = connection.createStatement(); | ||
| cleanup.deferCleanup(insertStatement); | ||
|
|
||
| testing.runWithSpan( | ||
| "parent", | ||
| () -> { | ||
| insertStatement.executeUpdate("INSERT INTO " + tableName + " VALUES(1)"); | ||
| connection.rollback(); | ||
| }); | ||
|
|
||
| testing.waitAndAssertTraces( | ||
| trace -> | ||
| trace.hasSpansSatisfyingExactly( | ||
| span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), | ||
| span -> | ||
| span.hasName("INSERT jdbcunittest." + tableName) | ||
| .hasKind(SpanKind.CLIENT) | ||
| .hasParent(trace.getSpan(0)) | ||
| .hasAttributesSatisfyingExactly( | ||
| equalTo(maybeStable(DB_SYSTEM), maybeStableDbSystemName(system)), | ||
| equalTo(maybeStable(DB_NAME), dbNameLower), | ||
| equalTo(DB_USER, emitStableDatabaseSemconv() ? null : username), | ||
| equalTo( | ||
| DB_CONNECTION_STRING, emitStableDatabaseSemconv() ? null : url), | ||
| equalTo( | ||
| maybeStable(DB_STATEMENT), | ||
| "INSERT INTO " + tableName + " VALUES(?)"), | ||
| equalTo(maybeStable(DB_OPERATION), "INSERT"), | ||
| equalTo(maybeStable(DB_SQL_TABLE), tableName)), | ||
| span -> | ||
| span.hasName("ROLLBACK") | ||
| .hasKind(SpanKind.CLIENT) | ||
| .hasParent(trace.getSpan(0)) | ||
| .hasAttributesSatisfyingExactly( | ||
| equalTo(maybeStable(DB_SYSTEM), maybeStableDbSystemName(system)), | ||
| equalTo(maybeStable(DB_NAME), dbNameLower), | ||
| equalTo(DB_USER, emitStableDatabaseSemconv() ? null : username), | ||
| equalTo( | ||
| DB_CONNECTION_STRING, | ||
| emitStableDatabaseSemconv() ? null : url)))); | ||
|
|
||
| Statement selectStatement = connection.createStatement(); | ||
| cleanup.deferCleanup(selectStatement); | ||
| ResultSet resultSet = selectStatement.executeQuery("SELECT COUNT(*) FROM " + tableName); | ||
| resultSet.next(); | ||
| assertThat(resultSet.getInt(1)).isEqualTo(0); | ||
|
||
| } finally { | ||
| connection.setAutoCommit(originalAutoCommit); | ||
| } | ||
| } | ||
|
|
||
| static Stream<Arguments> transactionOperationsStream() throws SQLException { | ||
| return Stream.of( | ||
| Arguments.of("h2", new org.h2.Driver().connect(jdbcUrls.get("h2"), null), null, "h2:mem:"), | ||
| Arguments.of( | ||
| "derby", | ||
| new EmbeddedDriver().connect(jdbcUrls.get("derby"), null), | ||
| "APP", | ||
| "derby:memory:"), | ||
| Arguments.of( | ||
| "hsqldb", new JDBCDriver().connect(jdbcUrls.get("hsqldb"), null), "SA", "hsqldb:mem:")); | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.