From 32aeb83878bd576d25094e0baf31d25b00806936 Mon Sep 17 00:00:00 2001 From: Alix Date: Mon, 14 Apr 2025 18:11:07 +0200 Subject: [PATCH 01/15] Added db.operation.parameter span attributes --- .../config/internal/CommonConfig.java | 8 + .../db/SqlClientAttributesExtractor.java | 61 ++- .../SqlClientAttributesExtractorBuilder.java | 16 +- .../semconv/db/SqlClientAttributesGetter.java | 8 + .../semconv/db/SqlStatementInfo.java | 11 +- .../semconv/db/SqlStatementSanitizer.java | 2 +- .../src/main/jflex/SqlSanitizer.jflex | 68 +++- .../db/SqlClientAttributesExtractorTest.java | 119 ++++++ .../semconv/db/SqlStatementSanitizerTest.java | 49 ++- instrumentation/jdbc/README.md | 7 +- .../jdbc/javaagent/build.gradle.kts | 4 + .../instrumentation/jdbc/JdbcSingletons.java | 5 + .../PreparedStatementInstrumentation.java | 40 +- .../jdbc/StatementInstrumentation.java | 11 +- .../jdbc/test/JdbcInstrumentationTest.java | 349 ++++++++++++------ .../jdbc/datasource/JdbcTelemetryBuilder.java | 12 +- .../jdbc/internal/DbRequest.java | 52 ++- .../jdbc/internal/JdbcAttributesGetter.java | 7 + .../jdbc/internal/JdbcData.java | 12 + .../internal/JdbcInstrumenterFactory.java | 11 +- .../OpenTelemetryPreparedStatement.java | 64 +++- .../jdbc/internal/OpenTelemetryStatement.java | 2 +- .../internal/OpenTelemetryConnectionTest.java | 73 +++- .../jdbc/TestPreparedStatement.java | 9 +- .../jdbc/DataSourcePostProcessor.java | 4 + .../properties/InstrumentationConfigUtil.java | 6 + ...itional-spring-configuration-metadata.json | 6 + 27 files changed, 842 insertions(+), 174 deletions(-) diff --git a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/config/internal/CommonConfig.java b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/config/internal/CommonConfig.java index bb64425c6da5..40991ae87320 100644 --- a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/config/internal/CommonConfig.java +++ b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/config/internal/CommonConfig.java @@ -29,6 +29,7 @@ public final class CommonConfig { private final Set knownHttpRequestMethods; private final EnduserConfig enduserConfig; private final boolean statementSanitizationEnabled; + private final boolean operationParameterEnabled; private final boolean emitExperimentalHttpClientTelemetry; private final boolean emitExperimentalHttpServerTelemetry; private final boolean redactQueryParameters; @@ -56,6 +57,9 @@ public CommonConfig(InstrumentationConfig config) { new ArrayList<>(HttpConstants.KNOWN_METHODS))); statementSanitizationEnabled = config.getBoolean("otel.instrumentation.common.db-statement-sanitizer.enabled", true); + // TODO change with common conf key + operationParameterEnabled = + config.getBoolean("otel.instrumentation.jdbc.operation-parameter.enabled", false); emitExperimentalHttpClientTelemetry = config.getBoolean("otel.instrumentation.http.client.emit-experimental-telemetry", false); redactQueryParameters = @@ -107,6 +111,10 @@ public boolean isStatementSanitizationEnabled() { return statementSanitizationEnabled; } + public boolean isOperationParameterEnabled() { + return operationParameterEnabled; + } + public boolean shouldEmitExperimentalHttpClientTelemetry() { return emitExperimentalHttpClientTelemetry; } diff --git a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlClientAttributesExtractor.java b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlClientAttributesExtractor.java index 733a8a2eaee1..2972c10ebc8f 100644 --- a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlClientAttributesExtractor.java +++ b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlClientAttributesExtractor.java @@ -12,7 +12,9 @@ import io.opentelemetry.context.Context; import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; import io.opentelemetry.instrumentation.api.internal.SemconvStability; +import io.opentelemetry.semconv.AttributeKeyTemplate; import java.util.Collection; +import java.util.Map; /** * Extractor of AttributeKey.stringKey("db.collection.name"); private static final AttributeKey DB_OPERATION_BATCH_SIZE = AttributeKey.longKey("db.operation.batch.size"); + private static final AttributeKeyTemplate DB_OPERATION_PARAMETER = + AttributeKeyTemplate.stringKeyTemplate("db.operation.parameter"); /** Creates the SQL client attributes extractor with default configuration. */ public static AttributesExtractor create( @@ -58,26 +62,34 @@ public static SqlClientAttributesExtractorBuilder oldSemconvTableAttribute; private final boolean statementSanitizationEnabled; + private final boolean operationParameterEnabled; SqlClientAttributesExtractor( SqlClientAttributesGetter getter, AttributeKey oldSemconvTableAttribute, - boolean statementSanitizationEnabled) { + boolean statementSanitizationEnabled, + boolean operationParameterEnabled) { super(getter); this.oldSemconvTableAttribute = oldSemconvTableAttribute; this.statementSanitizationEnabled = statementSanitizationEnabled; + this.operationParameterEnabled = operationParameterEnabled; } @Override + @SuppressWarnings("AlreadyChecked") public void onStart(AttributesBuilder attributes, Context parentContext, REQUEST request) { super.onStart(attributes, parentContext, request); Collection rawQueryTexts = getter.getRawQueryTexts(request); + Map preparedStatementParameters = getter.getOperationParameters(request); if (rawQueryTexts.isEmpty()) { return; } + Long batchSize = getter.getBatchSize(request); + boolean isBatch = batchSize != null && batchSize > 1; + if (SemconvStability.emitOldDatabaseSemconv()) { if (rawQueryTexts.size() == 1) { // for backcompat(?) String rawQueryText = rawQueryTexts.iterator().next(); @@ -91,12 +103,12 @@ public void onStart(AttributesBuilder attributes, Context parentContext, REQUEST if (!SQL_CALL.equals(operation)) { internalSet(attributes, oldSemconvTableAttribute, sanitizedStatement.getMainIdentifier()); } + setOperationParameters( + attributes, sanitizedStatement, isBatch, preparedStatementParameters); } } if (SemconvStability.emitStableDatabaseSemconv()) { - Long batchSize = getter.getBatchSize(request); - boolean isBatch = batchSize != null && batchSize > 1; if (isBatch) { internalSet(attributes, DB_OPERATION_BATCH_SIZE, batchSize); } @@ -112,6 +124,8 @@ public void onStart(AttributesBuilder attributes, Context parentContext, REQUEST if (!SQL_CALL.equals(operation)) { internalSet(attributes, DB_COLLECTION_NAME, sanitizedStatement.getMainIdentifier()); } + setOperationParameters( + attributes, sanitizedStatement, isBatch, preparedStatementParameters); } else { MultiQuery multiQuery = MultiQuery.analyze(getter.getRawQueryTexts(request), statementSanitizationEnabled); @@ -129,6 +143,33 @@ public void onStart(AttributesBuilder attributes, Context parentContext, REQUEST } } + private void setOperationParameters( + AttributesBuilder attributes, + SqlStatementInfo sanitizedStatement, + boolean isBatch, + Map preparedStatementParameters) { + if (sanitizedStatement.getParameters() != null && operationParameterEnabled && !isBatch) { + int currentPreparedStatementParametersIndex = 1; + for (Map.Entry entry : sanitizedStatement.getParameters().entrySet()) { + // in this case it means that the sanitizer parsed an existing ? + // or a postgres marked parameter. So we'll replace with data from the REQUEST + String key = entry.getKey(); + String value = entry.getValue(); + if (preparedStatementParameters != null + && (value.equalsIgnoreCase("?") || value.startsWith("$")) + && preparedStatementParameters.containsKey(currentPreparedStatementParametersIndex)) { + internalSet( + attributes, + DB_OPERATION_PARAMETER.getAttributeKey(key), + stringifyParameter( + preparedStatementParameters.get(currentPreparedStatementParametersIndex++))); + } else { + internalSet(attributes, DB_OPERATION_PARAMETER.getAttributeKey(key), value); + } + } + } + } + // String.join is not available on android private static String join(String delimiter, Collection collection) { StringBuilder builder = new StringBuilder(); @@ -140,4 +181,18 @@ private static String join(String delimiter, Collection collection) { } return builder.toString(); } + + // TODO define all string repr of objects + public static String stringifyParameter(Object object) { + if (object == null) { + return ""; + } else if (object instanceof String) { + return String.format("'%s'", object); + } else if (object instanceof Number) { + Number number = (Number) object; + return String.format("%s", number); + } + + return String.format("<%s>", object.getClass().getSimpleName()); + } } diff --git a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlClientAttributesExtractorBuilder.java b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlClientAttributesExtractorBuilder.java index 43cbc63062bd..6c48a4fa4bf1 100644 --- a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlClientAttributesExtractorBuilder.java +++ b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlClientAttributesExtractorBuilder.java @@ -20,6 +20,7 @@ public final class SqlClientAttributesExtractorBuilder { final SqlClientAttributesGetter getter; AttributeKey oldSemconvTableAttribute = DB_SQL_TABLE; boolean statementSanitizationEnabled = true; + boolean operationParameterEnabled = false; SqlClientAttributesExtractorBuilder(SqlClientAttributesGetter getter) { this.getter = getter; @@ -48,12 +49,25 @@ public SqlClientAttributesExtractorBuilder setStatementSaniti return this; } + /** + * Sets whether the {@code db.operation.parameter.} attributes extracted by the constructed + * {@link SqlClientAttributesExtractor} should be opted-in. If set to {@code true}, all parameters + * masked by the sanitization and all parameters from {@code PreparedStatement} will be exposed as + * attributes. Disabled by default. + */ + @CanIgnoreReturnValue + public SqlClientAttributesExtractorBuilder setOperationParameterEnabled( + boolean operationParameterEnabled) { + this.operationParameterEnabled = operationParameterEnabled; + return this; + } + /** * Returns a new {@link SqlClientAttributesExtractor} with the settings of this {@link * SqlClientAttributesExtractorBuilder}. */ public AttributesExtractor build() { return new SqlClientAttributesExtractor<>( - getter, oldSemconvTableAttribute, statementSanitizationEnabled); + getter, oldSemconvTableAttribute, statementSanitizationEnabled, operationParameterEnabled); } } diff --git a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlClientAttributesGetter.java b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlClientAttributesGetter.java index 1b795363e7b9..736ba24c0155 100644 --- a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlClientAttributesGetter.java +++ b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlClientAttributesGetter.java @@ -9,6 +9,7 @@ import static java.util.Collections.singleton; import java.util.Collection; +import java.util.Map; import javax.annotation.Nullable; /** @@ -66,4 +67,11 @@ default Collection getRawQueryTexts(REQUEST request) { default Long getBatchSize(REQUEST request) { return null; } + + /** TODO */ + // TODO: make this required to implement + @Nullable + default Map getOperationParameters(REQUEST request) { + return null; + } } diff --git a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlStatementInfo.java b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlStatementInfo.java index 04320206b060..9d9e9d5640b3 100644 --- a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlStatementInfo.java +++ b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlStatementInfo.java @@ -6,14 +6,18 @@ package io.opentelemetry.instrumentation.api.incubator.semconv.db; import com.google.auto.value.AutoValue; +import java.util.Map; import javax.annotation.Nullable; @AutoValue public abstract class SqlStatementInfo { public static SqlStatementInfo create( - @Nullable String fullStatement, @Nullable String operation, @Nullable String identifier) { - return new AutoValue_SqlStatementInfo(fullStatement, operation, identifier); + @Nullable String fullStatement, + @Nullable String operation, + @Nullable String identifier, + @Nullable Map parameters) { + return new AutoValue_SqlStatementInfo(fullStatement, operation, identifier, parameters); } @Nullable @@ -24,4 +28,7 @@ public static SqlStatementInfo create( @Nullable public abstract String getMainIdentifier(); + + @Nullable + public abstract Map getParameters(); } diff --git a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlStatementSanitizer.java b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlStatementSanitizer.java index 58fd4c3cc7dc..e8ecd98b4725 100644 --- a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlStatementSanitizer.java +++ b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlStatementSanitizer.java @@ -39,7 +39,7 @@ public SqlStatementInfo sanitize(@Nullable String statement) { public SqlStatementInfo sanitize(@Nullable String statement, SqlDialect dialect) { if (!statementSanitizationEnabled || statement == null) { - return SqlStatementInfo.create(statement, null, null); + return SqlStatementInfo.create(statement, null, null, null); } // sanitization result will not be cached for statements larger than the threshold to avoid // cache growing too large diff --git a/instrumentation-api-incubator/src/main/jflex/SqlSanitizer.jflex b/instrumentation-api-incubator/src/main/jflex/SqlSanitizer.jflex index 83a59fc0abc9..6c208aed9e1a 100644 --- a/instrumentation-api-incubator/src/main/jflex/SqlSanitizer.jflex +++ b/instrumentation-api-incubator/src/main/jflex/SqlSanitizer.jflex @@ -5,6 +5,8 @@ package io.opentelemetry.instrumentation.api.incubator.semconv.db; +import java.util.HashMap; +import java.util.Map; import java.util.regex.Pattern; %% @@ -37,6 +39,7 @@ DOLLAR_QUOTED_STR = "$$" [^$]* "$$" BACKTICK_QUOTED_STR = "`" [^`]* "`" POSTGRE_PARAM_MARKER = "$"[0-9]* WHITESPACE = [ \t\r\n]+ +QUESTION_MARK = "?" %{ static SqlStatementInfo sanitize(String statement, SqlDialect dialect) { @@ -53,7 +56,7 @@ WHITESPACE = [ \t\r\n]+ return sanitizer.getResult(); } catch (java.io.IOException e) { // should never happen - return SqlStatementInfo.create(null, null, null); + return SqlStatementInfo.create(null, null, null, null); } } @@ -66,6 +69,24 @@ WHITESPACE = [ \t\r\n]+ private final StringBuilder builder = new StringBuilder(); + private final Map parameters = new HashMap<>(); + private int currentParameterIndex = 0; + + private void storeParameterAndSanitize(boolean isNamed, boolean keepFragment) { + String value = yytext(); + if (isNamed) { + parameters.put(value, value); + } else { + parameters.put(Integer.toString(currentParameterIndex++), value); + } + + if (keepFragment) { + builder.append(value); + } else { + builder.append('?'); + } + } + private void appendCurrentFragment() { builder.append(zzBuffer, zzStartRead, zzMarkedPos - zzStartRead); } @@ -164,8 +185,13 @@ WHITESPACE = [ \t\r\n]+ return false; } - SqlStatementInfo getResult(String fullStatement) { - return SqlStatementInfo.create(fullStatement, getClass().getSimpleName().toUpperCase(java.util.Locale.ROOT), mainIdentifier); + SqlStatementInfo getResult(String fullStatement, Map parameters) { + return SqlStatementInfo.create( + fullStatement, + getClass().getSimpleName().toUpperCase(java.util.Locale.ROOT), + mainIdentifier, + parameters + ); } } @@ -195,19 +221,24 @@ WHITESPACE = [ \t\r\n]+ return true; } - SqlStatementInfo getResult(String fullStatement) { + SqlStatementInfo getResult(String fullStatement, Map parameters) { if (!"".equals(operationTarget)) { - return SqlStatementInfo.create(fullStatement, getClass().getSimpleName().toUpperCase(java.util.Locale.ROOT) + " " + operationTarget, mainIdentifier); + return SqlStatementInfo.create( + fullStatement, + getClass().getSimpleName().toUpperCase(java.util.Locale.ROOT) + " " + operationTarget, + mainIdentifier, + parameters + ); } - return super.getResult(fullStatement); + return super.getResult(fullStatement, parameters); } } private static class NoOp extends Operation { static final Operation INSTANCE = new NoOp(); - SqlStatementInfo getResult(String fullStatement) { - return SqlStatementInfo.create(fullStatement, null, null); + SqlStatementInfo getResult(String fullStatement, Map parameters) { + return SqlStatementInfo.create(fullStatement, null, null, parameters); } } @@ -363,9 +394,10 @@ WHITESPACE = [ \t\r\n]+ String fullStatement = builder.toString(); // Normalize all 'in (?, ?, ...)' statements to in (?) to reduce cardinality + // TODO merge this.parameters in a single list for merged ? String normalizedStatement = IN_STATEMENT_PATTERN.matcher(fullStatement).replaceAll(IN_STATEMENT_NORMALIZED); - return operation.getResult(normalizedStatement); + return operation.getResult(normalizedStatement, this.parameters); } %} @@ -529,13 +561,13 @@ WHITESPACE = [ \t\r\n]+ // here is where the actual sanitization happens {BASIC_NUM} | {HEX_NUM} | {QUOTED_STR} | {DOLLAR_QUOTED_STR} { - builder.append('?'); + storeParameterAndSanitize(false, false); if (isOverLimit()) return YYEOF; } {DOUBLE_QUOTED_STR} { if (dialect == SqlDialect.COUCHBASE) { - builder.append('?'); + storeParameterAndSanitize(false, false); } else { if (!insideComment && !extractionDone) { extractionDone = operation.handleIdentifier(); @@ -545,7 +577,7 @@ WHITESPACE = [ \t\r\n]+ if (isOverLimit()) return YYEOF; } - {BACKTICK_QUOTED_STR} | {POSTGRE_PARAM_MARKER} { + {BACKTICK_QUOTED_STR} { if (!insideComment && !extractionDone) { extractionDone = operation.handleIdentifier(); } @@ -553,6 +585,18 @@ WHITESPACE = [ \t\r\n]+ if (isOverLimit()) return YYEOF; } + {POSTGRE_PARAM_MARKER} { + if (!insideComment && !extractionDone) { + extractionDone = operation.handleIdentifier(); + } + storeParameterAndSanitize(true, true); + if (isOverLimit()) return YYEOF; + } + {QUESTION_MARK} { + storeParameterAndSanitize(false, true); + if (isOverLimit()) return YYEOF; + } + {WHITESPACE} { builder.append(' '); if (isOverLimit()) return YYEOF; diff --git a/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlClientAttributesExtractorTest.java b/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlClientAttributesExtractorTest.java index d3cd7dc03220..854de22e4bdd 100644 --- a/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlClientAttributesExtractorTest.java +++ b/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlClientAttributesExtractorTest.java @@ -6,6 +6,7 @@ package io.opentelemetry.instrumentation.api.incubator.semconv.db; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; +import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_OPERATION_PARAMETER; import static java.util.Collections.emptySet; import static java.util.Collections.singleton; import static org.assertj.core.api.Assertions.entry; @@ -22,6 +23,7 @@ import java.util.HashMap; import java.util.Map; import org.junit.jupiter.api.Test; +import org.spockframework.util.Nullable; @SuppressWarnings("deprecation") // using deprecated semconv class SqlClientAttributesExtractorTest { @@ -62,6 +64,30 @@ public Long getBatchSize(Map map) { return read(map, "db.operation.batch.size", Long.class); } + @SuppressWarnings("EmptyCatch") + @Nullable + @Override + public Map getOperationParameters(Map map) { + String parameterString = read(map, "db.operation.parameter"); + + if (parameterString == null) { + return null; + } + + Map parameters = new HashMap<>(); + for (String s : parameterString.split(";")) { + // cast basic types used in tests + Object value = s; + try { + value = Integer.parseInt(s); + } catch (NumberFormatException ignored) { + } + + parameters.put(parameters.size() + 1, value); + } + return parameters; + } + protected String read(Map map, String key) { return read(map, key, String.class); } @@ -387,4 +413,97 @@ void shouldIgnoreBatchSizeOne() { assertThat(endAttributes.build().isEmpty()).isTrue(); } + + @Test + void shouldExtractOperationParameters() { + // given + Map request = new HashMap<>(); + request.put("db.name", "potatoes"); + // a query with prepared parameters and parameters to sanitize + request.put( + "db.statement", + "SELECT col FROM table WHERE field1=? AND field2='A' AND field3=$2 AND field4=2"); + // a prepared parameters map + request.put("db.operation.parameter", "a;1"); + + Context context = Context.root(); + + AttributesExtractor, Void> underTest = + SqlClientAttributesExtractor.builder(new TestAttributesGetter()) + .setOperationParameterEnabled(true) + .build(); + + // when + AttributesBuilder startAttributes = Attributes.builder(); + underTest.onStart(startAttributes, context, request); + + AttributesBuilder endAttributes = Attributes.builder(); + underTest.onEnd(endAttributes, context, request, null, null); + + String prefix = DB_OPERATION_PARAMETER.getAttributeKey("").getKey(); + Attributes operationParameterAttributes = + startAttributes.removeIf(attribute -> !attribute.getKey().startsWith(prefix)).build(); + + // then + if (SemconvStability.emitStableDatabaseSemconv() && SemconvStability.emitOldDatabaseSemconv()) { + assertThat(operationParameterAttributes) + .containsOnly( + entry(DB_OPERATION_PARAMETER.getAttributeKey("0"), "'a'"), + entry(DB_OPERATION_PARAMETER.getAttributeKey("1"), "'A'"), + entry(DB_OPERATION_PARAMETER.getAttributeKey("$2"), "1"), + entry(DB_OPERATION_PARAMETER.getAttributeKey("2"), "2")); + } else if (SemconvStability.emitOldDatabaseSemconv()) { + assertThat(operationParameterAttributes) + .containsOnly( + entry(DB_OPERATION_PARAMETER.getAttributeKey("0"), "'a'"), + entry(DB_OPERATION_PARAMETER.getAttributeKey("1"), "'A'"), + entry(DB_OPERATION_PARAMETER.getAttributeKey("$2"), "1"), + entry(DB_OPERATION_PARAMETER.getAttributeKey("2"), "2")); + } else if (SemconvStability.emitStableDatabaseSemconv()) { + assertThat(operationParameterAttributes) + .containsOnly( + entry(DB_OPERATION_PARAMETER.getAttributeKey("0"), "'a'"), + entry(DB_OPERATION_PARAMETER.getAttributeKey("1"), "'A'"), + entry(DB_OPERATION_PARAMETER.getAttributeKey("$2"), "1"), + entry(DB_OPERATION_PARAMETER.getAttributeKey("2"), "2")); + } + + assertThat(endAttributes.build().isEmpty()).isTrue(); + } + + @Test + void shouldNotExtractOperationParametersForBatch() { + // given + Map request = new HashMap<>(); + request.put("db.name", "potatoes"); + request.put("db.statements", singleton("INSERT INTO potato VALUES(?)")); + request.put("db.operation.batch.size", 1L); + request.put("db.operation.parameter", "1"); + + Context context = Context.root(); + + AttributesExtractor, Void> underTest = + SqlClientAttributesExtractor.create(new TestMultiAttributesGetter()); + + // when + AttributesBuilder startAttributes = Attributes.builder(); + underTest.onStart(startAttributes, context, request); + + AttributesBuilder endAttributes = Attributes.builder(); + underTest.onEnd(endAttributes, context, request, null, null); + + // then + if (SemconvStability.emitStableDatabaseSemconv() && SemconvStability.emitOldDatabaseSemconv()) { + assertThat(startAttributes.build()) + .doesNotContainKey(DB_OPERATION_PARAMETER.getAttributeKey("0")); + } else if (SemconvStability.emitOldDatabaseSemconv()) { + assertThat(startAttributes.build()) + .doesNotContainKey(DB_OPERATION_PARAMETER.getAttributeKey("0")); + } else if (SemconvStability.emitStableDatabaseSemconv()) { + assertThat(startAttributes.build()) + .doesNotContainKey(DB_OPERATION_PARAMETER.getAttributeKey("0")); + } + + assertThat(endAttributes.build().isEmpty()).isTrue(); + } } diff --git a/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlStatementSanitizerTest.java b/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlStatementSanitizerTest.java index 8771ac1ed7c4..19b9a44ca7b0 100644 --- a/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlStatementSanitizerTest.java +++ b/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlStatementSanitizerTest.java @@ -7,6 +7,9 @@ import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; import java.util.Random; import java.util.function.Function; import java.util.stream.Stream; @@ -47,13 +50,19 @@ void simplifySql(String original, Function expectedFun @Test void veryLongSelectStatementsAreOk() { StringBuilder sb = new StringBuilder("SELECT * FROM table WHERE"); + Map parameters = new HashMap<>(); for (int i = 0; i < 2000; i++) { sb.append(" column").append(i).append("=123 and"); } String query = sb.toString(); String sanitizedQuery = query.replace("=123", "=?").substring(0, AutoSqlSanitizer.LIMIT); - SqlStatementInfo expected = SqlStatementInfo.create(sanitizedQuery, "SELECT", "table"); + sanitizedQuery + .chars() + .filter(c -> c == '?') + .forEach(c -> parameters.put(Integer.toString(parameters.size()), "123")); + SqlStatementInfo expected = + SqlStatementInfo.create(sanitizedQuery, "SELECT", "table", parameters); SqlStatementInfo result = SqlStatementSanitizer.create(true).sanitize(query); @@ -158,6 +167,36 @@ public void largeStatementCached() { assertThat(SqlStatementSanitizer.isCached(largeStatement)).isFalse(); } + @Test + public void parametersAreExtractedForEachSanitization() { + String s = "SELECT col FROM table WHERE field=1 AND field2=? AND field3=$1"; + Map parameters = new HashMap<>(); + parameters.put("0", "1"); + parameters.put("1", "?"); + parameters.put("$1", "$1"); + + SqlStatementInfo result = SqlStatementSanitizer.create(true).sanitize(s); + + assertThat(result.getParameters()).isEqualTo(parameters); + } + + @Test + public void parametersShouldNotCollideWithPreparedStatement() { + String s = "SELECT col FROM table WHERE field1=? AND field2='A' AND field3=$2 AND field4=2"; + String sanitized = + "SELECT col FROM table WHERE field1=? AND field2=? AND field3=$2 AND field4=?"; + Map parameters = new HashMap<>(); + parameters.put("0", "?"); + parameters.put("1", "'A'"); + parameters.put("$2", "$2"); + parameters.put("2", "2"); + + SqlStatementInfo result = SqlStatementSanitizer.create(true).sanitize(s); + + assertThat(result.getFullStatement()).isEqualTo(sanitized); + assertThat(result.getParameters()).isEqualTo(parameters); + } + static class SqlArgs implements ArgumentsProvider { @Override @@ -284,12 +323,12 @@ public Stream provideArguments(ExtensionContext context) { static class SimplifyArgs implements ArgumentsProvider { static Function expect(String operation, String identifier) { - return sql -> SqlStatementInfo.create(sql, operation, identifier); + return sql -> SqlStatementInfo.create(sql, operation, identifier, Collections.emptyMap()); } static Function expect( String sql, String operation, String identifier) { - return ignored -> SqlStatementInfo.create(sql, operation, identifier); + return ignored -> SqlStatementInfo.create(sql, operation, identifier, Collections.emptyMap()); } @Override @@ -401,12 +440,12 @@ public Stream provideArguments(ExtensionContext context) { static class DdlArgs implements ArgumentsProvider { static Function expect(String operation, String identifier) { - return sql -> SqlStatementInfo.create(sql, operation, identifier); + return sql -> SqlStatementInfo.create(sql, operation, identifier, Collections.emptyMap()); } static Function expect( String sql, String operation, String identifier) { - return ignored -> SqlStatementInfo.create(sql, operation, identifier); + return ignored -> SqlStatementInfo.create(sql, operation, identifier, Collections.emptyMap()); } @Override diff --git a/instrumentation/jdbc/README.md b/instrumentation/jdbc/README.md index 7e3440fd8c01..eb4bf1d5be44 100644 --- a/instrumentation/jdbc/README.md +++ b/instrumentation/jdbc/README.md @@ -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.operation-parameter.enabled` | Boolean | `false` | Enables the attribute db.operation.parameter. | diff --git a/instrumentation/jdbc/javaagent/build.gradle.kts b/instrumentation/jdbc/javaagent/build.gradle.kts index ac4617836eaf..6eb24e77949e 100644 --- a/instrumentation/jdbc/javaagent/build.gradle.kts +++ b/instrumentation/jdbc/javaagent/build.gradle.kts @@ -60,6 +60,7 @@ tasks { includeTestsMatching("SlickTest") } include("**/SlickTest.*") + jvmArgs("-Dotel.instrumentation.jdbc.operation-parameter.enabled=true") } test { @@ -67,6 +68,7 @@ tasks { excludeTestsMatching("SlickTest") } jvmArgs("-Dotel.instrumentation.jdbc-datasource.enabled=true") + jvmArgs("-Dotel.instrumentation.jdbc.operation-parameter.enabled=true") } val testStableSemconv by registering(Test::class) { @@ -75,6 +77,7 @@ tasks { } jvmArgs("-Dotel.instrumentation.jdbc-datasource.enabled=true") jvmArgs("-Dotel.semconv-stability.opt-in=database") + jvmArgs("-Dotel.instrumentation.jdbc.operation-parameter.enabled=true") } val testSlickStableSemconv by registering(Test::class) { @@ -83,6 +86,7 @@ tasks { } include("**/SlickTest.*") jvmArgs("-Dotel.semconv-stability.opt-in=database") + jvmArgs("-Dotel.instrumentation.jdbc.operation-parameter.enabled=true") } check { 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 e9c5290aa217..2a97ea0c4edf 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 @@ -46,6 +46,11 @@ public final class JdbcSingletons { .getBoolean( "otel.instrumentation.jdbc.statement-sanitizer.enabled", AgentCommonConfig.get().isStatementSanitizationEnabled())) + .setOperationParameterEnabled( + AgentInstrumentationConfig.get() + .getBoolean( + "otel.instrumentation.jdbc.operation-parameter.enabled", + AgentCommonConfig.get().isOperationParameterEnabled())) .build()) .addAttributesExtractor(ServerAttributesExtractor.create(netAttributesGetter)) .addAttributesExtractor( diff --git a/instrumentation/jdbc/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jdbc/PreparedStatementInstrumentation.java b/instrumentation/jdbc/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jdbc/PreparedStatementInstrumentation.java index a822ab98073e..d729aa30a56b 100644 --- a/instrumentation/jdbc/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jdbc/PreparedStatementInstrumentation.java +++ b/instrumentation/jdbc/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jdbc/PreparedStatementInstrumentation.java @@ -14,6 +14,7 @@ import static net.bytebuddy.matcher.ElementMatchers.named; import static net.bytebuddy.matcher.ElementMatchers.namedOneOf; import static net.bytebuddy.matcher.ElementMatchers.not; +import static net.bytebuddy.matcher.ElementMatchers.takesArgument; import static net.bytebuddy.matcher.ElementMatchers.takesArguments; import static net.bytebuddy.matcher.ElementMatchers.takesNoArguments; @@ -26,6 +27,7 @@ import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; import java.sql.PreparedStatement; import java.sql.Statement; +import java.util.Map; import net.bytebuddy.asm.Advice; import net.bytebuddy.description.type.TypeDescription; import net.bytebuddy.matcher.ElementMatcher; @@ -53,6 +55,18 @@ public void transform(TypeTransformer transformer) { transformer.applyAdviceToMethod( named("addBatch").and(takesNoArguments()).and(isPublic()), PreparedStatementInstrumentation.class.getName() + "$AddBatchAdvice"); + transformer.applyAdviceToMethod( + nameStartsWith("set") + .and(takesArguments(2)) + .and(takesArgument(0, int.class)) + .and(isPublic()), + PreparedStatementInstrumentation.class.getName() + "$Set2Advice"); + transformer.applyAdviceToMethod( + nameStartsWith("set") + .and(takesArguments(3)) + .and(takesArgument(0, int.class)) + .and(isPublic()), + PreparedStatementInstrumentation.class.getName() + "$Set3Advice"); } @SuppressWarnings("unused") @@ -84,7 +98,8 @@ public static void onEnter( } Context parentContext = currentContext(); - request = DbRequest.create(statement); + Map parameters = JdbcData.parameters.get(statement); + request = DbRequest.create(statement, parameters); if (request == null || !statementInstrumenter().shouldStart(parentContext, request)) { return; @@ -120,4 +135,27 @@ public static void addBatch(@Advice.This PreparedStatement statement) { JdbcData.addPreparedStatementBatch(statement); } } + + @SuppressWarnings("unused") + public static class Set2Advice { + @Advice.OnMethodExit(suppress = Throwable.class) + public static void onExit( + @Advice.This PreparedStatement statement, + @Advice.Argument(0) int index, + @Advice.Argument(1) Object value) { + JdbcData.addParameter(statement, index, value); + } + } + + @SuppressWarnings("unused") + public static class Set3Advice { + @Advice.OnMethodExit(suppress = Throwable.class) + public static void onExit( + @Advice.This PreparedStatement statement, + @Advice.Argument(0) int index, + @Advice.Argument(1) Object value1, + @Advice.Argument(1) Object value2) { + JdbcData.addParameter(statement, index, value1); + } + } } diff --git a/instrumentation/jdbc/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jdbc/StatementInstrumentation.java b/instrumentation/jdbc/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jdbc/StatementInstrumentation.java index d0dfd778457b..b22e965a3d5e 100644 --- a/instrumentation/jdbc/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jdbc/StatementInstrumentation.java +++ b/instrumentation/jdbc/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jdbc/StatementInstrumentation.java @@ -25,6 +25,7 @@ import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; import java.sql.PreparedStatement; import java.sql.Statement; +import java.util.Map; import net.bytebuddy.asm.Advice; import net.bytebuddy.description.type.TypeDescription; import net.bytebuddy.matcher.ElementMatcher; @@ -81,7 +82,7 @@ public static void onEnter( } Context parentContext = currentContext(); - request = DbRequest.create(statement, sql); + request = DbRequest.create(statement, sql, null); if (request == null || !statementInstrumenter().shouldStart(parentContext, request)) { return; @@ -156,17 +157,19 @@ public static void onEnter( if (statement instanceof PreparedStatement) { Long batchSize = JdbcData.getPreparedStatementBatchSize((PreparedStatement) statement); String sql = JdbcData.preparedStatement.get((PreparedStatement) statement); + Map parameters = JdbcData.parameters.get((PreparedStatement) statement); if (sql == null) { return; } - request = DbRequest.create(statement, sql, batchSize); + request = DbRequest.create(statement, sql, batchSize, parameters); } else { JdbcData.StatementBatchInfo batchInfo = JdbcData.getStatementBatchInfo(statement); if (batchInfo == null) { - request = DbRequest.create(statement, null); + request = DbRequest.create(statement, null, null); } else { request = - DbRequest.create(statement, batchInfo.getStatements(), batchInfo.getBatchSize()); + DbRequest.create( + statement, batchInfo.getStatements(), batchInfo.getBatchSize(), null); } } diff --git a/instrumentation/jdbc/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jdbc/test/JdbcInstrumentationTest.java b/instrumentation/jdbc/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jdbc/test/JdbcInstrumentationTest.java index 369a09f31fcf..7b59f6eb6e0e 100644 --- a/instrumentation/jdbc/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jdbc/test/JdbcInstrumentationTest.java +++ b/instrumentation/jdbc/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jdbc/test/JdbcInstrumentationTest.java @@ -19,6 +19,7 @@ import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_OPERATION; import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_OPERATION_BATCH_SIZE; import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_OPERATION_NAME; +import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_OPERATION_PARAMETER; import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_SQL_TABLE; import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_STATEMENT; import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_SYSTEM; @@ -38,6 +39,7 @@ import io.opentelemetry.instrumentation.testing.internal.AutoCleanupExtension; import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension; import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.sdk.testing.assertj.AttributeAssertion; import io.opentelemetry.sdk.testing.assertj.SpanDataAssert; import io.opentelemetry.sdk.testing.assertj.TraceAssert; import io.opentelemetry.semconv.incubating.CodeIncubatingAttributes; @@ -52,12 +54,14 @@ import java.sql.SQLException; import java.sql.Statement; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Properties; import java.util.function.Consumer; +import java.util.stream.Collectors; import java.util.stream.Stream; import javax.sql.DataSource; import org.apache.derby.jdbc.EmbeddedDataSource; @@ -218,7 +222,8 @@ static Stream basicStatementStream() throws SQLException { "SELECT ?", "SELECT " + dbNameLower, "h2:mem:", - null), + null, + Collections.singletonMap("0", "3")), Arguments.of( "derby", new EmbeddedDriver().connect(jdbcUrls.get("derby"), null), @@ -227,7 +232,8 @@ static Stream basicStatementStream() throws SQLException { "SELECT ? FROM SYSIBM.SYSDUMMY1", "SELECT SYSIBM.SYSDUMMY1", "derby:memory:", - "SYSIBM.SYSDUMMY1"), + "SYSIBM.SYSDUMMY1", + Collections.singletonMap("0", "3")), Arguments.of( "hsqldb", new JDBCDriver().connect(jdbcUrls.get("hsqldb"), null), @@ -236,7 +242,8 @@ static Stream basicStatementStream() throws SQLException { "SELECT ? FROM INFORMATION_SCHEMA.SYSTEM_USERS", "SELECT INFORMATION_SCHEMA.SYSTEM_USERS", "hsqldb:mem:", - "INFORMATION_SCHEMA.SYSTEM_USERS"), + "INFORMATION_SCHEMA.SYSTEM_USERS", + Collections.singletonMap("0", "3")), Arguments.of( "h2", new org.h2.Driver().connect(jdbcUrls.get("h2"), connectionProps), @@ -245,7 +252,8 @@ static Stream basicStatementStream() throws SQLException { "SELECT ?", "SELECT " + dbNameLower, "h2:mem:", - null), + null, + Collections.singletonMap("0", "3")), Arguments.of( "derby", new EmbeddedDriver().connect(jdbcUrls.get("derby"), connectionProps), @@ -254,7 +262,8 @@ static Stream basicStatementStream() throws SQLException { "SELECT ? FROM SYSIBM.SYSDUMMY1", "SELECT SYSIBM.SYSDUMMY1", "derby:memory:", - "SYSIBM.SYSDUMMY1"), + "SYSIBM.SYSDUMMY1", + Collections.singletonMap("0", "3")), Arguments.of( "hsqldb", new JDBCDriver().connect(jdbcUrls.get("hsqldb"), connectionProps), @@ -263,7 +272,8 @@ static Stream basicStatementStream() throws SQLException { "SELECT ? FROM INFORMATION_SCHEMA.SYSTEM_USERS", "SELECT INFORMATION_SCHEMA.SYSTEM_USERS", "hsqldb:mem:", - "INFORMATION_SCHEMA.SYSTEM_USERS"), + "INFORMATION_SCHEMA.SYSTEM_USERS", + Collections.singletonMap("0", "3")), Arguments.of( "h2", cpDatasources.get("tomcat").get("h2").getConnection(), @@ -272,7 +282,8 @@ static Stream basicStatementStream() throws SQLException { "SELECT ?", "SELECT " + dbNameLower, "h2:mem:", - null), + null, + Collections.singletonMap("0", "3")), Arguments.of( "derby", cpDatasources.get("tomcat").get("derby").getConnection(), @@ -281,7 +292,8 @@ static Stream basicStatementStream() throws SQLException { "SELECT ? FROM SYSIBM.SYSDUMMY1", "SELECT SYSIBM.SYSDUMMY1", "derby:memory:", - "SYSIBM.SYSDUMMY1"), + "SYSIBM.SYSDUMMY1", + Collections.singletonMap("0", "3")), Arguments.of( "hsqldb", cpDatasources.get("tomcat").get("hsqldb").getConnection(), @@ -290,7 +302,8 @@ static Stream basicStatementStream() throws SQLException { "SELECT ? FROM INFORMATION_SCHEMA.SYSTEM_USERS", "SELECT INFORMATION_SCHEMA.SYSTEM_USERS", "hsqldb:mem:", - "INFORMATION_SCHEMA.SYSTEM_USERS"), + "INFORMATION_SCHEMA.SYSTEM_USERS", + Collections.singletonMap("0", "3")), Arguments.of( "h2", cpDatasources.get("hikari").get("h2").getConnection(), @@ -299,7 +312,8 @@ static Stream basicStatementStream() throws SQLException { "SELECT ?", "SELECT " + dbNameLower, "h2:mem:", - null), + null, + Collections.singletonMap("0", "3")), Arguments.of( "derby", cpDatasources.get("hikari").get("derby").getConnection(), @@ -308,7 +322,8 @@ static Stream basicStatementStream() throws SQLException { "SELECT ? FROM SYSIBM.SYSDUMMY1", "SELECT SYSIBM.SYSDUMMY1", "derby:memory:", - "SYSIBM.SYSDUMMY1"), + "SYSIBM.SYSDUMMY1", + Collections.singletonMap("0", "3")), Arguments.of( "hsqldb", cpDatasources.get("hikari").get("hsqldb").getConnection(), @@ -317,7 +332,8 @@ static Stream basicStatementStream() throws SQLException { "SELECT ? FROM INFORMATION_SCHEMA.SYSTEM_USERS", "SELECT INFORMATION_SCHEMA.SYSTEM_USERS", "hsqldb:mem:", - "INFORMATION_SCHEMA.SYSTEM_USERS"), + "INFORMATION_SCHEMA.SYSTEM_USERS", + Collections.singletonMap("0", "3")), Arguments.of( "h2", cpDatasources.get("c3p0").get("h2").getConnection(), @@ -326,7 +342,8 @@ static Stream basicStatementStream() throws SQLException { "SELECT ?", "SELECT " + dbNameLower, "h2:mem:", - null), + null, + Collections.singletonMap("0", "3")), Arguments.of( "derby", cpDatasources.get("c3p0").get("derby").getConnection(), @@ -335,7 +352,8 @@ static Stream basicStatementStream() throws SQLException { "SELECT ? FROM SYSIBM.SYSDUMMY1", "SELECT SYSIBM.SYSDUMMY1", "derby:memory:", - "SYSIBM.SYSDUMMY1"), + "SYSIBM.SYSDUMMY1", + Collections.singletonMap("0", "3")), Arguments.of( "hsqldb", cpDatasources.get("c3p0").get("hsqldb").getConnection(), @@ -344,7 +362,8 @@ static Stream basicStatementStream() throws SQLException { "SELECT ? FROM INFORMATION_SCHEMA.SYSTEM_USERS", "SELECT INFORMATION_SCHEMA.SYSTEM_USERS", "hsqldb:mem:", - "INFORMATION_SCHEMA.SYSTEM_USERS")); + "INFORMATION_SCHEMA.SYSTEM_USERS", + Collections.singletonMap("0", "3"))); } @ParameterizedTest @@ -357,7 +376,8 @@ public void testBasicStatement( String sanitizedQuery, String spanName, String url, - String table) + String table, + Map parameters) throws SQLException { Statement statement = connection.createStatement(); cleanup.deferCleanup(statement); @@ -366,6 +386,24 @@ public void testBasicStatement( resultSet.next(); assertThat(resultSet.getInt(1)).isEqualTo(3); + List baseAssertions = + parameters.entrySet().stream() + .map( + entry -> + equalTo( + DB_OPERATION_PARAMETER.getAttributeKey(entry.getKey()), entry.getValue())) + .collect(Collectors.toList()); + + baseAssertions.addAll( + asList( + 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), sanitizedQuery), + equalTo(maybeStable(DB_OPERATION), "SELECT"), + equalTo(maybeStable(DB_SQL_TABLE), table))); + testing.waitAndAssertTraces( trace -> trace.hasSpansSatisfyingExactly( @@ -374,14 +412,7 @@ public void testBasicStatement( span.hasName(spanName) .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), sanitizedQuery), - equalTo(maybeStable(DB_OPERATION), "SELECT"), - equalTo(maybeStable(DB_SQL_TABLE), table)))); + .hasAttributesSatisfyingExactly(baseAssertions))); if (table != null) { assertDurationMetric( @@ -397,80 +428,95 @@ public void testBasicStatement( } } + private static Map parametersMapBuilder(String k) { + HashMap map = new HashMap() {}; + map.put("0", "3"); + map.put(k, "'Y'"); + return map; + } + static Stream preparedStatementStream() throws SQLException { return Stream.of( Arguments.of( "h2", new org.h2.Driver().connect(jdbcUrls.get("h2"), null), null, - "SELECT 3", - "SELECT ?", + "SELECT 3, $1", + "SELECT ?, $1", "SELECT " + dbNameLower, "h2:mem:", - null), + null, + parametersMapBuilder("$1")), Arguments.of( "derby", new EmbeddedDriver().connect(jdbcUrls.get("derby"), null), "APP", - "SELECT 3 FROM SYSIBM.SYSDUMMY1", - "SELECT ? FROM SYSIBM.SYSDUMMY1", + "SELECT 3 FROM SYSIBM.SYSDUMMY1 WHERE IBMREQD=?", + "SELECT ? FROM SYSIBM.SYSDUMMY1 WHERE IBMREQD=?", "SELECT SYSIBM.SYSDUMMY1", "derby:memory:", - "SYSIBM.SYSDUMMY1"), + "SYSIBM.SYSDUMMY1", + parametersMapBuilder("1")), Arguments.of( "h2", cpDatasources.get("tomcat").get("h2").getConnection(), null, - "SELECT 3", - "SELECT ?", + "SELECT 3, $1", + "SELECT ?, $1", "SELECT " + dbNameLower, "h2:mem:", - null), + null, + parametersMapBuilder("$1")), Arguments.of( "derby", cpDatasources.get("tomcat").get("derby").getConnection(), "APP", - "SELECT 3 FROM SYSIBM.SYSDUMMY1", - "SELECT ? FROM SYSIBM.SYSDUMMY1", + "SELECT 3 FROM SYSIBM.SYSDUMMY1 WHERE IBMREQD=?", + "SELECT ? FROM SYSIBM.SYSDUMMY1 WHERE IBMREQD=?", "SELECT SYSIBM.SYSDUMMY1", "derby:memory:", - "SYSIBM.SYSDUMMY1"), + "SYSIBM.SYSDUMMY1", + parametersMapBuilder("1")), Arguments.of( "h2", cpDatasources.get("hikari").get("h2").getConnection(), null, - "SELECT 3", - "SELECT ?", + "SELECT 3, $1", + "SELECT ?, $1", "SELECT " + dbNameLower, "h2:mem:", - null), + null, + parametersMapBuilder("$1")), Arguments.of( "derby", cpDatasources.get("hikari").get("derby").getConnection(), "APP", - "SELECT 3 FROM SYSIBM.SYSDUMMY1", - "SELECT ? FROM SYSIBM.SYSDUMMY1", + "SELECT 3 FROM SYSIBM.SYSDUMMY1 WHERE IBMREQD=?", + "SELECT ? FROM SYSIBM.SYSDUMMY1 WHERE IBMREQD=?", "SELECT SYSIBM.SYSDUMMY1", "derby:memory:", - "SYSIBM.SYSDUMMY1"), + "SYSIBM.SYSDUMMY1", + parametersMapBuilder("1")), Arguments.of( "h2", cpDatasources.get("c3p0").get("h2").getConnection(), null, - "SELECT 3", - "SELECT ?", + "SELECT 3, $1", + "SELECT ?, $1", "SELECT " + dbNameLower, "h2:mem:", - null), + null, + parametersMapBuilder("$1")), Arguments.of( "derby", cpDatasources.get("c3p0").get("derby").getConnection(), "APP", - "SELECT 3 FROM SYSIBM.SYSDUMMY1", - "SELECT ? FROM SYSIBM.SYSDUMMY1", + "SELECT 3 FROM SYSIBM.SYSDUMMY1 WHERE IBMREQD=?", + "SELECT ? FROM SYSIBM.SYSDUMMY1 WHERE IBMREQD=?", "SELECT SYSIBM.SYSDUMMY1", "derby:memory:", - "SYSIBM.SYSDUMMY1")); + "SYSIBM.SYSDUMMY1", + parametersMapBuilder("1"))); } @ParameterizedTest @@ -483,7 +529,8 @@ void testPreparedStatementExecute( String sanitizedQuery, String spanName, String url, - String table) + String table, + Map parameters) throws SQLException { PreparedStatement statement = connection.prepareStatement(query); cleanup.deferCleanup(statement); @@ -491,6 +538,7 @@ void testPreparedStatementExecute( testing.runWithSpan( "parent", () -> { + statement.setString(1, "Y"); statement.execute(); return statement.getResultSet(); }); @@ -498,6 +546,24 @@ void testPreparedStatementExecute( resultSet.next(); assertThat(resultSet.getInt(1)).isEqualTo(3); + List baseAssertions = + parameters.entrySet().stream() + .map( + entry -> + equalTo( + DB_OPERATION_PARAMETER.getAttributeKey(entry.getKey()), entry.getValue())) + .collect(Collectors.toList()); + + baseAssertions.addAll( + asList( + 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), sanitizedQuery), + equalTo(maybeStable(DB_OPERATION), "SELECT"), + equalTo(maybeStable(DB_SQL_TABLE), table))); + testing.waitAndAssertTraces( trace -> trace.hasSpansSatisfyingExactly( @@ -506,14 +572,7 @@ void testPreparedStatementExecute( span.hasName(spanName) .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), sanitizedQuery), - equalTo(maybeStable(DB_OPERATION), "SELECT"), - equalTo(maybeStable(DB_SQL_TABLE), table)))); + .hasAttributesSatisfyingExactly(baseAssertions))); } @ParameterizedTest @@ -526,15 +585,40 @@ void testPreparedStatementQuery( String sanitizedQuery, String spanName, String url, - String table) + String table, + Map parameters) throws SQLException { PreparedStatement statement = connection.prepareStatement(query); cleanup.deferCleanup(statement); - ResultSet resultSet = testing.runWithSpan("parent", () -> statement.executeQuery()); + ResultSet resultSet = + testing.runWithSpan( + "parent", + () -> { + statement.setString(1, "Y"); + return statement.executeQuery(); + }); resultSet.next(); assertThat(resultSet.getInt(1)).isEqualTo(3); + List baseAssertions = + parameters.entrySet().stream() + .map( + entry -> + equalTo( + DB_OPERATION_PARAMETER.getAttributeKey(entry.getKey()), entry.getValue())) + .collect(Collectors.toList()); + + baseAssertions.addAll( + asList( + 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), sanitizedQuery), + equalTo(maybeStable(DB_OPERATION), "SELECT"), + equalTo(maybeStable(DB_SQL_TABLE), table))); + testing.waitAndAssertTraces( trace -> trace.hasSpansSatisfyingExactly( @@ -543,14 +627,7 @@ void testPreparedStatementQuery( span.hasName(spanName) .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), sanitizedQuery), - equalTo(maybeStable(DB_OPERATION), "SELECT"), - equalTo(maybeStable(DB_SQL_TABLE), table)))); + .hasAttributesSatisfyingExactly(baseAssertions))); } @ParameterizedTest @@ -563,15 +640,40 @@ void testPreparedCall( String sanitizedQuery, String spanName, String url, - String table) + String table, + Map parameters) throws SQLException { CallableStatement statement = connection.prepareCall(query); cleanup.deferCleanup(statement); - ResultSet resultSet = testing.runWithSpan("parent", () -> statement.executeQuery()); + ResultSet resultSet = + testing.runWithSpan( + "parent", + () -> { + statement.setString(1, "Y"); + return statement.executeQuery(); + }); resultSet.next(); assertThat(resultSet.getInt(1)).isEqualTo(3); + List baseAssertions = + parameters.entrySet().stream() + .map( + entry -> + equalTo( + DB_OPERATION_PARAMETER.getAttributeKey(entry.getKey()), entry.getValue())) + .collect(Collectors.toList()); + + baseAssertions.addAll( + asList( + 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), sanitizedQuery), + equalTo(maybeStable(DB_OPERATION), "SELECT"), + equalTo(maybeStable(DB_SQL_TABLE), table))); + testing.waitAndAssertTraces( trace -> trace.hasSpansSatisfyingExactly( @@ -580,14 +682,7 @@ void testPreparedCall( span.hasName(spanName) .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), sanitizedQuery), - equalTo(maybeStable(DB_OPERATION), "SELECT"), - equalTo(maybeStable(DB_SQL_TABLE), table)))); + .hasAttributesSatisfyingExactly(baseAssertions))); } static Stream statementUpdateStream() throws SQLException { @@ -911,7 +1006,8 @@ static Stream connectionConstructorStream() { "SELECT ?;", "SELECT " + dbNameLower, "h2:mem:", - null), + null, + Collections.singletonMap("0", "3")), Arguments.of( true, "derby", @@ -922,7 +1018,8 @@ static Stream connectionConstructorStream() { "SELECT ? FROM SYSIBM.SYSDUMMY1", "SELECT SYSIBM.SYSDUMMY1", "derby:memory:", - "SYSIBM.SYSDUMMY1"), + "SYSIBM.SYSDUMMY1", + Collections.singletonMap("0", "3")), Arguments.of( false, "h2", @@ -933,7 +1030,8 @@ static Stream connectionConstructorStream() { "SELECT ?;", "SELECT " + dbNameLower, "h2:mem:", - null), + null, + Collections.singletonMap("0", "3")), Arguments.of( false, "derby", @@ -944,7 +1042,8 @@ static Stream connectionConstructorStream() { "SELECT ? FROM SYSIBM.SYSDUMMY1", "SELECT SYSIBM.SYSDUMMY1", "derby:memory:", - "SYSIBM.SYSDUMMY1")); + "SYSIBM.SYSDUMMY1", + Collections.singletonMap("0", "3"))); } @SuppressWarnings("CatchingUnchecked") @@ -960,7 +1059,8 @@ void testConnectionConstructorThrowing( String sanitizedQuery, String spanName, String url, - String table) + String table, + Map parameters) throws SQLException { Connection connection = null; @@ -988,6 +1088,25 @@ void testConnectionConstructorThrowing( rs.next(); assertThat(rs.getInt(1)).isEqualTo(3); + + List baseAssertions = + parameters.entrySet().stream() + .map( + entry -> + equalTo( + DB_OPERATION_PARAMETER.getAttributeKey(entry.getKey()), entry.getValue())) + .collect(Collectors.toList()); + + baseAssertions.addAll( + asList( + 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), sanitizedQuery), + equalTo(maybeStable(DB_OPERATION), "SELECT"), + equalTo(maybeStable(DB_SQL_TABLE), table))); + testing.waitAndAssertTraces( trace -> trace.hasSpansSatisfyingExactly( @@ -996,14 +1115,7 @@ void testConnectionConstructorThrowing( span.hasName(spanName) .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), sanitizedQuery), - equalTo(maybeStable(DB_OPERATION), "SELECT"), - equalTo(maybeStable(DB_SQL_TABLE), table)))); + .hasAttributesSatisfyingExactly(baseAssertions))); } static Stream getConnectionStream() { @@ -1127,7 +1239,8 @@ void testGetClientInfoException(String query) throws SQLException { equalTo( DB_CONNECTION_STRING, emitStableDatabaseSemconv() ? null : "testdb://localhost"), - equalTo(SERVER_ADDRESS, "localhost")))); + equalTo(SERVER_ADDRESS, "localhost"), + equalTo(DB_OPERATION_PARAMETER.getAttributeKey("0"), "123")))); } static Stream spanNameStream() { @@ -1139,7 +1252,8 @@ static Stream spanNameStream() { "SELECT test.table", "test", "SELECT", - "table"), + "table", + Collections.emptyMap()), Arguments.of( "jdbc:testdb://localhost?databaseName=test", "SELECT 42", @@ -1147,7 +1261,8 @@ static Stream spanNameStream() { "SELECT test", "test", "SELECT", - null), + null, + Collections.singletonMap("0", "42")), Arguments.of( "jdbc:testdb://localhost", "SELECT * FROM table", @@ -1155,7 +1270,8 @@ static Stream spanNameStream() { "SELECT table", null, "SELECT", - "table"), + "table", + Collections.emptyMap()), Arguments.of( "jdbc:testdb://localhost?databaseName=test", "CREATE TABLE table", @@ -1163,7 +1279,8 @@ static Stream spanNameStream() { "CREATE TABLE test.table", "test", "CREATE TABLE", - "table"), + "table", + Collections.emptyMap()), Arguments.of( "jdbc:testdb://localhost", "CREATE TABLE table", @@ -1171,7 +1288,8 @@ static Stream spanNameStream() { "CREATE TABLE table", null, "CREATE TABLE", - "table")); + "table", + Collections.emptyMap())); } @ParameterizedTest @@ -1183,7 +1301,8 @@ void testProduceProperSpanName( String spanName, String databaseName, String operation, - String table) + String table, + Map parameters) throws SQLException { Driver driver = new TestDriver(); Connection connection = driver.connect(url, null); @@ -1196,6 +1315,25 @@ void testProduceProperSpanName( statement.executeQuery(query); }); + List baseAssertions = + parameters.entrySet().stream() + .map( + entry -> + equalTo( + DB_OPERATION_PARAMETER.getAttributeKey(entry.getKey()), entry.getValue())) + .collect(Collectors.toList()); + + baseAssertions.addAll( + asList( + equalTo(maybeStable(DB_SYSTEM), "other_sql"), + equalTo(maybeStable(DB_NAME), databaseName), + equalTo( + DB_CONNECTION_STRING, emitStableDatabaseSemconv() ? null : "testdb://localhost"), + equalTo(maybeStable(DB_STATEMENT), sanitizedQuery), + equalTo(maybeStable(DB_OPERATION), operation), + equalTo(maybeStable(DB_SQL_TABLE), table), + equalTo(SERVER_ADDRESS, "localhost"))); + testing.waitAndAssertTraces( trace -> trace.hasSpansSatisfyingExactly( @@ -1204,16 +1342,7 @@ void testProduceProperSpanName( span.hasName(spanName) .hasKind(SpanKind.CLIENT) .hasParent(trace.getSpan(0)) - .hasAttributesSatisfyingExactly( - equalTo(maybeStable(DB_SYSTEM), "other_sql"), - equalTo(maybeStable(DB_NAME), databaseName), - equalTo( - DB_CONNECTION_STRING, - emitStableDatabaseSemconv() ? null : "testdb://localhost"), - equalTo(maybeStable(DB_STATEMENT), sanitizedQuery), - equalTo(maybeStable(DB_OPERATION), operation), - equalTo(maybeStable(DB_SQL_TABLE), table), - equalTo(SERVER_ADDRESS, "localhost")))); + .hasAttributesSatisfyingExactly(baseAssertions))); } @ParameterizedTest @@ -1265,7 +1394,8 @@ void testConnectionCached(String connectionPoolName) throws SQLException { maybeStable(DB_STATEMENT), "SELECT ? FROM INFORMATION_SCHEMA.SYSTEM_USERS"), equalTo(maybeStable(DB_OPERATION), "SELECT"), - equalTo(maybeStable(DB_SQL_TABLE), "INFORMATION_SCHEMA.SYSTEM_USERS"))); + equalTo(maybeStable(DB_SQL_TABLE), "INFORMATION_SCHEMA.SYSTEM_USERS"), + equalTo(DB_OPERATION_PARAMETER.getAttributeKey("0"), "3"))); for (int i = 0; i < numQueries; i++) { assertions.add(traceAssertConsumer); } @@ -1577,7 +1707,8 @@ void testSingleItemBatch(String system, Connection connection, String username, maybeStable(DB_STATEMENT), "INSERT INTO " + tableName + " VALUES(?)"), equalTo(maybeStable(DB_OPERATION), "INSERT"), - equalTo(maybeStable(DB_SQL_TABLE), tableName)))); + equalTo(maybeStable(DB_SQL_TABLE), tableName), + equalTo(DB_OPERATION_PARAMETER.getAttributeKey("0"), "1")))); } @ParameterizedTest 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 index 825b29547334..6525dcf75dbb 100644 --- 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 @@ -16,6 +16,7 @@ public final class JdbcTelemetryBuilder { private boolean dataSourceInstrumenterEnabled = true; private boolean statementInstrumenterEnabled = true; private boolean statementSanitizationEnabled = true; + private boolean operationParameterEnabled = false; JdbcTelemetryBuilder(OpenTelemetry openTelemetry) { this.openTelemetry = openTelemetry; @@ -42,12 +43,21 @@ public JdbcTelemetryBuilder setStatementSanitizationEnabled(boolean enabled) { return this; } + @CanIgnoreReturnValue + public JdbcTelemetryBuilder setOperationParameterEnabled(boolean enabled) { + this.operationParameterEnabled = 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)); + openTelemetry, + statementInstrumenterEnabled, + statementSanitizationEnabled, + operationParameterEnabled)); } } diff --git a/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/DbRequest.java b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/DbRequest.java index a5e8e0559178..bc34753cd8ed 100644 --- a/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/DbRequest.java +++ b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/DbRequest.java @@ -15,6 +15,7 @@ import java.sql.Statement; import java.util.Collection; import java.util.Collections; +import java.util.Map; import javax.annotation.Nullable; /** @@ -25,45 +26,67 @@ public abstract class DbRequest { @Nullable - public static DbRequest create(PreparedStatement statement) { - return create(statement, JdbcData.preparedStatement.get(statement)); + public static DbRequest create( + PreparedStatement statement, Map preparedStatementParameters) { + return create( + statement, JdbcData.preparedStatement.get(statement), preparedStatementParameters); } @Nullable - public static DbRequest create(Statement statement, String dbStatementString) { - return create(statement, dbStatementString, null); + public static DbRequest create( + Statement statement, + String dbStatementString, + Map preparedStatementParameters) { + return create(statement, dbStatementString, null, preparedStatementParameters); } @Nullable - public static DbRequest create(Statement statement, String dbStatementString, Long batchSize) { + public static DbRequest create( + Statement statement, + String dbStatementString, + Long batchSize, + Map preparedStatementParameters) { Connection connection = connectionFromStatement(statement); if (connection == null) { return null; } - return create(extractDbInfo(connection), dbStatementString, batchSize); + return create( + extractDbInfo(connection), dbStatementString, batchSize, preparedStatementParameters); } public static DbRequest create( - Statement statement, Collection queryTexts, Long batchSize) { + Statement statement, + Collection queryTexts, + Long batchSize, + Map preparedStatementParameters) { Connection connection = connectionFromStatement(statement); if (connection == null) { return null; } - return create(extractDbInfo(connection), queryTexts, batchSize); + return create(extractDbInfo(connection), queryTexts, batchSize, preparedStatementParameters); } public static DbRequest create(DbInfo dbInfo, String queryText) { - return create(dbInfo, queryText, null); + return create(dbInfo, queryText, null, null); } - public static DbRequest create(DbInfo dbInfo, String queryText, Long batchSize) { - return create(dbInfo, Collections.singletonList(queryText), batchSize); + public static DbRequest create( + DbInfo dbInfo, + String queryText, + Long batchSize, + Map preparedStatementParameters) { + return create( + dbInfo, Collections.singletonList(queryText), batchSize, preparedStatementParameters); } - public static DbRequest create(DbInfo dbInfo, Collection queryTexts, Long batchSize) { - return new AutoValue_DbRequest(dbInfo, queryTexts, batchSize); + public static DbRequest create( + DbInfo dbInfo, + Collection queryTexts, + Long batchSize, + Map preparedStatementParameters) { + return new AutoValue_DbRequest(dbInfo, queryTexts, batchSize, preparedStatementParameters); } public abstract DbInfo getDbInfo(); @@ -72,4 +95,7 @@ public static DbRequest create(DbInfo dbInfo, Collection queryTexts, Lon @Nullable public abstract Long getBatchSize(); + + @Nullable + public abstract Map getPreparedStatementParameters(); } diff --git a/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/JdbcAttributesGetter.java b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/JdbcAttributesGetter.java index 74cd1c52d51a..c77831827514 100644 --- a/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/JdbcAttributesGetter.java +++ b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/JdbcAttributesGetter.java @@ -9,6 +9,7 @@ import io.opentelemetry.instrumentation.jdbc.internal.dbinfo.DbInfo; import java.sql.SQLException; import java.util.Collection; +import java.util.Map; import javax.annotation.Nullable; /** @@ -62,4 +63,10 @@ public String getResponseStatus(@Nullable Void response, @Nullable Throwable err } return null; } + + @Nullable + @Override + public Map getOperationParameters(DbRequest request) { + return request.getPreparedStatementParameters(); + } } diff --git a/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/JdbcData.java b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/JdbcData.java index 08393a02df72..0719c87af171 100644 --- a/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/JdbcData.java +++ b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/JdbcData.java @@ -13,6 +13,7 @@ import java.sql.Statement; import java.util.ArrayList; import java.util.Collection; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.WeakHashMap; @@ -35,6 +36,8 @@ public final class JdbcData { private static final VirtualField preparedStatementBatch = VirtualField.find(PreparedStatement.class, PreparedStatementBatchInfo.class); + public static final VirtualField> parameters = + VirtualField.find(PreparedStatement.class, Map.class); private JdbcData() {} @@ -95,6 +98,15 @@ public static Long getPreparedStatementBatchSize(PreparedStatement statement) { return batchInfo != null ? batchInfo.getBatchSize() : null; } + public static void addParameter(PreparedStatement statement, int index, Object value) { + Map parametersMap = parameters.get(statement); + if (parametersMap == null) { + parametersMap = new HashMap<>(); + parameters.set(statement, parametersMap); + } + parametersMap.put(index, value); + } + /** * This class is internal and is hence not for public use. Its APIs are unstable and can change at * any time. 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 861e1dd11759..cb30827d5c80 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 @@ -39,11 +39,17 @@ public static Instrumenter createStatementInstrumenter( openTelemetry, true, ConfigPropertiesUtil.getBoolean( - "otel.instrumentation.common.db-statement-sanitizer.enabled", true)); + "otel.instrumentation.common.db-statement-sanitizer.enabled", true), + // TODO change with common conf key + ConfigPropertiesUtil.getBoolean( + "otel.instrumentation.jdbc.operation-parameter.enabled", true)); } public static Instrumenter createStatementInstrumenter( - OpenTelemetry openTelemetry, boolean enabled, boolean statementSanitizationEnabled) { + OpenTelemetry openTelemetry, + boolean enabled, + boolean statementSanitizationEnabled, + boolean operationParameterEnabled) { return Instrumenter.builder( openTelemetry, INSTRUMENTATION_NAME, @@ -51,6 +57,7 @@ public static Instrumenter createStatementInstrumenter( .addAttributesExtractor( SqlClientAttributesExtractor.builder(dbAttributesGetter) .setStatementSanitizationEnabled(statementSanitizationEnabled) + .setOperationParameterEnabled(operationParameterEnabled) .build()) .addAttributesExtractor(ServerAttributesExtractor.create(netAttributesGetter)) .addOperationMetrics(DbClientMetrics.get()) diff --git a/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/OpenTelemetryPreparedStatement.java b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/OpenTelemetryPreparedStatement.java index 8cd65828887a..2d97500f4c86 100644 --- a/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/OpenTelemetryPreparedStatement.java +++ b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/OpenTelemetryPreparedStatement.java @@ -43,10 +43,13 @@ import java.sql.Time; import java.sql.Timestamp; import java.util.Calendar; +import java.util.HashMap; +import java.util.Map; @SuppressWarnings("OverloadMethodsDeclarationOrder") class OpenTelemetryPreparedStatement extends OpenTelemetryStatement implements PreparedStatement { + private final Map parameters; public OpenTelemetryPreparedStatement( S delegate, @@ -55,6 +58,7 @@ public OpenTelemetryPreparedStatement( String query, Instrumenter instrumenter) { super(delegate, connection, dbInfo, query, instrumenter); + this.parameters = new HashMap<>(); } @Override @@ -76,157 +80,185 @@ public boolean execute() throws SQLException { @Override public void setNull(int parameterIndex, int sqlType) throws SQLException { delegate.setNull(parameterIndex, sqlType); + parameters.put(parameterIndex, null); } @SuppressWarnings("UngroupedOverloads") @Override public void setNull(int parameterIndex, int sqlType, String typeName) throws SQLException { delegate.setNull(parameterIndex, sqlType, typeName); + parameters.put(parameterIndex, null); } @Override public void setBoolean(int parameterIndex, boolean x) throws SQLException { delegate.setBoolean(parameterIndex, x); + parameters.put(parameterIndex, x); } @Override public void setByte(int parameterIndex, byte x) throws SQLException { delegate.setByte(parameterIndex, x); + parameters.put(parameterIndex, x); } @Override public void setShort(int parameterIndex, short x) throws SQLException { delegate.setShort(parameterIndex, x); + parameters.put(parameterIndex, x); } @Override public void setInt(int parameterIndex, int x) throws SQLException { delegate.setInt(parameterIndex, x); + parameters.put(parameterIndex, x); } @Override public void setLong(int parameterIndex, long x) throws SQLException { delegate.setLong(parameterIndex, x); + parameters.put(parameterIndex, x); } @Override public void setFloat(int parameterIndex, float x) throws SQLException { delegate.setFloat(parameterIndex, x); + parameters.put(parameterIndex, x); } @Override public void setDouble(int parameterIndex, double x) throws SQLException { delegate.setDouble(parameterIndex, x); + parameters.put(parameterIndex, x); } @Override public void setBigDecimal(int parameterIndex, BigDecimal x) throws SQLException { delegate.setBigDecimal(parameterIndex, x); + parameters.put(parameterIndex, x); } @Override public void setString(int parameterIndex, String x) throws SQLException { delegate.setString(parameterIndex, x); + parameters.put(parameterIndex, x); } @Override public void setBytes(int parameterIndex, byte[] x) throws SQLException { delegate.setBytes(parameterIndex, x); + parameters.put(parameterIndex, x); } @SuppressWarnings("UngroupedOverloads") @Override public void setDate(int parameterIndex, Date x) throws SQLException { delegate.setDate(parameterIndex, x); + parameters.put(parameterIndex, x); } @SuppressWarnings("UngroupedOverloads") @Override public void setDate(int parameterIndex, Date x, Calendar cal) throws SQLException { delegate.setDate(parameterIndex, x, cal); + parameters.put(parameterIndex, x); } @SuppressWarnings("UngroupedOverloads") @Override public void setTime(int parameterIndex, Time x) throws SQLException { delegate.setTime(parameterIndex, x); + parameters.put(parameterIndex, x); } @Override public void setTime(int parameterIndex, Time x, Calendar cal) throws SQLException { delegate.setTime(parameterIndex, x, cal); + parameters.put(parameterIndex, x); } @SuppressWarnings("UngroupedOverloads") @Override public void setTimestamp(int parameterIndex, Timestamp x) throws SQLException { delegate.setTimestamp(parameterIndex, x); + parameters.put(parameterIndex, x); } @SuppressWarnings("UngroupedOverloads") @Override public void setTimestamp(int parameterIndex, Timestamp x, Calendar cal) throws SQLException { delegate.setTimestamp(parameterIndex, x, cal); + parameters.put(parameterIndex, x); } @SuppressWarnings("UngroupedOverloads") @Override public void setAsciiStream(int parameterIndex, InputStream x, int length) throws SQLException { delegate.setAsciiStream(parameterIndex, x, length); + parameters.put(parameterIndex, x); } @SuppressWarnings("UngroupedOverloads") @Override public void setAsciiStream(int parameterIndex, InputStream x) throws SQLException { delegate.setAsciiStream(parameterIndex, x); + parameters.put(parameterIndex, x); } @SuppressWarnings("UngroupedOverloads") @Override public void setAsciiStream(int parameterIndex, InputStream x, long length) throws SQLException { delegate.setAsciiStream(parameterIndex, x, length); + parameters.put(parameterIndex, x); } @Override @Deprecated public void setUnicodeStream(int parameterIndex, InputStream x, int length) throws SQLException { delegate.setUnicodeStream(parameterIndex, x, length); + parameters.put(parameterIndex, x); } @SuppressWarnings("UngroupedOverloads") @Override public void setBinaryStream(int parameterIndex, InputStream x, int length) throws SQLException { delegate.setBinaryStream(parameterIndex, x, length); + parameters.put(parameterIndex, x); } @SuppressWarnings("UngroupedOverloads") @Override public void setBinaryStream(int parameterIndex, InputStream x, long length) throws SQLException { delegate.setBinaryStream(parameterIndex, x, length); + parameters.put(parameterIndex, x); } @SuppressWarnings("UngroupedOverloads") @Override public void setBinaryStream(int parameterIndex, InputStream x) throws SQLException { delegate.setBinaryStream(parameterIndex, x); + parameters.put(parameterIndex, x); } @SuppressWarnings("UngroupedOverloads") @Override public void setObject(int parameterIndex, Object x, int targetSqlType) throws SQLException { delegate.setObject(parameterIndex, x, targetSqlType); + parameters.put(parameterIndex, x); } @SuppressWarnings("UngroupedOverloads") @Override public void setObject(int parameterIndex, Object x) throws SQLException { delegate.setObject(parameterIndex, x); + parameters.put(parameterIndex, x); } @Override public void setObject(int parameterIndex, Object x, int targetSqlType, int scaleOrLength) throws SQLException { delegate.setObject(parameterIndex, x, targetSqlType, scaleOrLength); + parameters.put(parameterIndex, x); } @Override @@ -240,11 +272,13 @@ public void addBatch() throws SQLException { public void setCharacterStream(int parameterIndex, Reader reader, int length) throws SQLException { delegate.setCharacterStream(parameterIndex, reader, length); + parameters.put(parameterIndex, reader); } @Override public void setCharacterStream(int parameterIndex, Reader reader) throws SQLException { delegate.setCharacterStream(parameterIndex, reader); + parameters.put(parameterIndex, reader); } @SuppressWarnings("UngroupedOverloads") @@ -252,51 +286,60 @@ public void setCharacterStream(int parameterIndex, Reader reader) throws SQLExce public void setCharacterStream(int parameterIndex, Reader reader, long length) throws SQLException { delegate.setCharacterStream(parameterIndex, reader, length); + parameters.put(parameterIndex, reader); } @Override public void setRef(int parameterIndex, Ref x) throws SQLException { delegate.setRef(parameterIndex, x); + parameters.put(parameterIndex, x); } @Override public void setBlob(int parameterIndex, Blob x) throws SQLException { delegate.setBlob(parameterIndex, x); + parameters.put(parameterIndex, x); } @SuppressWarnings("UngroupedOverloads") @Override public void setBlob(int parameterIndex, InputStream inputStream) throws SQLException { delegate.setBlob(parameterIndex, inputStream); + parameters.put(parameterIndex, inputStream); } @Override public void setBlob(int parameterIndex, InputStream inputStream, long length) throws SQLException { delegate.setBlob(parameterIndex, inputStream, length); + parameters.put(parameterIndex, inputStream); } @SuppressWarnings("UngroupedOverloads") @Override public void setClob(int parameterIndex, Clob x) throws SQLException { delegate.setClob(parameterIndex, x); + parameters.put(parameterIndex, x); } @SuppressWarnings("UngroupedOverloads") @Override public void setClob(int parameterIndex, Reader reader) throws SQLException { delegate.setClob(parameterIndex, reader); + parameters.put(parameterIndex, reader); } @SuppressWarnings("UngroupedOverloads") @Override public void setClob(int parameterIndex, Reader reader, long length) throws SQLException { delegate.setClob(parameterIndex, reader, length); + parameters.put(parameterIndex, reader); } @Override public void setArray(int parameterIndex, Array x) throws SQLException { delegate.setArray(parameterIndex, x); + parameters.put(parameterIndex, x); } @Override @@ -307,6 +350,7 @@ public ResultSetMetaData getMetaData() throws SQLException { @Override public void setURL(int parameterIndex, URL x) throws SQLException { delegate.setURL(parameterIndex, x); + parameters.put(parameterIndex, x); } @Override @@ -317,11 +361,13 @@ public ParameterMetaData getParameterMetaData() throws SQLException { @Override public void setRowId(int parameterIndex, RowId x) throws SQLException { delegate.setRowId(parameterIndex, x); + parameters.put(parameterIndex, x); } @Override public void setNString(int parameterIndex, String value) throws SQLException { delegate.setNString(parameterIndex, value); + parameters.put(parameterIndex, value); } @SuppressWarnings("UngroupedOverloads") @@ -329,38 +375,45 @@ public void setNString(int parameterIndex, String value) throws SQLException { public void setNCharacterStream(int parameterIndex, Reader value, long length) throws SQLException { delegate.setNCharacterStream(parameterIndex, value, length); + parameters.put(parameterIndex, value); } @SuppressWarnings("UngroupedOverloads") @Override public void setNCharacterStream(int parameterIndex, Reader value) throws SQLException { delegate.setNCharacterStream(parameterIndex, value); + parameters.put(parameterIndex, value); } @Override public void setNClob(int parameterIndex, NClob value) throws SQLException { delegate.setNClob(parameterIndex, value); + parameters.put(parameterIndex, value); } @Override public void setNClob(int parameterIndex, Reader reader, long length) throws SQLException { delegate.setNClob(parameterIndex, reader, length); + parameters.put(parameterIndex, reader); } @SuppressWarnings("UngroupedOverloads") @Override public void setNClob(int parameterIndex, Reader reader) throws SQLException { delegate.setNClob(parameterIndex, reader); + parameters.put(parameterIndex, reader); } @Override public void setSQLXML(int parameterIndex, SQLXML xmlObject) throws SQLException { delegate.setSQLXML(parameterIndex, xmlObject); + parameters.put(parameterIndex, xmlObject); } @Override public void clearParameters() throws SQLException { delegate.clearParameters(); + parameters.clear(); } @Override @@ -368,8 +421,15 @@ public int[] executeBatch() throws SQLException { return wrapBatchCall(delegate::executeBatch); } + @Override + protected T wrapCall(String sql, ThrowingSupplier callable) + throws E { + DbRequest request = DbRequest.create(dbInfo, sql, null, parameters); + return wrapCall(request, callable); + } + private T wrapBatchCall(ThrowingSupplier callable) throws E { - DbRequest request = DbRequest.create(dbInfo, query, batchSize); + DbRequest request = DbRequest.create(dbInfo, query, batchSize, parameters); return wrapCall(request, callable); } @@ -378,11 +438,13 @@ private T wrapBatchCall(ThrowingSupplier callable @Override public void setObject(int parameterIndex, Object x, SQLType targetSqlType, int scaleOrLength) throws SQLException { + parameters.put(parameterIndex, x); delegate.setObject(parameterIndex, x, targetSqlType, scaleOrLength); } @Override public void setObject(int parameterIndex, Object x, SQLType targetSqlType) throws SQLException { + parameters.put(parameterIndex, x); delegate.setObject(parameterIndex, x, targetSqlType); } diff --git a/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/OpenTelemetryStatement.java b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/OpenTelemetryStatement.java index bd3558016d30..6de399364aa7 100644 --- a/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/OpenTelemetryStatement.java +++ b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/OpenTelemetryStatement.java @@ -384,7 +384,7 @@ protected T wrapCall(DbRequest request, ThrowingSupplie } private T wrapBatchCall(ThrowingSupplier callable) throws E { - DbRequest request = DbRequest.create(dbInfo, batchCommands, batchSize); + DbRequest request = DbRequest.create(dbInfo, batchCommands, batchSize, null); return wrapCall(request, callable); } } diff --git a/instrumentation/jdbc/library/src/test/java/io/opentelemetry/instrumentation/jdbc/internal/OpenTelemetryConnectionTest.java b/instrumentation/jdbc/library/src/test/java/io/opentelemetry/instrumentation/jdbc/internal/OpenTelemetryConnectionTest.java index a5d5eeea5ff6..99fd0af79bd6 100644 --- a/instrumentation/jdbc/library/src/test/java/io/opentelemetry/instrumentation/jdbc/internal/OpenTelemetryConnectionTest.java +++ b/instrumentation/jdbc/library/src/test/java/io/opentelemetry/instrumentation/jdbc/internal/OpenTelemetryConnectionTest.java @@ -15,6 +15,7 @@ import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_CONNECTION_STRING; import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_NAME; import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_OPERATION; +import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_OPERATION_PARAMETER; import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_SQL_TABLE; import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_STATEMENT; import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_SYSTEM; @@ -29,10 +30,15 @@ import io.opentelemetry.instrumentation.jdbc.internal.dbinfo.DbInfo; import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension; +import io.opentelemetry.sdk.testing.assertj.AttributeAssertion; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; @@ -201,6 +207,38 @@ void testVerifyPrepareCallReturnsOtelWrapper() throws Exception { connection.close(); } + @Test + void testVerifyPrepareStatementParameters() throws SQLException { + Instrumenter instrumenter = + createStatementInstrumenter(testing.getOpenTelemetry()); + DbInfo dbInfo = getDbInfo(); + OpenTelemetryConnection connection = + new OpenTelemetryConnection(new TestConnection(), dbInfo, instrumenter); + String query = "SELECT * FROM users WHERE id=? AND name=$2 AND age=3"; + String sanitized = "SELECT * FROM users WHERE id=? AND name=$2 AND age=?"; + PreparedStatement statement = connection.prepareStatement(query); + statement.setInt(1, 1); + statement.setString(2, "bob"); + + testing.runWithSpan( + "parent", + () -> { + ResultSet resultSet = statement.executeQuery(); + assertThat(resultSet).isInstanceOf(OpenTelemetryResultSet.class); + assertThat(resultSet.getStatement()).isEqualTo(statement); + }); + + jdbcTraceAssertion( + dbInfo, + sanitized, + equalTo(DB_OPERATION_PARAMETER.getAttributeKey("0"), "1"), + equalTo(DB_OPERATION_PARAMETER.getAttributeKey("$2"), "'bob'"), + equalTo(DB_OPERATION_PARAMETER.getAttributeKey("1"), "3")); + + statement.close(); + connection.close(); + } + private static DbInfo getDbInfo() { return DbInfo.builder() .system("my_system") @@ -215,7 +253,23 @@ private static DbInfo getDbInfo() { } @SuppressWarnings("deprecation") // old semconv - private static void jdbcTraceAssertion(DbInfo dbInfo, String query) { + private static void jdbcTraceAssertion( + DbInfo dbInfo, String query, AttributeAssertion... assertions) { + List baseAttributeAssertions = + Arrays.asList( + equalTo(maybeStable(DB_SYSTEM), maybeStableDbSystemName(dbInfo.getSystem())), + equalTo(maybeStable(DB_NAME), dbInfo.getName()), + equalTo(DB_USER, emitStableDatabaseSemconv() ? null : dbInfo.getUser()), + equalTo( + DB_CONNECTION_STRING, emitStableDatabaseSemconv() ? null : dbInfo.getShortUrl()), + equalTo(maybeStable(DB_STATEMENT), query), + equalTo(maybeStable(DB_OPERATION), "SELECT"), + equalTo(maybeStable(DB_SQL_TABLE), "users"), + equalTo(SERVER_ADDRESS, dbInfo.getHost()), + equalTo(SERVER_PORT, dbInfo.getPort())); + + List additionAttributeAssertions = Arrays.asList(assertions); + testing.waitAndAssertTraces( trace -> trace.hasSpansSatisfyingExactly( @@ -225,18 +279,9 @@ private static void jdbcTraceAssertion(DbInfo dbInfo, String query) { .hasKind(SpanKind.CLIENT) .hasParent(trace.getSpan(0)) .hasAttributesSatisfyingExactly( - equalTo( - maybeStable(DB_SYSTEM), - maybeStableDbSystemName(dbInfo.getSystem())), - equalTo(maybeStable(DB_NAME), dbInfo.getName()), - equalTo(DB_USER, emitStableDatabaseSemconv() ? null : dbInfo.getUser()), - equalTo( - DB_CONNECTION_STRING, - emitStableDatabaseSemconv() ? null : dbInfo.getShortUrl()), - equalTo(maybeStable(DB_STATEMENT), query), - equalTo(maybeStable(DB_OPERATION), "SELECT"), - equalTo(maybeStable(DB_SQL_TABLE), "users"), - equalTo(SERVER_ADDRESS, dbInfo.getHost()), - equalTo(SERVER_PORT, dbInfo.getPort())))); + Stream.concat( + baseAttributeAssertions.stream(), + additionAttributeAssertions.stream()) + .collect(Collectors.toList())))); } } diff --git a/instrumentation/jdbc/testing/src/main/java/io/opentelemetry/instrumentation/jdbc/TestPreparedStatement.java b/instrumentation/jdbc/testing/src/main/java/io/opentelemetry/instrumentation/jdbc/TestPreparedStatement.java index 8c8c0092a3db..68bb88038730 100644 --- a/instrumentation/jdbc/testing/src/main/java/io/opentelemetry/instrumentation/jdbc/TestPreparedStatement.java +++ b/instrumentation/jdbc/testing/src/main/java/io/opentelemetry/instrumentation/jdbc/TestPreparedStatement.java @@ -26,15 +26,20 @@ import java.sql.Time; import java.sql.Timestamp; import java.util.Calendar; +import java.util.HashMap; +import java.util.Map; class TestPreparedStatement extends TestStatement implements PreparedStatement { + Map parameters; TestPreparedStatement() { super(); + this.parameters = new HashMap<>(); } TestPreparedStatement(Connection connection) { super(connection); + this.parameters = new HashMap<>(); } @Override @@ -144,7 +149,9 @@ public void setDouble(int parameterIndex, double x) throws SQLException {} public void setFloat(int parameterIndex, float x) throws SQLException {} @Override - public void setInt(int parameterIndex, int x) throws SQLException {} + public void setInt(int parameterIndex, int x) throws SQLException { + parameters.put(parameterIndex, x); + } @Override public void setLong(int parameterIndex, long x) throws SQLException {} diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/jdbc/DataSourcePostProcessor.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/jdbc/DataSourcePostProcessor.java index f29513d01bc5..907af6448b0a 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/jdbc/DataSourcePostProcessor.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/jdbc/DataSourcePostProcessor.java @@ -55,6 +55,10 @@ public Object postProcessAfterInitialization(Object bean, String beanName) { InstrumentationConfigUtil.isStatementSanitizationEnabled( configPropertiesProvider.getObject(), "otel.instrumentation.jdbc.statement-sanitizer.enabled")) + .setOperationParameterEnabled( + InstrumentationConfigUtil.isOperationParameterEnabled( + configPropertiesProvider.getObject(), + "otel.instrumentation.jdbc.operation-parameter.enabled")) .build() .wrap(dataSource); } diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/properties/InstrumentationConfigUtil.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/properties/InstrumentationConfigUtil.java index fb5e582bfaa5..0b0b075bc1e6 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/properties/InstrumentationConfigUtil.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/properties/InstrumentationConfigUtil.java @@ -45,4 +45,10 @@ public static boolean isStatementSanitizationEnabled(ConfigProperties config, St return config.getBoolean( key, config.getBoolean("otel.instrumentation.common.db-statement-sanitizer.enabled", true)); } + + public static boolean isOperationParameterEnabled(ConfigProperties config, String key) { + // TODO change with common conf key + return config.getBoolean( + key, config.getBoolean("otel.instrumentation.jdbc.operation-parameter.enabled", false)); + } } diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/instrumentation/spring/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json index e339c3455df8..ff00f2883d8b 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -351,6 +351,12 @@ "description": "Enables the DB statement sanitization.", "defaultValue": true }, + { + "name": "otel.instrumentation.jdbc.operation-parameter.enabled", + "type": "java.lang.Boolean", + "description": "Enables the attribute db.operation.parameter.", + "defaultValue": false + }, { "name": "otel.instrumentation.kafka.enabled", "type": "java.lang.Boolean", From d1ebd7e357d95b7d383ae4915b3a25f0091edee6 Mon Sep 17 00:00:00 2001 From: Alix Date: Wed, 16 Apr 2025 11:25:45 +0200 Subject: [PATCH 02/15] add backward method on SqlStatementInfo for callers without parameters --- .../api/incubator/semconv/db/SqlStatementInfo.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlStatementInfo.java b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlStatementInfo.java index 9d9e9d5640b3..2e254e0fad08 100644 --- a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlStatementInfo.java +++ b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlStatementInfo.java @@ -20,6 +20,13 @@ public static SqlStatementInfo create( return new AutoValue_SqlStatementInfo(fullStatement, operation, identifier, parameters); } + public static SqlStatementInfo create( + @Nullable String fullStatement, + @Nullable String operation, + @Nullable String identifier) { + return new AutoValue_SqlStatementInfo(fullStatement, operation, identifier, null); + } + @Nullable public abstract String getFullStatement(); From 2d48543c9961e183e8af7a964102a2c1540b8e4f Mon Sep 17 00:00:00 2001 From: Alix Date: Wed, 16 Apr 2025 12:42:56 +0200 Subject: [PATCH 03/15] fix SlickTest with span attributes --- .../api/incubator/semconv/db/SqlStatementInfo.java | 4 +--- .../instrumentation/scalaexecutors/SlickTest.scala | 6 +++++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlStatementInfo.java b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlStatementInfo.java index 2e254e0fad08..0d6fa2a64da8 100644 --- a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlStatementInfo.java +++ b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlStatementInfo.java @@ -21,9 +21,7 @@ public static SqlStatementInfo create( } public static SqlStatementInfo create( - @Nullable String fullStatement, - @Nullable String operation, - @Nullable String identifier) { + @Nullable String fullStatement, @Nullable String operation, @Nullable String identifier) { return new AutoValue_SqlStatementInfo(fullStatement, operation, identifier, null); } diff --git a/instrumentation/jdbc/javaagent/src/test/scala/io/opentelemetry/javaagent/instrumentation/scalaexecutors/SlickTest.scala b/instrumentation/jdbc/javaagent/src/test/scala/io/opentelemetry/javaagent/instrumentation/scalaexecutors/SlickTest.scala index 79c74f466a16..80542e62ed6a 100644 --- a/instrumentation/jdbc/javaagent/src/test/scala/io/opentelemetry/javaagent/instrumentation/scalaexecutors/SlickTest.scala +++ b/instrumentation/jdbc/javaagent/src/test/scala/io/opentelemetry/javaagent/instrumentation/scalaexecutors/SlickTest.scala @@ -98,7 +98,11 @@ class SlickTest { if (emitStableDatabaseSemconv()) null else "h2:mem:" ), equalTo(maybeStable(DB_STATEMENT), "SELECT ?"), - equalTo(maybeStable(DB_OPERATION), "SELECT") + equalTo(maybeStable(DB_OPERATION), "SELECT"), + equalTo( + DB_OPERATION_PARAMETER.getAttributeKey("0"), + TestValue.toString + ) ) } ) From b3365f141ccf24bf23124bf56487946fdc2b2b6e Mon Sep 17 00:00:00 2001 From: Alix Date: Thu, 17 Apr 2025 12:58:00 +0200 Subject: [PATCH 04/15] Replace db.operation.parameter by db.query.parameter --- .../config/internal/CommonConfig.java | 10 ++-- .../db/SqlClientAttributesExtractor.java | 26 ++++----- .../SqlClientAttributesExtractorBuilder.java | 12 ++-- .../semconv/db/SqlClientAttributesGetter.java | 2 +- .../db/SqlClientAttributesExtractorTest.java | 56 +++++++++---------- instrumentation/jdbc/README.md | 2 +- .../jdbc/javaagent/build.gradle.kts | 8 +-- .../instrumentation/jdbc/JdbcSingletons.java | 6 +- .../jdbc/test/JdbcInstrumentationTest.java | 26 ++++----- .../scalaexecutors/SlickTest.scala | 2 +- .../jdbc/datasource/JdbcTelemetryBuilder.java | 8 +-- .../jdbc/internal/JdbcAttributesGetter.java | 2 +- .../internal/JdbcInstrumenterFactory.java | 7 +-- .../internal/OpenTelemetryConnectionTest.java | 10 ++-- .../jdbc/DataSourcePostProcessor.java | 6 +- .../properties/InstrumentationConfigUtil.java | 4 +- ...itional-spring-configuration-metadata.json | 4 +- 17 files changed, 92 insertions(+), 99 deletions(-) diff --git a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/config/internal/CommonConfig.java b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/config/internal/CommonConfig.java index 40991ae87320..04d02eb7e879 100644 --- a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/config/internal/CommonConfig.java +++ b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/config/internal/CommonConfig.java @@ -29,7 +29,7 @@ public final class CommonConfig { private final Set knownHttpRequestMethods; private final EnduserConfig enduserConfig; private final boolean statementSanitizationEnabled; - private final boolean operationParameterEnabled; + private final boolean queryParameterEnabled; private final boolean emitExperimentalHttpClientTelemetry; private final boolean emitExperimentalHttpServerTelemetry; private final boolean redactQueryParameters; @@ -58,8 +58,8 @@ public CommonConfig(InstrumentationConfig config) { statementSanitizationEnabled = config.getBoolean("otel.instrumentation.common.db-statement-sanitizer.enabled", true); // TODO change with common conf key - operationParameterEnabled = - config.getBoolean("otel.instrumentation.jdbc.operation-parameter.enabled", false); + queryParameterEnabled = + config.getBoolean("otel.instrumentation.jdbc.query-parameter.enabled", false); emitExperimentalHttpClientTelemetry = config.getBoolean("otel.instrumentation.http.client.emit-experimental-telemetry", false); redactQueryParameters = @@ -111,8 +111,8 @@ public boolean isStatementSanitizationEnabled() { return statementSanitizationEnabled; } - public boolean isOperationParameterEnabled() { - return operationParameterEnabled; + public boolean isQueryParameterEnabled() { + return queryParameterEnabled; } public boolean shouldEmitExperimentalHttpClientTelemetry() { diff --git a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlClientAttributesExtractor.java b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlClientAttributesExtractor.java index 2972c10ebc8f..bdc91601b041 100644 --- a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlClientAttributesExtractor.java +++ b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlClientAttributesExtractor.java @@ -40,8 +40,8 @@ public final class SqlClientAttributesExtractor AttributeKey.stringKey("db.collection.name"); private static final AttributeKey DB_OPERATION_BATCH_SIZE = AttributeKey.longKey("db.operation.batch.size"); - private static final AttributeKeyTemplate DB_OPERATION_PARAMETER = - AttributeKeyTemplate.stringKeyTemplate("db.operation.parameter"); + private static final AttributeKeyTemplate DB_QUERY_PARAMETER = + AttributeKeyTemplate.stringKeyTemplate("db.query.parameter"); /** Creates the SQL client attributes extractor with default configuration. */ public static AttributesExtractor create( @@ -62,17 +62,17 @@ public static SqlClientAttributesExtractorBuilder oldSemconvTableAttribute; private final boolean statementSanitizationEnabled; - private final boolean operationParameterEnabled; + private final boolean queryParameterEnabled; SqlClientAttributesExtractor( SqlClientAttributesGetter getter, AttributeKey oldSemconvTableAttribute, boolean statementSanitizationEnabled, - boolean operationParameterEnabled) { + boolean queryParameterEnabled) { super(getter); this.oldSemconvTableAttribute = oldSemconvTableAttribute; this.statementSanitizationEnabled = statementSanitizationEnabled; - this.operationParameterEnabled = operationParameterEnabled; + this.queryParameterEnabled = queryParameterEnabled; } @Override @@ -81,7 +81,7 @@ public void onStart(AttributesBuilder attributes, Context parentContext, REQUEST super.onStart(attributes, parentContext, request); Collection rawQueryTexts = getter.getRawQueryTexts(request); - Map preparedStatementParameters = getter.getOperationParameters(request); + Map preparedStatementParameters = getter.getQueryParameters(request); if (rawQueryTexts.isEmpty()) { return; @@ -103,8 +103,7 @@ public void onStart(AttributesBuilder attributes, Context parentContext, REQUEST if (!SQL_CALL.equals(operation)) { internalSet(attributes, oldSemconvTableAttribute, sanitizedStatement.getMainIdentifier()); } - setOperationParameters( - attributes, sanitizedStatement, isBatch, preparedStatementParameters); + setQueryParameters(attributes, sanitizedStatement, isBatch, preparedStatementParameters); } } @@ -124,8 +123,7 @@ public void onStart(AttributesBuilder attributes, Context parentContext, REQUEST if (!SQL_CALL.equals(operation)) { internalSet(attributes, DB_COLLECTION_NAME, sanitizedStatement.getMainIdentifier()); } - setOperationParameters( - attributes, sanitizedStatement, isBatch, preparedStatementParameters); + setQueryParameters(attributes, sanitizedStatement, isBatch, preparedStatementParameters); } else { MultiQuery multiQuery = MultiQuery.analyze(getter.getRawQueryTexts(request), statementSanitizationEnabled); @@ -143,12 +141,12 @@ public void onStart(AttributesBuilder attributes, Context parentContext, REQUEST } } - private void setOperationParameters( + private void setQueryParameters( AttributesBuilder attributes, SqlStatementInfo sanitizedStatement, boolean isBatch, Map preparedStatementParameters) { - if (sanitizedStatement.getParameters() != null && operationParameterEnabled && !isBatch) { + if (sanitizedStatement.getParameters() != null && queryParameterEnabled && !isBatch) { int currentPreparedStatementParametersIndex = 1; for (Map.Entry entry : sanitizedStatement.getParameters().entrySet()) { // in this case it means that the sanitizer parsed an existing ? @@ -160,11 +158,11 @@ private void setOperationParameters( && preparedStatementParameters.containsKey(currentPreparedStatementParametersIndex)) { internalSet( attributes, - DB_OPERATION_PARAMETER.getAttributeKey(key), + DB_QUERY_PARAMETER.getAttributeKey(key), stringifyParameter( preparedStatementParameters.get(currentPreparedStatementParametersIndex++))); } else { - internalSet(attributes, DB_OPERATION_PARAMETER.getAttributeKey(key), value); + internalSet(attributes, DB_QUERY_PARAMETER.getAttributeKey(key), value); } } } diff --git a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlClientAttributesExtractorBuilder.java b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlClientAttributesExtractorBuilder.java index 6c48a4fa4bf1..de345798dc2a 100644 --- a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlClientAttributesExtractorBuilder.java +++ b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlClientAttributesExtractorBuilder.java @@ -20,7 +20,7 @@ public final class SqlClientAttributesExtractorBuilder { final SqlClientAttributesGetter getter; AttributeKey oldSemconvTableAttribute = DB_SQL_TABLE; boolean statementSanitizationEnabled = true; - boolean operationParameterEnabled = false; + boolean queryParameterEnabled = false; SqlClientAttributesExtractorBuilder(SqlClientAttributesGetter getter) { this.getter = getter; @@ -50,15 +50,15 @@ public SqlClientAttributesExtractorBuilder setStatementSaniti } /** - * Sets whether the {@code db.operation.parameter.} attributes extracted by the constructed + * Sets whether the {@code db.query.parameter.} attributes extracted by the constructed * {@link SqlClientAttributesExtractor} should be opted-in. If set to {@code true}, all parameters * masked by the sanitization and all parameters from {@code PreparedStatement} will be exposed as * attributes. Disabled by default. */ @CanIgnoreReturnValue - public SqlClientAttributesExtractorBuilder setOperationParameterEnabled( - boolean operationParameterEnabled) { - this.operationParameterEnabled = operationParameterEnabled; + public SqlClientAttributesExtractorBuilder setQueryParameterEnabled( + boolean queryParameterEnabled) { + this.queryParameterEnabled = queryParameterEnabled; return this; } @@ -68,6 +68,6 @@ public SqlClientAttributesExtractorBuilder setOperationParame */ public AttributesExtractor build() { return new SqlClientAttributesExtractor<>( - getter, oldSemconvTableAttribute, statementSanitizationEnabled, operationParameterEnabled); + getter, oldSemconvTableAttribute, statementSanitizationEnabled, queryParameterEnabled); } } diff --git a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlClientAttributesGetter.java b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlClientAttributesGetter.java index 736ba24c0155..0fd5904038b6 100644 --- a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlClientAttributesGetter.java +++ b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlClientAttributesGetter.java @@ -71,7 +71,7 @@ default Long getBatchSize(REQUEST request) { /** TODO */ // TODO: make this required to implement @Nullable - default Map getOperationParameters(REQUEST request) { + default Map getQueryParameters(REQUEST request) { return null; } } diff --git a/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlClientAttributesExtractorTest.java b/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlClientAttributesExtractorTest.java index 854de22e4bdd..5aecbca31590 100644 --- a/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlClientAttributesExtractorTest.java +++ b/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlClientAttributesExtractorTest.java @@ -6,7 +6,7 @@ package io.opentelemetry.instrumentation.api.incubator.semconv.db; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; -import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_OPERATION_PARAMETER; +import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_QUERY_PARAMETER; import static java.util.Collections.emptySet; import static java.util.Collections.singleton; import static org.assertj.core.api.Assertions.entry; @@ -67,8 +67,8 @@ public Long getBatchSize(Map map) { @SuppressWarnings("EmptyCatch") @Nullable @Override - public Map getOperationParameters(Map map) { - String parameterString = read(map, "db.operation.parameter"); + public Map getQueryParameters(Map map) { + String parameterString = read(map, "db.query.parameter"); if (parameterString == null) { return null; @@ -415,7 +415,7 @@ void shouldIgnoreBatchSizeOne() { } @Test - void shouldExtractOperationParameters() { + void shouldExtractQueryParameters() { // given Map request = new HashMap<>(); request.put("db.name", "potatoes"); @@ -424,13 +424,13 @@ void shouldExtractOperationParameters() { "db.statement", "SELECT col FROM table WHERE field1=? AND field2='A' AND field3=$2 AND field4=2"); // a prepared parameters map - request.put("db.operation.parameter", "a;1"); + request.put("db.query.parameter", "a;1"); Context context = Context.root(); AttributesExtractor, Void> underTest = SqlClientAttributesExtractor.builder(new TestAttributesGetter()) - .setOperationParameterEnabled(true) + .setQueryParameterEnabled(true) .build(); // when @@ -440,45 +440,45 @@ void shouldExtractOperationParameters() { AttributesBuilder endAttributes = Attributes.builder(); underTest.onEnd(endAttributes, context, request, null, null); - String prefix = DB_OPERATION_PARAMETER.getAttributeKey("").getKey(); - Attributes operationParameterAttributes = + String prefix = DB_QUERY_PARAMETER.getAttributeKey("").getKey(); + Attributes queryParameterAttributes = startAttributes.removeIf(attribute -> !attribute.getKey().startsWith(prefix)).build(); // then if (SemconvStability.emitStableDatabaseSemconv() && SemconvStability.emitOldDatabaseSemconv()) { - assertThat(operationParameterAttributes) + assertThat(queryParameterAttributes) .containsOnly( - entry(DB_OPERATION_PARAMETER.getAttributeKey("0"), "'a'"), - entry(DB_OPERATION_PARAMETER.getAttributeKey("1"), "'A'"), - entry(DB_OPERATION_PARAMETER.getAttributeKey("$2"), "1"), - entry(DB_OPERATION_PARAMETER.getAttributeKey("2"), "2")); + entry(DB_QUERY_PARAMETER.getAttributeKey("0"), "'a'"), + entry(DB_QUERY_PARAMETER.getAttributeKey("1"), "'A'"), + entry(DB_QUERY_PARAMETER.getAttributeKey("$2"), "1"), + entry(DB_QUERY_PARAMETER.getAttributeKey("2"), "2")); } else if (SemconvStability.emitOldDatabaseSemconv()) { - assertThat(operationParameterAttributes) + assertThat(queryParameterAttributes) .containsOnly( - entry(DB_OPERATION_PARAMETER.getAttributeKey("0"), "'a'"), - entry(DB_OPERATION_PARAMETER.getAttributeKey("1"), "'A'"), - entry(DB_OPERATION_PARAMETER.getAttributeKey("$2"), "1"), - entry(DB_OPERATION_PARAMETER.getAttributeKey("2"), "2")); + entry(DB_QUERY_PARAMETER.getAttributeKey("0"), "'a'"), + entry(DB_QUERY_PARAMETER.getAttributeKey("1"), "'A'"), + entry(DB_QUERY_PARAMETER.getAttributeKey("$2"), "1"), + entry(DB_QUERY_PARAMETER.getAttributeKey("2"), "2")); } else if (SemconvStability.emitStableDatabaseSemconv()) { - assertThat(operationParameterAttributes) + assertThat(queryParameterAttributes) .containsOnly( - entry(DB_OPERATION_PARAMETER.getAttributeKey("0"), "'a'"), - entry(DB_OPERATION_PARAMETER.getAttributeKey("1"), "'A'"), - entry(DB_OPERATION_PARAMETER.getAttributeKey("$2"), "1"), - entry(DB_OPERATION_PARAMETER.getAttributeKey("2"), "2")); + entry(DB_QUERY_PARAMETER.getAttributeKey("0"), "'a'"), + entry(DB_QUERY_PARAMETER.getAttributeKey("1"), "'A'"), + entry(DB_QUERY_PARAMETER.getAttributeKey("$2"), "1"), + entry(DB_QUERY_PARAMETER.getAttributeKey("2"), "2")); } assertThat(endAttributes.build().isEmpty()).isTrue(); } @Test - void shouldNotExtractOperationParametersForBatch() { + void shouldNotExtractQueryParametersForBatch() { // given Map request = new HashMap<>(); request.put("db.name", "potatoes"); request.put("db.statements", singleton("INSERT INTO potato VALUES(?)")); request.put("db.operation.batch.size", 1L); - request.put("db.operation.parameter", "1"); + request.put("db.query.parameter", "1"); Context context = Context.root(); @@ -495,13 +495,13 @@ void shouldNotExtractOperationParametersForBatch() { // then if (SemconvStability.emitStableDatabaseSemconv() && SemconvStability.emitOldDatabaseSemconv()) { assertThat(startAttributes.build()) - .doesNotContainKey(DB_OPERATION_PARAMETER.getAttributeKey("0")); + .doesNotContainKey(DB_QUERY_PARAMETER.getAttributeKey("0")); } else if (SemconvStability.emitOldDatabaseSemconv()) { assertThat(startAttributes.build()) - .doesNotContainKey(DB_OPERATION_PARAMETER.getAttributeKey("0")); + .doesNotContainKey(DB_QUERY_PARAMETER.getAttributeKey("0")); } else if (SemconvStability.emitStableDatabaseSemconv()) { assertThat(startAttributes.build()) - .doesNotContainKey(DB_OPERATION_PARAMETER.getAttributeKey("0")); + .doesNotContainKey(DB_QUERY_PARAMETER.getAttributeKey("0")); } assertThat(endAttributes.build().isEmpty()).isTrue(); diff --git a/instrumentation/jdbc/README.md b/instrumentation/jdbc/README.md index eb4bf1d5be44..1eb5ea8927b6 100644 --- a/instrumentation/jdbc/README.md +++ b/instrumentation/jdbc/README.md @@ -3,4 +3,4 @@ | System property | Type | Default | Description | |---------------------------------------------------------|---------|---------|----------------------------------------------------| | `otel.instrumentation.jdbc.statement-sanitizer.enabled` | Boolean | `true` | Enables the DB statement sanitization. | -| `otel.instrumentation.jdbc.operation-parameter.enabled` | Boolean | `false` | Enables the attribute db.operation.parameter. | +| `otel.instrumentation.jdbc.query-parameter.enabled` | Boolean | `false` | Enables the attribute db.query.parameter. | diff --git a/instrumentation/jdbc/javaagent/build.gradle.kts b/instrumentation/jdbc/javaagent/build.gradle.kts index 6eb24e77949e..df3c8d16ed64 100644 --- a/instrumentation/jdbc/javaagent/build.gradle.kts +++ b/instrumentation/jdbc/javaagent/build.gradle.kts @@ -60,7 +60,7 @@ tasks { includeTestsMatching("SlickTest") } include("**/SlickTest.*") - jvmArgs("-Dotel.instrumentation.jdbc.operation-parameter.enabled=true") + jvmArgs("-Dotel.instrumentation.jdbc.query-parameter.enabled=true") } test { @@ -68,7 +68,7 @@ tasks { excludeTestsMatching("SlickTest") } jvmArgs("-Dotel.instrumentation.jdbc-datasource.enabled=true") - jvmArgs("-Dotel.instrumentation.jdbc.operation-parameter.enabled=true") + jvmArgs("-Dotel.instrumentation.jdbc.query-parameter.enabled=true") } val testStableSemconv by registering(Test::class) { @@ -77,7 +77,7 @@ tasks { } jvmArgs("-Dotel.instrumentation.jdbc-datasource.enabled=true") jvmArgs("-Dotel.semconv-stability.opt-in=database") - jvmArgs("-Dotel.instrumentation.jdbc.operation-parameter.enabled=true") + jvmArgs("-Dotel.instrumentation.jdbc.query-parameter.enabled=true") } val testSlickStableSemconv by registering(Test::class) { @@ -86,7 +86,7 @@ tasks { } include("**/SlickTest.*") jvmArgs("-Dotel.semconv-stability.opt-in=database") - jvmArgs("-Dotel.instrumentation.jdbc.operation-parameter.enabled=true") + jvmArgs("-Dotel.instrumentation.jdbc.query-parameter.enabled=true") } check { 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 2a97ea0c4edf..0d29b449f052 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 @@ -46,11 +46,11 @@ public final class JdbcSingletons { .getBoolean( "otel.instrumentation.jdbc.statement-sanitizer.enabled", AgentCommonConfig.get().isStatementSanitizationEnabled())) - .setOperationParameterEnabled( + .setQueryParameterEnabled( AgentInstrumentationConfig.get() .getBoolean( - "otel.instrumentation.jdbc.operation-parameter.enabled", - AgentCommonConfig.get().isOperationParameterEnabled())) + "otel.instrumentation.jdbc.query-parameter.enabled", + AgentCommonConfig.get().isQueryParameterEnabled())) .build()) .addAttributesExtractor(ServerAttributesExtractor.create(netAttributesGetter)) .addAttributesExtractor( diff --git a/instrumentation/jdbc/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jdbc/test/JdbcInstrumentationTest.java b/instrumentation/jdbc/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jdbc/test/JdbcInstrumentationTest.java index 7b59f6eb6e0e..6b8597fb2a82 100644 --- a/instrumentation/jdbc/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jdbc/test/JdbcInstrumentationTest.java +++ b/instrumentation/jdbc/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jdbc/test/JdbcInstrumentationTest.java @@ -19,7 +19,7 @@ import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_OPERATION; import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_OPERATION_BATCH_SIZE; import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_OPERATION_NAME; -import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_OPERATION_PARAMETER; +import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_QUERY_PARAMETER; import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_SQL_TABLE; import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_STATEMENT; import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_SYSTEM; @@ -390,8 +390,7 @@ public void testBasicStatement( parameters.entrySet().stream() .map( entry -> - equalTo( - DB_OPERATION_PARAMETER.getAttributeKey(entry.getKey()), entry.getValue())) + equalTo(DB_QUERY_PARAMETER.getAttributeKey(entry.getKey()), entry.getValue())) .collect(Collectors.toList()); baseAssertions.addAll( @@ -550,8 +549,7 @@ void testPreparedStatementExecute( parameters.entrySet().stream() .map( entry -> - equalTo( - DB_OPERATION_PARAMETER.getAttributeKey(entry.getKey()), entry.getValue())) + equalTo(DB_QUERY_PARAMETER.getAttributeKey(entry.getKey()), entry.getValue())) .collect(Collectors.toList()); baseAssertions.addAll( @@ -605,8 +603,7 @@ void testPreparedStatementQuery( parameters.entrySet().stream() .map( entry -> - equalTo( - DB_OPERATION_PARAMETER.getAttributeKey(entry.getKey()), entry.getValue())) + equalTo(DB_QUERY_PARAMETER.getAttributeKey(entry.getKey()), entry.getValue())) .collect(Collectors.toList()); baseAssertions.addAll( @@ -660,8 +657,7 @@ void testPreparedCall( parameters.entrySet().stream() .map( entry -> - equalTo( - DB_OPERATION_PARAMETER.getAttributeKey(entry.getKey()), entry.getValue())) + equalTo(DB_QUERY_PARAMETER.getAttributeKey(entry.getKey()), entry.getValue())) .collect(Collectors.toList()); baseAssertions.addAll( @@ -1093,8 +1089,7 @@ void testConnectionConstructorThrowing( parameters.entrySet().stream() .map( entry -> - equalTo( - DB_OPERATION_PARAMETER.getAttributeKey(entry.getKey()), entry.getValue())) + equalTo(DB_QUERY_PARAMETER.getAttributeKey(entry.getKey()), entry.getValue())) .collect(Collectors.toList()); baseAssertions.addAll( @@ -1240,7 +1235,7 @@ void testGetClientInfoException(String query) throws SQLException { DB_CONNECTION_STRING, emitStableDatabaseSemconv() ? null : "testdb://localhost"), equalTo(SERVER_ADDRESS, "localhost"), - equalTo(DB_OPERATION_PARAMETER.getAttributeKey("0"), "123")))); + equalTo(DB_QUERY_PARAMETER.getAttributeKey("0"), "123")))); } static Stream spanNameStream() { @@ -1319,8 +1314,7 @@ void testProduceProperSpanName( parameters.entrySet().stream() .map( entry -> - equalTo( - DB_OPERATION_PARAMETER.getAttributeKey(entry.getKey()), entry.getValue())) + equalTo(DB_QUERY_PARAMETER.getAttributeKey(entry.getKey()), entry.getValue())) .collect(Collectors.toList()); baseAssertions.addAll( @@ -1395,7 +1389,7 @@ void testConnectionCached(String connectionPoolName) throws SQLException { "SELECT ? FROM INFORMATION_SCHEMA.SYSTEM_USERS"), equalTo(maybeStable(DB_OPERATION), "SELECT"), equalTo(maybeStable(DB_SQL_TABLE), "INFORMATION_SCHEMA.SYSTEM_USERS"), - equalTo(DB_OPERATION_PARAMETER.getAttributeKey("0"), "3"))); + equalTo(DB_QUERY_PARAMETER.getAttributeKey("0"), "3"))); for (int i = 0; i < numQueries; i++) { assertions.add(traceAssertConsumer); } @@ -1708,7 +1702,7 @@ void testSingleItemBatch(String system, Connection connection, String username, "INSERT INTO " + tableName + " VALUES(?)"), equalTo(maybeStable(DB_OPERATION), "INSERT"), equalTo(maybeStable(DB_SQL_TABLE), tableName), - equalTo(DB_OPERATION_PARAMETER.getAttributeKey("0"), "1")))); + equalTo(DB_QUERY_PARAMETER.getAttributeKey("0"), "1")))); } @ParameterizedTest diff --git a/instrumentation/jdbc/javaagent/src/test/scala/io/opentelemetry/javaagent/instrumentation/scalaexecutors/SlickTest.scala b/instrumentation/jdbc/javaagent/src/test/scala/io/opentelemetry/javaagent/instrumentation/scalaexecutors/SlickTest.scala index 80542e62ed6a..4c2a5df5d001 100644 --- a/instrumentation/jdbc/javaagent/src/test/scala/io/opentelemetry/javaagent/instrumentation/scalaexecutors/SlickTest.scala +++ b/instrumentation/jdbc/javaagent/src/test/scala/io/opentelemetry/javaagent/instrumentation/scalaexecutors/SlickTest.scala @@ -100,7 +100,7 @@ class SlickTest { equalTo(maybeStable(DB_STATEMENT), "SELECT ?"), equalTo(maybeStable(DB_OPERATION), "SELECT"), equalTo( - DB_OPERATION_PARAMETER.getAttributeKey("0"), + DB_QUERY_PARAMETER.getAttributeKey("0"), TestValue.toString ) ) 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 index 6525dcf75dbb..bb8b4561fe1e 100644 --- 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 @@ -16,7 +16,7 @@ public final class JdbcTelemetryBuilder { private boolean dataSourceInstrumenterEnabled = true; private boolean statementInstrumenterEnabled = true; private boolean statementSanitizationEnabled = true; - private boolean operationParameterEnabled = false; + private boolean queryParameterEnabled = false; JdbcTelemetryBuilder(OpenTelemetry openTelemetry) { this.openTelemetry = openTelemetry; @@ -44,8 +44,8 @@ public JdbcTelemetryBuilder setStatementSanitizationEnabled(boolean enabled) { } @CanIgnoreReturnValue - public JdbcTelemetryBuilder setOperationParameterEnabled(boolean enabled) { - this.operationParameterEnabled = enabled; + public JdbcTelemetryBuilder setQueryParameterEnabled(boolean enabled) { + this.queryParameterEnabled = enabled; return this; } @@ -58,6 +58,6 @@ public JdbcTelemetry build() { openTelemetry, statementInstrumenterEnabled, statementSanitizationEnabled, - operationParameterEnabled)); + queryParameterEnabled)); } } diff --git a/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/JdbcAttributesGetter.java b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/JdbcAttributesGetter.java index c77831827514..29850eb6e7cd 100644 --- a/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/JdbcAttributesGetter.java +++ b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/JdbcAttributesGetter.java @@ -66,7 +66,7 @@ public String getResponseStatus(@Nullable Void response, @Nullable Throwable err @Nullable @Override - public Map getOperationParameters(DbRequest request) { + public Map getQueryParameters(DbRequest request) { return request.getPreparedStatementParameters(); } } 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 cb30827d5c80..0d7a92cec233 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 @@ -41,15 +41,14 @@ public static Instrumenter createStatementInstrumenter( ConfigPropertiesUtil.getBoolean( "otel.instrumentation.common.db-statement-sanitizer.enabled", true), // TODO change with common conf key - ConfigPropertiesUtil.getBoolean( - "otel.instrumentation.jdbc.operation-parameter.enabled", true)); + ConfigPropertiesUtil.getBoolean("otel.instrumentation.jdbc.query-parameter.enabled", true)); } public static Instrumenter createStatementInstrumenter( OpenTelemetry openTelemetry, boolean enabled, boolean statementSanitizationEnabled, - boolean operationParameterEnabled) { + boolean queryParameterEnabled) { return Instrumenter.builder( openTelemetry, INSTRUMENTATION_NAME, @@ -57,7 +56,7 @@ public static Instrumenter createStatementInstrumenter( .addAttributesExtractor( SqlClientAttributesExtractor.builder(dbAttributesGetter) .setStatementSanitizationEnabled(statementSanitizationEnabled) - .setOperationParameterEnabled(operationParameterEnabled) + .setQueryParameterEnabled(queryParameterEnabled) .build()) .addAttributesExtractor(ServerAttributesExtractor.create(netAttributesGetter)) .addOperationMetrics(DbClientMetrics.get()) diff --git a/instrumentation/jdbc/library/src/test/java/io/opentelemetry/instrumentation/jdbc/internal/OpenTelemetryConnectionTest.java b/instrumentation/jdbc/library/src/test/java/io/opentelemetry/instrumentation/jdbc/internal/OpenTelemetryConnectionTest.java index 99fd0af79bd6..3f4855fdbc5b 100644 --- a/instrumentation/jdbc/library/src/test/java/io/opentelemetry/instrumentation/jdbc/internal/OpenTelemetryConnectionTest.java +++ b/instrumentation/jdbc/library/src/test/java/io/opentelemetry/instrumentation/jdbc/internal/OpenTelemetryConnectionTest.java @@ -15,7 +15,7 @@ import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_CONNECTION_STRING; import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_NAME; import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_OPERATION; -import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_OPERATION_PARAMETER; +import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_QUERY_PARAMETER; import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_SQL_TABLE; import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_STATEMENT; import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_SYSTEM; @@ -207,6 +207,8 @@ void testVerifyPrepareCallReturnsOtelWrapper() throws Exception { connection.close(); } + // https://github.com/open-telemetry/semantic-conventions/pull/2093 + @SuppressWarnings("deprecation") @Test void testVerifyPrepareStatementParameters() throws SQLException { Instrumenter instrumenter = @@ -231,9 +233,9 @@ void testVerifyPrepareStatementParameters() throws SQLException { jdbcTraceAssertion( dbInfo, sanitized, - equalTo(DB_OPERATION_PARAMETER.getAttributeKey("0"), "1"), - equalTo(DB_OPERATION_PARAMETER.getAttributeKey("$2"), "'bob'"), - equalTo(DB_OPERATION_PARAMETER.getAttributeKey("1"), "3")); + equalTo(DB_QUERY_PARAMETER.getAttributeKey("0"), "1"), + equalTo(DB_QUERY_PARAMETER.getAttributeKey("$2"), "'bob'"), + equalTo(DB_QUERY_PARAMETER.getAttributeKey("1"), "3")); statement.close(); connection.close(); diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/jdbc/DataSourcePostProcessor.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/jdbc/DataSourcePostProcessor.java index 907af6448b0a..c922841c2cc3 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/jdbc/DataSourcePostProcessor.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/jdbc/DataSourcePostProcessor.java @@ -55,10 +55,10 @@ public Object postProcessAfterInitialization(Object bean, String beanName) { InstrumentationConfigUtil.isStatementSanitizationEnabled( configPropertiesProvider.getObject(), "otel.instrumentation.jdbc.statement-sanitizer.enabled")) - .setOperationParameterEnabled( - InstrumentationConfigUtil.isOperationParameterEnabled( + .setQueryParameterEnabled( + InstrumentationConfigUtil.isQueryParameterEnabled( configPropertiesProvider.getObject(), - "otel.instrumentation.jdbc.operation-parameter.enabled")) + "otel.instrumentation.jdbc.query-parameter.enabled")) .build() .wrap(dataSource); } diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/properties/InstrumentationConfigUtil.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/properties/InstrumentationConfigUtil.java index 0b0b075bc1e6..eefc6340b672 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/properties/InstrumentationConfigUtil.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/properties/InstrumentationConfigUtil.java @@ -46,9 +46,9 @@ public static boolean isStatementSanitizationEnabled(ConfigProperties config, St key, config.getBoolean("otel.instrumentation.common.db-statement-sanitizer.enabled", true)); } - public static boolean isOperationParameterEnabled(ConfigProperties config, String key) { + public static boolean isQueryParameterEnabled(ConfigProperties config, String key) { // TODO change with common conf key return config.getBoolean( - key, config.getBoolean("otel.instrumentation.jdbc.operation-parameter.enabled", false)); + key, config.getBoolean("otel.instrumentation.jdbc.query-parameter.enabled", false)); } } diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/instrumentation/spring/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json index ff00f2883d8b..a53172880e77 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -352,9 +352,9 @@ "defaultValue": true }, { - "name": "otel.instrumentation.jdbc.operation-parameter.enabled", + "name": "otel.instrumentation.jdbc.query-parameter.enabled", "type": "java.lang.Boolean", - "description": "Enables the attribute db.operation.parameter.", + "description": "Enables the attribute db.query.parameter.", "defaultValue": false }, { From a785cd69a1936b2b0a06bf2ccb7bcbc40839b7c3 Mon Sep 17 00:00:00 2001 From: Alix Date: Mon, 28 Apr 2025 12:25:32 +0200 Subject: [PATCH 05/15] Change -D flag name; explicit Advices override --- .../config/internal/CommonConfig.java | 8 - .../db/SqlClientAttributesExtractor.java | 52 +- .../SqlClientAttributesExtractorBuilder.java | 26 +- .../semconv/db/SqlClientAttributesGetter.java | 3 +- .../semconv/db/SqlStatementInfo.java | 13 +- .../semconv/db/SqlStatementSanitizer.java | 2 +- .../src/main/jflex/SqlSanitizer.jflex | 68 +- .../db/SqlClientAttributesExtractorTest.java | 8 +- .../semconv/db/SqlStatementSanitizerTest.java | 49 +- instrumentation/jdbc/README.md | 8 +- .../jdbc/javaagent/build.gradle.kts | 15 +- .../instrumentation/jdbc/JdbcSingletons.java | 5 +- .../PreparedStatementInstrumentation.java | 306 +++++- .../jdbc/StatementInstrumentation.java | 2 +- .../jdbc/test/JdbcInstrumentationTest.java | 347 ++---- .../test/PreparedStatementParametersTest.java | 992 ++++++++++++++++++ .../scalaexecutors/SlickTest.scala | 6 +- .../jdbc/datasource/JdbcTelemetryBuilder.java | 18 +- .../jdbc/internal/DbRequest.java | 14 +- .../jdbc/internal/JdbcAttributesGetter.java | 2 +- .../jdbc/internal/JdbcData.java | 6 +- .../internal/JdbcInstrumenterFactory.java | 21 +- .../JdbcPreparedStatementStringifier.java | 68 ++ .../OpenTelemetryPreparedStatement.java | 80 +- .../internal/OpenTelemetryConnectionTest.java | 9 +- .../jdbc/TestPreparedStatement.java | 4 +- .../jdbc/DataSourcePostProcessor.java | 8 +- .../properties/InstrumentationConfigUtil.java | 6 - ...itional-spring-configuration-metadata.json | 4 +- 29 files changed, 1619 insertions(+), 531 deletions(-) create mode 100644 instrumentation/jdbc/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jdbc/test/PreparedStatementParametersTest.java create mode 100644 instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/JdbcPreparedStatementStringifier.java diff --git a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/config/internal/CommonConfig.java b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/config/internal/CommonConfig.java index 04d02eb7e879..bb64425c6da5 100644 --- a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/config/internal/CommonConfig.java +++ b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/config/internal/CommonConfig.java @@ -29,7 +29,6 @@ public final class CommonConfig { private final Set knownHttpRequestMethods; private final EnduserConfig enduserConfig; private final boolean statementSanitizationEnabled; - private final boolean queryParameterEnabled; private final boolean emitExperimentalHttpClientTelemetry; private final boolean emitExperimentalHttpServerTelemetry; private final boolean redactQueryParameters; @@ -57,9 +56,6 @@ public CommonConfig(InstrumentationConfig config) { new ArrayList<>(HttpConstants.KNOWN_METHODS))); statementSanitizationEnabled = config.getBoolean("otel.instrumentation.common.db-statement-sanitizer.enabled", true); - // TODO change with common conf key - queryParameterEnabled = - config.getBoolean("otel.instrumentation.jdbc.query-parameter.enabled", false); emitExperimentalHttpClientTelemetry = config.getBoolean("otel.instrumentation.http.client.emit-experimental-telemetry", false); redactQueryParameters = @@ -111,10 +107,6 @@ public boolean isStatementSanitizationEnabled() { return statementSanitizationEnabled; } - public boolean isQueryParameterEnabled() { - return queryParameterEnabled; - } - public boolean shouldEmitExperimentalHttpClientTelemetry() { return emitExperimentalHttpClientTelemetry; } diff --git a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlClientAttributesExtractor.java b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlClientAttributesExtractor.java index bdc91601b041..a7acda510c4f 100644 --- a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlClientAttributesExtractor.java +++ b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlClientAttributesExtractor.java @@ -28,7 +28,7 @@ */ public final class SqlClientAttributesExtractor extends DbClientCommonAttributesExtractor< - REQUEST, RESPONSE, SqlClientAttributesGetter> { + REQUEST, RESPONSE, SqlClientAttributesGetter> { // copied from DbIncubatingAttributes private static final AttributeKey DB_OPERATION = AttributeKey.stringKey("db.operation"); @@ -62,17 +62,18 @@ public static SqlClientAttributesExtractorBuilder oldSemconvTableAttribute; private final boolean statementSanitizationEnabled; - private final boolean queryParameterEnabled; + private final boolean captureQueryParameters; SqlClientAttributesExtractor( SqlClientAttributesGetter getter, AttributeKey oldSemconvTableAttribute, boolean statementSanitizationEnabled, - boolean queryParameterEnabled) { + boolean captureQueryParameters) { super(getter); this.oldSemconvTableAttribute = oldSemconvTableAttribute; - this.statementSanitizationEnabled = statementSanitizationEnabled; - this.queryParameterEnabled = queryParameterEnabled; + // captureQueryParameters disables statementSanitizationEnabled + this.statementSanitizationEnabled = !captureQueryParameters && statementSanitizationEnabled; + this.captureQueryParameters = captureQueryParameters; } @Override @@ -81,7 +82,7 @@ public void onStart(AttributesBuilder attributes, Context parentContext, REQUEST super.onStart(attributes, parentContext, request); Collection rawQueryTexts = getter.getRawQueryTexts(request); - Map preparedStatementParameters = getter.getQueryParameters(request); + Map preparedStatementParameters = getter.getQueryParameters(request); if (rawQueryTexts.isEmpty()) { return; @@ -103,7 +104,7 @@ public void onStart(AttributesBuilder attributes, Context parentContext, REQUEST if (!SQL_CALL.equals(operation)) { internalSet(attributes, oldSemconvTableAttribute, sanitizedStatement.getMainIdentifier()); } - setQueryParameters(attributes, sanitizedStatement, isBatch, preparedStatementParameters); + setQueryParameters(attributes, isBatch, preparedStatementParameters); } } @@ -123,7 +124,7 @@ public void onStart(AttributesBuilder attributes, Context parentContext, REQUEST if (!SQL_CALL.equals(operation)) { internalSet(attributes, DB_COLLECTION_NAME, sanitizedStatement.getMainIdentifier()); } - setQueryParameters(attributes, sanitizedStatement, isBatch, preparedStatementParameters); + setQueryParameters(attributes, isBatch, preparedStatementParameters); } else { MultiQuery multiQuery = MultiQuery.analyze(getter.getRawQueryTexts(request), statementSanitizationEnabled); @@ -143,27 +144,13 @@ public void onStart(AttributesBuilder attributes, Context parentContext, REQUEST private void setQueryParameters( AttributesBuilder attributes, - SqlStatementInfo sanitizedStatement, boolean isBatch, - Map preparedStatementParameters) { - if (sanitizedStatement.getParameters() != null && queryParameterEnabled && !isBatch) { - int currentPreparedStatementParametersIndex = 1; - for (Map.Entry entry : sanitizedStatement.getParameters().entrySet()) { - // in this case it means that the sanitizer parsed an existing ? - // or a postgres marked parameter. So we'll replace with data from the REQUEST + Map preparedStatementParameters) { + if (captureQueryParameters && !isBatch && preparedStatementParameters != null) { + for (Map.Entry entry : preparedStatementParameters.entrySet()) { String key = entry.getKey(); String value = entry.getValue(); - if (preparedStatementParameters != null - && (value.equalsIgnoreCase("?") || value.startsWith("$")) - && preparedStatementParameters.containsKey(currentPreparedStatementParametersIndex)) { - internalSet( - attributes, - DB_QUERY_PARAMETER.getAttributeKey(key), - stringifyParameter( - preparedStatementParameters.get(currentPreparedStatementParametersIndex++))); - } else { - internalSet(attributes, DB_QUERY_PARAMETER.getAttributeKey(key), value); - } + internalSet(attributes, DB_QUERY_PARAMETER.getAttributeKey(key), value); } } } @@ -180,17 +167,4 @@ private static String join(String delimiter, Collection collection) { return builder.toString(); } - // TODO define all string repr of objects - public static String stringifyParameter(Object object) { - if (object == null) { - return ""; - } else if (object instanceof String) { - return String.format("'%s'", object); - } else if (object instanceof Number) { - Number number = (Number) object; - return String.format("%s", number); - } - - return String.format("<%s>", object.getClass().getSimpleName()); - } } diff --git a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlClientAttributesExtractorBuilder.java b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlClientAttributesExtractorBuilder.java index de345798dc2a..eb3ab94cbe42 100644 --- a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlClientAttributesExtractorBuilder.java +++ b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlClientAttributesExtractorBuilder.java @@ -20,7 +20,7 @@ public final class SqlClientAttributesExtractorBuilder { final SqlClientAttributesGetter getter; AttributeKey oldSemconvTableAttribute = DB_SQL_TABLE; boolean statementSanitizationEnabled = true; - boolean queryParameterEnabled = false; + boolean captureQueryParameters = false; SqlClientAttributesExtractorBuilder(SqlClientAttributesGetter getter) { this.getter = getter; @@ -46,19 +46,29 @@ public SqlClientAttributesExtractorBuilder setTableAttribute( public SqlClientAttributesExtractorBuilder setStatementSanitizationEnabled( boolean statementSanitizationEnabled) { this.statementSanitizationEnabled = statementSanitizationEnabled; + + if (statementSanitizationEnabled) { + this.captureQueryParameters = false; + } + return this; } /** * Sets whether the {@code db.query.parameter.} attributes extracted by the constructed - * {@link SqlClientAttributesExtractor} should be opted-in. If set to {@code true}, all parameters - * masked by the sanitization and all parameters from {@code PreparedStatement} will be exposed as - * attributes. Disabled by default. + * {@link SqlClientAttributesExtractor} should be opted-in. If set to {@code true}, + * all parameters from {@code PreparedStatement} will be exposed as attributes. + * Disabled by default. */ @CanIgnoreReturnValue - public SqlClientAttributesExtractorBuilder setQueryParameterEnabled( - boolean queryParameterEnabled) { - this.queryParameterEnabled = queryParameterEnabled; + public SqlClientAttributesExtractorBuilder setCaptureQueryParameters( + boolean captureQueryParameters) { + this.captureQueryParameters = captureQueryParameters; + + if (captureQueryParameters) { + this.statementSanitizationEnabled = false; + } + return this; } @@ -68,6 +78,6 @@ public SqlClientAttributesExtractorBuilder setQueryParameterE */ public AttributesExtractor build() { return new SqlClientAttributesExtractor<>( - getter, oldSemconvTableAttribute, statementSanitizationEnabled, queryParameterEnabled); + getter, oldSemconvTableAttribute, statementSanitizationEnabled, captureQueryParameters); } } diff --git a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlClientAttributesGetter.java b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlClientAttributesGetter.java index 0fd5904038b6..e4e28d4a5aa1 100644 --- a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlClientAttributesGetter.java +++ b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlClientAttributesGetter.java @@ -68,10 +68,9 @@ default Long getBatchSize(REQUEST request) { return null; } - /** TODO */ // TODO: make this required to implement @Nullable - default Map getQueryParameters(REQUEST request) { + default Map getQueryParameters(REQUEST request) { return null; } } diff --git a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlStatementInfo.java b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlStatementInfo.java index 0d6fa2a64da8..e0bc634b7c2b 100644 --- a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlStatementInfo.java +++ b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlStatementInfo.java @@ -6,7 +6,6 @@ package io.opentelemetry.instrumentation.api.incubator.semconv.db; import com.google.auto.value.AutoValue; -import java.util.Map; import javax.annotation.Nullable; @AutoValue @@ -15,14 +14,8 @@ public abstract class SqlStatementInfo { public static SqlStatementInfo create( @Nullable String fullStatement, @Nullable String operation, - @Nullable String identifier, - @Nullable Map parameters) { - return new AutoValue_SqlStatementInfo(fullStatement, operation, identifier, parameters); - } - - public static SqlStatementInfo create( - @Nullable String fullStatement, @Nullable String operation, @Nullable String identifier) { - return new AutoValue_SqlStatementInfo(fullStatement, operation, identifier, null); + @Nullable String identifier) { + return new AutoValue_SqlStatementInfo(fullStatement, operation, identifier); } @Nullable @@ -34,6 +27,4 @@ public static SqlStatementInfo create( @Nullable public abstract String getMainIdentifier(); - @Nullable - public abstract Map getParameters(); } diff --git a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlStatementSanitizer.java b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlStatementSanitizer.java index e8ecd98b4725..58fd4c3cc7dc 100644 --- a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlStatementSanitizer.java +++ b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlStatementSanitizer.java @@ -39,7 +39,7 @@ public SqlStatementInfo sanitize(@Nullable String statement) { public SqlStatementInfo sanitize(@Nullable String statement, SqlDialect dialect) { if (!statementSanitizationEnabled || statement == null) { - return SqlStatementInfo.create(statement, null, null, null); + return SqlStatementInfo.create(statement, null, null); } // sanitization result will not be cached for statements larger than the threshold to avoid // cache growing too large diff --git a/instrumentation-api-incubator/src/main/jflex/SqlSanitizer.jflex b/instrumentation-api-incubator/src/main/jflex/SqlSanitizer.jflex index 6c208aed9e1a..83a59fc0abc9 100644 --- a/instrumentation-api-incubator/src/main/jflex/SqlSanitizer.jflex +++ b/instrumentation-api-incubator/src/main/jflex/SqlSanitizer.jflex @@ -5,8 +5,6 @@ package io.opentelemetry.instrumentation.api.incubator.semconv.db; -import java.util.HashMap; -import java.util.Map; import java.util.regex.Pattern; %% @@ -39,7 +37,6 @@ DOLLAR_QUOTED_STR = "$$" [^$]* "$$" BACKTICK_QUOTED_STR = "`" [^`]* "`" POSTGRE_PARAM_MARKER = "$"[0-9]* WHITESPACE = [ \t\r\n]+ -QUESTION_MARK = "?" %{ static SqlStatementInfo sanitize(String statement, SqlDialect dialect) { @@ -56,7 +53,7 @@ QUESTION_MARK = "?" return sanitizer.getResult(); } catch (java.io.IOException e) { // should never happen - return SqlStatementInfo.create(null, null, null, null); + return SqlStatementInfo.create(null, null, null); } } @@ -69,24 +66,6 @@ QUESTION_MARK = "?" private final StringBuilder builder = new StringBuilder(); - private final Map parameters = new HashMap<>(); - private int currentParameterIndex = 0; - - private void storeParameterAndSanitize(boolean isNamed, boolean keepFragment) { - String value = yytext(); - if (isNamed) { - parameters.put(value, value); - } else { - parameters.put(Integer.toString(currentParameterIndex++), value); - } - - if (keepFragment) { - builder.append(value); - } else { - builder.append('?'); - } - } - private void appendCurrentFragment() { builder.append(zzBuffer, zzStartRead, zzMarkedPos - zzStartRead); } @@ -185,13 +164,8 @@ QUESTION_MARK = "?" return false; } - SqlStatementInfo getResult(String fullStatement, Map parameters) { - return SqlStatementInfo.create( - fullStatement, - getClass().getSimpleName().toUpperCase(java.util.Locale.ROOT), - mainIdentifier, - parameters - ); + SqlStatementInfo getResult(String fullStatement) { + return SqlStatementInfo.create(fullStatement, getClass().getSimpleName().toUpperCase(java.util.Locale.ROOT), mainIdentifier); } } @@ -221,24 +195,19 @@ QUESTION_MARK = "?" return true; } - SqlStatementInfo getResult(String fullStatement, Map parameters) { + SqlStatementInfo getResult(String fullStatement) { if (!"".equals(operationTarget)) { - return SqlStatementInfo.create( - fullStatement, - getClass().getSimpleName().toUpperCase(java.util.Locale.ROOT) + " " + operationTarget, - mainIdentifier, - parameters - ); + return SqlStatementInfo.create(fullStatement, getClass().getSimpleName().toUpperCase(java.util.Locale.ROOT) + " " + operationTarget, mainIdentifier); } - return super.getResult(fullStatement, parameters); + return super.getResult(fullStatement); } } private static class NoOp extends Operation { static final Operation INSTANCE = new NoOp(); - SqlStatementInfo getResult(String fullStatement, Map parameters) { - return SqlStatementInfo.create(fullStatement, null, null, parameters); + SqlStatementInfo getResult(String fullStatement) { + return SqlStatementInfo.create(fullStatement, null, null); } } @@ -394,10 +363,9 @@ QUESTION_MARK = "?" String fullStatement = builder.toString(); // Normalize all 'in (?, ?, ...)' statements to in (?) to reduce cardinality - // TODO merge this.parameters in a single list for merged ? String normalizedStatement = IN_STATEMENT_PATTERN.matcher(fullStatement).replaceAll(IN_STATEMENT_NORMALIZED); - return operation.getResult(normalizedStatement, this.parameters); + return operation.getResult(normalizedStatement); } %} @@ -561,13 +529,13 @@ QUESTION_MARK = "?" // here is where the actual sanitization happens {BASIC_NUM} | {HEX_NUM} | {QUOTED_STR} | {DOLLAR_QUOTED_STR} { - storeParameterAndSanitize(false, false); + builder.append('?'); if (isOverLimit()) return YYEOF; } {DOUBLE_QUOTED_STR} { if (dialect == SqlDialect.COUCHBASE) { - storeParameterAndSanitize(false, false); + builder.append('?'); } else { if (!insideComment && !extractionDone) { extractionDone = operation.handleIdentifier(); @@ -577,7 +545,7 @@ QUESTION_MARK = "?" if (isOverLimit()) return YYEOF; } - {BACKTICK_QUOTED_STR} { + {BACKTICK_QUOTED_STR} | {POSTGRE_PARAM_MARKER} { if (!insideComment && !extractionDone) { extractionDone = operation.handleIdentifier(); } @@ -585,18 +553,6 @@ QUESTION_MARK = "?" if (isOverLimit()) return YYEOF; } - {POSTGRE_PARAM_MARKER} { - if (!insideComment && !extractionDone) { - extractionDone = operation.handleIdentifier(); - } - storeParameterAndSanitize(true, true); - if (isOverLimit()) return YYEOF; - } - {QUESTION_MARK} { - storeParameterAndSanitize(false, true); - if (isOverLimit()) return YYEOF; - } - {WHITESPACE} { builder.append(' '); if (isOverLimit()) return YYEOF; diff --git a/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlClientAttributesExtractorTest.java b/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlClientAttributesExtractorTest.java index 5aecbca31590..344a80e3c250 100644 --- a/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlClientAttributesExtractorTest.java +++ b/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlClientAttributesExtractorTest.java @@ -67,14 +67,14 @@ public Long getBatchSize(Map map) { @SuppressWarnings("EmptyCatch") @Nullable @Override - public Map getQueryParameters(Map map) { + public Map getQueryParameters(Map map) { String parameterString = read(map, "db.query.parameter"); if (parameterString == null) { return null; } - Map parameters = new HashMap<>(); + Map parameters = new HashMap<>(); for (String s : parameterString.split(";")) { // cast basic types used in tests Object value = s; @@ -83,7 +83,7 @@ public Map getQueryParameters(Map map) { } catch (NumberFormatException ignored) { } - parameters.put(parameters.size() + 1, value); + parameters.put(Integer.toString(parameters.size() + 1), value); } return parameters; } @@ -430,7 +430,7 @@ void shouldExtractQueryParameters() { AttributesExtractor, Void> underTest = SqlClientAttributesExtractor.builder(new TestAttributesGetter()) - .setQueryParameterEnabled(true) + .setCaptureQueryParameters(true) .build(); // when diff --git a/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlStatementSanitizerTest.java b/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlStatementSanitizerTest.java index 19b9a44ca7b0..8771ac1ed7c4 100644 --- a/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlStatementSanitizerTest.java +++ b/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlStatementSanitizerTest.java @@ -7,9 +7,6 @@ import static org.assertj.core.api.AssertionsForClassTypes.assertThat; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; import java.util.Random; import java.util.function.Function; import java.util.stream.Stream; @@ -50,19 +47,13 @@ void simplifySql(String original, Function expectedFun @Test void veryLongSelectStatementsAreOk() { StringBuilder sb = new StringBuilder("SELECT * FROM table WHERE"); - Map parameters = new HashMap<>(); for (int i = 0; i < 2000; i++) { sb.append(" column").append(i).append("=123 and"); } String query = sb.toString(); String sanitizedQuery = query.replace("=123", "=?").substring(0, AutoSqlSanitizer.LIMIT); - sanitizedQuery - .chars() - .filter(c -> c == '?') - .forEach(c -> parameters.put(Integer.toString(parameters.size()), "123")); - SqlStatementInfo expected = - SqlStatementInfo.create(sanitizedQuery, "SELECT", "table", parameters); + SqlStatementInfo expected = SqlStatementInfo.create(sanitizedQuery, "SELECT", "table"); SqlStatementInfo result = SqlStatementSanitizer.create(true).sanitize(query); @@ -167,36 +158,6 @@ public void largeStatementCached() { assertThat(SqlStatementSanitizer.isCached(largeStatement)).isFalse(); } - @Test - public void parametersAreExtractedForEachSanitization() { - String s = "SELECT col FROM table WHERE field=1 AND field2=? AND field3=$1"; - Map parameters = new HashMap<>(); - parameters.put("0", "1"); - parameters.put("1", "?"); - parameters.put("$1", "$1"); - - SqlStatementInfo result = SqlStatementSanitizer.create(true).sanitize(s); - - assertThat(result.getParameters()).isEqualTo(parameters); - } - - @Test - public void parametersShouldNotCollideWithPreparedStatement() { - String s = "SELECT col FROM table WHERE field1=? AND field2='A' AND field3=$2 AND field4=2"; - String sanitized = - "SELECT col FROM table WHERE field1=? AND field2=? AND field3=$2 AND field4=?"; - Map parameters = new HashMap<>(); - parameters.put("0", "?"); - parameters.put("1", "'A'"); - parameters.put("$2", "$2"); - parameters.put("2", "2"); - - SqlStatementInfo result = SqlStatementSanitizer.create(true).sanitize(s); - - assertThat(result.getFullStatement()).isEqualTo(sanitized); - assertThat(result.getParameters()).isEqualTo(parameters); - } - static class SqlArgs implements ArgumentsProvider { @Override @@ -323,12 +284,12 @@ public Stream provideArguments(ExtensionContext context) { static class SimplifyArgs implements ArgumentsProvider { static Function expect(String operation, String identifier) { - return sql -> SqlStatementInfo.create(sql, operation, identifier, Collections.emptyMap()); + return sql -> SqlStatementInfo.create(sql, operation, identifier); } static Function expect( String sql, String operation, String identifier) { - return ignored -> SqlStatementInfo.create(sql, operation, identifier, Collections.emptyMap()); + return ignored -> SqlStatementInfo.create(sql, operation, identifier); } @Override @@ -440,12 +401,12 @@ public Stream provideArguments(ExtensionContext context) { static class DdlArgs implements ArgumentsProvider { static Function expect(String operation, String identifier) { - return sql -> SqlStatementInfo.create(sql, operation, identifier, Collections.emptyMap()); + return sql -> SqlStatementInfo.create(sql, operation, identifier); } static Function expect( String sql, String operation, String identifier) { - return ignored -> SqlStatementInfo.create(sql, operation, identifier, Collections.emptyMap()); + return ignored -> SqlStatementInfo.create(sql, operation, identifier); } @Override diff --git a/instrumentation/jdbc/README.md b/instrumentation/jdbc/README.md index 1eb5ea8927b6..0cc193697918 100644 --- a/instrumentation/jdbc/README.md +++ b/instrumentation/jdbc/README.md @@ -1,6 +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. | -| `otel.instrumentation.jdbc.query-parameter.enabled` | Boolean | `false` | Enables the attribute db.query.parameter. | +| System property | Type | Default | Description | +|---------------------------------------------------------|---------|---------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `otel.instrumentation.jdbc.statement-sanitizer.enabled` | Boolean | `true` | Enables the DB statement sanitization. | +| `otel.instrumentation.jdbc.capture-query-parameters` | Boolean | `false` | Enables the attribute db.query.parameter.\.

WARNING: captured query parameters may contain sensitive information such as passwords, personally identifiable information or protected health info. Exposing such info may result in substantial fines and penalties or criminal liability. Consult your peers, superiors and a legal counsel before enabling this option.

This option will disable otel.instrumentation.jdbc.statement-sanitizer | diff --git a/instrumentation/jdbc/javaagent/build.gradle.kts b/instrumentation/jdbc/javaagent/build.gradle.kts index df3c8d16ed64..be106066c3a3 100644 --- a/instrumentation/jdbc/javaagent/build.gradle.kts +++ b/instrumentation/jdbc/javaagent/build.gradle.kts @@ -60,24 +60,23 @@ tasks { includeTestsMatching("SlickTest") } include("**/SlickTest.*") - jvmArgs("-Dotel.instrumentation.jdbc.query-parameter.enabled=true") } test { filter { excludeTestsMatching("SlickTest") + excludeTestsMatching("PreparedStatementParametersTest") } jvmArgs("-Dotel.instrumentation.jdbc-datasource.enabled=true") - jvmArgs("-Dotel.instrumentation.jdbc.query-parameter.enabled=true") } val testStableSemconv by registering(Test::class) { filter { excludeTestsMatching("SlickTest") + excludeTestsMatching("PreparedStatementParametersTest") } jvmArgs("-Dotel.instrumentation.jdbc-datasource.enabled=true") jvmArgs("-Dotel.semconv-stability.opt-in=database") - jvmArgs("-Dotel.instrumentation.jdbc.query-parameter.enabled=true") } val testSlickStableSemconv by registering(Test::class) { @@ -86,12 +85,20 @@ tasks { } include("**/SlickTest.*") jvmArgs("-Dotel.semconv-stability.opt-in=database") - jvmArgs("-Dotel.instrumentation.jdbc.query-parameter.enabled=true") + } + + val testCaptureParameters by registering(Test::class) { + filter { + includeTestsMatching("PreparedStatementParametersTest") + } + jvmArgs("-Dotel.instrumentation.jdbc-datasource.enabled=true") + jvmArgs("-Dotel.instrumentation.jdbc.capture-query-parameters=true") } check { dependsOn(testSlick) dependsOn(testStableSemconv) dependsOn(testSlickStableSemconv) + dependsOn(testCaptureParameters) } } 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 0d29b449f052..66da8ea0a155 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 @@ -46,11 +46,10 @@ public final class JdbcSingletons { .getBoolean( "otel.instrumentation.jdbc.statement-sanitizer.enabled", AgentCommonConfig.get().isStatementSanitizationEnabled())) - .setQueryParameterEnabled( + .setCaptureQueryParameters( AgentInstrumentationConfig.get() .getBoolean( - "otel.instrumentation.jdbc.query-parameter.enabled", - AgentCommonConfig.get().isQueryParameterEnabled())) + "otel.instrumentation.jdbc.capture-query-parameters", false)) .build()) .addAttributesExtractor(ServerAttributesExtractor.create(netAttributesGetter)) .addAttributesExtractor( diff --git a/instrumentation/jdbc/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jdbc/PreparedStatementInstrumentation.java b/instrumentation/jdbc/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jdbc/PreparedStatementInstrumentation.java index d729aa30a56b..fda0b6fc7352 100644 --- a/instrumentation/jdbc/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jdbc/PreparedStatementInstrumentation.java +++ b/instrumentation/jdbc/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jdbc/PreparedStatementInstrumentation.java @@ -5,6 +5,8 @@ package io.opentelemetry.javaagent.instrumentation.jdbc; +import static io.opentelemetry.instrumentation.jdbc.internal.JdbcPreparedStatementStringifier.stringifyNullParameter; +import static io.opentelemetry.instrumentation.jdbc.internal.JdbcPreparedStatementStringifier.stringifyParameter; import static io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge.currentContext; import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed; import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.implementsInterface; @@ -25,8 +27,15 @@ import io.opentelemetry.javaagent.bootstrap.CallDepth; import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; +import java.math.BigDecimal; +import java.net.URL; +import java.sql.Date; import java.sql.PreparedStatement; +import java.sql.RowId; import java.sql.Statement; +import java.sql.Time; +import java.sql.Timestamp; +import java.util.Calendar; import java.util.Map; import net.bytebuddy.asm.Advice; import net.bytebuddy.description.type.TypeDescription; @@ -56,17 +65,70 @@ public void transform(TypeTransformer transformer) { named("addBatch").and(takesNoArguments()).and(isPublic()), PreparedStatementInstrumentation.class.getName() + "$AddBatchAdvice"); transformer.applyAdviceToMethod( - nameStartsWith("set") - .and(takesArguments(2)) - .and(takesArgument(0, int.class)) + named("setNull").and(takesArgument(0, int.class)).and(takesArguments(2)).and(isPublic()), + PreparedStatementInstrumentation.class.getName() + "$SetNull2Advice"); + transformer.applyAdviceToMethod( + named("setNull").and(takesArgument(0, int.class)).and(takesArguments(3)).and(isPublic()), + PreparedStatementInstrumentation.class.getName() + "$SetNull3Advice"); + transformer.applyAdviceToMethod( + named("setBoolean").and(takesArgument(0, int.class)).and(isPublic()), + PreparedStatementInstrumentation.class.getName() + "$SetBooleanAdvice"); + transformer.applyAdviceToMethod( + named("setByte").and(takesArgument(0, int.class)).and(isPublic()), + PreparedStatementInstrumentation.class.getName() + "$SetByteAdvice"); + transformer.applyAdviceToMethod( + named("setShort").and(takesArgument(0, int.class)).and(isPublic()), + PreparedStatementInstrumentation.class.getName() + "$SetShortAdvice"); + transformer.applyAdviceToMethod( + named("setInt").and(takesArgument(0, int.class)).and(isPublic()), + PreparedStatementInstrumentation.class.getName() + "$SetIntAdvice"); + transformer.applyAdviceToMethod( + named("setLong").and(takesArgument(0, int.class)).and(isPublic()), + PreparedStatementInstrumentation.class.getName() + "$SetLongAdvice"); + transformer.applyAdviceToMethod( + named("setFloat").and(takesArgument(0, int.class)).and(isPublic()), + PreparedStatementInstrumentation.class.getName() + "$SetFloatAdvice"); + transformer.applyAdviceToMethod( + named("setDouble").and(takesArgument(0, int.class)).and(isPublic()), + PreparedStatementInstrumentation.class.getName() + "$SetDoubleAdvice"); + transformer.applyAdviceToMethod( + named("setBigDecimal").and(takesArgument(0, int.class)).and(isPublic()), + PreparedStatementInstrumentation.class.getName() + "$SetBigDecimalAdvice"); + transformer.applyAdviceToMethod( + named("setString").and(takesArgument(0, int.class)).and(isPublic()), + PreparedStatementInstrumentation.class.getName() + "$SetStringAdvice"); + transformer.applyAdviceToMethod( + named("setBytes").and(takesArgument(0, int.class)).and(isPublic()), + PreparedStatementInstrumentation.class.getName() + "$SetBytesAdvice"); + transformer.applyAdviceToMethod( + named("setDate").and(takesArgument(0, int.class)).and(takesArguments(2)).and(isPublic()), + PreparedStatementInstrumentation.class.getName() + "$SetDate2Advice"); + transformer.applyAdviceToMethod( + named("setDate").and(takesArgument(0, int.class)).and(takesArguments(3)).and(isPublic()), + PreparedStatementInstrumentation.class.getName() + "$SetDate3Advice"); + transformer.applyAdviceToMethod( + named("setTime").and(takesArgument(0, int.class)).and(takesArguments(2)).and(isPublic()), + PreparedStatementInstrumentation.class.getName() + "$SetTime2Advice"); + transformer.applyAdviceToMethod( + named("setTime").and(takesArgument(0, int.class)).and(takesArguments(3)).and(isPublic()), + PreparedStatementInstrumentation.class.getName() + "$SetTime3Advice"); + transformer.applyAdviceToMethod( + named("setTimestamp").and(takesArgument(0, int.class)).and(takesArguments(2)) .and(isPublic()), - PreparedStatementInstrumentation.class.getName() + "$Set2Advice"); + PreparedStatementInstrumentation.class.getName() + "$SetTimestamp2Advice"); transformer.applyAdviceToMethod( - nameStartsWith("set") - .and(takesArguments(3)) - .and(takesArgument(0, int.class)) + named("setTimestamp").and(takesArgument(0, int.class)).and(takesArguments(3)) .and(isPublic()), - PreparedStatementInstrumentation.class.getName() + "$Set3Advice"); + PreparedStatementInstrumentation.class.getName() + "$SetTimestamp3Advice"); + transformer.applyAdviceToMethod( + named("setURL").and(takesArgument(0, int.class)).and(isPublic()), + PreparedStatementInstrumentation.class.getName() + "$SetURLAdvice"); + transformer.applyAdviceToMethod( + named("setRowId").and(takesArgument(0, int.class)).and(isPublic()), + PreparedStatementInstrumentation.class.getName() + "$SetRowIdAdvice"); + transformer.applyAdviceToMethod( + named("setNString").and(takesArgument(0, int.class)).and(isPublic()), + PreparedStatementInstrumentation.class.getName() + "$SetNStringAdvice"); } @SuppressWarnings("unused") @@ -98,7 +160,7 @@ public static void onEnter( } Context parentContext = currentContext(); - Map parameters = JdbcData.parameters.get(statement); + Map parameters = JdbcData.parameters.get(statement); request = DbRequest.create(statement, parameters); if (request == null || !statementInstrumenter().shouldStart(parentContext, request)) { @@ -137,25 +199,237 @@ public static void addBatch(@Advice.This PreparedStatement statement) { } @SuppressWarnings("unused") - public static class Set2Advice { + public static class SetNull2Advice { + @Advice.OnMethodExit(suppress = Throwable.class) + public static void onExit( + @Advice.This PreparedStatement statement, + @Advice.Argument(0) int index, + @Advice.Argument(1) int sqlType) { + JdbcData.addParameter(statement, Integer.toString(index - 1), stringifyNullParameter()); + } + } + + @SuppressWarnings("unused") + public static class SetNull3Advice { + @Advice.OnMethodExit(suppress = Throwable.class) + public static void onExit( + @Advice.This PreparedStatement statement, + @Advice.Argument(0) int index, + @Advice.Argument(1) int sqlType, + @Advice.Argument(2) String typeName) { + JdbcData.addParameter(statement, Integer.toString(index - 1), stringifyNullParameter()); + } + } + + @SuppressWarnings("unused") + public static class SetBooleanAdvice { + @Advice.OnMethodExit(suppress = Throwable.class) + public static void onExit( + @Advice.This PreparedStatement statement, + @Advice.Argument(0) int index, + @Advice.Argument(1) boolean value) { + JdbcData.addParameter(statement, Integer.toString(index - 1), stringifyParameter(value)); + } + } + + @SuppressWarnings("unused") + public static class SetByteAdvice { + @Advice.OnMethodExit(suppress = Throwable.class) + public static void onExit( + @Advice.This PreparedStatement statement, + @Advice.Argument(0) int index, + @Advice.Argument(1) byte value) { + JdbcData.addParameter(statement, Integer.toString(index - 1), stringifyParameter(value)); + } + } + + @SuppressWarnings("unused") + public static class SetShortAdvice { + @Advice.OnMethodExit(suppress = Throwable.class) + public static void onExit( + @Advice.This PreparedStatement statement, + @Advice.Argument(0) int index, + @Advice.Argument(1) short value) { + JdbcData.addParameter(statement, Integer.toString(index - 1), stringifyParameter(value)); + } + } + + @SuppressWarnings("unused") + public static class SetIntAdvice { + @Advice.OnMethodExit(suppress = Throwable.class) + public static void onExit( + @Advice.This PreparedStatement statement, + @Advice.Argument(0) int index, + @Advice.Argument(1) int value) { + JdbcData.addParameter(statement, Integer.toString(index - 1), stringifyParameter(value)); + } + } + + @SuppressWarnings("unused") + public static class SetLongAdvice { + @Advice.OnMethodExit(suppress = Throwable.class) + public static void onExit( + @Advice.This PreparedStatement statement, + @Advice.Argument(0) int index, + @Advice.Argument(1) long value) { + JdbcData.addParameter(statement, Integer.toString(index - 1), stringifyParameter(value)); + } + } + + @SuppressWarnings("unused") + public static class SetFloatAdvice { + @Advice.OnMethodExit(suppress = Throwable.class) + public static void onExit( + @Advice.This PreparedStatement statement, + @Advice.Argument(0) int index, + @Advice.Argument(1) float value) { + JdbcData.addParameter(statement, Integer.toString(index - 1), stringifyParameter(value)); + } + } + + @SuppressWarnings("unused") + public static class SetDoubleAdvice { + @Advice.OnMethodExit(suppress = Throwable.class) + public static void onExit( + @Advice.This PreparedStatement statement, + @Advice.Argument(0) int index, + @Advice.Argument(1) double value) { + JdbcData.addParameter(statement, Integer.toString(index - 1), stringifyParameter(value)); + } + } + + @SuppressWarnings("unused") + public static class SetBigDecimalAdvice { + @Advice.OnMethodExit(suppress = Throwable.class) + public static void onExit( + @Advice.This PreparedStatement statement, + @Advice.Argument(0) int index, + @Advice.Argument(1) BigDecimal value) { + JdbcData.addParameter(statement, Integer.toString(index - 1), stringifyParameter(value)); + } + } + + @SuppressWarnings("unused") + public static class SetStringAdvice { + @Advice.OnMethodExit(suppress = Throwable.class) + public static void onExit( + @Advice.This PreparedStatement statement, + @Advice.Argument(0) int index, + @Advice.Argument(1) String value) { + JdbcData.addParameter(statement, Integer.toString(index - 1), stringifyParameter(value)); + } + } + + @SuppressWarnings("unused") + public static class SetBytesAdvice { + @Advice.OnMethodExit(suppress = Throwable.class) + public static void onExit( + @Advice.This PreparedStatement statement, + @Advice.Argument(0) int index, + @Advice.Argument(1) byte[] value) { + JdbcData.addParameter(statement, Integer.toString(index - 1), stringifyParameter(value)); + } + } + + @SuppressWarnings("unused") + public static class SetDate2Advice { + @Advice.OnMethodExit(suppress = Throwable.class) + public static void onExit( + @Advice.This PreparedStatement statement, + @Advice.Argument(0) int index, + @Advice.Argument(1) Date value) { + JdbcData.addParameter(statement, Integer.toString(index - 1), stringifyParameter(value)); + } + } + + @SuppressWarnings("unused") + public static class SetDate3Advice { + @Advice.OnMethodExit(suppress = Throwable.class) + public static void onExit( + @Advice.This PreparedStatement statement, + @Advice.Argument(0) int index, + @Advice.Argument(1) Date value, + @Advice.Argument(2) Calendar calendar) { + JdbcData.addParameter(statement, Integer.toString(index - 1), stringifyParameter(value)); + } + } + + @SuppressWarnings("unused") + public static class SetTime2Advice { + @Advice.OnMethodExit(suppress = Throwable.class) + public static void onExit( + @Advice.This PreparedStatement statement, + @Advice.Argument(0) int index, + @Advice.Argument(1) Time value) { + JdbcData.addParameter(statement, Integer.toString(index - 1), stringifyParameter(value)); + } + } + + @SuppressWarnings("unused") + public static class SetTime3Advice { + @Advice.OnMethodExit(suppress = Throwable.class) + public static void onExit( + @Advice.This PreparedStatement statement, + @Advice.Argument(0) int index, + @Advice.Argument(1) Time value, + @Advice.Argument(2) Calendar calendar) { + JdbcData.addParameter(statement, Integer.toString(index - 1), stringifyParameter(value)); + } + } + + @SuppressWarnings("unused") + public static class SetTimestamp2Advice { + @Advice.OnMethodExit(suppress = Throwable.class) + public static void onExit( + @Advice.This PreparedStatement statement, + @Advice.Argument(0) int index, + @Advice.Argument(1) Timestamp value) { + JdbcData.addParameter(statement, Integer.toString(index - 1), stringifyParameter(value)); + } + } + + @SuppressWarnings("unused") + public static class SetTimestamp3Advice { + @Advice.OnMethodExit(suppress = Throwable.class) + public static void onExit( + @Advice.This PreparedStatement statement, + @Advice.Argument(0) int index, + @Advice.Argument(1) Timestamp value, + @Advice.Argument(2) Calendar calendar) { + JdbcData.addParameter(statement, Integer.toString(index - 1), stringifyParameter(value)); + } + } + + @SuppressWarnings({"unused", "IdentifierName"}) + public static class SetURLAdvice { + @Advice.OnMethodExit(suppress = Throwable.class) + public static void onExit( + @Advice.This PreparedStatement statement, + @Advice.Argument(0) int index, + @Advice.Argument(1) URL value) { + JdbcData.addParameter(statement, Integer.toString(index - 1), stringifyParameter(value)); + } + } + + @SuppressWarnings("unused") + public static class SetRowIdAdvice { @Advice.OnMethodExit(suppress = Throwable.class) public static void onExit( @Advice.This PreparedStatement statement, @Advice.Argument(0) int index, - @Advice.Argument(1) Object value) { - JdbcData.addParameter(statement, index, value); + @Advice.Argument(1) RowId value) { + JdbcData.addParameter(statement, Integer.toString(index - 1), stringifyParameter(value)); } } @SuppressWarnings("unused") - public static class Set3Advice { + public static class SetNStringAdvice { @Advice.OnMethodExit(suppress = Throwable.class) public static void onExit( @Advice.This PreparedStatement statement, @Advice.Argument(0) int index, - @Advice.Argument(1) Object value1, - @Advice.Argument(1) Object value2) { - JdbcData.addParameter(statement, index, value1); + @Advice.Argument(1) String value) { + JdbcData.addParameter(statement, Integer.toString(index - 1), stringifyParameter(value)); } } } diff --git a/instrumentation/jdbc/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jdbc/StatementInstrumentation.java b/instrumentation/jdbc/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jdbc/StatementInstrumentation.java index b22e965a3d5e..4bfa9ff02846 100644 --- a/instrumentation/jdbc/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jdbc/StatementInstrumentation.java +++ b/instrumentation/jdbc/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jdbc/StatementInstrumentation.java @@ -157,7 +157,7 @@ public static void onEnter( if (statement instanceof PreparedStatement) { Long batchSize = JdbcData.getPreparedStatementBatchSize((PreparedStatement) statement); String sql = JdbcData.preparedStatement.get((PreparedStatement) statement); - Map parameters = JdbcData.parameters.get((PreparedStatement) statement); + Map parameters = JdbcData.parameters.get((PreparedStatement) statement); if (sql == null) { return; } diff --git a/instrumentation/jdbc/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jdbc/test/JdbcInstrumentationTest.java b/instrumentation/jdbc/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jdbc/test/JdbcInstrumentationTest.java index 6b8597fb2a82..bdc4d9c8af61 100644 --- a/instrumentation/jdbc/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jdbc/test/JdbcInstrumentationTest.java +++ b/instrumentation/jdbc/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jdbc/test/JdbcInstrumentationTest.java @@ -19,7 +19,6 @@ import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_OPERATION; import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_OPERATION_BATCH_SIZE; import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_OPERATION_NAME; -import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_QUERY_PARAMETER; import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_SQL_TABLE; import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_STATEMENT; import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_SYSTEM; @@ -39,7 +38,6 @@ import io.opentelemetry.instrumentation.testing.internal.AutoCleanupExtension; import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension; import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; -import io.opentelemetry.sdk.testing.assertj.AttributeAssertion; import io.opentelemetry.sdk.testing.assertj.SpanDataAssert; import io.opentelemetry.sdk.testing.assertj.TraceAssert; import io.opentelemetry.semconv.incubating.CodeIncubatingAttributes; @@ -54,14 +52,12 @@ import java.sql.SQLException; import java.sql.Statement; import java.util.ArrayList; -import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Properties; import java.util.function.Consumer; -import java.util.stream.Collectors; import java.util.stream.Stream; import javax.sql.DataSource; import org.apache.derby.jdbc.EmbeddedDataSource; @@ -222,8 +218,7 @@ static Stream basicStatementStream() throws SQLException { "SELECT ?", "SELECT " + dbNameLower, "h2:mem:", - null, - Collections.singletonMap("0", "3")), + null), Arguments.of( "derby", new EmbeddedDriver().connect(jdbcUrls.get("derby"), null), @@ -232,8 +227,7 @@ static Stream basicStatementStream() throws SQLException { "SELECT ? FROM SYSIBM.SYSDUMMY1", "SELECT SYSIBM.SYSDUMMY1", "derby:memory:", - "SYSIBM.SYSDUMMY1", - Collections.singletonMap("0", "3")), + "SYSIBM.SYSDUMMY1"), Arguments.of( "hsqldb", new JDBCDriver().connect(jdbcUrls.get("hsqldb"), null), @@ -242,8 +236,7 @@ static Stream basicStatementStream() throws SQLException { "SELECT ? FROM INFORMATION_SCHEMA.SYSTEM_USERS", "SELECT INFORMATION_SCHEMA.SYSTEM_USERS", "hsqldb:mem:", - "INFORMATION_SCHEMA.SYSTEM_USERS", - Collections.singletonMap("0", "3")), + "INFORMATION_SCHEMA.SYSTEM_USERS"), Arguments.of( "h2", new org.h2.Driver().connect(jdbcUrls.get("h2"), connectionProps), @@ -252,8 +245,7 @@ static Stream basicStatementStream() throws SQLException { "SELECT ?", "SELECT " + dbNameLower, "h2:mem:", - null, - Collections.singletonMap("0", "3")), + null), Arguments.of( "derby", new EmbeddedDriver().connect(jdbcUrls.get("derby"), connectionProps), @@ -262,8 +254,7 @@ static Stream basicStatementStream() throws SQLException { "SELECT ? FROM SYSIBM.SYSDUMMY1", "SELECT SYSIBM.SYSDUMMY1", "derby:memory:", - "SYSIBM.SYSDUMMY1", - Collections.singletonMap("0", "3")), + "SYSIBM.SYSDUMMY1"), Arguments.of( "hsqldb", new JDBCDriver().connect(jdbcUrls.get("hsqldb"), connectionProps), @@ -272,8 +263,7 @@ static Stream basicStatementStream() throws SQLException { "SELECT ? FROM INFORMATION_SCHEMA.SYSTEM_USERS", "SELECT INFORMATION_SCHEMA.SYSTEM_USERS", "hsqldb:mem:", - "INFORMATION_SCHEMA.SYSTEM_USERS", - Collections.singletonMap("0", "3")), + "INFORMATION_SCHEMA.SYSTEM_USERS"), Arguments.of( "h2", cpDatasources.get("tomcat").get("h2").getConnection(), @@ -282,8 +272,7 @@ static Stream basicStatementStream() throws SQLException { "SELECT ?", "SELECT " + dbNameLower, "h2:mem:", - null, - Collections.singletonMap("0", "3")), + null), Arguments.of( "derby", cpDatasources.get("tomcat").get("derby").getConnection(), @@ -292,8 +281,7 @@ static Stream basicStatementStream() throws SQLException { "SELECT ? FROM SYSIBM.SYSDUMMY1", "SELECT SYSIBM.SYSDUMMY1", "derby:memory:", - "SYSIBM.SYSDUMMY1", - Collections.singletonMap("0", "3")), + "SYSIBM.SYSDUMMY1"), Arguments.of( "hsqldb", cpDatasources.get("tomcat").get("hsqldb").getConnection(), @@ -302,8 +290,7 @@ static Stream basicStatementStream() throws SQLException { "SELECT ? FROM INFORMATION_SCHEMA.SYSTEM_USERS", "SELECT INFORMATION_SCHEMA.SYSTEM_USERS", "hsqldb:mem:", - "INFORMATION_SCHEMA.SYSTEM_USERS", - Collections.singletonMap("0", "3")), + "INFORMATION_SCHEMA.SYSTEM_USERS"), Arguments.of( "h2", cpDatasources.get("hikari").get("h2").getConnection(), @@ -312,8 +299,7 @@ static Stream basicStatementStream() throws SQLException { "SELECT ?", "SELECT " + dbNameLower, "h2:mem:", - null, - Collections.singletonMap("0", "3")), + null), Arguments.of( "derby", cpDatasources.get("hikari").get("derby").getConnection(), @@ -322,8 +308,7 @@ static Stream basicStatementStream() throws SQLException { "SELECT ? FROM SYSIBM.SYSDUMMY1", "SELECT SYSIBM.SYSDUMMY1", "derby:memory:", - "SYSIBM.SYSDUMMY1", - Collections.singletonMap("0", "3")), + "SYSIBM.SYSDUMMY1"), Arguments.of( "hsqldb", cpDatasources.get("hikari").get("hsqldb").getConnection(), @@ -332,8 +317,7 @@ static Stream basicStatementStream() throws SQLException { "SELECT ? FROM INFORMATION_SCHEMA.SYSTEM_USERS", "SELECT INFORMATION_SCHEMA.SYSTEM_USERS", "hsqldb:mem:", - "INFORMATION_SCHEMA.SYSTEM_USERS", - Collections.singletonMap("0", "3")), + "INFORMATION_SCHEMA.SYSTEM_USERS"), Arguments.of( "h2", cpDatasources.get("c3p0").get("h2").getConnection(), @@ -342,8 +326,7 @@ static Stream basicStatementStream() throws SQLException { "SELECT ?", "SELECT " + dbNameLower, "h2:mem:", - null, - Collections.singletonMap("0", "3")), + null), Arguments.of( "derby", cpDatasources.get("c3p0").get("derby").getConnection(), @@ -352,8 +335,7 @@ static Stream basicStatementStream() throws SQLException { "SELECT ? FROM SYSIBM.SYSDUMMY1", "SELECT SYSIBM.SYSDUMMY1", "derby:memory:", - "SYSIBM.SYSDUMMY1", - Collections.singletonMap("0", "3")), + "SYSIBM.SYSDUMMY1"), Arguments.of( "hsqldb", cpDatasources.get("c3p0").get("hsqldb").getConnection(), @@ -362,8 +344,7 @@ static Stream basicStatementStream() throws SQLException { "SELECT ? FROM INFORMATION_SCHEMA.SYSTEM_USERS", "SELECT INFORMATION_SCHEMA.SYSTEM_USERS", "hsqldb:mem:", - "INFORMATION_SCHEMA.SYSTEM_USERS", - Collections.singletonMap("0", "3"))); + "INFORMATION_SCHEMA.SYSTEM_USERS")); } @ParameterizedTest @@ -376,8 +357,7 @@ public void testBasicStatement( String sanitizedQuery, String spanName, String url, - String table, - Map parameters) + String table) throws SQLException { Statement statement = connection.createStatement(); cleanup.deferCleanup(statement); @@ -386,23 +366,6 @@ public void testBasicStatement( resultSet.next(); assertThat(resultSet.getInt(1)).isEqualTo(3); - List baseAssertions = - parameters.entrySet().stream() - .map( - entry -> - equalTo(DB_QUERY_PARAMETER.getAttributeKey(entry.getKey()), entry.getValue())) - .collect(Collectors.toList()); - - baseAssertions.addAll( - asList( - 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), sanitizedQuery), - equalTo(maybeStable(DB_OPERATION), "SELECT"), - equalTo(maybeStable(DB_SQL_TABLE), table))); - testing.waitAndAssertTraces( trace -> trace.hasSpansSatisfyingExactly( @@ -411,7 +374,14 @@ public void testBasicStatement( span.hasName(spanName) .hasKind(SpanKind.CLIENT) .hasParent(trace.getSpan(0)) - .hasAttributesSatisfyingExactly(baseAssertions))); + .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), sanitizedQuery), + equalTo(maybeStable(DB_OPERATION), "SELECT"), + equalTo(maybeStable(DB_SQL_TABLE), table)))); if (table != null) { assertDurationMetric( @@ -427,95 +397,80 @@ public void testBasicStatement( } } - private static Map parametersMapBuilder(String k) { - HashMap map = new HashMap() {}; - map.put("0", "3"); - map.put(k, "'Y'"); - return map; - } - static Stream preparedStatementStream() throws SQLException { return Stream.of( Arguments.of( "h2", new org.h2.Driver().connect(jdbcUrls.get("h2"), null), null, - "SELECT 3, $1", - "SELECT ?, $1", + "SELECT 3", + "SELECT ?", "SELECT " + dbNameLower, "h2:mem:", - null, - parametersMapBuilder("$1")), + null), Arguments.of( "derby", new EmbeddedDriver().connect(jdbcUrls.get("derby"), null), "APP", - "SELECT 3 FROM SYSIBM.SYSDUMMY1 WHERE IBMREQD=?", - "SELECT ? FROM SYSIBM.SYSDUMMY1 WHERE IBMREQD=?", + "SELECT 3 FROM SYSIBM.SYSDUMMY1", + "SELECT ? FROM SYSIBM.SYSDUMMY1", "SELECT SYSIBM.SYSDUMMY1", "derby:memory:", - "SYSIBM.SYSDUMMY1", - parametersMapBuilder("1")), + "SYSIBM.SYSDUMMY1"), Arguments.of( "h2", cpDatasources.get("tomcat").get("h2").getConnection(), null, - "SELECT 3, $1", - "SELECT ?, $1", + "SELECT 3", + "SELECT ?", "SELECT " + dbNameLower, "h2:mem:", - null, - parametersMapBuilder("$1")), + null), Arguments.of( "derby", cpDatasources.get("tomcat").get("derby").getConnection(), "APP", - "SELECT 3 FROM SYSIBM.SYSDUMMY1 WHERE IBMREQD=?", - "SELECT ? FROM SYSIBM.SYSDUMMY1 WHERE IBMREQD=?", + "SELECT 3 FROM SYSIBM.SYSDUMMY1", + "SELECT ? FROM SYSIBM.SYSDUMMY1", "SELECT SYSIBM.SYSDUMMY1", "derby:memory:", - "SYSIBM.SYSDUMMY1", - parametersMapBuilder("1")), + "SYSIBM.SYSDUMMY1"), Arguments.of( "h2", cpDatasources.get("hikari").get("h2").getConnection(), null, - "SELECT 3, $1", - "SELECT ?, $1", + "SELECT 3", + "SELECT ?", "SELECT " + dbNameLower, "h2:mem:", - null, - parametersMapBuilder("$1")), + null), Arguments.of( "derby", cpDatasources.get("hikari").get("derby").getConnection(), "APP", - "SELECT 3 FROM SYSIBM.SYSDUMMY1 WHERE IBMREQD=?", - "SELECT ? FROM SYSIBM.SYSDUMMY1 WHERE IBMREQD=?", + "SELECT 3 FROM SYSIBM.SYSDUMMY1", + "SELECT ? FROM SYSIBM.SYSDUMMY1", "SELECT SYSIBM.SYSDUMMY1", "derby:memory:", - "SYSIBM.SYSDUMMY1", - parametersMapBuilder("1")), + "SYSIBM.SYSDUMMY1"), Arguments.of( "h2", cpDatasources.get("c3p0").get("h2").getConnection(), null, - "SELECT 3, $1", - "SELECT ?, $1", + "SELECT 3", + "SELECT ?", "SELECT " + dbNameLower, "h2:mem:", - null, - parametersMapBuilder("$1")), + null), Arguments.of( "derby", cpDatasources.get("c3p0").get("derby").getConnection(), "APP", - "SELECT 3 FROM SYSIBM.SYSDUMMY1 WHERE IBMREQD=?", - "SELECT ? FROM SYSIBM.SYSDUMMY1 WHERE IBMREQD=?", + "SELECT 3 FROM SYSIBM.SYSDUMMY1", + "SELECT ? FROM SYSIBM.SYSDUMMY1", "SELECT SYSIBM.SYSDUMMY1", "derby:memory:", - "SYSIBM.SYSDUMMY1", - parametersMapBuilder("1"))); + "SYSIBM.SYSDUMMY1")); } @ParameterizedTest @@ -528,8 +483,7 @@ void testPreparedStatementExecute( String sanitizedQuery, String spanName, String url, - String table, - Map parameters) + String table) throws SQLException { PreparedStatement statement = connection.prepareStatement(query); cleanup.deferCleanup(statement); @@ -537,7 +491,6 @@ void testPreparedStatementExecute( testing.runWithSpan( "parent", () -> { - statement.setString(1, "Y"); statement.execute(); return statement.getResultSet(); }); @@ -545,23 +498,6 @@ void testPreparedStatementExecute( resultSet.next(); assertThat(resultSet.getInt(1)).isEqualTo(3); - List baseAssertions = - parameters.entrySet().stream() - .map( - entry -> - equalTo(DB_QUERY_PARAMETER.getAttributeKey(entry.getKey()), entry.getValue())) - .collect(Collectors.toList()); - - baseAssertions.addAll( - asList( - 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), sanitizedQuery), - equalTo(maybeStable(DB_OPERATION), "SELECT"), - equalTo(maybeStable(DB_SQL_TABLE), table))); - testing.waitAndAssertTraces( trace -> trace.hasSpansSatisfyingExactly( @@ -570,7 +506,14 @@ void testPreparedStatementExecute( span.hasName(spanName) .hasKind(SpanKind.CLIENT) .hasParent(trace.getSpan(0)) - .hasAttributesSatisfyingExactly(baseAssertions))); + .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), sanitizedQuery), + equalTo(maybeStable(DB_OPERATION), "SELECT"), + equalTo(maybeStable(DB_SQL_TABLE), table)))); } @ParameterizedTest @@ -583,39 +526,15 @@ void testPreparedStatementQuery( String sanitizedQuery, String spanName, String url, - String table, - Map parameters) + String table) throws SQLException { PreparedStatement statement = connection.prepareStatement(query); cleanup.deferCleanup(statement); - ResultSet resultSet = - testing.runWithSpan( - "parent", - () -> { - statement.setString(1, "Y"); - return statement.executeQuery(); - }); + ResultSet resultSet = testing.runWithSpan("parent", () -> statement.executeQuery()); resultSet.next(); assertThat(resultSet.getInt(1)).isEqualTo(3); - List baseAssertions = - parameters.entrySet().stream() - .map( - entry -> - equalTo(DB_QUERY_PARAMETER.getAttributeKey(entry.getKey()), entry.getValue())) - .collect(Collectors.toList()); - - baseAssertions.addAll( - asList( - 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), sanitizedQuery), - equalTo(maybeStable(DB_OPERATION), "SELECT"), - equalTo(maybeStable(DB_SQL_TABLE), table))); - testing.waitAndAssertTraces( trace -> trace.hasSpansSatisfyingExactly( @@ -624,7 +543,14 @@ void testPreparedStatementQuery( span.hasName(spanName) .hasKind(SpanKind.CLIENT) .hasParent(trace.getSpan(0)) - .hasAttributesSatisfyingExactly(baseAssertions))); + .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), sanitizedQuery), + equalTo(maybeStable(DB_OPERATION), "SELECT"), + equalTo(maybeStable(DB_SQL_TABLE), table)))); } @ParameterizedTest @@ -637,39 +563,15 @@ void testPreparedCall( String sanitizedQuery, String spanName, String url, - String table, - Map parameters) + String table) throws SQLException { CallableStatement statement = connection.prepareCall(query); cleanup.deferCleanup(statement); - ResultSet resultSet = - testing.runWithSpan( - "parent", - () -> { - statement.setString(1, "Y"); - return statement.executeQuery(); - }); + ResultSet resultSet = testing.runWithSpan("parent", () -> statement.executeQuery()); resultSet.next(); assertThat(resultSet.getInt(1)).isEqualTo(3); - List baseAssertions = - parameters.entrySet().stream() - .map( - entry -> - equalTo(DB_QUERY_PARAMETER.getAttributeKey(entry.getKey()), entry.getValue())) - .collect(Collectors.toList()); - - baseAssertions.addAll( - asList( - 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), sanitizedQuery), - equalTo(maybeStable(DB_OPERATION), "SELECT"), - equalTo(maybeStable(DB_SQL_TABLE), table))); - testing.waitAndAssertTraces( trace -> trace.hasSpansSatisfyingExactly( @@ -678,7 +580,14 @@ void testPreparedCall( span.hasName(spanName) .hasKind(SpanKind.CLIENT) .hasParent(trace.getSpan(0)) - .hasAttributesSatisfyingExactly(baseAssertions))); + .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), sanitizedQuery), + equalTo(maybeStable(DB_OPERATION), "SELECT"), + equalTo(maybeStable(DB_SQL_TABLE), table)))); } static Stream statementUpdateStream() throws SQLException { @@ -1002,8 +911,7 @@ static Stream connectionConstructorStream() { "SELECT ?;", "SELECT " + dbNameLower, "h2:mem:", - null, - Collections.singletonMap("0", "3")), + null), Arguments.of( true, "derby", @@ -1014,8 +922,7 @@ static Stream connectionConstructorStream() { "SELECT ? FROM SYSIBM.SYSDUMMY1", "SELECT SYSIBM.SYSDUMMY1", "derby:memory:", - "SYSIBM.SYSDUMMY1", - Collections.singletonMap("0", "3")), + "SYSIBM.SYSDUMMY1"), Arguments.of( false, "h2", @@ -1026,8 +933,7 @@ static Stream connectionConstructorStream() { "SELECT ?;", "SELECT " + dbNameLower, "h2:mem:", - null, - Collections.singletonMap("0", "3")), + null), Arguments.of( false, "derby", @@ -1038,8 +944,7 @@ static Stream connectionConstructorStream() { "SELECT ? FROM SYSIBM.SYSDUMMY1", "SELECT SYSIBM.SYSDUMMY1", "derby:memory:", - "SYSIBM.SYSDUMMY1", - Collections.singletonMap("0", "3"))); + "SYSIBM.SYSDUMMY1")); } @SuppressWarnings("CatchingUnchecked") @@ -1055,8 +960,7 @@ void testConnectionConstructorThrowing( String sanitizedQuery, String spanName, String url, - String table, - Map parameters) + String table) throws SQLException { Connection connection = null; @@ -1084,24 +988,6 @@ void testConnectionConstructorThrowing( rs.next(); assertThat(rs.getInt(1)).isEqualTo(3); - - List baseAssertions = - parameters.entrySet().stream() - .map( - entry -> - equalTo(DB_QUERY_PARAMETER.getAttributeKey(entry.getKey()), entry.getValue())) - .collect(Collectors.toList()); - - baseAssertions.addAll( - asList( - 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), sanitizedQuery), - equalTo(maybeStable(DB_OPERATION), "SELECT"), - equalTo(maybeStable(DB_SQL_TABLE), table))); - testing.waitAndAssertTraces( trace -> trace.hasSpansSatisfyingExactly( @@ -1110,7 +996,14 @@ void testConnectionConstructorThrowing( span.hasName(spanName) .hasKind(SpanKind.CLIENT) .hasParent(trace.getSpan(0)) - .hasAttributesSatisfyingExactly(baseAssertions))); + .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), sanitizedQuery), + equalTo(maybeStable(DB_OPERATION), "SELECT"), + equalTo(maybeStable(DB_SQL_TABLE), table)))); } static Stream getConnectionStream() { @@ -1234,8 +1127,7 @@ void testGetClientInfoException(String query) throws SQLException { equalTo( DB_CONNECTION_STRING, emitStableDatabaseSemconv() ? null : "testdb://localhost"), - equalTo(SERVER_ADDRESS, "localhost"), - equalTo(DB_QUERY_PARAMETER.getAttributeKey("0"), "123")))); + equalTo(SERVER_ADDRESS, "localhost")))); } static Stream spanNameStream() { @@ -1247,8 +1139,7 @@ static Stream spanNameStream() { "SELECT test.table", "test", "SELECT", - "table", - Collections.emptyMap()), + "table"), Arguments.of( "jdbc:testdb://localhost?databaseName=test", "SELECT 42", @@ -1256,8 +1147,7 @@ static Stream spanNameStream() { "SELECT test", "test", "SELECT", - null, - Collections.singletonMap("0", "42")), + null), Arguments.of( "jdbc:testdb://localhost", "SELECT * FROM table", @@ -1265,8 +1155,7 @@ static Stream spanNameStream() { "SELECT table", null, "SELECT", - "table", - Collections.emptyMap()), + "table"), Arguments.of( "jdbc:testdb://localhost?databaseName=test", "CREATE TABLE table", @@ -1274,8 +1163,7 @@ static Stream spanNameStream() { "CREATE TABLE test.table", "test", "CREATE TABLE", - "table", - Collections.emptyMap()), + "table"), Arguments.of( "jdbc:testdb://localhost", "CREATE TABLE table", @@ -1283,8 +1171,7 @@ static Stream spanNameStream() { "CREATE TABLE table", null, "CREATE TABLE", - "table", - Collections.emptyMap())); + "table")); } @ParameterizedTest @@ -1296,8 +1183,7 @@ void testProduceProperSpanName( String spanName, String databaseName, String operation, - String table, - Map parameters) + String table) throws SQLException { Driver driver = new TestDriver(); Connection connection = driver.connect(url, null); @@ -1310,24 +1196,6 @@ void testProduceProperSpanName( statement.executeQuery(query); }); - List baseAssertions = - parameters.entrySet().stream() - .map( - entry -> - equalTo(DB_QUERY_PARAMETER.getAttributeKey(entry.getKey()), entry.getValue())) - .collect(Collectors.toList()); - - baseAssertions.addAll( - asList( - equalTo(maybeStable(DB_SYSTEM), "other_sql"), - equalTo(maybeStable(DB_NAME), databaseName), - equalTo( - DB_CONNECTION_STRING, emitStableDatabaseSemconv() ? null : "testdb://localhost"), - equalTo(maybeStable(DB_STATEMENT), sanitizedQuery), - equalTo(maybeStable(DB_OPERATION), operation), - equalTo(maybeStable(DB_SQL_TABLE), table), - equalTo(SERVER_ADDRESS, "localhost"))); - testing.waitAndAssertTraces( trace -> trace.hasSpansSatisfyingExactly( @@ -1336,7 +1204,16 @@ DB_CONNECTION_STRING, emitStableDatabaseSemconv() ? null : "testdb://localhost") span.hasName(spanName) .hasKind(SpanKind.CLIENT) .hasParent(trace.getSpan(0)) - .hasAttributesSatisfyingExactly(baseAssertions))); + .hasAttributesSatisfyingExactly( + equalTo(maybeStable(DB_SYSTEM), "other_sql"), + equalTo(maybeStable(DB_NAME), databaseName), + equalTo( + DB_CONNECTION_STRING, + emitStableDatabaseSemconv() ? null : "testdb://localhost"), + equalTo(maybeStable(DB_STATEMENT), sanitizedQuery), + equalTo(maybeStable(DB_OPERATION), operation), + equalTo(maybeStable(DB_SQL_TABLE), table), + equalTo(SERVER_ADDRESS, "localhost")))); } @ParameterizedTest @@ -1388,8 +1265,7 @@ void testConnectionCached(String connectionPoolName) throws SQLException { maybeStable(DB_STATEMENT), "SELECT ? FROM INFORMATION_SCHEMA.SYSTEM_USERS"), equalTo(maybeStable(DB_OPERATION), "SELECT"), - equalTo(maybeStable(DB_SQL_TABLE), "INFORMATION_SCHEMA.SYSTEM_USERS"), - equalTo(DB_QUERY_PARAMETER.getAttributeKey("0"), "3"))); + equalTo(maybeStable(DB_SQL_TABLE), "INFORMATION_SCHEMA.SYSTEM_USERS"))); for (int i = 0; i < numQueries; i++) { assertions.add(traceAssertConsumer); } @@ -1655,8 +1531,8 @@ void testMultiBatch(String system, Connection connection, String username, Strin maybeStable(DB_STATEMENT), emitStableDatabaseSemconv() ? "INSERT INTO " - + tableName1 - + " VALUES(?); INSERT INTO multi_batch_test_2 VALUES(?)" + + tableName1 + + " VALUES(?); INSERT INTO multi_batch_test_2 VALUES(?)" : null), equalTo( maybeStable(DB_OPERATION), @@ -1701,8 +1577,7 @@ void testSingleItemBatch(String system, Connection connection, String username, maybeStable(DB_STATEMENT), "INSERT INTO " + tableName + " VALUES(?)"), equalTo(maybeStable(DB_OPERATION), "INSERT"), - equalTo(maybeStable(DB_SQL_TABLE), tableName), - equalTo(DB_QUERY_PARAMETER.getAttributeKey("0"), "1")))); + equalTo(maybeStable(DB_SQL_TABLE), tableName)))); } @ParameterizedTest diff --git a/instrumentation/jdbc/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jdbc/test/PreparedStatementParametersTest.java b/instrumentation/jdbc/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jdbc/test/PreparedStatementParametersTest.java new file mode 100644 index 000000000000..0f58c69f7677 --- /dev/null +++ b/instrumentation/jdbc/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jdbc/test/PreparedStatementParametersTest.java @@ -0,0 +1,992 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.jdbc.test; + +import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitStableDatabaseSemconv; +import static io.opentelemetry.instrumentation.testing.junit.db.SemconvStabilityUtil.maybeStable; +import static io.opentelemetry.instrumentation.testing.junit.db.SemconvStabilityUtil.maybeStableDbSystemName; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; +import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_CONNECTION_STRING; +import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_NAME; +import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_OPERATION; +import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_QUERY_PARAMETER; +import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_SQL_TABLE; +import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_STATEMENT; +import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_SYSTEM; +import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_USER; + +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Maps; +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.instrumentation.testing.internal.AutoCleanupExtension; +import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import java.math.BigDecimal; +import java.net.URI; +import java.sql.Connection; +import java.sql.Date; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Time; +import java.sql.Timestamp; +import java.sql.Types; +import java.util.Calendar; +import java.util.Locale; +import java.util.Map; +import java.util.Properties; +import java.util.stream.Stream; +import org.apache.derby.jdbc.EmbeddedDriver; +import org.hsqldb.jdbc.JDBCDriver; +import org.junit.Ignore; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +@SuppressWarnings("deprecation") // using deprecated semconv +class PreparedStatementParametersTest { + + @RegisterExtension + static final AutoCleanupExtension cleanup = AutoCleanupExtension.create(); + + @RegisterExtension + static final InstrumentationExtension testing = AgentInstrumentationExtension.create(); + + private static final String dbName = "jdbcUnitTest"; + private static final String dbNameLower = dbName.toLowerCase(Locale.ROOT); + + private static final Map jdbcUrls = + ImmutableMap.of( + "h2", "jdbc:h2:mem:" + dbName, + "derby", "jdbc:derby:memory:" + dbName, + "hsqldb", "jdbc:hsqldb:mem:" + dbName); + private static final Map jdbcUserNames = Maps.newHashMap(); + private static final Properties connectionProps = new Properties(); + + static { + jdbcUserNames.put("derby", "APP"); + jdbcUserNames.put("h2", null); + jdbcUserNames.put("hsqldb", "SA"); + + connectionProps.put("databaseName", "someDb"); + connectionProps.put("OPEN_NEW", "true"); // So H2 doesn't complain about username/password. + connectionProps.put("create", "true"); + } + + @BeforeAll + static void setUp() {} + + @AfterAll + static void tearDown() {} + + static Stream preparedStatementStream() throws SQLException { + return Stream.of( + Arguments.of( + "h2", + new org.h2.Driver().connect(jdbcUrls.get("h2"), connectionProps), + null, + "SELECT 3, ?", + "SELECT 3, ?", + "SELECT " + dbNameLower, + "h2:mem:", + null), + Arguments.of( + "derby", + new EmbeddedDriver().connect(jdbcUrls.get("derby"), connectionProps), + "APP", + "SELECT 3 FROM SYSIBM.SYSDUMMY1 WHERE IBMREQD=? OR 1=1", + "SELECT 3 FROM SYSIBM.SYSDUMMY1 WHERE IBMREQD=? OR 1=1", + "SELECT SYSIBM.SYSDUMMY1", + "derby:memory:", + "SYSIBM.SYSDUMMY1"), + Arguments.of( + "hsqldb", + new JDBCDriver().connect(jdbcUrls.get("hsqldb"), connectionProps), + "SA", + "SELECT 3 FROM INFORMATION_SCHEMA.SYSTEM_USERS WHERE USER_NAME=? OR 1=1", + "SELECT 3 FROM INFORMATION_SCHEMA.SYSTEM_USERS WHERE USER_NAME=? OR 1=1", + "SELECT INFORMATION_SCHEMA.SYSTEM_USERS", + "hsqldb:mem:", + "INFORMATION_SCHEMA.SYSTEM_USERS") + ); + } + + @ParameterizedTest + @MethodSource("preparedStatementStream") + void testNull2PreparedStatementParameter( + String system, + Connection connection, + String username, + String query, + String sanitizedQuery, + String spanName, + String url, + String table) + throws SQLException { + PreparedStatement statement = connection.prepareStatement(query); + cleanup.deferCleanup(statement); + + ResultSet resultSet = testing.runWithSpan( + "parent", + () -> { + statement.setNull(1, Types.INTEGER); + statement.execute(); + return statement.getResultSet(); + }); + + resultSet.next(); + assertThat(resultSet.getInt(1)).isEqualTo(3); + + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), + span -> + span.hasName(spanName) + .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), sanitizedQuery), + equalTo(maybeStable(DB_OPERATION), "SELECT"), + equalTo(maybeStable(DB_SQL_TABLE), table), + equalTo(DB_QUERY_PARAMETER.getAttributeKey("0"), "")))); + } + + @ParameterizedTest + @MethodSource("preparedStatementStream") + void testNull3PreparedStatementParameter( + String system, + Connection connection, + String username, + String query, + String sanitizedQuery, + String spanName, + String url, + String table) + throws SQLException { + PreparedStatement statement = connection.prepareStatement(query); + cleanup.deferCleanup(statement); + + ResultSet resultSet = testing.runWithSpan( + "parent", + () -> { + statement.setNull(1, Types.INTEGER, "Integer"); + statement.execute(); + return statement.getResultSet(); + }); + + resultSet.next(); + assertThat(resultSet.getInt(1)).isEqualTo(3); + + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), + span -> + span.hasName(spanName) + .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), sanitizedQuery), + equalTo(maybeStable(DB_OPERATION), "SELECT"), + equalTo(maybeStable(DB_SQL_TABLE), table), + equalTo(DB_QUERY_PARAMETER.getAttributeKey("0"), "")))); + } + + @ParameterizedTest + @MethodSource("preparedStatementStream") + void testBytePreparedStatementParameter( + String system, + Connection connection, + String username, + String query, + String sanitizedQuery, + String spanName, + String url, + String table) + throws SQLException { + PreparedStatement statement = connection.prepareStatement(query); + cleanup.deferCleanup(statement); + + ResultSet resultSet = testing.runWithSpan( + "parent", + () -> { + statement.setByte(1, (byte) 0); + statement.execute(); + return statement.getResultSet(); + }); + + resultSet.next(); + assertThat(resultSet.getInt(1)).isEqualTo(3); + + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), + span -> + span.hasName(spanName) + .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), sanitizedQuery), + equalTo(maybeStable(DB_OPERATION), "SELECT"), + equalTo(maybeStable(DB_SQL_TABLE), table), + equalTo(DB_QUERY_PARAMETER.getAttributeKey("0"), "0x00")))); + } + + @ParameterizedTest + @MethodSource("preparedStatementStream") + void testBytesPreparedStatementParameter( + String system, + Connection connection, + String username, + String query, + String sanitizedQuery, + String spanName, + String url, + String table) + throws SQLException { + String updatedColumn = query.replace("USER_NAME=?", "HEXTORAW('FF')=?"); + String updatedColumnSanitized = sanitizedQuery.replace("USER_NAME=?", "HEXTORAW('FF')=?"); + PreparedStatement statement = connection.prepareStatement(updatedColumn); + cleanup.deferCleanup(statement); + + ResultSet resultSet = testing.runWithSpan( + "parent", + () -> { + statement.setBytes(1, new byte[] {(byte) 0, 0}); + statement.execute(); + return statement.getResultSet(); + }); + + resultSet.next(); + assertThat(resultSet.getInt(1)).isEqualTo(3); + + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), + span -> + span.hasName(spanName) + .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), updatedColumnSanitized), + equalTo(maybeStable(DB_OPERATION), "SELECT"), + equalTo(maybeStable(DB_SQL_TABLE), table), + equalTo(DB_QUERY_PARAMETER.getAttributeKey("0"), "0x0000")))); + } + + @ParameterizedTest + @MethodSource("preparedStatementStream") + void testShortPreparedStatementParameter( + String system, + Connection connection, + String username, + String query, + String sanitizedQuery, + String spanName, + String url, + String table) + throws SQLException { + PreparedStatement statement = connection.prepareStatement(query); + cleanup.deferCleanup(statement); + + ResultSet resultSet = testing.runWithSpan( + "parent", + () -> { + statement.setShort(1, (short)0); + statement.execute(); + return statement.getResultSet(); + }); + + resultSet.next(); + assertThat(resultSet.getInt(1)).isEqualTo(3); + + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), + span -> + span.hasName(spanName) + .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), sanitizedQuery), + equalTo(maybeStable(DB_OPERATION), "SELECT"), + equalTo(maybeStable(DB_SQL_TABLE), table), + equalTo(DB_QUERY_PARAMETER.getAttributeKey("0"), "0")))); + } + + @ParameterizedTest + @MethodSource("preparedStatementStream") + void testIntPreparedStatementParameter( + String system, + Connection connection, + String username, + String query, + String sanitizedQuery, + String spanName, + String url, + String table) + throws SQLException { + PreparedStatement statement = connection.prepareStatement(query); + cleanup.deferCleanup(statement); + + ResultSet resultSet = testing.runWithSpan( + "parent", + () -> { + statement.setInt(1, 0); + statement.execute(); + return statement.getResultSet(); + }); + + resultSet.next(); + assertThat(resultSet.getInt(1)).isEqualTo(3); + + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), + span -> + span.hasName(spanName) + .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), sanitizedQuery), + equalTo(maybeStable(DB_OPERATION), "SELECT"), + equalTo(maybeStable(DB_SQL_TABLE), table), + equalTo(DB_QUERY_PARAMETER.getAttributeKey("0"), "0")))); + } + + @ParameterizedTest + @MethodSource("preparedStatementStream") + void testLongPreparedStatementParameter( + String system, + Connection connection, + String username, + String query, + String sanitizedQuery, + String spanName, + String url, + String table) + throws SQLException { + PreparedStatement statement = connection.prepareStatement(query); + cleanup.deferCleanup(statement); + + ResultSet resultSet = testing.runWithSpan( + "parent", + () -> { + statement.setLong(1, 0); + statement.execute(); + return statement.getResultSet(); + }); + + resultSet.next(); + assertThat(resultSet.getInt(1)).isEqualTo(3); + + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), + span -> + span.hasName(spanName) + .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), sanitizedQuery), + equalTo(maybeStable(DB_OPERATION), "SELECT"), + equalTo(maybeStable(DB_SQL_TABLE), table), + equalTo(DB_QUERY_PARAMETER.getAttributeKey("0"), "0")))); + } + + @ParameterizedTest + @MethodSource("preparedStatementStream") + void testFloatPreparedStatementParameter( + String system, + Connection connection, + String username, + String query, + String sanitizedQuery, + String spanName, + String url, + String table) + throws SQLException { + PreparedStatement statement = connection.prepareStatement(query); + cleanup.deferCleanup(statement); + + ResultSet resultSet = testing.runWithSpan( + "parent", + () -> { + statement.setFloat(1, (float) 0.1); + statement.execute(); + return statement.getResultSet(); + }); + + resultSet.next(); + assertThat(resultSet.getInt(1)).isEqualTo(3); + + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), + span -> + span.hasName(spanName) + .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), sanitizedQuery), + equalTo(maybeStable(DB_OPERATION), "SELECT"), + equalTo(maybeStable(DB_SQL_TABLE), table), + equalTo(DB_QUERY_PARAMETER.getAttributeKey("0"), "0.1")))); + } + + @ParameterizedTest + @MethodSource("preparedStatementStream") + void testDoublePreparedStatementParameter( + String system, + Connection connection, + String username, + String query, + String sanitizedQuery, + String spanName, + String url, + String table) + throws SQLException { + PreparedStatement statement = connection.prepareStatement(query); + cleanup.deferCleanup(statement); + + ResultSet resultSet = testing.runWithSpan( + "parent", + () -> { + statement.setDouble(1, 0.1); + statement.execute(); + return statement.getResultSet(); + }); + + resultSet.next(); + assertThat(resultSet.getInt(1)).isEqualTo(3); + + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), + span -> + span.hasName(spanName) + .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), sanitizedQuery), + equalTo(maybeStable(DB_OPERATION), "SELECT"), + equalTo(maybeStable(DB_SQL_TABLE), table), + equalTo(DB_QUERY_PARAMETER.getAttributeKey("0"), "0.1")))); + } + + @ParameterizedTest + @MethodSource("preparedStatementStream") + void testBigDecimalPreparedStatementParameter( + String system, + Connection connection, + String username, + String query, + String sanitizedQuery, + String spanName, + String url, + String table) + throws SQLException { + PreparedStatement statement = connection.prepareStatement(query); + cleanup.deferCleanup(statement); + + ResultSet resultSet = testing.runWithSpan( + "parent", + () -> { + statement.setBigDecimal(1, BigDecimal.ZERO); + statement.execute(); + return statement.getResultSet(); + }); + + resultSet.next(); + assertThat(resultSet.getInt(1)).isEqualTo(3); + + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), + span -> + span.hasName(spanName) + .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), sanitizedQuery), + equalTo(maybeStable(DB_OPERATION), "SELECT"), + equalTo(maybeStable(DB_SQL_TABLE), table), + equalTo(DB_QUERY_PARAMETER.getAttributeKey("0"), "0")))); + } + + @ParameterizedTest + @MethodSource("preparedStatementStream") + void testStringPreparedStatementParameter( + String system, + Connection connection, + String username, + String query, + String sanitizedQuery, + String spanName, + String url, + String table) + throws SQLException { + PreparedStatement statement = connection.prepareStatement(query); + cleanup.deferCleanup(statement); + + ResultSet resultSet = testing.runWithSpan( + "parent", + () -> { + statement.setString(1, "S"); + statement.execute(); + return statement.getResultSet(); + }); + + resultSet.next(); + assertThat(resultSet.getInt(1)).isEqualTo(3); + + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), + span -> + span.hasName(spanName) + .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), sanitizedQuery), + equalTo(maybeStable(DB_OPERATION), "SELECT"), + equalTo(maybeStable(DB_SQL_TABLE), table), + equalTo(DB_QUERY_PARAMETER.getAttributeKey("0"), "'S'")))); + } + + @ParameterizedTest + @MethodSource("preparedStatementStream") + void testDate2PreparedStatementParameter( + String system, + Connection connection, + String username, + String query, + String sanitizedQuery, + String spanName, + String url, + String table) + throws SQLException { + String updatedColumn = query.replace("USER_NAME=?", "CURDATE()=?"); + String updatedColumnSanitized = sanitizedQuery.replace("USER_NAME=?", "CURDATE()=?"); + PreparedStatement statement = connection.prepareStatement(updatedColumn); + cleanup.deferCleanup(statement); + + ResultSet resultSet = testing.runWithSpan( + "parent", + () -> { + statement.setDate(1, Date.valueOf("2000-01-01")); + statement.execute(); + return statement.getResultSet(); + }); + + resultSet.next(); + assertThat(resultSet.getInt(1)).isEqualTo(3); + + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), + span -> + span.hasName(spanName) + .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), updatedColumnSanitized), + equalTo(maybeStable(DB_OPERATION), "SELECT"), + equalTo(maybeStable(DB_SQL_TABLE), table), + equalTo(DB_QUERY_PARAMETER.getAttributeKey("0"), "'2000-01-01'")))); + } + + @ParameterizedTest + @MethodSource("preparedStatementStream") + void testDate3PreparedStatementParameter( + String system, + Connection connection, + String username, + String query, + String sanitizedQuery, + String spanName, + String url, + String table) + throws SQLException { + String updatedColumn = query.replace("USER_NAME=?", "CURDATE()=?"); + String updatedColumnSanitized = sanitizedQuery.replace("USER_NAME=?", "CURDATE()=?"); + PreparedStatement statement = connection.prepareStatement(updatedColumn); + cleanup.deferCleanup(statement); + + ResultSet resultSet = testing.runWithSpan( + "parent", + () -> { + statement.setDate(1, Date.valueOf("2000-01-01"), Calendar.getInstance()); + statement.execute(); + return statement.getResultSet(); + }); + + resultSet.next(); + assertThat(resultSet.getInt(1)).isEqualTo(3); + + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), + span -> + span.hasName(spanName) + .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), updatedColumnSanitized), + equalTo(maybeStable(DB_OPERATION), "SELECT"), + equalTo(maybeStable(DB_SQL_TABLE), table), + equalTo(DB_QUERY_PARAMETER.getAttributeKey("0"), "'2000-01-01'")))); + } + + @ParameterizedTest + @MethodSource("preparedStatementStream") + void testTime2PreparedStatementParameter( + String system, + Connection connection, + String username, + String query, + String sanitizedQuery, + String spanName, + String url, + String table) + throws SQLException { + String updatedColumn = query.replace("USER_NAME=?", "CURTIME()=?"); + String updatedColumnSanitized = sanitizedQuery.replace("USER_NAME=?", "CURTIME()=?"); + PreparedStatement statement = connection.prepareStatement(updatedColumn); + cleanup.deferCleanup(statement); + + ResultSet resultSet = testing.runWithSpan( + "parent", + () -> { + statement.setTime(1, Time.valueOf("00:00:00")); + statement.execute(); + return statement.getResultSet(); + }); + + resultSet.next(); + assertThat(resultSet.getInt(1)).isEqualTo(3); + + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), + span -> + span.hasName(spanName) + .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), updatedColumnSanitized), + equalTo(maybeStable(DB_OPERATION), "SELECT"), + equalTo(maybeStable(DB_SQL_TABLE), table), + equalTo(DB_QUERY_PARAMETER.getAttributeKey("0"), "'00:00:00'")))); + } + + @ParameterizedTest + @MethodSource("preparedStatementStream") + void testTime3PreparedStatementParameter( + String system, + Connection connection, + String username, + String query, + String sanitizedQuery, + String spanName, + String url, + String table) + throws SQLException { + String updatedColumn = query.replace("USER_NAME=?", "CURTIME()=?"); + String updatedColumnSanitized = sanitizedQuery.replace("USER_NAME=?", "CURTIME()=?"); + PreparedStatement statement = connection.prepareStatement(updatedColumn); + cleanup.deferCleanup(statement); + + ResultSet resultSet = testing.runWithSpan( + "parent", + () -> { + statement.setTime(1, Time.valueOf("00:00:00"), Calendar.getInstance()); + statement.execute(); + return statement.getResultSet(); + }); + + resultSet.next(); + assertThat(resultSet.getInt(1)).isEqualTo(3); + + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), + span -> + span.hasName(spanName) + .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), updatedColumnSanitized), + equalTo(maybeStable(DB_OPERATION), "SELECT"), + equalTo(maybeStable(DB_SQL_TABLE), table), + equalTo(DB_QUERY_PARAMETER.getAttributeKey("0"), "'00:00:00'")))); + } + + @ParameterizedTest + @MethodSource("preparedStatementStream") + void testTimestamp2PreparedStatementParameter( + String system, + Connection connection, + String username, + String query, + String sanitizedQuery, + String spanName, + String url, + String table) + throws SQLException { + String updatedColumn = query.replace("USER_NAME=?", "NOW()=?"); + String updatedColumnSanitized = sanitizedQuery.replace("USER_NAME=?", "NOW()=?"); + PreparedStatement statement = connection.prepareStatement(updatedColumn); + cleanup.deferCleanup(statement); + + ResultSet resultSet = testing.runWithSpan( + "parent", + () -> { + statement.setTimestamp(1, Timestamp.valueOf("2000-01-01 00:00:00")); + statement.execute(); + return statement.getResultSet(); + }); + + resultSet.next(); + assertThat(resultSet.getInt(1)).isEqualTo(3); + + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), + span -> + span.hasName(spanName) + .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), updatedColumnSanitized), + equalTo(maybeStable(DB_OPERATION), "SELECT"), + equalTo(maybeStable(DB_SQL_TABLE), table), + equalTo(DB_QUERY_PARAMETER.getAttributeKey("0"), "'2000-01-01 00:00:00.0'")))); + } + + @ParameterizedTest + @MethodSource("preparedStatementStream") + void testTimestamp3PreparedStatementParameter( + String system, + Connection connection, + String username, + String query, + String sanitizedQuery, + String spanName, + String url, + String table) + throws SQLException { + String updatedColumn = query.replace("USER_NAME=?", "NOW()=?"); + String updatedColumnSanitized = sanitizedQuery.replace("USER_NAME=?", "NOW()=?"); + PreparedStatement statement = connection.prepareStatement(updatedColumn); + cleanup.deferCleanup(statement); + + ResultSet resultSet = testing.runWithSpan( + "parent", + () -> { + statement.setTimestamp(1, Timestamp.valueOf("2000-01-01 00:00:00"), Calendar.getInstance()); + statement.execute(); + return statement.getResultSet(); + }); + + resultSet.next(); + assertThat(resultSet.getInt(1)).isEqualTo(3); + + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), + span -> + span.hasName(spanName) + .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), updatedColumnSanitized), + equalTo(maybeStable(DB_OPERATION), "SELECT"), + equalTo(maybeStable(DB_SQL_TABLE), table), + equalTo(DB_QUERY_PARAMETER.getAttributeKey("0"), "'2000-01-01 00:00:00.0'")))); + } + + @Ignore("not supported by tested drivers") + void testURLPreparedStatementParameter( + String system, + Connection connection, + String username, + String query, + String sanitizedQuery, + String spanName, + String url, + String table) + throws Exception { + PreparedStatement statement = connection.prepareStatement(query); + cleanup.deferCleanup(statement); + + ResultSet resultSet = testing.runWithSpan( + "parent", + () -> { + statement.setURL(1, URI.create("http://localhost:8080").toURL()); + statement.execute(); + return statement.getResultSet(); + }); + + resultSet.next(); + assertThat(resultSet.getInt(1)).isEqualTo(3); + + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), + span -> + span.hasName(spanName) + .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), sanitizedQuery), + equalTo(maybeStable(DB_OPERATION), "SELECT"), + equalTo(maybeStable(DB_SQL_TABLE), table), + equalTo(DB_QUERY_PARAMETER.getAttributeKey("0"), "'http://localhost:8080'")))); + } + + @ParameterizedTest + @MethodSource("preparedStatementStream") + void testNStringPreparedStatementParameter( + String system, + Connection connection, + String username, + String query, + String sanitizedQuery, + String spanName, + String url, + String table) + throws SQLException { + if (!system.equalsIgnoreCase( "derby")) { + PreparedStatement statement = connection.prepareStatement(query); + cleanup.deferCleanup(statement); + + ResultSet resultSet = testing.runWithSpan( + "parent", + () -> { + statement.setNString(1, "S"); + statement.execute(); + return statement.getResultSet(); + }); + + resultSet.next(); + assertThat(resultSet.getInt(1)).isEqualTo(3); + + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), + span -> + span.hasName(spanName) + .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), sanitizedQuery), + equalTo(maybeStable(DB_OPERATION), "SELECT"), + equalTo(maybeStable(DB_SQL_TABLE), table), + equalTo(DB_QUERY_PARAMETER.getAttributeKey("0"), "'S'")))); + } + } + +} diff --git a/instrumentation/jdbc/javaagent/src/test/scala/io/opentelemetry/javaagent/instrumentation/scalaexecutors/SlickTest.scala b/instrumentation/jdbc/javaagent/src/test/scala/io/opentelemetry/javaagent/instrumentation/scalaexecutors/SlickTest.scala index 4c2a5df5d001..79c74f466a16 100644 --- a/instrumentation/jdbc/javaagent/src/test/scala/io/opentelemetry/javaagent/instrumentation/scalaexecutors/SlickTest.scala +++ b/instrumentation/jdbc/javaagent/src/test/scala/io/opentelemetry/javaagent/instrumentation/scalaexecutors/SlickTest.scala @@ -98,11 +98,7 @@ class SlickTest { if (emitStableDatabaseSemconv()) null else "h2:mem:" ), equalTo(maybeStable(DB_STATEMENT), "SELECT ?"), - equalTo(maybeStable(DB_OPERATION), "SELECT"), - equalTo( - DB_QUERY_PARAMETER.getAttributeKey("0"), - TestValue.toString - ) + equalTo(maybeStable(DB_OPERATION), "SELECT") ) } ) 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 index bb8b4561fe1e..12faa2dea3f9 100644 --- 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 @@ -16,7 +16,7 @@ public final class JdbcTelemetryBuilder { private boolean dataSourceInstrumenterEnabled = true; private boolean statementInstrumenterEnabled = true; private boolean statementSanitizationEnabled = true; - private boolean queryParameterEnabled = false; + private boolean captureQueryParameters = false; JdbcTelemetryBuilder(OpenTelemetry openTelemetry) { this.openTelemetry = openTelemetry; @@ -40,12 +40,22 @@ public JdbcTelemetryBuilder setStatementInstrumenterEnabled(boolean enabled) { @CanIgnoreReturnValue public JdbcTelemetryBuilder setStatementSanitizationEnabled(boolean enabled) { this.statementSanitizationEnabled = enabled; + + if (enabled) { + this.captureQueryParameters = false; + } + return this; } @CanIgnoreReturnValue - public JdbcTelemetryBuilder setQueryParameterEnabled(boolean enabled) { - this.queryParameterEnabled = enabled; + public JdbcTelemetryBuilder setCaptureQueryParameters(boolean enabled) { + this.captureQueryParameters = enabled; + + if (enabled) { + this.statementSanitizationEnabled = false; + } + return this; } @@ -58,6 +68,6 @@ public JdbcTelemetry build() { openTelemetry, statementInstrumenterEnabled, statementSanitizationEnabled, - queryParameterEnabled)); + captureQueryParameters)); } } diff --git a/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/DbRequest.java b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/DbRequest.java index bc34753cd8ed..ac3af0d555fe 100644 --- a/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/DbRequest.java +++ b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/DbRequest.java @@ -27,7 +27,7 @@ public abstract class DbRequest { @Nullable public static DbRequest create( - PreparedStatement statement, Map preparedStatementParameters) { + PreparedStatement statement, Map preparedStatementParameters) { return create( statement, JdbcData.preparedStatement.get(statement), preparedStatementParameters); } @@ -36,7 +36,7 @@ public static DbRequest create( public static DbRequest create( Statement statement, String dbStatementString, - Map preparedStatementParameters) { + Map preparedStatementParameters) { return create(statement, dbStatementString, null, preparedStatementParameters); } @@ -45,7 +45,7 @@ public static DbRequest create( Statement statement, String dbStatementString, Long batchSize, - Map preparedStatementParameters) { + Map preparedStatementParameters) { Connection connection = connectionFromStatement(statement); if (connection == null) { return null; @@ -59,7 +59,7 @@ public static DbRequest create( Statement statement, Collection queryTexts, Long batchSize, - Map preparedStatementParameters) { + Map preparedStatementParameters) { Connection connection = connectionFromStatement(statement); if (connection == null) { return null; @@ -76,7 +76,7 @@ public static DbRequest create( DbInfo dbInfo, String queryText, Long batchSize, - Map preparedStatementParameters) { + Map preparedStatementParameters) { return create( dbInfo, Collections.singletonList(queryText), batchSize, preparedStatementParameters); } @@ -85,7 +85,7 @@ public static DbRequest create( DbInfo dbInfo, Collection queryTexts, Long batchSize, - Map preparedStatementParameters) { + Map preparedStatementParameters) { return new AutoValue_DbRequest(dbInfo, queryTexts, batchSize, preparedStatementParameters); } @@ -97,5 +97,5 @@ public static DbRequest create( public abstract Long getBatchSize(); @Nullable - public abstract Map getPreparedStatementParameters(); + public abstract Map getPreparedStatementParameters(); } diff --git a/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/JdbcAttributesGetter.java b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/JdbcAttributesGetter.java index 29850eb6e7cd..dc9c83ae6696 100644 --- a/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/JdbcAttributesGetter.java +++ b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/JdbcAttributesGetter.java @@ -66,7 +66,7 @@ public String getResponseStatus(@Nullable Void response, @Nullable Throwable err @Nullable @Override - public Map getQueryParameters(DbRequest request) { + public Map getQueryParameters(DbRequest request) { return request.getPreparedStatementParameters(); } } diff --git a/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/JdbcData.java b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/JdbcData.java index 0719c87af171..a1ebae16f2ed 100644 --- a/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/JdbcData.java +++ b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/JdbcData.java @@ -36,7 +36,7 @@ public final class JdbcData { private static final VirtualField preparedStatementBatch = VirtualField.find(PreparedStatement.class, PreparedStatementBatchInfo.class); - public static final VirtualField> parameters = + public static final VirtualField> parameters = VirtualField.find(PreparedStatement.class, Map.class); private JdbcData() {} @@ -98,8 +98,8 @@ public static Long getPreparedStatementBatchSize(PreparedStatement statement) { return batchInfo != null ? batchInfo.getBatchSize() : null; } - public static void addParameter(PreparedStatement statement, int index, Object value) { - Map parametersMap = parameters.get(statement); + public static void addParameter(PreparedStatement statement, String index, String value) { + Map parametersMap = parameters.get(statement); if (parametersMap == null) { parametersMap = new HashMap<>(); parameters.set(statement, parametersMap); 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 0d7a92cec233..38a87a5dbe19 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 @@ -40,15 +40,28 @@ public static Instrumenter createStatementInstrumenter( true, ConfigPropertiesUtil.getBoolean( "otel.instrumentation.common.db-statement-sanitizer.enabled", true), - // TODO change with common conf key - ConfigPropertiesUtil.getBoolean("otel.instrumentation.jdbc.query-parameter.enabled", true)); + ConfigPropertiesUtil.getBoolean( + "otel.instrumentation.jdbc.capture-query-parameters", false)); + } + + public static Instrumenter createStatementInstrumenter( + OpenTelemetry openTelemetry, + boolean captureQueryParameters + ) { + return createStatementInstrumenter( + openTelemetry, + true, + ConfigPropertiesUtil.getBoolean( + "otel.instrumentation.common.db-statement-sanitizer.enabled", true), + ConfigPropertiesUtil.getBoolean( + "otel.instrumentation.jdbc.capture-query-parameters", captureQueryParameters)); } public static Instrumenter createStatementInstrumenter( OpenTelemetry openTelemetry, boolean enabled, boolean statementSanitizationEnabled, - boolean queryParameterEnabled) { + boolean captureQueryParameters) { return Instrumenter.builder( openTelemetry, INSTRUMENTATION_NAME, @@ -56,7 +69,7 @@ public static Instrumenter createStatementInstrumenter( .addAttributesExtractor( SqlClientAttributesExtractor.builder(dbAttributesGetter) .setStatementSanitizationEnabled(statementSanitizationEnabled) - .setQueryParameterEnabled(queryParameterEnabled) + .setCaptureQueryParameters(captureQueryParameters) .build()) .addAttributesExtractor(ServerAttributesExtractor.create(netAttributesGetter)) .addOperationMetrics(DbClientMetrics.get()) diff --git a/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/JdbcPreparedStatementStringifier.java b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/JdbcPreparedStatementStringifier.java new file mode 100644 index 000000000000..54fe1ce3ad54 --- /dev/null +++ b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/JdbcPreparedStatementStringifier.java @@ -0,0 +1,68 @@ +package io.opentelemetry.instrumentation.jdbc.internal; + +import java.net.URL; +import java.sql.Date; +import java.sql.RowId; +import java.sql.Time; +import java.sql.Timestamp; + +/** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. + */ +public class JdbcPreparedStatementStringifier { + + private JdbcPreparedStatementStringifier() {} + + public static String stringifyParameter(String value) { + return String.format("'%s'", value); + } + + public static String stringifyParameter(Number value) { + return String.format("%s", value); + } + + public static String stringifyParameter(boolean value) { + return String.format("%s", value); + } + + @SuppressWarnings("DefaultLocale") + public static String stringifyParameter(byte value) { + return String.format("0x%02x", value); + } + + @SuppressWarnings("DefaultLocale") + public static String stringifyParameter(byte[] value) { + StringBuilder builder = new StringBuilder(); + builder.append("0x"); + for (Byte b : value) { + builder.append(String.format("%02x", b)); + } + return builder.toString(); + } + + public static String stringifyParameter(Date value) { + return String.format("'%s'", value.toString()); + } + + public static String stringifyParameter(Time value) { + return String.format("'%s'", value.toString()); + } + + public static String stringifyParameter(Timestamp value) { + return String.format("'%s'", value.toString()); + } + + public static String stringifyParameter(URL value) { + return String.format("'%s'", value.toString()); + } + + public static String stringifyParameter(RowId value) { + return String.format("'%s'", value.toString()); + } + + public static String stringifyNullParameter() { + return ""; + } + +} diff --git a/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/OpenTelemetryPreparedStatement.java b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/OpenTelemetryPreparedStatement.java index 2d97500f4c86..b1d08e5f885e 100644 --- a/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/OpenTelemetryPreparedStatement.java +++ b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/OpenTelemetryPreparedStatement.java @@ -46,10 +46,13 @@ import java.util.HashMap; import java.util.Map; +import static io.opentelemetry.instrumentation.jdbc.internal.JdbcPreparedStatementStringifier.stringifyNullParameter; +import static io.opentelemetry.instrumentation.jdbc.internal.JdbcPreparedStatementStringifier.stringifyParameter; + @SuppressWarnings("OverloadMethodsDeclarationOrder") class OpenTelemetryPreparedStatement extends OpenTelemetryStatement implements PreparedStatement { - private final Map parameters; + private final Map parameters; public OpenTelemetryPreparedStatement( S delegate, @@ -61,6 +64,10 @@ public OpenTelemetryPreparedStatement( this.parameters = new HashMap<>(); } + private void putParameter(int index, String value) { + parameters.put(Integer.toString(index - 1), value); + } + @Override public ResultSet executeQuery() throws SQLException { return new OpenTelemetryResultSet(wrapCall(query, delegate::executeQuery), this); @@ -80,185 +87,175 @@ public boolean execute() throws SQLException { @Override public void setNull(int parameterIndex, int sqlType) throws SQLException { delegate.setNull(parameterIndex, sqlType); - parameters.put(parameterIndex, null); + putParameter(parameterIndex, stringifyNullParameter()); } @SuppressWarnings("UngroupedOverloads") @Override public void setNull(int parameterIndex, int sqlType, String typeName) throws SQLException { delegate.setNull(parameterIndex, sqlType, typeName); - parameters.put(parameterIndex, null); + putParameter(parameterIndex, stringifyNullParameter()); } @Override public void setBoolean(int parameterIndex, boolean x) throws SQLException { delegate.setBoolean(parameterIndex, x); - parameters.put(parameterIndex, x); + putParameter(parameterIndex, stringifyParameter(x)); } @Override public void setByte(int parameterIndex, byte x) throws SQLException { delegate.setByte(parameterIndex, x); - parameters.put(parameterIndex, x); + putParameter(parameterIndex, stringifyParameter(x)); } @Override public void setShort(int parameterIndex, short x) throws SQLException { delegate.setShort(parameterIndex, x); - parameters.put(parameterIndex, x); + putParameter(parameterIndex, stringifyParameter(x)); } @Override public void setInt(int parameterIndex, int x) throws SQLException { delegate.setInt(parameterIndex, x); - parameters.put(parameterIndex, x); + putParameter(parameterIndex, stringifyParameter(x)); } @Override public void setLong(int parameterIndex, long x) throws SQLException { delegate.setLong(parameterIndex, x); - parameters.put(parameterIndex, x); + putParameter(parameterIndex, stringifyParameter(x)); } @Override public void setFloat(int parameterIndex, float x) throws SQLException { delegate.setFloat(parameterIndex, x); - parameters.put(parameterIndex, x); + putParameter(parameterIndex, stringifyParameter(x)); } @Override public void setDouble(int parameterIndex, double x) throws SQLException { delegate.setDouble(parameterIndex, x); - parameters.put(parameterIndex, x); + putParameter(parameterIndex, stringifyParameter(x)); } @Override public void setBigDecimal(int parameterIndex, BigDecimal x) throws SQLException { delegate.setBigDecimal(parameterIndex, x); - parameters.put(parameterIndex, x); + putParameter(parameterIndex, stringifyParameter(x)); } @Override public void setString(int parameterIndex, String x) throws SQLException { delegate.setString(parameterIndex, x); - parameters.put(parameterIndex, x); + putParameter(parameterIndex, stringifyParameter(x)); } @Override public void setBytes(int parameterIndex, byte[] x) throws SQLException { delegate.setBytes(parameterIndex, x); - parameters.put(parameterIndex, x); + putParameter(parameterIndex, stringifyParameter(x)); } @SuppressWarnings("UngroupedOverloads") @Override public void setDate(int parameterIndex, Date x) throws SQLException { delegate.setDate(parameterIndex, x); - parameters.put(parameterIndex, x); + putParameter(parameterIndex, stringifyParameter(x)); } @SuppressWarnings("UngroupedOverloads") @Override public void setDate(int parameterIndex, Date x, Calendar cal) throws SQLException { delegate.setDate(parameterIndex, x, cal); - parameters.put(parameterIndex, x); + putParameter(parameterIndex, stringifyParameter(x)); } @SuppressWarnings("UngroupedOverloads") @Override public void setTime(int parameterIndex, Time x) throws SQLException { delegate.setTime(parameterIndex, x); - parameters.put(parameterIndex, x); + putParameter(parameterIndex, stringifyParameter(x)); } @Override public void setTime(int parameterIndex, Time x, Calendar cal) throws SQLException { delegate.setTime(parameterIndex, x, cal); - parameters.put(parameterIndex, x); + putParameter(parameterIndex, stringifyParameter(x)); } @SuppressWarnings("UngroupedOverloads") @Override public void setTimestamp(int parameterIndex, Timestamp x) throws SQLException { delegate.setTimestamp(parameterIndex, x); - parameters.put(parameterIndex, x); + putParameter(parameterIndex, stringifyParameter(x)); } @SuppressWarnings("UngroupedOverloads") @Override public void setTimestamp(int parameterIndex, Timestamp x, Calendar cal) throws SQLException { delegate.setTimestamp(parameterIndex, x, cal); - parameters.put(parameterIndex, x); + putParameter(parameterIndex, stringifyParameter(x)); } @SuppressWarnings("UngroupedOverloads") @Override public void setAsciiStream(int parameterIndex, InputStream x, int length) throws SQLException { delegate.setAsciiStream(parameterIndex, x, length); - parameters.put(parameterIndex, x); } @SuppressWarnings("UngroupedOverloads") @Override public void setAsciiStream(int parameterIndex, InputStream x) throws SQLException { delegate.setAsciiStream(parameterIndex, x); - parameters.put(parameterIndex, x); } @SuppressWarnings("UngroupedOverloads") @Override public void setAsciiStream(int parameterIndex, InputStream x, long length) throws SQLException { delegate.setAsciiStream(parameterIndex, x, length); - parameters.put(parameterIndex, x); } @Override @Deprecated public void setUnicodeStream(int parameterIndex, InputStream x, int length) throws SQLException { delegate.setUnicodeStream(parameterIndex, x, length); - parameters.put(parameterIndex, x); } @SuppressWarnings("UngroupedOverloads") @Override public void setBinaryStream(int parameterIndex, InputStream x, int length) throws SQLException { delegate.setBinaryStream(parameterIndex, x, length); - parameters.put(parameterIndex, x); } @SuppressWarnings("UngroupedOverloads") @Override public void setBinaryStream(int parameterIndex, InputStream x, long length) throws SQLException { delegate.setBinaryStream(parameterIndex, x, length); - parameters.put(parameterIndex, x); } @SuppressWarnings("UngroupedOverloads") @Override public void setBinaryStream(int parameterIndex, InputStream x) throws SQLException { delegate.setBinaryStream(parameterIndex, x); - parameters.put(parameterIndex, x); } @SuppressWarnings("UngroupedOverloads") @Override public void setObject(int parameterIndex, Object x, int targetSqlType) throws SQLException { delegate.setObject(parameterIndex, x, targetSqlType); - parameters.put(parameterIndex, x); } @SuppressWarnings("UngroupedOverloads") @Override public void setObject(int parameterIndex, Object x) throws SQLException { delegate.setObject(parameterIndex, x); - parameters.put(parameterIndex, x); } @Override public void setObject(int parameterIndex, Object x, int targetSqlType, int scaleOrLength) throws SQLException { delegate.setObject(parameterIndex, x, targetSqlType, scaleOrLength); - parameters.put(parameterIndex, x); } @Override @@ -272,13 +269,11 @@ public void addBatch() throws SQLException { public void setCharacterStream(int parameterIndex, Reader reader, int length) throws SQLException { delegate.setCharacterStream(parameterIndex, reader, length); - parameters.put(parameterIndex, reader); } @Override public void setCharacterStream(int parameterIndex, Reader reader) throws SQLException { delegate.setCharacterStream(parameterIndex, reader); - parameters.put(parameterIndex, reader); } @SuppressWarnings("UngroupedOverloads") @@ -286,60 +281,51 @@ public void setCharacterStream(int parameterIndex, Reader reader) throws SQLExce public void setCharacterStream(int parameterIndex, Reader reader, long length) throws SQLException { delegate.setCharacterStream(parameterIndex, reader, length); - parameters.put(parameterIndex, reader); } @Override public void setRef(int parameterIndex, Ref x) throws SQLException { delegate.setRef(parameterIndex, x); - parameters.put(parameterIndex, x); } @Override public void setBlob(int parameterIndex, Blob x) throws SQLException { delegate.setBlob(parameterIndex, x); - parameters.put(parameterIndex, x); } @SuppressWarnings("UngroupedOverloads") @Override public void setBlob(int parameterIndex, InputStream inputStream) throws SQLException { delegate.setBlob(parameterIndex, inputStream); - parameters.put(parameterIndex, inputStream); } @Override public void setBlob(int parameterIndex, InputStream inputStream, long length) throws SQLException { delegate.setBlob(parameterIndex, inputStream, length); - parameters.put(parameterIndex, inputStream); } @SuppressWarnings("UngroupedOverloads") @Override public void setClob(int parameterIndex, Clob x) throws SQLException { delegate.setClob(parameterIndex, x); - parameters.put(parameterIndex, x); } @SuppressWarnings("UngroupedOverloads") @Override public void setClob(int parameterIndex, Reader reader) throws SQLException { delegate.setClob(parameterIndex, reader); - parameters.put(parameterIndex, reader); } @SuppressWarnings("UngroupedOverloads") @Override public void setClob(int parameterIndex, Reader reader, long length) throws SQLException { delegate.setClob(parameterIndex, reader, length); - parameters.put(parameterIndex, reader); } @Override public void setArray(int parameterIndex, Array x) throws SQLException { delegate.setArray(parameterIndex, x); - parameters.put(parameterIndex, x); } @Override @@ -350,7 +336,7 @@ public ResultSetMetaData getMetaData() throws SQLException { @Override public void setURL(int parameterIndex, URL x) throws SQLException { delegate.setURL(parameterIndex, x); - parameters.put(parameterIndex, x); + putParameter(parameterIndex, stringifyParameter(x)); } @Override @@ -361,13 +347,13 @@ public ParameterMetaData getParameterMetaData() throws SQLException { @Override public void setRowId(int parameterIndex, RowId x) throws SQLException { delegate.setRowId(parameterIndex, x); - parameters.put(parameterIndex, x); + putParameter(parameterIndex, stringifyParameter(x)); } @Override public void setNString(int parameterIndex, String value) throws SQLException { delegate.setNString(parameterIndex, value); - parameters.put(parameterIndex, value); + putParameter(parameterIndex, stringifyParameter(value)); } @SuppressWarnings("UngroupedOverloads") @@ -375,39 +361,33 @@ public void setNString(int parameterIndex, String value) throws SQLException { public void setNCharacterStream(int parameterIndex, Reader value, long length) throws SQLException { delegate.setNCharacterStream(parameterIndex, value, length); - parameters.put(parameterIndex, value); } @SuppressWarnings("UngroupedOverloads") @Override public void setNCharacterStream(int parameterIndex, Reader value) throws SQLException { delegate.setNCharacterStream(parameterIndex, value); - parameters.put(parameterIndex, value); } @Override public void setNClob(int parameterIndex, NClob value) throws SQLException { delegate.setNClob(parameterIndex, value); - parameters.put(parameterIndex, value); } @Override public void setNClob(int parameterIndex, Reader reader, long length) throws SQLException { delegate.setNClob(parameterIndex, reader, length); - parameters.put(parameterIndex, reader); } @SuppressWarnings("UngroupedOverloads") @Override public void setNClob(int parameterIndex, Reader reader) throws SQLException { delegate.setNClob(parameterIndex, reader); - parameters.put(parameterIndex, reader); } @Override public void setSQLXML(int parameterIndex, SQLXML xmlObject) throws SQLException { delegate.setSQLXML(parameterIndex, xmlObject); - parameters.put(parameterIndex, xmlObject); } @Override @@ -438,13 +418,11 @@ private T wrapBatchCall(ThrowingSupplier callable @Override public void setObject(int parameterIndex, Object x, SQLType targetSqlType, int scaleOrLength) throws SQLException { - parameters.put(parameterIndex, x); delegate.setObject(parameterIndex, x, targetSqlType, scaleOrLength); } @Override public void setObject(int parameterIndex, Object x, SQLType targetSqlType) throws SQLException { - parameters.put(parameterIndex, x); delegate.setObject(parameterIndex, x, targetSqlType); } diff --git a/instrumentation/jdbc/library/src/test/java/io/opentelemetry/instrumentation/jdbc/internal/OpenTelemetryConnectionTest.java b/instrumentation/jdbc/library/src/test/java/io/opentelemetry/instrumentation/jdbc/internal/OpenTelemetryConnectionTest.java index 3f4855fdbc5b..b35f87e6a996 100644 --- a/instrumentation/jdbc/library/src/test/java/io/opentelemetry/instrumentation/jdbc/internal/OpenTelemetryConnectionTest.java +++ b/instrumentation/jdbc/library/src/test/java/io/opentelemetry/instrumentation/jdbc/internal/OpenTelemetryConnectionTest.java @@ -212,12 +212,12 @@ void testVerifyPrepareCallReturnsOtelWrapper() throws Exception { @Test void testVerifyPrepareStatementParameters() throws SQLException { Instrumenter instrumenter = - createStatementInstrumenter(testing.getOpenTelemetry()); + createStatementInstrumenter(testing.getOpenTelemetry(), true); DbInfo dbInfo = getDbInfo(); OpenTelemetryConnection connection = new OpenTelemetryConnection(new TestConnection(), dbInfo, instrumenter); - String query = "SELECT * FROM users WHERE id=? AND name=$2 AND age=3"; - String sanitized = "SELECT * FROM users WHERE id=? AND name=$2 AND age=?"; + String query = "SELECT * FROM users WHERE id=? AND name=? AND age=3"; + String sanitized = "SELECT * FROM users WHERE id=? AND name=? AND age=3"; PreparedStatement statement = connection.prepareStatement(query); statement.setInt(1, 1); statement.setString(2, "bob"); @@ -234,8 +234,7 @@ void testVerifyPrepareStatementParameters() throws SQLException { dbInfo, sanitized, equalTo(DB_QUERY_PARAMETER.getAttributeKey("0"), "1"), - equalTo(DB_QUERY_PARAMETER.getAttributeKey("$2"), "'bob'"), - equalTo(DB_QUERY_PARAMETER.getAttributeKey("1"), "3")); + equalTo(DB_QUERY_PARAMETER.getAttributeKey("1"), "'bob'")); statement.close(); connection.close(); diff --git a/instrumentation/jdbc/testing/src/main/java/io/opentelemetry/instrumentation/jdbc/TestPreparedStatement.java b/instrumentation/jdbc/testing/src/main/java/io/opentelemetry/instrumentation/jdbc/TestPreparedStatement.java index 68bb88038730..e1ca4649e10b 100644 --- a/instrumentation/jdbc/testing/src/main/java/io/opentelemetry/instrumentation/jdbc/TestPreparedStatement.java +++ b/instrumentation/jdbc/testing/src/main/java/io/opentelemetry/instrumentation/jdbc/TestPreparedStatement.java @@ -30,7 +30,7 @@ import java.util.Map; class TestPreparedStatement extends TestStatement implements PreparedStatement { - Map parameters; + Map parameters; TestPreparedStatement() { super(); @@ -150,7 +150,7 @@ public void setFloat(int parameterIndex, float x) throws SQLException {} @Override public void setInt(int parameterIndex, int x) throws SQLException { - parameters.put(parameterIndex, x); + parameters.put(Integer.toString(parameterIndex), Integer.toString(x)); } @Override diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/jdbc/DataSourcePostProcessor.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/jdbc/DataSourcePostProcessor.java index c922841c2cc3..da759ffd048d 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/jdbc/DataSourcePostProcessor.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/jdbc/DataSourcePostProcessor.java @@ -55,10 +55,10 @@ public Object postProcessAfterInitialization(Object bean, String beanName) { InstrumentationConfigUtil.isStatementSanitizationEnabled( configPropertiesProvider.getObject(), "otel.instrumentation.jdbc.statement-sanitizer.enabled")) - .setQueryParameterEnabled( - InstrumentationConfigUtil.isQueryParameterEnabled( - configPropertiesProvider.getObject(), - "otel.instrumentation.jdbc.query-parameter.enabled")) + .setCaptureQueryParameters( + configPropertiesProvider + .getObject() + .getBoolean("otel.instrumentation.jdbc.capture-query-parameters", false)) .build() .wrap(dataSource); } diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/properties/InstrumentationConfigUtil.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/properties/InstrumentationConfigUtil.java index eefc6340b672..fb5e582bfaa5 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/properties/InstrumentationConfigUtil.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/properties/InstrumentationConfigUtil.java @@ -45,10 +45,4 @@ public static boolean isStatementSanitizationEnabled(ConfigProperties config, St return config.getBoolean( key, config.getBoolean("otel.instrumentation.common.db-statement-sanitizer.enabled", true)); } - - public static boolean isQueryParameterEnabled(ConfigProperties config, String key) { - // TODO change with common conf key - return config.getBoolean( - key, config.getBoolean("otel.instrumentation.jdbc.query-parameter.enabled", false)); - } } diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/instrumentation/spring/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json index a53172880e77..de01009c89b3 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -352,9 +352,9 @@ "defaultValue": true }, { - "name": "otel.instrumentation.jdbc.query-parameter.enabled", + "name": "otel.instrumentation.jdbc.capture-query-parameters", "type": "java.lang.Boolean", - "description": "Enables the attribute db.query.parameter.", + "description": "Enables the attribute db.query.parameter.\\.

WARNING: captured query parameters may contain sensitive information such as passwords, personally identifiable information or protected health info. Exposing such info may result in substantial fines and penalties or criminal liability. Consult your peers, superiors and a legal counsel before enabling this option.

This option will disable otel.instrumentation.jdbc.statement-sanitizer", "defaultValue": false }, { From 99ce35fffe529bfd8e7621f1e782d1c8233aae3a Mon Sep 17 00:00:00 2001 From: Alix Date: Mon, 28 Apr 2025 12:36:38 +0200 Subject: [PATCH 06/15] format with spotlessApply --- .../db/SqlClientAttributesExtractor.java | 3 +- .../SqlClientAttributesExtractorBuilder.java | 5 +- .../semconv/db/SqlStatementInfo.java | 5 +- .../PreparedStatementInstrumentation.java | 8 +- .../jdbc/test/JdbcInstrumentationTest.java | 4 +- .../test/PreparedStatementParametersTest.java | 311 ++++++++++-------- .../internal/JdbcInstrumenterFactory.java | 4 +- .../JdbcPreparedStatementStringifier.java | 6 +- .../OpenTelemetryPreparedStatement.java | 6 +- .../internal/OpenTelemetryConnectionTest.java | 3 +- 10 files changed, 190 insertions(+), 165 deletions(-) diff --git a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlClientAttributesExtractor.java b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlClientAttributesExtractor.java index a7acda510c4f..2fe99d418128 100644 --- a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlClientAttributesExtractor.java +++ b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlClientAttributesExtractor.java @@ -28,7 +28,7 @@ */ public final class SqlClientAttributesExtractor extends DbClientCommonAttributesExtractor< - REQUEST, RESPONSE, SqlClientAttributesGetter> { + REQUEST, RESPONSE, SqlClientAttributesGetter> { // copied from DbIncubatingAttributes private static final AttributeKey DB_OPERATION = AttributeKey.stringKey("db.operation"); @@ -166,5 +166,4 @@ private static String join(String delimiter, Collection collection) { } return builder.toString(); } - } diff --git a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlClientAttributesExtractorBuilder.java b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlClientAttributesExtractorBuilder.java index eb3ab94cbe42..b91aef0b4123 100644 --- a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlClientAttributesExtractorBuilder.java +++ b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlClientAttributesExtractorBuilder.java @@ -56,9 +56,8 @@ public SqlClientAttributesExtractorBuilder setStatementSaniti /** * Sets whether the {@code db.query.parameter.} attributes extracted by the constructed - * {@link SqlClientAttributesExtractor} should be opted-in. If set to {@code true}, - * all parameters from {@code PreparedStatement} will be exposed as attributes. - * Disabled by default. + * {@link SqlClientAttributesExtractor} should be opted-in. If set to {@code true}, all parameters + * from {@code PreparedStatement} will be exposed as attributes. Disabled by default. */ @CanIgnoreReturnValue public SqlClientAttributesExtractorBuilder setCaptureQueryParameters( diff --git a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlStatementInfo.java b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlStatementInfo.java index e0bc634b7c2b..04320206b060 100644 --- a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlStatementInfo.java +++ b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlStatementInfo.java @@ -12,9 +12,7 @@ public abstract class SqlStatementInfo { public static SqlStatementInfo create( - @Nullable String fullStatement, - @Nullable String operation, - @Nullable String identifier) { + @Nullable String fullStatement, @Nullable String operation, @Nullable String identifier) { return new AutoValue_SqlStatementInfo(fullStatement, operation, identifier); } @@ -26,5 +24,4 @@ public static SqlStatementInfo create( @Nullable public abstract String getMainIdentifier(); - } diff --git a/instrumentation/jdbc/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jdbc/PreparedStatementInstrumentation.java b/instrumentation/jdbc/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jdbc/PreparedStatementInstrumentation.java index fda0b6fc7352..d6c130e57cd5 100644 --- a/instrumentation/jdbc/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jdbc/PreparedStatementInstrumentation.java +++ b/instrumentation/jdbc/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jdbc/PreparedStatementInstrumentation.java @@ -113,11 +113,15 @@ public void transform(TypeTransformer transformer) { named("setTime").and(takesArgument(0, int.class)).and(takesArguments(3)).and(isPublic()), PreparedStatementInstrumentation.class.getName() + "$SetTime3Advice"); transformer.applyAdviceToMethod( - named("setTimestamp").and(takesArgument(0, int.class)).and(takesArguments(2)) + named("setTimestamp") + .and(takesArgument(0, int.class)) + .and(takesArguments(2)) .and(isPublic()), PreparedStatementInstrumentation.class.getName() + "$SetTimestamp2Advice"); transformer.applyAdviceToMethod( - named("setTimestamp").and(takesArgument(0, int.class)).and(takesArguments(3)) + named("setTimestamp") + .and(takesArgument(0, int.class)) + .and(takesArguments(3)) .and(isPublic()), PreparedStatementInstrumentation.class.getName() + "$SetTimestamp3Advice"); transformer.applyAdviceToMethod( diff --git a/instrumentation/jdbc/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jdbc/test/JdbcInstrumentationTest.java b/instrumentation/jdbc/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jdbc/test/JdbcInstrumentationTest.java index bdc4d9c8af61..369a09f31fcf 100644 --- a/instrumentation/jdbc/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jdbc/test/JdbcInstrumentationTest.java +++ b/instrumentation/jdbc/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jdbc/test/JdbcInstrumentationTest.java @@ -1531,8 +1531,8 @@ void testMultiBatch(String system, Connection connection, String username, Strin maybeStable(DB_STATEMENT), emitStableDatabaseSemconv() ? "INSERT INTO " - + tableName1 - + " VALUES(?); INSERT INTO multi_batch_test_2 VALUES(?)" + + tableName1 + + " VALUES(?); INSERT INTO multi_batch_test_2 VALUES(?)" : null), equalTo( maybeStable(DB_OPERATION), diff --git a/instrumentation/jdbc/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jdbc/test/PreparedStatementParametersTest.java b/instrumentation/jdbc/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jdbc/test/PreparedStatementParametersTest.java index 0f58c69f7677..c381f8154b56 100644 --- a/instrumentation/jdbc/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jdbc/test/PreparedStatementParametersTest.java +++ b/instrumentation/jdbc/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jdbc/test/PreparedStatementParametersTest.java @@ -53,8 +53,7 @@ @SuppressWarnings("deprecation") // using deprecated semconv class PreparedStatementParametersTest { - @RegisterExtension - static final AutoCleanupExtension cleanup = AutoCleanupExtension.create(); + @RegisterExtension static final AutoCleanupExtension cleanup = AutoCleanupExtension.create(); @RegisterExtension static final InstrumentationExtension testing = AgentInstrumentationExtension.create(); @@ -114,8 +113,7 @@ static Stream preparedStatementStream() throws SQLException { "SELECT 3 FROM INFORMATION_SCHEMA.SYSTEM_USERS WHERE USER_NAME=? OR 1=1", "SELECT INFORMATION_SCHEMA.SYSTEM_USERS", "hsqldb:mem:", - "INFORMATION_SCHEMA.SYSTEM_USERS") - ); + "INFORMATION_SCHEMA.SYSTEM_USERS")); } @ParameterizedTest @@ -133,13 +131,14 @@ void testNull2PreparedStatementParameter( PreparedStatement statement = connection.prepareStatement(query); cleanup.deferCleanup(statement); - ResultSet resultSet = testing.runWithSpan( - "parent", - () -> { - statement.setNull(1, Types.INTEGER); - statement.execute(); - return statement.getResultSet(); - }); + ResultSet resultSet = + testing.runWithSpan( + "parent", + () -> { + statement.setNull(1, Types.INTEGER); + statement.execute(); + return statement.getResultSet(); + }); resultSet.next(); assertThat(resultSet.getInt(1)).isEqualTo(3); @@ -178,13 +177,14 @@ void testNull3PreparedStatementParameter( PreparedStatement statement = connection.prepareStatement(query); cleanup.deferCleanup(statement); - ResultSet resultSet = testing.runWithSpan( - "parent", - () -> { - statement.setNull(1, Types.INTEGER, "Integer"); - statement.execute(); - return statement.getResultSet(); - }); + ResultSet resultSet = + testing.runWithSpan( + "parent", + () -> { + statement.setNull(1, Types.INTEGER, "Integer"); + statement.execute(); + return statement.getResultSet(); + }); resultSet.next(); assertThat(resultSet.getInt(1)).isEqualTo(3); @@ -223,13 +223,14 @@ void testBytePreparedStatementParameter( PreparedStatement statement = connection.prepareStatement(query); cleanup.deferCleanup(statement); - ResultSet resultSet = testing.runWithSpan( - "parent", - () -> { - statement.setByte(1, (byte) 0); - statement.execute(); - return statement.getResultSet(); - }); + ResultSet resultSet = + testing.runWithSpan( + "parent", + () -> { + statement.setByte(1, (byte) 0); + statement.execute(); + return statement.getResultSet(); + }); resultSet.next(); assertThat(resultSet.getInt(1)).isEqualTo(3); @@ -270,13 +271,14 @@ void testBytesPreparedStatementParameter( PreparedStatement statement = connection.prepareStatement(updatedColumn); cleanup.deferCleanup(statement); - ResultSet resultSet = testing.runWithSpan( - "parent", - () -> { - statement.setBytes(1, new byte[] {(byte) 0, 0}); - statement.execute(); - return statement.getResultSet(); - }); + ResultSet resultSet = + testing.runWithSpan( + "parent", + () -> { + statement.setBytes(1, new byte[] {(byte) 0, 0}); + statement.execute(); + return statement.getResultSet(); + }); resultSet.next(); assertThat(resultSet.getInt(1)).isEqualTo(3); @@ -315,13 +317,14 @@ void testShortPreparedStatementParameter( PreparedStatement statement = connection.prepareStatement(query); cleanup.deferCleanup(statement); - ResultSet resultSet = testing.runWithSpan( - "parent", - () -> { - statement.setShort(1, (short)0); - statement.execute(); - return statement.getResultSet(); - }); + ResultSet resultSet = + testing.runWithSpan( + "parent", + () -> { + statement.setShort(1, (short) 0); + statement.execute(); + return statement.getResultSet(); + }); resultSet.next(); assertThat(resultSet.getInt(1)).isEqualTo(3); @@ -360,13 +363,14 @@ void testIntPreparedStatementParameter( PreparedStatement statement = connection.prepareStatement(query); cleanup.deferCleanup(statement); - ResultSet resultSet = testing.runWithSpan( - "parent", - () -> { - statement.setInt(1, 0); - statement.execute(); - return statement.getResultSet(); - }); + ResultSet resultSet = + testing.runWithSpan( + "parent", + () -> { + statement.setInt(1, 0); + statement.execute(); + return statement.getResultSet(); + }); resultSet.next(); assertThat(resultSet.getInt(1)).isEqualTo(3); @@ -405,13 +409,14 @@ void testLongPreparedStatementParameter( PreparedStatement statement = connection.prepareStatement(query); cleanup.deferCleanup(statement); - ResultSet resultSet = testing.runWithSpan( - "parent", - () -> { - statement.setLong(1, 0); - statement.execute(); - return statement.getResultSet(); - }); + ResultSet resultSet = + testing.runWithSpan( + "parent", + () -> { + statement.setLong(1, 0); + statement.execute(); + return statement.getResultSet(); + }); resultSet.next(); assertThat(resultSet.getInt(1)).isEqualTo(3); @@ -450,13 +455,14 @@ void testFloatPreparedStatementParameter( PreparedStatement statement = connection.prepareStatement(query); cleanup.deferCleanup(statement); - ResultSet resultSet = testing.runWithSpan( - "parent", - () -> { - statement.setFloat(1, (float) 0.1); - statement.execute(); - return statement.getResultSet(); - }); + ResultSet resultSet = + testing.runWithSpan( + "parent", + () -> { + statement.setFloat(1, (float) 0.1); + statement.execute(); + return statement.getResultSet(); + }); resultSet.next(); assertThat(resultSet.getInt(1)).isEqualTo(3); @@ -495,13 +501,14 @@ void testDoublePreparedStatementParameter( PreparedStatement statement = connection.prepareStatement(query); cleanup.deferCleanup(statement); - ResultSet resultSet = testing.runWithSpan( - "parent", - () -> { - statement.setDouble(1, 0.1); - statement.execute(); - return statement.getResultSet(); - }); + ResultSet resultSet = + testing.runWithSpan( + "parent", + () -> { + statement.setDouble(1, 0.1); + statement.execute(); + return statement.getResultSet(); + }); resultSet.next(); assertThat(resultSet.getInt(1)).isEqualTo(3); @@ -540,13 +547,14 @@ void testBigDecimalPreparedStatementParameter( PreparedStatement statement = connection.prepareStatement(query); cleanup.deferCleanup(statement); - ResultSet resultSet = testing.runWithSpan( - "parent", - () -> { - statement.setBigDecimal(1, BigDecimal.ZERO); - statement.execute(); - return statement.getResultSet(); - }); + ResultSet resultSet = + testing.runWithSpan( + "parent", + () -> { + statement.setBigDecimal(1, BigDecimal.ZERO); + statement.execute(); + return statement.getResultSet(); + }); resultSet.next(); assertThat(resultSet.getInt(1)).isEqualTo(3); @@ -585,13 +593,14 @@ void testStringPreparedStatementParameter( PreparedStatement statement = connection.prepareStatement(query); cleanup.deferCleanup(statement); - ResultSet resultSet = testing.runWithSpan( - "parent", - () -> { - statement.setString(1, "S"); - statement.execute(); - return statement.getResultSet(); - }); + ResultSet resultSet = + testing.runWithSpan( + "parent", + () -> { + statement.setString(1, "S"); + statement.execute(); + return statement.getResultSet(); + }); resultSet.next(); assertThat(resultSet.getInt(1)).isEqualTo(3); @@ -632,13 +641,14 @@ void testDate2PreparedStatementParameter( PreparedStatement statement = connection.prepareStatement(updatedColumn); cleanup.deferCleanup(statement); - ResultSet resultSet = testing.runWithSpan( - "parent", - () -> { - statement.setDate(1, Date.valueOf("2000-01-01")); - statement.execute(); - return statement.getResultSet(); - }); + ResultSet resultSet = + testing.runWithSpan( + "parent", + () -> { + statement.setDate(1, Date.valueOf("2000-01-01")); + statement.execute(); + return statement.getResultSet(); + }); resultSet.next(); assertThat(resultSet.getInt(1)).isEqualTo(3); @@ -679,13 +689,14 @@ void testDate3PreparedStatementParameter( PreparedStatement statement = connection.prepareStatement(updatedColumn); cleanup.deferCleanup(statement); - ResultSet resultSet = testing.runWithSpan( - "parent", - () -> { - statement.setDate(1, Date.valueOf("2000-01-01"), Calendar.getInstance()); - statement.execute(); - return statement.getResultSet(); - }); + ResultSet resultSet = + testing.runWithSpan( + "parent", + () -> { + statement.setDate(1, Date.valueOf("2000-01-01"), Calendar.getInstance()); + statement.execute(); + return statement.getResultSet(); + }); resultSet.next(); assertThat(resultSet.getInt(1)).isEqualTo(3); @@ -726,13 +737,14 @@ void testTime2PreparedStatementParameter( PreparedStatement statement = connection.prepareStatement(updatedColumn); cleanup.deferCleanup(statement); - ResultSet resultSet = testing.runWithSpan( - "parent", - () -> { - statement.setTime(1, Time.valueOf("00:00:00")); - statement.execute(); - return statement.getResultSet(); - }); + ResultSet resultSet = + testing.runWithSpan( + "parent", + () -> { + statement.setTime(1, Time.valueOf("00:00:00")); + statement.execute(); + return statement.getResultSet(); + }); resultSet.next(); assertThat(resultSet.getInt(1)).isEqualTo(3); @@ -773,13 +785,14 @@ void testTime3PreparedStatementParameter( PreparedStatement statement = connection.prepareStatement(updatedColumn); cleanup.deferCleanup(statement); - ResultSet resultSet = testing.runWithSpan( - "parent", - () -> { - statement.setTime(1, Time.valueOf("00:00:00"), Calendar.getInstance()); - statement.execute(); - return statement.getResultSet(); - }); + ResultSet resultSet = + testing.runWithSpan( + "parent", + () -> { + statement.setTime(1, Time.valueOf("00:00:00"), Calendar.getInstance()); + statement.execute(); + return statement.getResultSet(); + }); resultSet.next(); assertThat(resultSet.getInt(1)).isEqualTo(3); @@ -820,13 +833,14 @@ void testTimestamp2PreparedStatementParameter( PreparedStatement statement = connection.prepareStatement(updatedColumn); cleanup.deferCleanup(statement); - ResultSet resultSet = testing.runWithSpan( - "parent", - () -> { - statement.setTimestamp(1, Timestamp.valueOf("2000-01-01 00:00:00")); - statement.execute(); - return statement.getResultSet(); - }); + ResultSet resultSet = + testing.runWithSpan( + "parent", + () -> { + statement.setTimestamp(1, Timestamp.valueOf("2000-01-01 00:00:00")); + statement.execute(); + return statement.getResultSet(); + }); resultSet.next(); assertThat(resultSet.getInt(1)).isEqualTo(3); @@ -847,7 +861,9 @@ void testTimestamp2PreparedStatementParameter( equalTo(maybeStable(DB_STATEMENT), updatedColumnSanitized), equalTo(maybeStable(DB_OPERATION), "SELECT"), equalTo(maybeStable(DB_SQL_TABLE), table), - equalTo(DB_QUERY_PARAMETER.getAttributeKey("0"), "'2000-01-01 00:00:00.0'")))); + equalTo( + DB_QUERY_PARAMETER.getAttributeKey("0"), + "'2000-01-01 00:00:00.0'")))); } @ParameterizedTest @@ -867,13 +883,15 @@ void testTimestamp3PreparedStatementParameter( PreparedStatement statement = connection.prepareStatement(updatedColumn); cleanup.deferCleanup(statement); - ResultSet resultSet = testing.runWithSpan( - "parent", - () -> { - statement.setTimestamp(1, Timestamp.valueOf("2000-01-01 00:00:00"), Calendar.getInstance()); - statement.execute(); - return statement.getResultSet(); - }); + ResultSet resultSet = + testing.runWithSpan( + "parent", + () -> { + statement.setTimestamp( + 1, Timestamp.valueOf("2000-01-01 00:00:00"), Calendar.getInstance()); + statement.execute(); + return statement.getResultSet(); + }); resultSet.next(); assertThat(resultSet.getInt(1)).isEqualTo(3); @@ -894,7 +912,9 @@ void testTimestamp3PreparedStatementParameter( equalTo(maybeStable(DB_STATEMENT), updatedColumnSanitized), equalTo(maybeStable(DB_OPERATION), "SELECT"), equalTo(maybeStable(DB_SQL_TABLE), table), - equalTo(DB_QUERY_PARAMETER.getAttributeKey("0"), "'2000-01-01 00:00:00.0'")))); + equalTo( + DB_QUERY_PARAMETER.getAttributeKey("0"), + "'2000-01-01 00:00:00.0'")))); } @Ignore("not supported by tested drivers") @@ -911,13 +931,14 @@ void testURLPreparedStatementParameter( PreparedStatement statement = connection.prepareStatement(query); cleanup.deferCleanup(statement); - ResultSet resultSet = testing.runWithSpan( - "parent", - () -> { - statement.setURL(1, URI.create("http://localhost:8080").toURL()); - statement.execute(); - return statement.getResultSet(); - }); + ResultSet resultSet = + testing.runWithSpan( + "parent", + () -> { + statement.setURL(1, URI.create("http://localhost:8080").toURL()); + statement.execute(); + return statement.getResultSet(); + }); resultSet.next(); assertThat(resultSet.getInt(1)).isEqualTo(3); @@ -938,7 +959,9 @@ void testURLPreparedStatementParameter( equalTo(maybeStable(DB_STATEMENT), sanitizedQuery), equalTo(maybeStable(DB_OPERATION), "SELECT"), equalTo(maybeStable(DB_SQL_TABLE), table), - equalTo(DB_QUERY_PARAMETER.getAttributeKey("0"), "'http://localhost:8080'")))); + equalTo( + DB_QUERY_PARAMETER.getAttributeKey("0"), + "'http://localhost:8080'")))); } @ParameterizedTest @@ -953,17 +976,18 @@ void testNStringPreparedStatementParameter( String url, String table) throws SQLException { - if (!system.equalsIgnoreCase( "derby")) { + if (!system.equalsIgnoreCase("derby")) { PreparedStatement statement = connection.prepareStatement(query); cleanup.deferCleanup(statement); - ResultSet resultSet = testing.runWithSpan( - "parent", - () -> { - statement.setNString(1, "S"); - statement.execute(); - return statement.getResultSet(); - }); + ResultSet resultSet = + testing.runWithSpan( + "parent", + () -> { + statement.setNString(1, "S"); + statement.execute(); + return statement.getResultSet(); + }); resultSet.next(); assertThat(resultSet.getInt(1)).isEqualTo(3); @@ -980,13 +1004,12 @@ void testNStringPreparedStatementParameter( 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( + DB_CONNECTION_STRING, emitStableDatabaseSemconv() ? null : url), equalTo(maybeStable(DB_STATEMENT), sanitizedQuery), equalTo(maybeStable(DB_OPERATION), "SELECT"), equalTo(maybeStable(DB_SQL_TABLE), table), equalTo(DB_QUERY_PARAMETER.getAttributeKey("0"), "'S'")))); } } - } 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 38a87a5dbe19..2b3aa2e8e925 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 @@ -45,9 +45,7 @@ public static Instrumenter createStatementInstrumenter( } public static Instrumenter createStatementInstrumenter( - OpenTelemetry openTelemetry, - boolean captureQueryParameters - ) { + OpenTelemetry openTelemetry, boolean captureQueryParameters) { return createStatementInstrumenter( openTelemetry, true, diff --git a/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/JdbcPreparedStatementStringifier.java b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/JdbcPreparedStatementStringifier.java index 54fe1ce3ad54..19f5b11360b6 100644 --- a/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/JdbcPreparedStatementStringifier.java +++ b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/JdbcPreparedStatementStringifier.java @@ -1,3 +1,8 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + package io.opentelemetry.instrumentation.jdbc.internal; import java.net.URL; @@ -64,5 +69,4 @@ public static String stringifyParameter(RowId value) { public static String stringifyNullParameter() { return ""; } - } diff --git a/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/OpenTelemetryPreparedStatement.java b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/OpenTelemetryPreparedStatement.java index 09d90ea559ef..b2ac9a315362 100644 --- a/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/OpenTelemetryPreparedStatement.java +++ b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/OpenTelemetryPreparedStatement.java @@ -20,6 +20,9 @@ package io.opentelemetry.instrumentation.jdbc.internal; +import static io.opentelemetry.instrumentation.jdbc.internal.JdbcPreparedStatementStringifier.stringifyNullParameter; +import static io.opentelemetry.instrumentation.jdbc.internal.JdbcPreparedStatementStringifier.stringifyParameter; + import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; import io.opentelemetry.instrumentation.jdbc.internal.dbinfo.DbInfo; import java.io.InputStream; @@ -46,9 +49,6 @@ import java.util.HashMap; import java.util.Map; -import static io.opentelemetry.instrumentation.jdbc.internal.JdbcPreparedStatementStringifier.stringifyNullParameter; -import static io.opentelemetry.instrumentation.jdbc.internal.JdbcPreparedStatementStringifier.stringifyParameter; - @SuppressWarnings("OverloadMethodsDeclarationOrder") class OpenTelemetryPreparedStatement extends OpenTelemetryStatement implements PreparedStatement { diff --git a/instrumentation/jdbc/library/src/test/java/io/opentelemetry/instrumentation/jdbc/internal/OpenTelemetryConnectionTest.java b/instrumentation/jdbc/library/src/test/java/io/opentelemetry/instrumentation/jdbc/internal/OpenTelemetryConnectionTest.java index abd7a791ea92..c0513ca6f795 100644 --- a/instrumentation/jdbc/library/src/test/java/io/opentelemetry/instrumentation/jdbc/internal/OpenTelemetryConnectionTest.java +++ b/instrumentation/jdbc/library/src/test/java/io/opentelemetry/instrumentation/jdbc/internal/OpenTelemetryConnectionTest.java @@ -282,7 +282,8 @@ private static void jdbcTraceAssertion(DbInfo dbInfo, String query) { } @SuppressWarnings("deprecation") // old semconv - private static void jdbcTraceAssertion(DbInfo dbInfo, String query, String operation, AttributeAssertion... assertions) { + private static void jdbcTraceAssertion( + DbInfo dbInfo, String query, String operation, AttributeAssertion... assertions) { List baseAttributeAssertions = Arrays.asList( equalTo(maybeStable(DB_SYSTEM), maybeStableDbSystemName(dbInfo.getSystem())), From bf6753376ac397bbd079f7c678ae15bc731f6a03 Mon Sep 17 00:00:00 2001 From: Alix Date: Mon, 28 Apr 2025 13:38:56 +0200 Subject: [PATCH 07/15] fix tests not updated with latest changes --- .../db/SqlClientAttributesExtractorTest.java | 27 +++++-------------- 1 file changed, 7 insertions(+), 20 deletions(-) diff --git a/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlClientAttributesExtractorTest.java b/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlClientAttributesExtractorTest.java index 344a80e3c250..f740789d8bea 100644 --- a/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlClientAttributesExtractorTest.java +++ b/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlClientAttributesExtractorTest.java @@ -74,16 +74,9 @@ public Map getQueryParameters(Map map) { return null; } - Map parameters = new HashMap<>(); + Map parameters = new HashMap<>(); for (String s : parameterString.split(";")) { - // cast basic types used in tests - Object value = s; - try { - value = Integer.parseInt(s); - } catch (NumberFormatException ignored) { - } - - parameters.put(Integer.toString(parameters.size() + 1), value); + parameters.put(Integer.toString(parameters.size()), s); } return parameters; } @@ -422,9 +415,9 @@ void shouldExtractQueryParameters() { // a query with prepared parameters and parameters to sanitize request.put( "db.statement", - "SELECT col FROM table WHERE field1=? AND field2='A' AND field3=$2 AND field4=2"); + "SELECT col FROM table WHERE field1=? AND field2='A' AND field3=? AND field4=2"); // a prepared parameters map - request.put("db.query.parameter", "a;1"); + request.put("db.query.parameter", "'a';1"); Context context = Context.root(); @@ -449,23 +442,17 @@ void shouldExtractQueryParameters() { assertThat(queryParameterAttributes) .containsOnly( entry(DB_QUERY_PARAMETER.getAttributeKey("0"), "'a'"), - entry(DB_QUERY_PARAMETER.getAttributeKey("1"), "'A'"), - entry(DB_QUERY_PARAMETER.getAttributeKey("$2"), "1"), - entry(DB_QUERY_PARAMETER.getAttributeKey("2"), "2")); + entry(DB_QUERY_PARAMETER.getAttributeKey("1"), "1")); } else if (SemconvStability.emitOldDatabaseSemconv()) { assertThat(queryParameterAttributes) .containsOnly( entry(DB_QUERY_PARAMETER.getAttributeKey("0"), "'a'"), - entry(DB_QUERY_PARAMETER.getAttributeKey("1"), "'A'"), - entry(DB_QUERY_PARAMETER.getAttributeKey("$2"), "1"), - entry(DB_QUERY_PARAMETER.getAttributeKey("2"), "2")); + entry(DB_QUERY_PARAMETER.getAttributeKey("1"), "1")); } else if (SemconvStability.emitStableDatabaseSemconv()) { assertThat(queryParameterAttributes) .containsOnly( entry(DB_QUERY_PARAMETER.getAttributeKey("0"), "'a'"), - entry(DB_QUERY_PARAMETER.getAttributeKey("1"), "'A'"), - entry(DB_QUERY_PARAMETER.getAttributeKey("$2"), "1"), - entry(DB_QUERY_PARAMETER.getAttributeKey("2"), "2")); + entry(DB_QUERY_PARAMETER.getAttributeKey("1"), "1")); } assertThat(endAttributes.build().isEmpty()).isTrue(); From 9d6f08a2c871d206d325c20fd5ba885d122e8303 Mon Sep 17 00:00:00 2001 From: Alix Date: Mon, 28 Apr 2025 14:32:59 +0200 Subject: [PATCH 08/15] fix naming conventions & tests database initialization --- .../PreparedStatementInstrumentation.java | 8 +-- .../test/PreparedStatementParametersTest.java | 55 +------------------ 2 files changed, 7 insertions(+), 56 deletions(-) diff --git a/instrumentation/jdbc/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jdbc/PreparedStatementInstrumentation.java b/instrumentation/jdbc/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jdbc/PreparedStatementInstrumentation.java index d6c130e57cd5..aacf2a078e28 100644 --- a/instrumentation/jdbc/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jdbc/PreparedStatementInstrumentation.java +++ b/instrumentation/jdbc/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jdbc/PreparedStatementInstrumentation.java @@ -126,13 +126,13 @@ public void transform(TypeTransformer transformer) { PreparedStatementInstrumentation.class.getName() + "$SetTimestamp3Advice"); transformer.applyAdviceToMethod( named("setURL").and(takesArgument(0, int.class)).and(isPublic()), - PreparedStatementInstrumentation.class.getName() + "$SetURLAdvice"); + PreparedStatementInstrumentation.class.getName() + "$SetUrlAdvice"); transformer.applyAdviceToMethod( named("setRowId").and(takesArgument(0, int.class)).and(isPublic()), PreparedStatementInstrumentation.class.getName() + "$SetRowIdAdvice"); transformer.applyAdviceToMethod( named("setNString").and(takesArgument(0, int.class)).and(isPublic()), - PreparedStatementInstrumentation.class.getName() + "$SetNStringAdvice"); + PreparedStatementInstrumentation.class.getName() + "$SetNstringAdvice"); } @SuppressWarnings("unused") @@ -405,7 +405,7 @@ public static void onExit( } @SuppressWarnings({"unused", "IdentifierName"}) - public static class SetURLAdvice { + public static class SetUrlAdvice { @Advice.OnMethodExit(suppress = Throwable.class) public static void onExit( @Advice.This PreparedStatement statement, @@ -427,7 +427,7 @@ public static void onExit( } @SuppressWarnings("unused") - public static class SetNStringAdvice { + public static class SetNstringAdvice { @Advice.OnMethodExit(suppress = Throwable.class) public static void onExit( @Advice.This PreparedStatement statement, diff --git a/instrumentation/jdbc/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jdbc/test/PreparedStatementParametersTest.java b/instrumentation/jdbc/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jdbc/test/PreparedStatementParametersTest.java index c381f8154b56..49d7eea19732 100644 --- a/instrumentation/jdbc/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jdbc/test/PreparedStatementParametersTest.java +++ b/instrumentation/jdbc/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jdbc/test/PreparedStatementParametersTest.java @@ -26,7 +26,6 @@ import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension; import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; import java.math.BigDecimal; -import java.net.URI; import java.sql.Connection; import java.sql.Date; import java.sql.PreparedStatement; @@ -42,7 +41,6 @@ import java.util.stream.Stream; import org.apache.derby.jdbc.EmbeddedDriver; import org.hsqldb.jdbc.JDBCDriver; -import org.junit.Ignore; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.extension.RegisterExtension; @@ -89,7 +87,7 @@ static Stream preparedStatementStream() throws SQLException { return Stream.of( Arguments.of( "h2", - new org.h2.Driver().connect(jdbcUrls.get("h2"), connectionProps), + new org.h2.Driver().connect(jdbcUrls.get("h2"), null), null, "SELECT 3, ?", "SELECT 3, ?", @@ -107,7 +105,7 @@ static Stream preparedStatementStream() throws SQLException { "SYSIBM.SYSDUMMY1"), Arguments.of( "hsqldb", - new JDBCDriver().connect(jdbcUrls.get("hsqldb"), connectionProps), + new JDBCDriver().connect(jdbcUrls.get("hsqldb"), null), "SA", "SELECT 3 FROM INFORMATION_SCHEMA.SYSTEM_USERS WHERE USER_NAME=? OR 1=1", "SELECT 3 FROM INFORMATION_SCHEMA.SYSTEM_USERS WHERE USER_NAME=? OR 1=1", @@ -917,56 +915,9 @@ void testTimestamp3PreparedStatementParameter( "'2000-01-01 00:00:00.0'")))); } - @Ignore("not supported by tested drivers") - void testURLPreparedStatementParameter( - String system, - Connection connection, - String username, - String query, - String sanitizedQuery, - String spanName, - String url, - String table) - throws Exception { - PreparedStatement statement = connection.prepareStatement(query); - cleanup.deferCleanup(statement); - - ResultSet resultSet = - testing.runWithSpan( - "parent", - () -> { - statement.setURL(1, URI.create("http://localhost:8080").toURL()); - statement.execute(); - return statement.getResultSet(); - }); - - resultSet.next(); - assertThat(resultSet.getInt(1)).isEqualTo(3); - - testing.waitAndAssertTraces( - trace -> - trace.hasSpansSatisfyingExactly( - span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), - span -> - span.hasName(spanName) - .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), sanitizedQuery), - equalTo(maybeStable(DB_OPERATION), "SELECT"), - equalTo(maybeStable(DB_SQL_TABLE), table), - equalTo( - DB_QUERY_PARAMETER.getAttributeKey("0"), - "'http://localhost:8080'")))); - } - @ParameterizedTest @MethodSource("preparedStatementStream") - void testNStringPreparedStatementParameter( + void testNstringPreparedStatementParameter( String system, Connection connection, String username, From f6702f0b63b76d5285a04a3cb71fb7bf9d2d8a8f Mon Sep 17 00:00:00 2001 From: Alix Date: Wed, 30 Apr 2025 14:37:21 +0200 Subject: [PATCH 09/15] only store parameters if feature enabled & factorize agent advices & unify stringify --- .../db/SqlClientAttributesExtractor.java | 18 +- .../SqlClientAttributesExtractorBuilder.java | 10 - .../db/SqlClientAttributesExtractorTest.java | 21 +- .../instrumentation/jdbc/JdbcSingletons.java | 10 +- .../PreparedStatementInstrumentation.java | 340 ++++-------------- .../jdbc/StatementInstrumentation.java | 7 +- .../test/PreparedStatementParametersTest.java | 163 +-------- .../jdbc/OpenTelemetryDriver.java | 6 +- .../jdbc/datasource/JdbcTelemetry.java | 10 +- .../jdbc/datasource/JdbcTelemetryBuilder.java | 3 +- .../datasource/OpenTelemetryDataSource.java | 12 +- .../jdbc/internal/DbRequest.java | 14 +- .../jdbc/internal/JdbcAttributesGetter.java | 10 +- .../jdbc/internal/JdbcData.java | 15 +- .../internal/JdbcInstrumenterFactory.java | 8 +- .../JdbcPreparedStatementStringifier.java | 35 +- .../OpenTelemetryCallableStatement.java | 5 +- .../internal/OpenTelemetryConnection.java | 43 ++- .../OpenTelemetryPreparedStatement.java | 14 +- .../internal/OpenTelemetryConnectionTest.java | 68 +++- 20 files changed, 250 insertions(+), 562 deletions(-) diff --git a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlClientAttributesExtractor.java b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlClientAttributesExtractor.java index 2fe99d418128..92eb3f260803 100644 --- a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlClientAttributesExtractor.java +++ b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlClientAttributesExtractor.java @@ -82,7 +82,7 @@ public void onStart(AttributesBuilder attributes, Context parentContext, REQUEST super.onStart(attributes, parentContext, request); Collection rawQueryTexts = getter.getRawQueryTexts(request); - Map preparedStatementParameters = getter.getQueryParameters(request); + Map queryParameters = getter.getQueryParameters(request); if (rawQueryTexts.isEmpty()) { return; @@ -104,7 +104,7 @@ public void onStart(AttributesBuilder attributes, Context parentContext, REQUEST if (!SQL_CALL.equals(operation)) { internalSet(attributes, oldSemconvTableAttribute, sanitizedStatement.getMainIdentifier()); } - setQueryParameters(attributes, isBatch, preparedStatementParameters); + setQueryParameters(attributes, isBatch, queryParameters); } } @@ -124,7 +124,7 @@ public void onStart(AttributesBuilder attributes, Context parentContext, REQUEST if (!SQL_CALL.equals(operation)) { internalSet(attributes, DB_COLLECTION_NAME, sanitizedStatement.getMainIdentifier()); } - setQueryParameters(attributes, isBatch, preparedStatementParameters); + setQueryParameters(attributes, isBatch, queryParameters); } else { MultiQuery multiQuery = MultiQuery.analyze(getter.getRawQueryTexts(request), statementSanitizationEnabled); @@ -143,14 +143,14 @@ public void onStart(AttributesBuilder attributes, Context parentContext, REQUEST } private void setQueryParameters( - AttributesBuilder attributes, - boolean isBatch, - Map preparedStatementParameters) { - if (captureQueryParameters && !isBatch && preparedStatementParameters != null) { - for (Map.Entry entry : preparedStatementParameters.entrySet()) { + AttributesBuilder attributes, boolean isBatch, Map queryParameters) { + if (captureQueryParameters && !isBatch && queryParameters != null) { + for (Map.Entry entry : queryParameters.entrySet()) { String key = entry.getKey(); String value = entry.getValue(); - internalSet(attributes, DB_QUERY_PARAMETER.getAttributeKey(key), value); + if (value != null) { + internalSet(attributes, DB_QUERY_PARAMETER.getAttributeKey(key), value); + } } } } diff --git a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlClientAttributesExtractorBuilder.java b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlClientAttributesExtractorBuilder.java index b91aef0b4123..ecd2b89066d9 100644 --- a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlClientAttributesExtractorBuilder.java +++ b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlClientAttributesExtractorBuilder.java @@ -46,11 +46,6 @@ public SqlClientAttributesExtractorBuilder setTableAttribute( public SqlClientAttributesExtractorBuilder setStatementSanitizationEnabled( boolean statementSanitizationEnabled) { this.statementSanitizationEnabled = statementSanitizationEnabled; - - if (statementSanitizationEnabled) { - this.captureQueryParameters = false; - } - return this; } @@ -63,11 +58,6 @@ public SqlClientAttributesExtractorBuilder setStatementSaniti public SqlClientAttributesExtractorBuilder setCaptureQueryParameters( boolean captureQueryParameters) { this.captureQueryParameters = captureQueryParameters; - - if (captureQueryParameters) { - this.statementSanitizationEnabled = false; - } - return this; } diff --git a/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlClientAttributesExtractorTest.java b/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlClientAttributesExtractorTest.java index f740789d8bea..7537f2b90b37 100644 --- a/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlClientAttributesExtractorTest.java +++ b/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlClientAttributesExtractorTest.java @@ -64,7 +64,6 @@ public Long getBatchSize(Map map) { return read(map, "db.operation.batch.size", Long.class); } - @SuppressWarnings("EmptyCatch") @Nullable @Override public Map getQueryParameters(Map map) { @@ -438,22 +437,10 @@ void shouldExtractQueryParameters() { startAttributes.removeIf(attribute -> !attribute.getKey().startsWith(prefix)).build(); // then - if (SemconvStability.emitStableDatabaseSemconv() && SemconvStability.emitOldDatabaseSemconv()) { - assertThat(queryParameterAttributes) - .containsOnly( - entry(DB_QUERY_PARAMETER.getAttributeKey("0"), "'a'"), - entry(DB_QUERY_PARAMETER.getAttributeKey("1"), "1")); - } else if (SemconvStability.emitOldDatabaseSemconv()) { - assertThat(queryParameterAttributes) - .containsOnly( - entry(DB_QUERY_PARAMETER.getAttributeKey("0"), "'a'"), - entry(DB_QUERY_PARAMETER.getAttributeKey("1"), "1")); - } else if (SemconvStability.emitStableDatabaseSemconv()) { - assertThat(queryParameterAttributes) - .containsOnly( - entry(DB_QUERY_PARAMETER.getAttributeKey("0"), "'a'"), - entry(DB_QUERY_PARAMETER.getAttributeKey("1"), "1")); - } + assertThat(queryParameterAttributes) + .containsOnly( + entry(DB_QUERY_PARAMETER.getAttributeKey("0"), "'a'"), + entry(DB_QUERY_PARAMETER.getAttributeKey("1"), "1")); assertThat(endAttributes.build().isEmpty()).isTrue(); } 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 66da8ea0a155..a71627290e84 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 @@ -29,11 +29,16 @@ public final class JdbcSingletons { private static final Instrumenter STATEMENT_INSTRUMENTER; public static final Instrumenter DATASOURCE_INSTRUMENTER = createDataSourceInstrumenter(GlobalOpenTelemetry.get(), true); + public static final boolean CAPTURE_QUERY_PARAMETERS; static { JdbcAttributesGetter dbAttributesGetter = new JdbcAttributesGetter(); JdbcNetworkAttributesGetter netAttributesGetter = new JdbcNetworkAttributesGetter(); + CAPTURE_QUERY_PARAMETERS = + AgentInstrumentationConfig.get() + .getBoolean("otel.instrumentation.jdbc.capture-query-parameters", false); + STATEMENT_INSTRUMENTER = Instrumenter.builder( GlobalOpenTelemetry.get(), @@ -46,10 +51,7 @@ public final class JdbcSingletons { .getBoolean( "otel.instrumentation.jdbc.statement-sanitizer.enabled", AgentCommonConfig.get().isStatementSanitizationEnabled())) - .setCaptureQueryParameters( - AgentInstrumentationConfig.get() - .getBoolean( - "otel.instrumentation.jdbc.capture-query-parameters", false)) + .setCaptureQueryParameters(CAPTURE_QUERY_PARAMETERS) .build()) .addAttributesExtractor(ServerAttributesExtractor.create(netAttributesGetter)) .addAttributesExtractor( diff --git a/instrumentation/jdbc/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jdbc/PreparedStatementInstrumentation.java b/instrumentation/jdbc/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jdbc/PreparedStatementInstrumentation.java index aacf2a078e28..407109cb8c89 100644 --- a/instrumentation/jdbc/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jdbc/PreparedStatementInstrumentation.java +++ b/instrumentation/jdbc/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jdbc/PreparedStatementInstrumentation.java @@ -5,11 +5,11 @@ package io.opentelemetry.javaagent.instrumentation.jdbc; -import static io.opentelemetry.instrumentation.jdbc.internal.JdbcPreparedStatementStringifier.stringifyNullParameter; import static io.opentelemetry.instrumentation.jdbc.internal.JdbcPreparedStatementStringifier.stringifyParameter; import static io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge.currentContext; import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed; import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.implementsInterface; +import static io.opentelemetry.javaagent.instrumentation.jdbc.JdbcSingletons.CAPTURE_QUERY_PARAMETERS; import static io.opentelemetry.javaagent.instrumentation.jdbc.JdbcSingletons.statementInstrumenter; import static net.bytebuddy.matcher.ElementMatchers.isPublic; import static net.bytebuddy.matcher.ElementMatchers.nameStartsWith; @@ -27,7 +27,6 @@ import io.opentelemetry.javaagent.bootstrap.CallDepth; import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; -import java.math.BigDecimal; import java.net.URL; import java.sql.Date; import java.sql.PreparedStatement; @@ -65,74 +64,32 @@ public void transform(TypeTransformer transformer) { named("addBatch").and(takesNoArguments()).and(isPublic()), PreparedStatementInstrumentation.class.getName() + "$AddBatchAdvice"); transformer.applyAdviceToMethod( - named("setNull").and(takesArgument(0, int.class)).and(takesArguments(2)).and(isPublic()), - PreparedStatementInstrumentation.class.getName() + "$SetNull2Advice"); - transformer.applyAdviceToMethod( - named("setNull").and(takesArgument(0, int.class)).and(takesArguments(3)).and(isPublic()), - PreparedStatementInstrumentation.class.getName() + "$SetNull3Advice"); - transformer.applyAdviceToMethod( - named("setBoolean").and(takesArgument(0, int.class)).and(isPublic()), - PreparedStatementInstrumentation.class.getName() + "$SetBooleanAdvice"); - transformer.applyAdviceToMethod( - named("setByte").and(takesArgument(0, int.class)).and(isPublic()), - PreparedStatementInstrumentation.class.getName() + "$SetByteAdvice"); - transformer.applyAdviceToMethod( - named("setShort").and(takesArgument(0, int.class)).and(isPublic()), - PreparedStatementInstrumentation.class.getName() + "$SetShortAdvice"); - transformer.applyAdviceToMethod( - named("setInt").and(takesArgument(0, int.class)).and(isPublic()), - PreparedStatementInstrumentation.class.getName() + "$SetIntAdvice"); - transformer.applyAdviceToMethod( - named("setLong").and(takesArgument(0, int.class)).and(isPublic()), - PreparedStatementInstrumentation.class.getName() + "$SetLongAdvice"); - transformer.applyAdviceToMethod( - named("setFloat").and(takesArgument(0, int.class)).and(isPublic()), - PreparedStatementInstrumentation.class.getName() + "$SetFloatAdvice"); - transformer.applyAdviceToMethod( - named("setDouble").and(takesArgument(0, int.class)).and(isPublic()), - PreparedStatementInstrumentation.class.getName() + "$SetDoubleAdvice"); - transformer.applyAdviceToMethod( - named("setBigDecimal").and(takesArgument(0, int.class)).and(isPublic()), - PreparedStatementInstrumentation.class.getName() + "$SetBigDecimalAdvice"); - transformer.applyAdviceToMethod( - named("setString").and(takesArgument(0, int.class)).and(isPublic()), - PreparedStatementInstrumentation.class.getName() + "$SetStringAdvice"); - transformer.applyAdviceToMethod( - named("setBytes").and(takesArgument(0, int.class)).and(isPublic()), - PreparedStatementInstrumentation.class.getName() + "$SetBytesAdvice"); - transformer.applyAdviceToMethod( - named("setDate").and(takesArgument(0, int.class)).and(takesArguments(2)).and(isPublic()), - PreparedStatementInstrumentation.class.getName() + "$SetDate2Advice"); - transformer.applyAdviceToMethod( - named("setDate").and(takesArgument(0, int.class)).and(takesArguments(3)).and(isPublic()), - PreparedStatementInstrumentation.class.getName() + "$SetDate3Advice"); - transformer.applyAdviceToMethod( - named("setTime").and(takesArgument(0, int.class)).and(takesArguments(2)).and(isPublic()), - PreparedStatementInstrumentation.class.getName() + "$SetTime2Advice"); - transformer.applyAdviceToMethod( - named("setTime").and(takesArgument(0, int.class)).and(takesArguments(3)).and(isPublic()), - PreparedStatementInstrumentation.class.getName() + "$SetTime3Advice"); - transformer.applyAdviceToMethod( - named("setTimestamp") + namedOneOf( + "setBoolean", + "setShort", + "setInt", + "setLong", + "setFloat", + "setDouble", + "setBigDecimal", + "setString", + "setDate", + "setTime", + "setTimestamp", + "setURL", + "setRowId", + "setNString") .and(takesArgument(0, int.class)) .and(takesArguments(2)) .and(isPublic()), - PreparedStatementInstrumentation.class.getName() + "$SetTimestamp2Advice"); + PreparedStatementInstrumentation.class.getName() + "$SetParameter2Advice"); transformer.applyAdviceToMethod( - named("setTimestamp") + namedOneOf("setDate", "setTime", "setTimestamp") .and(takesArgument(0, int.class)) + .and(takesArgument(2, Calendar.class)) .and(takesArguments(3)) .and(isPublic()), - PreparedStatementInstrumentation.class.getName() + "$SetTimestamp3Advice"); - transformer.applyAdviceToMethod( - named("setURL").and(takesArgument(0, int.class)).and(isPublic()), - PreparedStatementInstrumentation.class.getName() + "$SetUrlAdvice"); - transformer.applyAdviceToMethod( - named("setRowId").and(takesArgument(0, int.class)).and(isPublic()), - PreparedStatementInstrumentation.class.getName() + "$SetRowIdAdvice"); - transformer.applyAdviceToMethod( - named("setNString").and(takesArgument(0, int.class)).and(isPublic()), - PreparedStatementInstrumentation.class.getName() + "$SetNstringAdvice"); + PreparedStatementInstrumentation.class.getName() + "$SetTimeParameter3Advice"); } @SuppressWarnings("unused") @@ -203,237 +160,68 @@ public static void addBatch(@Advice.This PreparedStatement statement) { } @SuppressWarnings("unused") - public static class SetNull2Advice { - @Advice.OnMethodExit(suppress = Throwable.class) - public static void onExit( - @Advice.This PreparedStatement statement, - @Advice.Argument(0) int index, - @Advice.Argument(1) int sqlType) { - JdbcData.addParameter(statement, Integer.toString(index - 1), stringifyNullParameter()); - } - } - - @SuppressWarnings("unused") - public static class SetNull3Advice { - @Advice.OnMethodExit(suppress = Throwable.class) - public static void onExit( - @Advice.This PreparedStatement statement, - @Advice.Argument(0) int index, - @Advice.Argument(1) int sqlType, - @Advice.Argument(2) String typeName) { - JdbcData.addParameter(statement, Integer.toString(index - 1), stringifyNullParameter()); - } - } - - @SuppressWarnings("unused") - public static class SetBooleanAdvice { - @Advice.OnMethodExit(suppress = Throwable.class) - public static void onExit( - @Advice.This PreparedStatement statement, - @Advice.Argument(0) int index, - @Advice.Argument(1) boolean value) { - JdbcData.addParameter(statement, Integer.toString(index - 1), stringifyParameter(value)); - } - } - - @SuppressWarnings("unused") - public static class SetByteAdvice { - @Advice.OnMethodExit(suppress = Throwable.class) - public static void onExit( - @Advice.This PreparedStatement statement, - @Advice.Argument(0) int index, - @Advice.Argument(1) byte value) { - JdbcData.addParameter(statement, Integer.toString(index - 1), stringifyParameter(value)); - } - } - - @SuppressWarnings("unused") - public static class SetShortAdvice { - @Advice.OnMethodExit(suppress = Throwable.class) - public static void onExit( - @Advice.This PreparedStatement statement, - @Advice.Argument(0) int index, - @Advice.Argument(1) short value) { - JdbcData.addParameter(statement, Integer.toString(index - 1), stringifyParameter(value)); - } - } - - @SuppressWarnings("unused") - public static class SetIntAdvice { - @Advice.OnMethodExit(suppress = Throwable.class) - public static void onExit( - @Advice.This PreparedStatement statement, - @Advice.Argument(0) int index, - @Advice.Argument(1) int value) { - JdbcData.addParameter(statement, Integer.toString(index - 1), stringifyParameter(value)); - } - } - - @SuppressWarnings("unused") - public static class SetLongAdvice { - @Advice.OnMethodExit(suppress = Throwable.class) - public static void onExit( - @Advice.This PreparedStatement statement, - @Advice.Argument(0) int index, - @Advice.Argument(1) long value) { - JdbcData.addParameter(statement, Integer.toString(index - 1), stringifyParameter(value)); - } - } - - @SuppressWarnings("unused") - public static class SetFloatAdvice { - @Advice.OnMethodExit(suppress = Throwable.class) - public static void onExit( - @Advice.This PreparedStatement statement, - @Advice.Argument(0) int index, - @Advice.Argument(1) float value) { - JdbcData.addParameter(statement, Integer.toString(index - 1), stringifyParameter(value)); - } - } - - @SuppressWarnings("unused") - public static class SetDoubleAdvice { + public static class SetParameter2Advice { @Advice.OnMethodExit(suppress = Throwable.class) public static void onExit( @Advice.This PreparedStatement statement, @Advice.Argument(0) int index, - @Advice.Argument(1) double value) { - JdbcData.addParameter(statement, Integer.toString(index - 1), stringifyParameter(value)); - } - } - - @SuppressWarnings("unused") - public static class SetBigDecimalAdvice { - @Advice.OnMethodExit(suppress = Throwable.class) - public static void onExit( - @Advice.This PreparedStatement statement, - @Advice.Argument(0) int index, - @Advice.Argument(1) BigDecimal value) { - JdbcData.addParameter(statement, Integer.toString(index - 1), stringifyParameter(value)); - } - } - - @SuppressWarnings("unused") - public static class SetStringAdvice { - @Advice.OnMethodExit(suppress = Throwable.class) - public static void onExit( - @Advice.This PreparedStatement statement, - @Advice.Argument(0) int index, - @Advice.Argument(1) String value) { - JdbcData.addParameter(statement, Integer.toString(index - 1), stringifyParameter(value)); - } - } - - @SuppressWarnings("unused") - public static class SetBytesAdvice { - @Advice.OnMethodExit(suppress = Throwable.class) - public static void onExit( - @Advice.This PreparedStatement statement, - @Advice.Argument(0) int index, - @Advice.Argument(1) byte[] value) { - JdbcData.addParameter(statement, Integer.toString(index - 1), stringifyParameter(value)); - } - } - - @SuppressWarnings("unused") - public static class SetDate2Advice { - @Advice.OnMethodExit(suppress = Throwable.class) - public static void onExit( - @Advice.This PreparedStatement statement, - @Advice.Argument(0) int index, - @Advice.Argument(1) Date value) { - JdbcData.addParameter(statement, Integer.toString(index - 1), stringifyParameter(value)); - } - } - - @SuppressWarnings("unused") - public static class SetDate3Advice { - @Advice.OnMethodExit(suppress = Throwable.class) - public static void onExit( - @Advice.This PreparedStatement statement, - @Advice.Argument(0) int index, - @Advice.Argument(1) Date value, - @Advice.Argument(2) Calendar calendar) { - JdbcData.addParameter(statement, Integer.toString(index - 1), stringifyParameter(value)); - } - } - - @SuppressWarnings("unused") - public static class SetTime2Advice { - @Advice.OnMethodExit(suppress = Throwable.class) - public static void onExit( - @Advice.This PreparedStatement statement, - @Advice.Argument(0) int index, - @Advice.Argument(1) Time value) { - JdbcData.addParameter(statement, Integer.toString(index - 1), stringifyParameter(value)); - } - } + @Advice.Argument(1) Object value) { + if (!CAPTURE_QUERY_PARAMETERS) { + return; + } - @SuppressWarnings("unused") - public static class SetTime3Advice { - @Advice.OnMethodExit(suppress = Throwable.class) - public static void onExit( - @Advice.This PreparedStatement statement, - @Advice.Argument(0) int index, - @Advice.Argument(1) Time value, - @Advice.Argument(2) Calendar calendar) { - JdbcData.addParameter(statement, Integer.toString(index - 1), stringifyParameter(value)); - } - } + String str = null; + + if (value instanceof Boolean) { + str = stringifyParameter((Boolean) value); + } else if (value instanceof Number) { + // Short, Int, Long, Float, Double, BigDecimal + str = stringifyParameter((Number) value); + } else if (value instanceof String) { + str = stringifyParameter((String) value); + } else if (value instanceof Date) { + str = stringifyParameter((Date) value); + } else if (value instanceof Time) { + str = stringifyParameter((Time) value); + } else if (value instanceof Timestamp) { + str = stringifyParameter((Timestamp) value); + } else if (value instanceof URL) { + str = stringifyParameter((URL) value); + } else if (value instanceof RowId) { + str = stringifyParameter((RowId) value); + } - @SuppressWarnings("unused") - public static class SetTimestamp2Advice { - @Advice.OnMethodExit(suppress = Throwable.class) - public static void onExit( - @Advice.This PreparedStatement statement, - @Advice.Argument(0) int index, - @Advice.Argument(1) Timestamp value) { - JdbcData.addParameter(statement, Integer.toString(index - 1), stringifyParameter(value)); + if (str != null) { + JdbcData.addParameter(statement, Integer.toString(index - 1), str); + } } } @SuppressWarnings("unused") - public static class SetTimestamp3Advice { + public static class SetTimeParameter3Advice { @Advice.OnMethodExit(suppress = Throwable.class) public static void onExit( @Advice.This PreparedStatement statement, @Advice.Argument(0) int index, - @Advice.Argument(1) Timestamp value, + @Advice.Argument(1) Object value, @Advice.Argument(2) Calendar calendar) { - JdbcData.addParameter(statement, Integer.toString(index - 1), stringifyParameter(value)); - } - } + if (!CAPTURE_QUERY_PARAMETERS) { + return; + } - @SuppressWarnings({"unused", "IdentifierName"}) - public static class SetUrlAdvice { - @Advice.OnMethodExit(suppress = Throwable.class) - public static void onExit( - @Advice.This PreparedStatement statement, - @Advice.Argument(0) int index, - @Advice.Argument(1) URL value) { - JdbcData.addParameter(statement, Integer.toString(index - 1), stringifyParameter(value)); - } - } + String str = null; - @SuppressWarnings("unused") - public static class SetRowIdAdvice { - @Advice.OnMethodExit(suppress = Throwable.class) - public static void onExit( - @Advice.This PreparedStatement statement, - @Advice.Argument(0) int index, - @Advice.Argument(1) RowId value) { - JdbcData.addParameter(statement, Integer.toString(index - 1), stringifyParameter(value)); - } - } + if (value instanceof Date) { + str = stringifyParameter((Date) value); + } else if (value instanceof Time) { + str = stringifyParameter((Time) value); + } else if (value instanceof Timestamp) { + str = stringifyParameter((Timestamp) value); + } - @SuppressWarnings("unused") - public static class SetNstringAdvice { - @Advice.OnMethodExit(suppress = Throwable.class) - public static void onExit( - @Advice.This PreparedStatement statement, - @Advice.Argument(0) int index, - @Advice.Argument(1) String value) { - JdbcData.addParameter(statement, Integer.toString(index - 1), stringifyParameter(value)); + if (str != null) { + JdbcData.addParameter(statement, Integer.toString(index - 1), str); + } } } } diff --git a/instrumentation/jdbc/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jdbc/StatementInstrumentation.java b/instrumentation/jdbc/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jdbc/StatementInstrumentation.java index 2df214e4ff3f..11867af96764 100644 --- a/instrumentation/jdbc/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jdbc/StatementInstrumentation.java +++ b/instrumentation/jdbc/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jdbc/StatementInstrumentation.java @@ -85,7 +85,7 @@ public static void onEnter( } Context parentContext = currentContext(); - request = DbRequest.create(statement, sql, null); + request = DbRequest.create(statement, sql); if (request == null || !statementInstrumenter().shouldStart(parentContext, request)) { return; @@ -168,11 +168,10 @@ public static void onEnter( } else { JdbcData.StatementBatchInfo batchInfo = JdbcData.getStatementBatchInfo(statement); if (batchInfo == null) { - request = DbRequest.create(statement, null, null); + request = DbRequest.create(statement, null); } else { request = - DbRequest.create( - statement, batchInfo.getStatements(), batchInfo.getBatchSize(), null); + DbRequest.create(statement, batchInfo.getStatements(), batchInfo.getBatchSize()); } } diff --git a/instrumentation/jdbc/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jdbc/test/PreparedStatementParametersTest.java b/instrumentation/jdbc/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jdbc/test/PreparedStatementParametersTest.java index 49d7eea19732..719c055f82be 100644 --- a/instrumentation/jdbc/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jdbc/test/PreparedStatementParametersTest.java +++ b/instrumentation/jdbc/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jdbc/test/PreparedStatementParametersTest.java @@ -33,7 +33,6 @@ import java.sql.SQLException; import java.sql.Time; import java.sql.Timestamp; -import java.sql.Types; import java.util.Calendar; import java.util.Locale; import java.util.Map; @@ -116,7 +115,7 @@ static Stream preparedStatementStream() throws SQLException { @ParameterizedTest @MethodSource("preparedStatementStream") - void testNull2PreparedStatementParameter( + void testBooleanPreparedStatementParameter( String system, Connection connection, String username, @@ -133,7 +132,7 @@ void testNull2PreparedStatementParameter( testing.runWithSpan( "parent", () -> { - statement.setNull(1, Types.INTEGER); + statement.setBoolean(1, true); statement.execute(); return statement.getResultSet(); }); @@ -157,147 +156,7 @@ void testNull2PreparedStatementParameter( equalTo(maybeStable(DB_STATEMENT), sanitizedQuery), equalTo(maybeStable(DB_OPERATION), "SELECT"), equalTo(maybeStable(DB_SQL_TABLE), table), - equalTo(DB_QUERY_PARAMETER.getAttributeKey("0"), "")))); - } - - @ParameterizedTest - @MethodSource("preparedStatementStream") - void testNull3PreparedStatementParameter( - String system, - Connection connection, - String username, - String query, - String sanitizedQuery, - String spanName, - String url, - String table) - throws SQLException { - PreparedStatement statement = connection.prepareStatement(query); - cleanup.deferCleanup(statement); - - ResultSet resultSet = - testing.runWithSpan( - "parent", - () -> { - statement.setNull(1, Types.INTEGER, "Integer"); - statement.execute(); - return statement.getResultSet(); - }); - - resultSet.next(); - assertThat(resultSet.getInt(1)).isEqualTo(3); - - testing.waitAndAssertTraces( - trace -> - trace.hasSpansSatisfyingExactly( - span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), - span -> - span.hasName(spanName) - .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), sanitizedQuery), - equalTo(maybeStable(DB_OPERATION), "SELECT"), - equalTo(maybeStable(DB_SQL_TABLE), table), - equalTo(DB_QUERY_PARAMETER.getAttributeKey("0"), "")))); - } - - @ParameterizedTest - @MethodSource("preparedStatementStream") - void testBytePreparedStatementParameter( - String system, - Connection connection, - String username, - String query, - String sanitizedQuery, - String spanName, - String url, - String table) - throws SQLException { - PreparedStatement statement = connection.prepareStatement(query); - cleanup.deferCleanup(statement); - - ResultSet resultSet = - testing.runWithSpan( - "parent", - () -> { - statement.setByte(1, (byte) 0); - statement.execute(); - return statement.getResultSet(); - }); - - resultSet.next(); - assertThat(resultSet.getInt(1)).isEqualTo(3); - - testing.waitAndAssertTraces( - trace -> - trace.hasSpansSatisfyingExactly( - span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), - span -> - span.hasName(spanName) - .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), sanitizedQuery), - equalTo(maybeStable(DB_OPERATION), "SELECT"), - equalTo(maybeStable(DB_SQL_TABLE), table), - equalTo(DB_QUERY_PARAMETER.getAttributeKey("0"), "0x00")))); - } - - @ParameterizedTest - @MethodSource("preparedStatementStream") - void testBytesPreparedStatementParameter( - String system, - Connection connection, - String username, - String query, - String sanitizedQuery, - String spanName, - String url, - String table) - throws SQLException { - String updatedColumn = query.replace("USER_NAME=?", "HEXTORAW('FF')=?"); - String updatedColumnSanitized = sanitizedQuery.replace("USER_NAME=?", "HEXTORAW('FF')=?"); - PreparedStatement statement = connection.prepareStatement(updatedColumn); - cleanup.deferCleanup(statement); - - ResultSet resultSet = - testing.runWithSpan( - "parent", - () -> { - statement.setBytes(1, new byte[] {(byte) 0, 0}); - statement.execute(); - return statement.getResultSet(); - }); - - resultSet.next(); - assertThat(resultSet.getInt(1)).isEqualTo(3); - - testing.waitAndAssertTraces( - trace -> - trace.hasSpansSatisfyingExactly( - span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), - span -> - span.hasName(spanName) - .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), updatedColumnSanitized), - equalTo(maybeStable(DB_OPERATION), "SELECT"), - equalTo(maybeStable(DB_SQL_TABLE), table), - equalTo(DB_QUERY_PARAMETER.getAttributeKey("0"), "0x0000")))); + equalTo(DB_QUERY_PARAMETER.getAttributeKey("0"), "true")))); } @ParameterizedTest @@ -619,7 +478,7 @@ void testStringPreparedStatementParameter( equalTo(maybeStable(DB_STATEMENT), sanitizedQuery), equalTo(maybeStable(DB_OPERATION), "SELECT"), equalTo(maybeStable(DB_SQL_TABLE), table), - equalTo(DB_QUERY_PARAMETER.getAttributeKey("0"), "'S'")))); + equalTo(DB_QUERY_PARAMETER.getAttributeKey("0"), "S")))); } @ParameterizedTest @@ -667,7 +526,7 @@ void testDate2PreparedStatementParameter( equalTo(maybeStable(DB_STATEMENT), updatedColumnSanitized), equalTo(maybeStable(DB_OPERATION), "SELECT"), equalTo(maybeStable(DB_SQL_TABLE), table), - equalTo(DB_QUERY_PARAMETER.getAttributeKey("0"), "'2000-01-01'")))); + equalTo(DB_QUERY_PARAMETER.getAttributeKey("0"), "2000-01-01")))); } @ParameterizedTest @@ -715,7 +574,7 @@ void testDate3PreparedStatementParameter( equalTo(maybeStable(DB_STATEMENT), updatedColumnSanitized), equalTo(maybeStable(DB_OPERATION), "SELECT"), equalTo(maybeStable(DB_SQL_TABLE), table), - equalTo(DB_QUERY_PARAMETER.getAttributeKey("0"), "'2000-01-01'")))); + equalTo(DB_QUERY_PARAMETER.getAttributeKey("0"), "2000-01-01")))); } @ParameterizedTest @@ -763,7 +622,7 @@ void testTime2PreparedStatementParameter( equalTo(maybeStable(DB_STATEMENT), updatedColumnSanitized), equalTo(maybeStable(DB_OPERATION), "SELECT"), equalTo(maybeStable(DB_SQL_TABLE), table), - equalTo(DB_QUERY_PARAMETER.getAttributeKey("0"), "'00:00:00'")))); + equalTo(DB_QUERY_PARAMETER.getAttributeKey("0"), "00:00:00")))); } @ParameterizedTest @@ -811,7 +670,7 @@ void testTime3PreparedStatementParameter( equalTo(maybeStable(DB_STATEMENT), updatedColumnSanitized), equalTo(maybeStable(DB_OPERATION), "SELECT"), equalTo(maybeStable(DB_SQL_TABLE), table), - equalTo(DB_QUERY_PARAMETER.getAttributeKey("0"), "'00:00:00'")))); + equalTo(DB_QUERY_PARAMETER.getAttributeKey("0"), "00:00:00")))); } @ParameterizedTest @@ -861,7 +720,7 @@ void testTimestamp2PreparedStatementParameter( equalTo(maybeStable(DB_SQL_TABLE), table), equalTo( DB_QUERY_PARAMETER.getAttributeKey("0"), - "'2000-01-01 00:00:00.0'")))); + "2000-01-01 00:00:00.0")))); } @ParameterizedTest @@ -912,7 +771,7 @@ void testTimestamp3PreparedStatementParameter( equalTo(maybeStable(DB_SQL_TABLE), table), equalTo( DB_QUERY_PARAMETER.getAttributeKey("0"), - "'2000-01-01 00:00:00.0'")))); + "2000-01-01 00:00:00.0")))); } @ParameterizedTest @@ -960,7 +819,7 @@ DB_CONNECTION_STRING, emitStableDatabaseSemconv() ? null : url), equalTo(maybeStable(DB_STATEMENT), sanitizedQuery), equalTo(maybeStable(DB_OPERATION), "SELECT"), equalTo(maybeStable(DB_SQL_TABLE), table), - equalTo(DB_QUERY_PARAMETER.getAttributeKey("0"), "'S'")))); + equalTo(DB_QUERY_PARAMETER.getAttributeKey("0"), "S")))); } } } diff --git a/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/OpenTelemetryDriver.java b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/OpenTelemetryDriver.java index d50039ca9bdd..d09e00f86694 100644 --- a/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/OpenTelemetryDriver.java +++ b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/OpenTelemetryDriver.java @@ -244,7 +244,11 @@ public Connection connect(String url, Properties info) throws SQLException { Instrumenter statementInstrumenter = JdbcInstrumenterFactory.createStatementInstrumenter(openTelemetry); - return OpenTelemetryConnection.create(connection, dbInfo, statementInstrumenter); + + boolean captureQueryParameters = JdbcInstrumenterFactory.captureQueryParameters(); + + return OpenTelemetryConnection.create( + connection, dbInfo, statementInstrumenter, captureQueryParameters); } @Override 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 index cc857b823563..7bd9a614ec80 100644 --- 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 @@ -26,16 +26,22 @@ public static JdbcTelemetryBuilder builder(OpenTelemetry openTelemetry) { private final Instrumenter dataSourceInstrumenter; private final Instrumenter statementInstrumenter; + private final boolean captureQueryParameters; JdbcTelemetry( Instrumenter dataSourceInstrumenter, - Instrumenter statementInstrumenter) { + Instrumenter statementInstrumenter, + boolean captureQueryParameters) { this.dataSourceInstrumenter = dataSourceInstrumenter; this.statementInstrumenter = statementInstrumenter; + this.captureQueryParameters = captureQueryParameters; } public DataSource wrap(DataSource dataSource) { return new OpenTelemetryDataSource( - dataSource, this.dataSourceInstrumenter, this.statementInstrumenter); + dataSource, + this.dataSourceInstrumenter, + this.statementInstrumenter, + this.captureQueryParameters); } } 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 index 12faa2dea3f9..00531b6d60e1 100644 --- 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 @@ -68,6 +68,7 @@ public JdbcTelemetry build() { openTelemetry, statementInstrumenterEnabled, statementSanitizationEnabled, - captureQueryParameters)); + captureQueryParameters), + captureQueryParameters); } } 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 deb0425ff223..45210b4b1b9a 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 @@ -48,6 +48,7 @@ public class OpenTelemetryDataSource implements DataSource, AutoCloseable { private final Instrumenter dataSourceInstrumenter; private final Instrumenter statementInstrumenter; private volatile DbInfo cachedDbInfo; + private final boolean captureQueryParameters; /** * Create a OpenTelemetry DataSource wrapping another DataSource. @@ -71,6 +72,7 @@ public OpenTelemetryDataSource(DataSource delegate, OpenTelemetry openTelemetry) this.delegate = delegate; this.dataSourceInstrumenter = createDataSourceInstrumenter(openTelemetry, true); this.statementInstrumenter = createStatementInstrumenter(openTelemetry); + this.captureQueryParameters = false; } /** @@ -83,24 +85,28 @@ public OpenTelemetryDataSource(DataSource delegate, OpenTelemetry openTelemetry) OpenTelemetryDataSource( DataSource delegate, Instrumenter dataSourceInstrumenter, - Instrumenter statementInstrumenter) { + Instrumenter statementInstrumenter, + boolean captureQueryParameters) { this.delegate = delegate; this.dataSourceInstrumenter = dataSourceInstrumenter; this.statementInstrumenter = statementInstrumenter; + this.captureQueryParameters = captureQueryParameters; } @Override public Connection getConnection() throws SQLException { Connection connection = wrapCall(delegate::getConnection); DbInfo dbInfo = getDbInfo(connection); - return OpenTelemetryConnection.create(connection, dbInfo, statementInstrumenter); + return OpenTelemetryConnection.create( + connection, dbInfo, statementInstrumenter, captureQueryParameters); } @Override public Connection getConnection(String username, String password) throws SQLException { Connection connection = wrapCall(() -> delegate.getConnection(username, password)); DbInfo dbInfo = getDbInfo(connection); - return OpenTelemetryConnection.create(connection, dbInfo, statementInstrumenter); + return OpenTelemetryConnection.create( + connection, dbInfo, statementInstrumenter, captureQueryParameters); } @Override diff --git a/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/DbRequest.java b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/DbRequest.java index ac3af0d555fe..f9568fccaa65 100644 --- a/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/DbRequest.java +++ b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/DbRequest.java @@ -32,6 +32,11 @@ public static DbRequest create( statement, JdbcData.preparedStatement.get(statement), preparedStatementParameters); } + @Nullable + public static DbRequest create(Statement statement, String dbStatementString) { + return create(statement, dbStatementString, null, Collections.emptyMap()); + } + @Nullable public static DbRequest create( Statement statement, @@ -56,20 +61,17 @@ public static DbRequest create( } public static DbRequest create( - Statement statement, - Collection queryTexts, - Long batchSize, - Map preparedStatementParameters) { + Statement statement, Collection queryTexts, Long batchSize) { Connection connection = connectionFromStatement(statement); if (connection == null) { return null; } - return create(extractDbInfo(connection), queryTexts, batchSize, preparedStatementParameters); + return create(extractDbInfo(connection), queryTexts, batchSize, Collections.emptyMap()); } public static DbRequest create(DbInfo dbInfo, String queryText) { - return create(dbInfo, queryText, null, null); + return create(dbInfo, queryText, null, Collections.emptyMap()); } public static DbRequest create( diff --git a/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/JdbcAttributesGetter.java b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/JdbcAttributesGetter.java index dc9c83ae6696..3f687614c52d 100644 --- a/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/JdbcAttributesGetter.java +++ b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/JdbcAttributesGetter.java @@ -9,6 +9,7 @@ import io.opentelemetry.instrumentation.jdbc.internal.dbinfo.DbInfo; import java.sql.SQLException; import java.util.Collection; +import java.util.Collections; import java.util.Map; import javax.annotation.Nullable; @@ -64,9 +65,14 @@ public String getResponseStatus(@Nullable Void response, @Nullable Throwable err return null; } - @Nullable @Override public Map getQueryParameters(DbRequest request) { - return request.getPreparedStatementParameters(); + Map parameters = request.getPreparedStatementParameters(); + + if (parameters == null) { + return Collections.emptyMap(); + } + + return parameters; } } diff --git a/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/JdbcData.java b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/JdbcData.java index 50a29570edca..d465e9299874 100644 --- a/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/JdbcData.java +++ b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/JdbcData.java @@ -106,16 +106,19 @@ public static void close(Statement statement) { PreparedStatement prepared = (PreparedStatement) statement; preparedStatement.set(prepared, null); preparedStatementBatch.set(prepared, null); + parameters.set(prepared, null); } } - public static void addParameter(PreparedStatement statement, String index, String value) { - Map parametersMap = parameters.get(statement); - if (parametersMap == null) { - parametersMap = new HashMap<>(); - parameters.set(statement, parametersMap); + public static void addParameter(PreparedStatement statement, String key, String value) { + if (value != null) { + Map parametersMap = parameters.get(statement); + if (parametersMap == null) { + parametersMap = new HashMap<>(); + parameters.set(statement, parametersMap); + } + parametersMap.put(key, value); } - parametersMap.put(index, value); } /** 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 2b3aa2e8e925..a72a753cd2a9 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 @@ -29,6 +29,11 @@ public final class JdbcInstrumenterFactory { private static final JdbcNetworkAttributesGetter netAttributesGetter = new JdbcNetworkAttributesGetter(); + public static boolean captureQueryParameters() { + return ConfigPropertiesUtil.getBoolean( + "otel.instrumentation.jdbc.capture-query-parameters", false); + } + public static Instrumenter createStatementInstrumenter() { return createStatementInstrumenter(GlobalOpenTelemetry.get()); } @@ -40,8 +45,7 @@ public static Instrumenter createStatementInstrumenter( true, ConfigPropertiesUtil.getBoolean( "otel.instrumentation.common.db-statement-sanitizer.enabled", true), - ConfigPropertiesUtil.getBoolean( - "otel.instrumentation.jdbc.capture-query-parameters", false)); + captureQueryParameters()); } public static Instrumenter createStatementInstrumenter( diff --git a/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/JdbcPreparedStatementStringifier.java b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/JdbcPreparedStatementStringifier.java index 19f5b11360b6..ac28c2cc71b1 100644 --- a/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/JdbcPreparedStatementStringifier.java +++ b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/JdbcPreparedStatementStringifier.java @@ -20,53 +20,34 @@ public class JdbcPreparedStatementStringifier { private JdbcPreparedStatementStringifier() {} public static String stringifyParameter(String value) { - return String.format("'%s'", value); + return value; } public static String stringifyParameter(Number value) { - return String.format("%s", value); + return value != null ? value.toString() : null; } public static String stringifyParameter(boolean value) { - return String.format("%s", value); - } - - @SuppressWarnings("DefaultLocale") - public static String stringifyParameter(byte value) { - return String.format("0x%02x", value); - } - - @SuppressWarnings("DefaultLocale") - public static String stringifyParameter(byte[] value) { - StringBuilder builder = new StringBuilder(); - builder.append("0x"); - for (Byte b : value) { - builder.append(String.format("%02x", b)); - } - return builder.toString(); + return value ? Boolean.TRUE.toString() : Boolean.FALSE.toString(); } public static String stringifyParameter(Date value) { - return String.format("'%s'", value.toString()); + return value != null ? value.toString() : null; } public static String stringifyParameter(Time value) { - return String.format("'%s'", value.toString()); + return value != null ? value.toString() : null; } public static String stringifyParameter(Timestamp value) { - return String.format("'%s'", value.toString()); + return value != null ? value.toString() : null; } public static String stringifyParameter(URL value) { - return String.format("'%s'", value.toString()); + return value != null ? value.toString() : null; } public static String stringifyParameter(RowId value) { - return String.format("'%s'", value.toString()); - } - - public static String stringifyNullParameter() { - return ""; + return value != null ? value.toString() : null; } } diff --git a/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/OpenTelemetryCallableStatement.java b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/OpenTelemetryCallableStatement.java index f2c25b172de7..7d6b1eab0920 100644 --- a/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/OpenTelemetryCallableStatement.java +++ b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/OpenTelemetryCallableStatement.java @@ -51,8 +51,9 @@ public OpenTelemetryCallableStatement( OpenTelemetryConnection connection, DbInfo dbInfo, String query, - Instrumenter instrumenter) { - super(delegate, connection, dbInfo, query, instrumenter); + Instrumenter instrumenter, + boolean captureQueryParameters) { + super(delegate, connection, dbInfo, query, instrumenter, captureQueryParameters); } @Override diff --git a/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/OpenTelemetryConnection.java b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/OpenTelemetryConnection.java index 8fe5ca26ab1d..0ec11c7c2331 100644 --- a/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/OpenTelemetryConnection.java +++ b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/OpenTelemetryConnection.java @@ -52,12 +52,17 @@ public class OpenTelemetryConnection implements Connection { protected final Connection delegate; private final DbInfo dbInfo; protected final Instrumenter statementInstrumenter; + private final boolean captureQueryParameters; protected OpenTelemetryConnection( - Connection delegate, DbInfo dbInfo, Instrumenter statementInstrumenter) { + Connection delegate, + DbInfo dbInfo, + Instrumenter statementInstrumenter, + boolean captureQueryParameters) { this.delegate = delegate; this.dbInfo = dbInfo; this.statementInstrumenter = statementInstrumenter; + this.captureQueryParameters = captureQueryParameters; } // visible for testing @@ -71,11 +76,16 @@ static boolean hasJdbc43() { } public static Connection create( - Connection delegate, DbInfo dbInfo, Instrumenter statementInstrumenter) { + Connection delegate, + DbInfo dbInfo, + Instrumenter statementInstrumenter, + boolean captureQueryParameters) { if (hasJdbc43) { - return new OpenTelemetryConnectionJdbc43(delegate, dbInfo, statementInstrumenter); + return new OpenTelemetryConnectionJdbc43( + delegate, dbInfo, statementInstrumenter, captureQueryParameters); } - return new OpenTelemetryConnection(delegate, dbInfo, statementInstrumenter); + return new OpenTelemetryConnection( + delegate, dbInfo, statementInstrumenter, captureQueryParameters); } @Override @@ -103,7 +113,7 @@ public Statement createStatement( public PreparedStatement prepareStatement(String sql) throws SQLException { PreparedStatement statement = delegate.prepareStatement(sql); return new OpenTelemetryPreparedStatement<>( - statement, this, dbInfo, sql, statementInstrumenter); + statement, this, dbInfo, sql, statementInstrumenter, captureQueryParameters); } @Override @@ -112,7 +122,7 @@ public PreparedStatement prepareStatement(String sql, int resultSetType, int res PreparedStatement statement = delegate.prepareStatement(sql, resultSetType, resultSetConcurrency); return new OpenTelemetryPreparedStatement<>( - statement, this, dbInfo, sql, statementInstrumenter); + statement, this, dbInfo, sql, statementInstrumenter, captureQueryParameters); } @Override @@ -122,35 +132,35 @@ public PreparedStatement prepareStatement( PreparedStatement statement = delegate.prepareStatement(sql, resultSetType, resultSetConcurrency, resultSetHoldability); return new OpenTelemetryPreparedStatement<>( - statement, this, dbInfo, sql, statementInstrumenter); + statement, this, dbInfo, sql, statementInstrumenter, captureQueryParameters); } @Override public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) throws SQLException { PreparedStatement statement = delegate.prepareStatement(sql, autoGeneratedKeys); return new OpenTelemetryPreparedStatement<>( - statement, this, dbInfo, sql, statementInstrumenter); + statement, this, dbInfo, sql, statementInstrumenter, captureQueryParameters); } @Override public PreparedStatement prepareStatement(String sql, int[] columnIndexes) throws SQLException { PreparedStatement statement = delegate.prepareStatement(sql, columnIndexes); return new OpenTelemetryPreparedStatement<>( - statement, this, dbInfo, sql, statementInstrumenter); + statement, this, dbInfo, sql, statementInstrumenter, captureQueryParameters); } @Override public PreparedStatement prepareStatement(String sql, String[] columnNames) throws SQLException { PreparedStatement statement = delegate.prepareStatement(sql, columnNames); return new OpenTelemetryPreparedStatement<>( - statement, this, dbInfo, sql, statementInstrumenter); + statement, this, dbInfo, sql, statementInstrumenter, captureQueryParameters); } @Override public CallableStatement prepareCall(String sql) throws SQLException { CallableStatement statement = delegate.prepareCall(sql); return new OpenTelemetryCallableStatement<>( - statement, this, dbInfo, sql, statementInstrumenter); + statement, this, dbInfo, sql, statementInstrumenter, captureQueryParameters); } @Override @@ -158,7 +168,7 @@ public CallableStatement prepareCall(String sql, int resultSetType, int resultSe throws SQLException { CallableStatement statement = delegate.prepareCall(sql, resultSetType, resultSetConcurrency); return new OpenTelemetryCallableStatement<>( - statement, this, dbInfo, sql, statementInstrumenter); + statement, this, dbInfo, sql, statementInstrumenter, captureQueryParameters); } @Override @@ -168,7 +178,7 @@ public CallableStatement prepareCall( CallableStatement statement = delegate.prepareCall(sql, resultSetType, resultSetConcurrency, resultSetHoldability); return new OpenTelemetryCallableStatement<>( - statement, this, dbInfo, sql, statementInstrumenter); + statement, this, dbInfo, sql, statementInstrumenter, captureQueryParameters); } @Override @@ -393,8 +403,11 @@ public DbInfo getDbInfo() { // JDBC 4.3 static class OpenTelemetryConnectionJdbc43 extends OpenTelemetryConnection { OpenTelemetryConnectionJdbc43( - Connection delegate, DbInfo dbInfo, Instrumenter statementInstrumenter) { - super(delegate, dbInfo, statementInstrumenter); + Connection delegate, + DbInfo dbInfo, + Instrumenter statementInstrumenter, + boolean captureQueryParameters) { + super(delegate, dbInfo, statementInstrumenter, captureQueryParameters); } @SuppressWarnings("Since15") diff --git a/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/OpenTelemetryPreparedStatement.java b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/OpenTelemetryPreparedStatement.java index b2ac9a315362..64142b24c215 100644 --- a/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/OpenTelemetryPreparedStatement.java +++ b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/OpenTelemetryPreparedStatement.java @@ -20,7 +20,6 @@ package io.opentelemetry.instrumentation.jdbc.internal; -import static io.opentelemetry.instrumentation.jdbc.internal.JdbcPreparedStatementStringifier.stringifyNullParameter; import static io.opentelemetry.instrumentation.jdbc.internal.JdbcPreparedStatementStringifier.stringifyParameter; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; @@ -52,6 +51,7 @@ @SuppressWarnings("OverloadMethodsDeclarationOrder") class OpenTelemetryPreparedStatement extends OpenTelemetryStatement implements PreparedStatement { + private final boolean captureQueryParameters; private final Map parameters; public OpenTelemetryPreparedStatement( @@ -59,13 +59,17 @@ public OpenTelemetryPreparedStatement( OpenTelemetryConnection connection, DbInfo dbInfo, String query, - Instrumenter instrumenter) { + Instrumenter instrumenter, + boolean captureQueryParameters) { super(delegate, connection, dbInfo, query, instrumenter); + this.captureQueryParameters = captureQueryParameters; this.parameters = new HashMap<>(); } private void putParameter(int index, String value) { - parameters.put(Integer.toString(index - 1), value); + if (this.captureQueryParameters) { + parameters.put(Integer.toString(index - 1), value); + } } @Override @@ -87,14 +91,12 @@ public boolean execute() throws SQLException { @Override public void setNull(int parameterIndex, int sqlType) throws SQLException { delegate.setNull(parameterIndex, sqlType); - putParameter(parameterIndex, stringifyNullParameter()); } @SuppressWarnings("UngroupedOverloads") @Override public void setNull(int parameterIndex, int sqlType, String typeName) throws SQLException { delegate.setNull(parameterIndex, sqlType, typeName); - putParameter(parameterIndex, stringifyNullParameter()); } @Override @@ -106,7 +108,6 @@ public void setBoolean(int parameterIndex, boolean x) throws SQLException { @Override public void setByte(int parameterIndex, byte x) throws SQLException { delegate.setByte(parameterIndex, x); - putParameter(parameterIndex, stringifyParameter(x)); } @Override @@ -154,7 +155,6 @@ public void setString(int parameterIndex, String x) throws SQLException { @Override public void setBytes(int parameterIndex, byte[] x) throws SQLException { delegate.setBytes(parameterIndex, x); - putParameter(parameterIndex, stringifyParameter(x)); } @SuppressWarnings("UngroupedOverloads") diff --git a/instrumentation/jdbc/library/src/test/java/io/opentelemetry/instrumentation/jdbc/internal/OpenTelemetryConnectionTest.java b/instrumentation/jdbc/library/src/test/java/io/opentelemetry/instrumentation/jdbc/internal/OpenTelemetryConnectionTest.java index c0513ca6f795..a045b8b0588a 100644 --- a/instrumentation/jdbc/library/src/test/java/io/opentelemetry/instrumentation/jdbc/internal/OpenTelemetryConnectionTest.java +++ b/instrumentation/jdbc/library/src/test/java/io/opentelemetry/instrumentation/jdbc/internal/OpenTelemetryConnectionTest.java @@ -31,11 +31,18 @@ import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension; import io.opentelemetry.sdk.testing.assertj.AttributeAssertion; +import java.math.BigDecimal; +import java.net.MalformedURLException; +import java.net.URI; +import java.sql.Date; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; +import java.sql.Time; +import java.sql.Timestamp; import java.util.Arrays; +import java.util.Calendar; import java.util.List; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -53,7 +60,7 @@ void testVerifyCreateStatement() throws SQLException { createStatementInstrumenter(testing.getOpenTelemetry()); DbInfo dbInfo = getDbInfo(); OpenTelemetryConnection connection = - new OpenTelemetryConnection(new TestConnection(), dbInfo, instrumenter); + new OpenTelemetryConnection(new TestConnection(), dbInfo, instrumenter, false); String query = "SELECT * FROM users"; Statement statement = connection.createStatement(); @@ -75,7 +82,7 @@ void testVerifyCreateStatementReturnsOtelWrapper() throws Exception { OpenTelemetry ot = OpenTelemetry.propagating(ContextPropagators.noop()); Instrumenter instrumenter = createStatementInstrumenter(ot); OpenTelemetryConnection connection = - new OpenTelemetryConnection(new TestConnection(), DbInfo.DEFAULT, instrumenter); + new OpenTelemetryConnection(new TestConnection(), DbInfo.DEFAULT, instrumenter, false); assertThat(connection.createStatement()).isInstanceOf(OpenTelemetryStatement.class); assertThat(connection.createStatement(0, 0)).isInstanceOf(OpenTelemetryStatement.class); @@ -92,7 +99,7 @@ void testVerifyPrepareStatement() throws SQLException { createStatementInstrumenter(testing.getOpenTelemetry()); DbInfo dbInfo = getDbInfo(); OpenTelemetryConnection connection = - new OpenTelemetryConnection(new TestConnection(), dbInfo, instrumenter); + new OpenTelemetryConnection(new TestConnection(), dbInfo, instrumenter, false); String query = "SELECT * FROM users"; PreparedStatement statement = connection.prepareStatement(query); @@ -117,7 +124,7 @@ void testVerifyPrepareStatementUpdate() throws SQLException { createStatementInstrumenter(testing.getOpenTelemetry()); DbInfo dbInfo = getDbInfo(); OpenTelemetryConnection connection = - new OpenTelemetryConnection(new TestConnection(), dbInfo, instrumenter); + new OpenTelemetryConnection(new TestConnection(), dbInfo, instrumenter, false); String query = "UPDATE users SET name = name"; PreparedStatement statement = connection.prepareStatement(query); @@ -140,7 +147,7 @@ void testVerifyPrepareStatementQuery() throws SQLException { createStatementInstrumenter(testing.getOpenTelemetry()); DbInfo dbInfo = getDbInfo(); OpenTelemetryConnection connection = - new OpenTelemetryConnection(new TestConnection(), dbInfo, instrumenter); + new OpenTelemetryConnection(new TestConnection(), dbInfo, instrumenter, false); String query = "SELECT * FROM users"; PreparedStatement statement = connection.prepareStatement(query); @@ -164,7 +171,7 @@ void testVerifyPrepareStatementReturnsOtelWrapper() throws Exception { OpenTelemetry ot = OpenTelemetry.propagating(ContextPropagators.noop()); Instrumenter instrumenter = createStatementInstrumenter(ot); OpenTelemetryConnection connection = - new OpenTelemetryConnection(new TestConnection(), DbInfo.DEFAULT, instrumenter); + new OpenTelemetryConnection(new TestConnection(), DbInfo.DEFAULT, instrumenter, false); String query = "SELECT * FROM users"; assertThat(connection.prepareStatement(query)) @@ -192,7 +199,7 @@ void testVerifyPrepareCall() throws SQLException { createStatementInstrumenter(testing.getOpenTelemetry()); DbInfo dbInfo = getDbInfo(); OpenTelemetryConnection connection = - new OpenTelemetryConnection(new TestConnection(), dbInfo, instrumenter); + new OpenTelemetryConnection(new TestConnection(), dbInfo, instrumenter, false); String query = "SELECT * FROM users"; PreparedStatement statement = connection.prepareCall(query); @@ -214,7 +221,7 @@ void testVerifyPrepareCallReturnsOtelWrapper() throws Exception { OpenTelemetry ot = OpenTelemetry.propagating(ContextPropagators.noop()); Instrumenter instrumenter = createStatementInstrumenter(ot); OpenTelemetryConnection connection = - new OpenTelemetryConnection(new TestConnection(), DbInfo.DEFAULT, instrumenter); + new OpenTelemetryConnection(new TestConnection(), DbInfo.DEFAULT, instrumenter, false); String query = "SELECT * FROM users"; assertThat(connection.prepareCall(query)).isInstanceOf(OpenTelemetryCallableStatement.class); @@ -233,17 +240,32 @@ void testVerifyPrepareCallReturnsOtelWrapper() throws Exception { // https://github.com/open-telemetry/semantic-conventions/pull/2093 @SuppressWarnings("deprecation") @Test - void testVerifyPrepareStatementParameters() throws SQLException { + void testVerifyPrepareStatementParameters() throws SQLException, MalformedURLException { Instrumenter instrumenter = createStatementInstrumenter(testing.getOpenTelemetry(), true); DbInfo dbInfo = getDbInfo(); OpenTelemetryConnection connection = - new OpenTelemetryConnection(new TestConnection(), dbInfo, instrumenter); - String query = "SELECT * FROM users WHERE id=? AND name=? AND age=3"; - String sanitized = "SELECT * FROM users WHERE id=? AND name=? AND age=3"; + new OpenTelemetryConnection(new TestConnection(), dbInfo, instrumenter, true); + String query = "SELECT * FROM users WHERE id=? AND age=3"; + String sanitized = "SELECT * FROM users WHERE id=? AND age=3"; PreparedStatement statement = connection.prepareStatement(query); - statement.setInt(1, 1); - statement.setString(2, "bob"); + // doesn't need to match the number of placeholders in this context + statement.setBoolean(1, true); + statement.setShort(2, (short) 1); + statement.setInt(3, 2); + statement.setLong(4, 3); + statement.setFloat(5, 4); + statement.setDouble(6, 5.5); + statement.setBigDecimal(7, BigDecimal.valueOf(6)); + statement.setString(8, "S"); + statement.setDate(9, Date.valueOf("2000-01-01")); + statement.setDate(10, Date.valueOf("2000-01-02"), Calendar.getInstance()); + statement.setTime(11, Time.valueOf("00:00:00")); + statement.setTime(12, Time.valueOf("00:00:01"), Calendar.getInstance()); + statement.setTimestamp(13, Timestamp.valueOf("2000-01-01 00:00:00")); + statement.setTimestamp(14, Timestamp.valueOf("2000-01-01 00:00:01"), Calendar.getInstance()); + statement.setURL(15, URI.create("http://localhost:8080").toURL()); + statement.setNString(16, "S"); testing.runWithSpan( "parent", @@ -257,8 +279,22 @@ void testVerifyPrepareStatementParameters() throws SQLException { dbInfo, sanitized, "SELECT", - equalTo(DB_QUERY_PARAMETER.getAttributeKey("0"), "1"), - equalTo(DB_QUERY_PARAMETER.getAttributeKey("1"), "'bob'")); + equalTo(DB_QUERY_PARAMETER.getAttributeKey("0"), "true"), + equalTo(DB_QUERY_PARAMETER.getAttributeKey("1"), "1"), + equalTo(DB_QUERY_PARAMETER.getAttributeKey("2"), "2"), + equalTo(DB_QUERY_PARAMETER.getAttributeKey("3"), "3"), + equalTo(DB_QUERY_PARAMETER.getAttributeKey("4"), "4.0"), + equalTo(DB_QUERY_PARAMETER.getAttributeKey("5"), "5.5"), + equalTo(DB_QUERY_PARAMETER.getAttributeKey("6"), "6"), + equalTo(DB_QUERY_PARAMETER.getAttributeKey("7"), "S"), + equalTo(DB_QUERY_PARAMETER.getAttributeKey("8"), "2000-01-01"), + equalTo(DB_QUERY_PARAMETER.getAttributeKey("9"), "2000-01-02"), + equalTo(DB_QUERY_PARAMETER.getAttributeKey("10"), "00:00:00"), + equalTo(DB_QUERY_PARAMETER.getAttributeKey("11"), "00:00:01"), + equalTo(DB_QUERY_PARAMETER.getAttributeKey("12"), "2000-01-01 00:00:00.0"), + equalTo(DB_QUERY_PARAMETER.getAttributeKey("13"), "2000-01-01 00:00:01.0"), + equalTo(DB_QUERY_PARAMETER.getAttributeKey("14"), "http://localhost:8080"), + equalTo(DB_QUERY_PARAMETER.getAttributeKey("15"), "S")); statement.close(); connection.close(); From 18eb6f83eeb57b521cc62c7d62ee4776aaa9bd15 Mon Sep 17 00:00:00 2001 From: Alix Date: Wed, 30 Apr 2025 14:48:22 +0200 Subject: [PATCH 10/15] send back emptyMap instead of null --- .../api/incubator/semconv/db/SqlClientAttributesGetter.java | 4 ++-- .../semconv/db/SqlClientAttributesExtractorTest.java | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlClientAttributesGetter.java b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlClientAttributesGetter.java index e4e28d4a5aa1..bdf64e638a1c 100644 --- a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlClientAttributesGetter.java +++ b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlClientAttributesGetter.java @@ -9,6 +9,7 @@ import static java.util.Collections.singleton; import java.util.Collection; +import java.util.Collections; import java.util.Map; import javax.annotation.Nullable; @@ -69,8 +70,7 @@ default Long getBatchSize(REQUEST request) { } // TODO: make this required to implement - @Nullable default Map getQueryParameters(REQUEST request) { - return null; + return Collections.emptyMap(); } } diff --git a/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlClientAttributesExtractorTest.java b/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlClientAttributesExtractorTest.java index 7537f2b90b37..2a42564a252c 100644 --- a/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlClientAttributesExtractorTest.java +++ b/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlClientAttributesExtractorTest.java @@ -70,7 +70,7 @@ public Map getQueryParameters(Map map) { String parameterString = read(map, "db.query.parameter"); if (parameterString == null) { - return null; + return Collections.emptyMap(); } Map parameters = new HashMap<>(); From b22ffe6044ba6f0044fac916b63d323c81449e32 Mon Sep 17 00:00:00 2001 From: Alix Date: Sun, 4 May 2025 10:36:22 +0200 Subject: [PATCH 11/15] update metadata.yaml with capture-query-parameters --- instrumentation/jdbc/metadata.yaml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/instrumentation/jdbc/metadata.yaml b/instrumentation/jdbc/metadata.yaml index e52ce68e1aef..a0d7884cb21b 100644 --- a/instrumentation/jdbc/metadata.yaml +++ b/instrumentation/jdbc/metadata.yaml @@ -14,3 +14,12 @@ configurations: - name: otel.instrumentation.common.peer-service-mapping description: Used to specify a mapping from host names or IP addresses to peer services. default: "" + - name: otel.instrumentation.jdbc.capture-query-parameters + description: > + Enables the attribute db.query.parameter.. + WARNING: captured query parameters may contain sensitive information such as passwords, + personally identifiable information or protected health info. + Exposing such info may result in substantial fines and penalties or criminal liability. + Consult your peers, superiors and a legal counsel before enabling this option. + This option will disable otel.instrumentation.jdbc.statement-sanitizer + default: false From c4c877e3d59197fab9150efe2d70a6ff95c9f597 Mon Sep 17 00:00:00 2001 From: Lauri Tulmin Date: Fri, 9 May 2025 10:24:11 +0300 Subject: [PATCH 12/15] review --- .../db/SqlClientAttributesExtractor.java | 13 ++--- .../SqlClientAttributesExtractorBuilder.java | 11 ++-- .../db/SqlClientAttributesExtractorTest.java | 2 - instrumentation/jdbc/README.md | 8 +-- .../PreparedStatementInstrumentation.java | 50 ++++++++--------- .../jdbc/StatementInstrumentation.java | 4 +- .../jdbc/datasource/JdbcTelemetryBuilder.java | 19 ++++--- .../jdbc/internal/DbRequest.java | 1 - .../jdbc/internal/JdbcAttributesGetter.java | 9 +--- .../jdbc/internal/JdbcData.java | 28 +++++++--- .../JdbcPreparedStatementStringifier.java | 53 ------------------- .../OpenTelemetryPreparedStatement.java | 43 ++++++++------- .../jdbc/internal/OpenTelemetryStatement.java | 3 +- 13 files changed, 97 insertions(+), 147 deletions(-) delete mode 100644 instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/JdbcPreparedStatementStringifier.java diff --git a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlClientAttributesExtractor.java b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlClientAttributesExtractor.java index 92eb3f260803..ca60e051f4eb 100644 --- a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlClientAttributesExtractor.java +++ b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlClientAttributesExtractor.java @@ -71,18 +71,16 @@ public static SqlClientAttributesExtractorBuilder rawQueryTexts = getter.getRawQueryTexts(request); - Map queryParameters = getter.getQueryParameters(request); if (rawQueryTexts.isEmpty()) { return; @@ -104,7 +102,6 @@ public void onStart(AttributesBuilder attributes, Context parentContext, REQUEST if (!SQL_CALL.equals(operation)) { internalSet(attributes, oldSemconvTableAttribute, sanitizedStatement.getMainIdentifier()); } - setQueryParameters(attributes, isBatch, queryParameters); } } @@ -124,7 +121,6 @@ public void onStart(AttributesBuilder attributes, Context parentContext, REQUEST if (!SQL_CALL.equals(operation)) { internalSet(attributes, DB_COLLECTION_NAME, sanitizedStatement.getMainIdentifier()); } - setQueryParameters(attributes, isBatch, queryParameters); } else { MultiQuery multiQuery = MultiQuery.analyze(getter.getRawQueryTexts(request), statementSanitizationEnabled); @@ -140,6 +136,9 @@ public void onStart(AttributesBuilder attributes, Context parentContext, REQUEST } } } + + Map queryParameters = getter.getQueryParameters(request); + setQueryParameters(attributes, isBatch, queryParameters); } private void setQueryParameters( @@ -148,9 +147,7 @@ private void setQueryParameters( for (Map.Entry entry : queryParameters.entrySet()) { String key = entry.getKey(); String value = entry.getValue(); - if (value != null) { - internalSet(attributes, DB_QUERY_PARAMETER.getAttributeKey(key), value); - } + internalSet(attributes, DB_QUERY_PARAMETER.getAttributeKey(key), value); } } } diff --git a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlClientAttributesExtractorBuilder.java b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlClientAttributesExtractorBuilder.java index ecd2b89066d9..3a3059690867 100644 --- a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlClientAttributesExtractorBuilder.java +++ b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlClientAttributesExtractorBuilder.java @@ -50,9 +50,14 @@ public SqlClientAttributesExtractorBuilder setStatementSaniti } /** - * Sets whether the {@code db.query.parameter.} attributes extracted by the constructed - * {@link SqlClientAttributesExtractor} should be opted-in. If set to {@code true}, all parameters - * from {@code PreparedStatement} will be exposed as attributes. Disabled by default. + * Sets whether the query parameters should be captured as span attributes named {@code + * db.query.parameter.}. Enabling this option disables the statement sanitization. Disabled + * by default. + * + *

WARNING: captured query parameters may contain sensitive information such as passwords, + * personally identifiable information or protected health info. Exposing such info may result in + * substantial fines and penalties or criminal liability. Consult your peers, superiors and a + * legal counsel before enabling this option. */ @CanIgnoreReturnValue public SqlClientAttributesExtractorBuilder setCaptureQueryParameters( diff --git a/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlClientAttributesExtractorTest.java b/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlClientAttributesExtractorTest.java index 2a42564a252c..e83877e98cea 100644 --- a/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlClientAttributesExtractorTest.java +++ b/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlClientAttributesExtractorTest.java @@ -23,7 +23,6 @@ import java.util.HashMap; import java.util.Map; import org.junit.jupiter.api.Test; -import org.spockframework.util.Nullable; @SuppressWarnings("deprecation") // using deprecated semconv class SqlClientAttributesExtractorTest { @@ -64,7 +63,6 @@ public Long getBatchSize(Map map) { return read(map, "db.operation.batch.size", Long.class); } - @Nullable @Override public Map getQueryParameters(Map map) { String parameterString = read(map, "db.query.parameter"); diff --git a/instrumentation/jdbc/README.md b/instrumentation/jdbc/README.md index 0cc193697918..ff7e215a6bc4 100644 --- a/instrumentation/jdbc/README.md +++ b/instrumentation/jdbc/README.md @@ -1,6 +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. | -| `otel.instrumentation.jdbc.capture-query-parameters` | Boolean | `false` | Enables the attribute db.query.parameter.\.

WARNING: captured query parameters may contain sensitive information such as passwords, personally identifiable information or protected health info. Exposing such info may result in substantial fines and penalties or criminal liability. Consult your peers, superiors and a legal counsel before enabling this option.

This option will disable otel.instrumentation.jdbc.statement-sanitizer | +| System property | Type | Default | Description | +|---------------------------------------------------------|---------|---------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `otel.instrumentation.jdbc.statement-sanitizer.enabled` | Boolean | `true` | Enables the DB statement sanitization. | +| `otel.instrumentation.jdbc.capture-query-parameters` | Boolean | `false` | Enable the capture of query parameters as span attributes. Enabling this option disables the statement sanitization.

WARNING: captured query parameters may contain sensitive information such as passwords, personally identifiable information or protected health info. Exposing such info may result in substantial fines and penalties or criminal liability. Consult your peers, superiors and a legal counsel before enabling this option. | diff --git a/instrumentation/jdbc/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jdbc/PreparedStatementInstrumentation.java b/instrumentation/jdbc/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jdbc/PreparedStatementInstrumentation.java index 407109cb8c89..38fad89aa090 100644 --- a/instrumentation/jdbc/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jdbc/PreparedStatementInstrumentation.java +++ b/instrumentation/jdbc/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jdbc/PreparedStatementInstrumentation.java @@ -5,7 +5,6 @@ package io.opentelemetry.javaagent.instrumentation.jdbc; -import static io.opentelemetry.instrumentation.jdbc.internal.JdbcPreparedStatementStringifier.stringifyParameter; import static io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge.currentContext; import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed; import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.implementsInterface; @@ -90,6 +89,9 @@ public void transform(TypeTransformer transformer) { .and(takesArguments(3)) .and(isPublic()), PreparedStatementInstrumentation.class.getName() + "$SetTimeParameter3Advice"); + transformer.applyAdviceToMethod( + named("clearParameters").and(takesNoArguments()).and(isPublic()), + PreparedStatementInstrumentation.class.getName() + "$ClearParametersAdvice"); } @SuppressWarnings("unused") @@ -121,7 +123,7 @@ public static void onEnter( } Context parentContext = currentContext(); - Map parameters = JdbcData.parameters.get(statement); + Map parameters = JdbcData.getParameters(statement); request = DbRequest.create(statement, parameters); if (request == null || !statementInstrumenter().shouldStart(parentContext, request)) { @@ -172,23 +174,16 @@ public static void onExit( String str = null; - if (value instanceof Boolean) { - str = stringifyParameter((Boolean) value); - } else if (value instanceof Number) { - // Short, Int, Long, Float, Double, BigDecimal - str = stringifyParameter((Number) value); - } else if (value instanceof String) { - str = stringifyParameter((String) value); - } else if (value instanceof Date) { - str = stringifyParameter((Date) value); - } else if (value instanceof Time) { - str = stringifyParameter((Time) value); - } else if (value instanceof Timestamp) { - str = stringifyParameter((Timestamp) value); - } else if (value instanceof URL) { - str = stringifyParameter((URL) value); - } else if (value instanceof RowId) { - str = stringifyParameter((RowId) value); + if (value instanceof Boolean + // Short, Int, Long, Float, Double, BigDecimal + || value instanceof Number + || value instanceof String + || value instanceof Date + || value instanceof Time + || value instanceof Timestamp + || value instanceof URL + || value instanceof RowId) { + str = value.toString(); } if (str != null) { @@ -211,12 +206,8 @@ public static void onExit( String str = null; - if (value instanceof Date) { - str = stringifyParameter((Date) value); - } else if (value instanceof Time) { - str = stringifyParameter((Time) value); - } else if (value instanceof Timestamp) { - str = stringifyParameter((Timestamp) value); + if (value instanceof Date || value instanceof Time || value instanceof Timestamp) { + str = value.toString(); } if (str != null) { @@ -224,4 +215,13 @@ public static void onExit( } } } + + @SuppressWarnings("unused") + public static class ClearParametersAdvice { + + @Advice.OnMethodEnter(suppress = Throwable.class) + public static void clearBatch(@Advice.This PreparedStatement statement) { + JdbcData.clearParameters(statement); + } + } } diff --git a/instrumentation/jdbc/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jdbc/StatementInstrumentation.java b/instrumentation/jdbc/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jdbc/StatementInstrumentation.java index 11867af96764..81b252b87fa2 100644 --- a/instrumentation/jdbc/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jdbc/StatementInstrumentation.java +++ b/instrumentation/jdbc/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jdbc/StatementInstrumentation.java @@ -158,12 +158,12 @@ public static void onEnter( Context parentContext = currentContext(); if (statement instanceof PreparedStatement) { - Long batchSize = JdbcData.getPreparedStatementBatchSize((PreparedStatement) statement); String sql = JdbcData.preparedStatement.get((PreparedStatement) statement); - Map parameters = JdbcData.parameters.get((PreparedStatement) statement); if (sql == null) { return; } + Long batchSize = JdbcData.getPreparedStatementBatchSize((PreparedStatement) statement); + Map parameters = JdbcData.getParameters((PreparedStatement) statement); request = DbRequest.create(statement, sql, batchSize, parameters); } else { JdbcData.StatementBatchInfo batchInfo = JdbcData.getStatementBatchInfo(statement); 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 index 00531b6d60e1..9ca614d2b228 100644 --- 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 @@ -40,22 +40,21 @@ public JdbcTelemetryBuilder setStatementInstrumenterEnabled(boolean enabled) { @CanIgnoreReturnValue public JdbcTelemetryBuilder setStatementSanitizationEnabled(boolean enabled) { this.statementSanitizationEnabled = enabled; - - if (enabled) { - this.captureQueryParameters = false; - } - return this; } + /** + * Configures whether parameters are captured for JDBC Statements. Enabling this option disables + * the statement sanitization. Disabled by default. + * + *

WARNING: captured query parameters may contain sensitive information such as passwords, + * personally identifiable information or protected health info. Exposing such info may result in + * substantial fines and penalties or criminal liability. Consult your peers, superiors and a + * legal counsel before enabling this option. + */ @CanIgnoreReturnValue public JdbcTelemetryBuilder setCaptureQueryParameters(boolean enabled) { this.captureQueryParameters = enabled; - - if (enabled) { - this.statementSanitizationEnabled = false; - } - return this; } diff --git a/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/DbRequest.java b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/DbRequest.java index f9568fccaa65..8c365f988932 100644 --- a/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/DbRequest.java +++ b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/DbRequest.java @@ -98,6 +98,5 @@ public static DbRequest create( @Nullable public abstract Long getBatchSize(); - @Nullable public abstract Map getPreparedStatementParameters(); } diff --git a/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/JdbcAttributesGetter.java b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/JdbcAttributesGetter.java index 3f687614c52d..73b5ed275973 100644 --- a/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/JdbcAttributesGetter.java +++ b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/JdbcAttributesGetter.java @@ -9,7 +9,6 @@ import io.opentelemetry.instrumentation.jdbc.internal.dbinfo.DbInfo; import java.sql.SQLException; import java.util.Collection; -import java.util.Collections; import java.util.Map; import javax.annotation.Nullable; @@ -67,12 +66,6 @@ public String getResponseStatus(@Nullable Void response, @Nullable Throwable err @Override public Map getQueryParameters(DbRequest request) { - Map parameters = request.getPreparedStatementParameters(); - - if (parameters == null) { - return Collections.emptyMap(); - } - - return parameters; + return request.getPreparedStatementParameters(); } } diff --git a/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/JdbcData.java b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/JdbcData.java index d465e9299874..c21e3ef76249 100644 --- a/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/JdbcData.java +++ b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/JdbcData.java @@ -13,6 +13,7 @@ import java.sql.Statement; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -36,7 +37,7 @@ public final class JdbcData { private static final VirtualField preparedStatementBatch = VirtualField.find(PreparedStatement.class, PreparedStatementBatchInfo.class); - public static final VirtualField> parameters = + private static final VirtualField> parameters = VirtualField.find(PreparedStatement.class, Map.class); private JdbcData() {} @@ -110,15 +111,26 @@ public static void close(Statement statement) { } } + public static Map getParameters(PreparedStatement statement) { + Map parametersMap = parameters.get(statement); + return parametersMap != null ? parametersMap : Collections.emptyMap(); + } + public static void addParameter(PreparedStatement statement, String key, String value) { - if (value != null) { - Map parametersMap = parameters.get(statement); - if (parametersMap == null) { - parametersMap = new HashMap<>(); - parameters.set(statement, parametersMap); - } - parametersMap.put(key, value); + if (value == null) { + return; } + + Map parametersMap = parameters.get(statement); + if (parametersMap == null) { + parametersMap = new HashMap<>(); + parameters.set(statement, parametersMap); + } + parametersMap.put(key, value); + } + + public static void clearParameters(PreparedStatement statement) { + parameters.set(statement, null); } /** diff --git a/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/JdbcPreparedStatementStringifier.java b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/JdbcPreparedStatementStringifier.java deleted file mode 100644 index ac28c2cc71b1..000000000000 --- a/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/JdbcPreparedStatementStringifier.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.instrumentation.jdbc.internal; - -import java.net.URL; -import java.sql.Date; -import java.sql.RowId; -import java.sql.Time; -import java.sql.Timestamp; - -/** - * This class is internal and is hence not for public use. Its APIs are unstable and can change at - * any time. - */ -public class JdbcPreparedStatementStringifier { - - private JdbcPreparedStatementStringifier() {} - - public static String stringifyParameter(String value) { - return value; - } - - public static String stringifyParameter(Number value) { - return value != null ? value.toString() : null; - } - - public static String stringifyParameter(boolean value) { - return value ? Boolean.TRUE.toString() : Boolean.FALSE.toString(); - } - - public static String stringifyParameter(Date value) { - return value != null ? value.toString() : null; - } - - public static String stringifyParameter(Time value) { - return value != null ? value.toString() : null; - } - - public static String stringifyParameter(Timestamp value) { - return value != null ? value.toString() : null; - } - - public static String stringifyParameter(URL value) { - return value != null ? value.toString() : null; - } - - public static String stringifyParameter(RowId value) { - return value != null ? value.toString() : null; - } -} diff --git a/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/OpenTelemetryPreparedStatement.java b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/OpenTelemetryPreparedStatement.java index 64142b24c215..a5b4e7f6520c 100644 --- a/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/OpenTelemetryPreparedStatement.java +++ b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/OpenTelemetryPreparedStatement.java @@ -20,8 +20,6 @@ package io.opentelemetry.instrumentation.jdbc.internal; -import static io.opentelemetry.instrumentation.jdbc.internal.JdbcPreparedStatementStringifier.stringifyParameter; - import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; import io.opentelemetry.instrumentation.jdbc.internal.dbinfo.DbInfo; import java.io.InputStream; @@ -66,9 +64,9 @@ public OpenTelemetryPreparedStatement( this.parameters = new HashMap<>(); } - private void putParameter(int index, String value) { - if (this.captureQueryParameters) { - parameters.put(Integer.toString(index - 1), value); + private void putParameter(int index, Object value) { + if (this.captureQueryParameters && value != null) { + parameters.put(Integer.toString(index - 1), value.toString()); } } @@ -102,54 +100,55 @@ public void setNull(int parameterIndex, int sqlType, String typeName) throws SQL @Override public void setBoolean(int parameterIndex, boolean x) throws SQLException { delegate.setBoolean(parameterIndex, x); - putParameter(parameterIndex, stringifyParameter(x)); + putParameter(parameterIndex, String.valueOf(x)); } @Override public void setByte(int parameterIndex, byte x) throws SQLException { delegate.setByte(parameterIndex, x); + putParameter(parameterIndex, String.valueOf(x)); } @Override public void setShort(int parameterIndex, short x) throws SQLException { delegate.setShort(parameterIndex, x); - putParameter(parameterIndex, stringifyParameter(x)); + putParameter(parameterIndex, String.valueOf(x)); } @Override public void setInt(int parameterIndex, int x) throws SQLException { delegate.setInt(parameterIndex, x); - putParameter(parameterIndex, stringifyParameter(x)); + putParameter(parameterIndex, String.valueOf(x)); } @Override public void setLong(int parameterIndex, long x) throws SQLException { delegate.setLong(parameterIndex, x); - putParameter(parameterIndex, stringifyParameter(x)); + putParameter(parameterIndex, String.valueOf(x)); } @Override public void setFloat(int parameterIndex, float x) throws SQLException { delegate.setFloat(parameterIndex, x); - putParameter(parameterIndex, stringifyParameter(x)); + putParameter(parameterIndex, String.valueOf(x)); } @Override public void setDouble(int parameterIndex, double x) throws SQLException { delegate.setDouble(parameterIndex, x); - putParameter(parameterIndex, stringifyParameter(x)); + putParameter(parameterIndex, String.valueOf(x)); } @Override public void setBigDecimal(int parameterIndex, BigDecimal x) throws SQLException { delegate.setBigDecimal(parameterIndex, x); - putParameter(parameterIndex, stringifyParameter(x)); + putParameter(parameterIndex, x); } @Override public void setString(int parameterIndex, String x) throws SQLException { delegate.setString(parameterIndex, x); - putParameter(parameterIndex, stringifyParameter(x)); + putParameter(parameterIndex, x); } @Override @@ -161,41 +160,41 @@ public void setBytes(int parameterIndex, byte[] x) throws SQLException { @Override public void setDate(int parameterIndex, Date x) throws SQLException { delegate.setDate(parameterIndex, x); - putParameter(parameterIndex, stringifyParameter(x)); + putParameter(parameterIndex, x); } @SuppressWarnings("UngroupedOverloads") @Override public void setDate(int parameterIndex, Date x, Calendar cal) throws SQLException { delegate.setDate(parameterIndex, x, cal); - putParameter(parameterIndex, stringifyParameter(x)); + putParameter(parameterIndex, x); } @SuppressWarnings("UngroupedOverloads") @Override public void setTime(int parameterIndex, Time x) throws SQLException { delegate.setTime(parameterIndex, x); - putParameter(parameterIndex, stringifyParameter(x)); + putParameter(parameterIndex, x); } @Override public void setTime(int parameterIndex, Time x, Calendar cal) throws SQLException { delegate.setTime(parameterIndex, x, cal); - putParameter(parameterIndex, stringifyParameter(x)); + putParameter(parameterIndex, x); } @SuppressWarnings("UngroupedOverloads") @Override public void setTimestamp(int parameterIndex, Timestamp x) throws SQLException { delegate.setTimestamp(parameterIndex, x); - putParameter(parameterIndex, stringifyParameter(x)); + putParameter(parameterIndex, x); } @SuppressWarnings("UngroupedOverloads") @Override public void setTimestamp(int parameterIndex, Timestamp x, Calendar cal) throws SQLException { delegate.setTimestamp(parameterIndex, x, cal); - putParameter(parameterIndex, stringifyParameter(x)); + putParameter(parameterIndex, x); } @SuppressWarnings("UngroupedOverloads") @@ -336,7 +335,7 @@ public ResultSetMetaData getMetaData() throws SQLException { @Override public void setURL(int parameterIndex, URL x) throws SQLException { delegate.setURL(parameterIndex, x); - putParameter(parameterIndex, stringifyParameter(x)); + putParameter(parameterIndex, x); } @Override @@ -347,13 +346,13 @@ public ParameterMetaData getParameterMetaData() throws SQLException { @Override public void setRowId(int parameterIndex, RowId x) throws SQLException { delegate.setRowId(parameterIndex, x); - putParameter(parameterIndex, stringifyParameter(x)); + putParameter(parameterIndex, x); } @Override public void setNString(int parameterIndex, String value) throws SQLException { delegate.setNString(parameterIndex, value); - putParameter(parameterIndex, stringifyParameter(value)); + putParameter(parameterIndex, value); } @SuppressWarnings("UngroupedOverloads") diff --git a/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/OpenTelemetryStatement.java b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/OpenTelemetryStatement.java index 05a8af3b2ef2..759f2d7192fb 100644 --- a/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/OpenTelemetryStatement.java +++ b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/OpenTelemetryStatement.java @@ -30,6 +30,7 @@ import java.sql.SQLWarning; import java.sql.Statement; import java.util.ArrayList; +import java.util.Collections; import java.util.List; class OpenTelemetryStatement implements Statement { @@ -385,7 +386,7 @@ protected T wrapCall(DbRequest request, ThrowingSupplie } private T wrapBatchCall(ThrowingSupplier callable) throws E { - DbRequest request = DbRequest.create(dbInfo, batchCommands, batchSize, null); + DbRequest request = DbRequest.create(dbInfo, batchCommands, batchSize, Collections.emptyMap()); return wrapCall(request, callable); } } From afa52b86e054ccdec330303abfa8f0d2686dbfed Mon Sep 17 00:00:00 2001 From: Lauri Tulmin Date: Fri, 9 May 2025 11:29:59 +0300 Subject: [PATCH 13/15] simplify and fix test --- .../db/SqlClientAttributesExtractorTest.java | 40 +++++++------------ 1 file changed, 14 insertions(+), 26 deletions(-) diff --git a/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlClientAttributesExtractorTest.java b/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlClientAttributesExtractorTest.java index e83877e98cea..a8ae1c806240 100644 --- a/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlClientAttributesExtractorTest.java +++ b/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlClientAttributesExtractorTest.java @@ -63,19 +63,12 @@ public Long getBatchSize(Map map) { return read(map, "db.operation.batch.size", Long.class); } + @SuppressWarnings("unchecked") @Override public Map getQueryParameters(Map map) { - String parameterString = read(map, "db.query.parameter"); - - if (parameterString == null) { - return Collections.emptyMap(); - } - - Map parameters = new HashMap<>(); - for (String s : parameterString.split(";")) { - parameters.put(Integer.toString(parameters.size()), s); - } - return parameters; + Map parameters = + (Map) read(map, "db.query.parameter", Map.class); + return parameters != null ? parameters : Collections.emptyMap(); } protected String read(Map map, String key) { @@ -414,7 +407,10 @@ void shouldExtractQueryParameters() { "db.statement", "SELECT col FROM table WHERE field1=? AND field2='A' AND field3=? AND field4=2"); // a prepared parameters map - request.put("db.query.parameter", "'a';1"); + Map parameterMap = new HashMap<>(); + parameterMap.put("0", "'a'"); + parameterMap.put("1", "1"); + request.put("db.query.parameter", parameterMap); Context context = Context.root(); @@ -449,13 +445,15 @@ void shouldNotExtractQueryParametersForBatch() { Map request = new HashMap<>(); request.put("db.name", "potatoes"); request.put("db.statements", singleton("INSERT INTO potato VALUES(?)")); - request.put("db.operation.batch.size", 1L); - request.put("db.query.parameter", "1"); + request.put("db.operation.batch.size", 2L); + request.put("db.query.parameter", Collections.singletonMap("0", "1")); Context context = Context.root(); AttributesExtractor, Void> underTest = - SqlClientAttributesExtractor.create(new TestMultiAttributesGetter()); + SqlClientAttributesExtractor.builder(new TestMultiAttributesGetter()) + .setCaptureQueryParameters(true) + .build(); // when AttributesBuilder startAttributes = Attributes.builder(); @@ -465,17 +463,7 @@ void shouldNotExtractQueryParametersForBatch() { underTest.onEnd(endAttributes, context, request, null, null); // then - if (SemconvStability.emitStableDatabaseSemconv() && SemconvStability.emitOldDatabaseSemconv()) { - assertThat(startAttributes.build()) - .doesNotContainKey(DB_QUERY_PARAMETER.getAttributeKey("0")); - } else if (SemconvStability.emitOldDatabaseSemconv()) { - assertThat(startAttributes.build()) - .doesNotContainKey(DB_QUERY_PARAMETER.getAttributeKey("0")); - } else if (SemconvStability.emitStableDatabaseSemconv()) { - assertThat(startAttributes.build()) - .doesNotContainKey(DB_QUERY_PARAMETER.getAttributeKey("0")); - } - + assertThat(startAttributes.build()).doesNotContainKey(DB_QUERY_PARAMETER.getAttributeKey("0")); assertThat(endAttributes.build().isEmpty()).isTrue(); } } From bd16245c13e106dc4529e468d20b48fec91af486 Mon Sep 17 00:00:00 2001 From: Lauri Tulmin Date: Fri, 9 May 2025 11:30:12 +0300 Subject: [PATCH 14/15] simplify --- .../jdbc/javaagent/build.gradle.kts | 1 - .../test/PreparedStatementParametersTest.java | 713 ++++++------------ 2 files changed, 222 insertions(+), 492 deletions(-) diff --git a/instrumentation/jdbc/javaagent/build.gradle.kts b/instrumentation/jdbc/javaagent/build.gradle.kts index be106066c3a3..9a68c787b59e 100644 --- a/instrumentation/jdbc/javaagent/build.gradle.kts +++ b/instrumentation/jdbc/javaagent/build.gradle.kts @@ -91,7 +91,6 @@ tasks { filter { includeTestsMatching("PreparedStatementParametersTest") } - jvmArgs("-Dotel.instrumentation.jdbc-datasource.enabled=true") jvmArgs("-Dotel.instrumentation.jdbc.capture-query-parameters=true") } diff --git a/instrumentation/jdbc/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jdbc/test/PreparedStatementParametersTest.java b/instrumentation/jdbc/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jdbc/test/PreparedStatementParametersTest.java index 719c055f82be..fc5db502d82d 100644 --- a/instrumentation/jdbc/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jdbc/test/PreparedStatementParametersTest.java +++ b/instrumentation/jdbc/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jdbc/test/PreparedStatementParametersTest.java @@ -40,8 +40,7 @@ import java.util.stream.Stream; import org.apache.derby.jdbc.EmbeddedDriver; import org.hsqldb.jdbc.JDBCDriver; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Assumptions; import org.junit.jupiter.api.extension.RegisterExtension; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; @@ -76,12 +75,6 @@ class PreparedStatementParametersTest { connectionProps.put("create", "true"); } - @BeforeAll - static void setUp() {} - - @AfterAll - static void tearDown() {} - static Stream preparedStatementStream() throws SQLException { return Stream.of( Arguments.of( @@ -125,38 +118,17 @@ void testBooleanPreparedStatementParameter( String url, String table) throws SQLException { - PreparedStatement statement = connection.prepareStatement(query); - cleanup.deferCleanup(statement); - - ResultSet resultSet = - testing.runWithSpan( - "parent", - () -> { - statement.setBoolean(1, true); - statement.execute(); - return statement.getResultSet(); - }); - - resultSet.next(); - assertThat(resultSet.getInt(1)).isEqualTo(3); - - testing.waitAndAssertTraces( - trace -> - trace.hasSpansSatisfyingExactly( - span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), - span -> - span.hasName(spanName) - .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), sanitizedQuery), - equalTo(maybeStable(DB_OPERATION), "SELECT"), - equalTo(maybeStable(DB_SQL_TABLE), table), - equalTo(DB_QUERY_PARAMETER.getAttributeKey("0"), "true")))); + test( + system, + connection, + username, + query, + sanitizedQuery, + spanName, + url, + table, + statement -> statement.setBoolean(1, true), + "true"); } @ParameterizedTest @@ -171,38 +143,17 @@ void testShortPreparedStatementParameter( String url, String table) throws SQLException { - PreparedStatement statement = connection.prepareStatement(query); - cleanup.deferCleanup(statement); - - ResultSet resultSet = - testing.runWithSpan( - "parent", - () -> { - statement.setShort(1, (short) 0); - statement.execute(); - return statement.getResultSet(); - }); - - resultSet.next(); - assertThat(resultSet.getInt(1)).isEqualTo(3); - - testing.waitAndAssertTraces( - trace -> - trace.hasSpansSatisfyingExactly( - span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), - span -> - span.hasName(spanName) - .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), sanitizedQuery), - equalTo(maybeStable(DB_OPERATION), "SELECT"), - equalTo(maybeStable(DB_SQL_TABLE), table), - equalTo(DB_QUERY_PARAMETER.getAttributeKey("0"), "0")))); + test( + system, + connection, + username, + query, + sanitizedQuery, + spanName, + url, + table, + statement -> statement.setShort(1, (short) 0), + "0"); } @ParameterizedTest @@ -217,38 +168,17 @@ void testIntPreparedStatementParameter( String url, String table) throws SQLException { - PreparedStatement statement = connection.prepareStatement(query); - cleanup.deferCleanup(statement); - - ResultSet resultSet = - testing.runWithSpan( - "parent", - () -> { - statement.setInt(1, 0); - statement.execute(); - return statement.getResultSet(); - }); - - resultSet.next(); - assertThat(resultSet.getInt(1)).isEqualTo(3); - - testing.waitAndAssertTraces( - trace -> - trace.hasSpansSatisfyingExactly( - span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), - span -> - span.hasName(spanName) - .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), sanitizedQuery), - equalTo(maybeStable(DB_OPERATION), "SELECT"), - equalTo(maybeStable(DB_SQL_TABLE), table), - equalTo(DB_QUERY_PARAMETER.getAttributeKey("0"), "0")))); + test( + system, + connection, + username, + query, + sanitizedQuery, + spanName, + url, + table, + statement -> statement.setInt(1, 0), + "0"); } @ParameterizedTest @@ -263,38 +193,17 @@ void testLongPreparedStatementParameter( String url, String table) throws SQLException { - PreparedStatement statement = connection.prepareStatement(query); - cleanup.deferCleanup(statement); - - ResultSet resultSet = - testing.runWithSpan( - "parent", - () -> { - statement.setLong(1, 0); - statement.execute(); - return statement.getResultSet(); - }); - - resultSet.next(); - assertThat(resultSet.getInt(1)).isEqualTo(3); - - testing.waitAndAssertTraces( - trace -> - trace.hasSpansSatisfyingExactly( - span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), - span -> - span.hasName(spanName) - .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), sanitizedQuery), - equalTo(maybeStable(DB_OPERATION), "SELECT"), - equalTo(maybeStable(DB_SQL_TABLE), table), - equalTo(DB_QUERY_PARAMETER.getAttributeKey("0"), "0")))); + test( + system, + connection, + username, + query, + sanitizedQuery, + spanName, + url, + table, + statement -> statement.setLong(1, 0), + "0"); } @ParameterizedTest @@ -309,38 +218,17 @@ void testFloatPreparedStatementParameter( String url, String table) throws SQLException { - PreparedStatement statement = connection.prepareStatement(query); - cleanup.deferCleanup(statement); - - ResultSet resultSet = - testing.runWithSpan( - "parent", - () -> { - statement.setFloat(1, (float) 0.1); - statement.execute(); - return statement.getResultSet(); - }); - - resultSet.next(); - assertThat(resultSet.getInt(1)).isEqualTo(3); - - testing.waitAndAssertTraces( - trace -> - trace.hasSpansSatisfyingExactly( - span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), - span -> - span.hasName(spanName) - .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), sanitizedQuery), - equalTo(maybeStable(DB_OPERATION), "SELECT"), - equalTo(maybeStable(DB_SQL_TABLE), table), - equalTo(DB_QUERY_PARAMETER.getAttributeKey("0"), "0.1")))); + test( + system, + connection, + username, + query, + sanitizedQuery, + spanName, + url, + table, + statement -> statement.setFloat(1, 0.1f), + "0.1"); } @ParameterizedTest @@ -355,38 +243,17 @@ void testDoublePreparedStatementParameter( String url, String table) throws SQLException { - PreparedStatement statement = connection.prepareStatement(query); - cleanup.deferCleanup(statement); - - ResultSet resultSet = - testing.runWithSpan( - "parent", - () -> { - statement.setDouble(1, 0.1); - statement.execute(); - return statement.getResultSet(); - }); - - resultSet.next(); - assertThat(resultSet.getInt(1)).isEqualTo(3); - - testing.waitAndAssertTraces( - trace -> - trace.hasSpansSatisfyingExactly( - span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), - span -> - span.hasName(spanName) - .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), sanitizedQuery), - equalTo(maybeStable(DB_OPERATION), "SELECT"), - equalTo(maybeStable(DB_SQL_TABLE), table), - equalTo(DB_QUERY_PARAMETER.getAttributeKey("0"), "0.1")))); + test( + system, + connection, + username, + query, + sanitizedQuery, + spanName, + url, + table, + statement -> statement.setDouble(1, 0.1), + "0.1"); } @ParameterizedTest @@ -401,38 +268,17 @@ void testBigDecimalPreparedStatementParameter( String url, String table) throws SQLException { - PreparedStatement statement = connection.prepareStatement(query); - cleanup.deferCleanup(statement); - - ResultSet resultSet = - testing.runWithSpan( - "parent", - () -> { - statement.setBigDecimal(1, BigDecimal.ZERO); - statement.execute(); - return statement.getResultSet(); - }); - - resultSet.next(); - assertThat(resultSet.getInt(1)).isEqualTo(3); - - testing.waitAndAssertTraces( - trace -> - trace.hasSpansSatisfyingExactly( - span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), - span -> - span.hasName(spanName) - .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), sanitizedQuery), - equalTo(maybeStable(DB_OPERATION), "SELECT"), - equalTo(maybeStable(DB_SQL_TABLE), table), - equalTo(DB_QUERY_PARAMETER.getAttributeKey("0"), "0")))); + test( + system, + connection, + username, + query, + sanitizedQuery, + spanName, + url, + table, + statement -> statement.setBigDecimal(1, BigDecimal.ZERO), + "0"); } @ParameterizedTest @@ -447,38 +293,17 @@ void testStringPreparedStatementParameter( String url, String table) throws SQLException { - PreparedStatement statement = connection.prepareStatement(query); - cleanup.deferCleanup(statement); - - ResultSet resultSet = - testing.runWithSpan( - "parent", - () -> { - statement.setString(1, "S"); - statement.execute(); - return statement.getResultSet(); - }); - - resultSet.next(); - assertThat(resultSet.getInt(1)).isEqualTo(3); - - testing.waitAndAssertTraces( - trace -> - trace.hasSpansSatisfyingExactly( - span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), - span -> - span.hasName(spanName) - .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), sanitizedQuery), - equalTo(maybeStable(DB_OPERATION), "SELECT"), - equalTo(maybeStable(DB_SQL_TABLE), table), - equalTo(DB_QUERY_PARAMETER.getAttributeKey("0"), "S")))); + test( + system, + connection, + username, + query, + sanitizedQuery, + spanName, + url, + table, + statement -> statement.setString(1, "S"), + "S"); } @ParameterizedTest @@ -493,40 +318,20 @@ void testDate2PreparedStatementParameter( String url, String table) throws SQLException { - String updatedColumn = query.replace("USER_NAME=?", "CURDATE()=?"); - String updatedColumnSanitized = sanitizedQuery.replace("USER_NAME=?", "CURDATE()=?"); - PreparedStatement statement = connection.prepareStatement(updatedColumn); - cleanup.deferCleanup(statement); - - ResultSet resultSet = - testing.runWithSpan( - "parent", - () -> { - statement.setDate(1, Date.valueOf("2000-01-01")); - statement.execute(); - return statement.getResultSet(); - }); - - resultSet.next(); - assertThat(resultSet.getInt(1)).isEqualTo(3); - - testing.waitAndAssertTraces( - trace -> - trace.hasSpansSatisfyingExactly( - span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), - span -> - span.hasName(spanName) - .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), updatedColumnSanitized), - equalTo(maybeStable(DB_OPERATION), "SELECT"), - equalTo(maybeStable(DB_SQL_TABLE), table), - equalTo(DB_QUERY_PARAMETER.getAttributeKey("0"), "2000-01-01")))); + String updatedQuery = query.replace("USER_NAME=?", "CURDATE()=?"); + String updatedQuerySanitized = sanitizedQuery.replace("USER_NAME=?", "CURDATE()=?"); + + test( + system, + connection, + username, + updatedQuery, + updatedQuerySanitized, + spanName, + url, + table, + statement -> statement.setDate(1, Date.valueOf("2000-01-01")), + "2000-01-01"); } @ParameterizedTest @@ -541,40 +346,20 @@ void testDate3PreparedStatementParameter( String url, String table) throws SQLException { - String updatedColumn = query.replace("USER_NAME=?", "CURDATE()=?"); - String updatedColumnSanitized = sanitizedQuery.replace("USER_NAME=?", "CURDATE()=?"); - PreparedStatement statement = connection.prepareStatement(updatedColumn); - cleanup.deferCleanup(statement); - - ResultSet resultSet = - testing.runWithSpan( - "parent", - () -> { - statement.setDate(1, Date.valueOf("2000-01-01"), Calendar.getInstance()); - statement.execute(); - return statement.getResultSet(); - }); - - resultSet.next(); - assertThat(resultSet.getInt(1)).isEqualTo(3); - - testing.waitAndAssertTraces( - trace -> - trace.hasSpansSatisfyingExactly( - span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), - span -> - span.hasName(spanName) - .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), updatedColumnSanitized), - equalTo(maybeStable(DB_OPERATION), "SELECT"), - equalTo(maybeStable(DB_SQL_TABLE), table), - equalTo(DB_QUERY_PARAMETER.getAttributeKey("0"), "2000-01-01")))); + String updatedQuery = query.replace("USER_NAME=?", "CURDATE()=?"); + String updatedQuerySanitized = sanitizedQuery.replace("USER_NAME=?", "CURDATE()=?"); + + test( + system, + connection, + username, + updatedQuery, + updatedQuerySanitized, + spanName, + url, + table, + statement -> statement.setDate(1, Date.valueOf("2000-01-01"), Calendar.getInstance()), + "2000-01-01"); } @ParameterizedTest @@ -589,40 +374,20 @@ void testTime2PreparedStatementParameter( String url, String table) throws SQLException { - String updatedColumn = query.replace("USER_NAME=?", "CURTIME()=?"); - String updatedColumnSanitized = sanitizedQuery.replace("USER_NAME=?", "CURTIME()=?"); - PreparedStatement statement = connection.prepareStatement(updatedColumn); - cleanup.deferCleanup(statement); - - ResultSet resultSet = - testing.runWithSpan( - "parent", - () -> { - statement.setTime(1, Time.valueOf("00:00:00")); - statement.execute(); - return statement.getResultSet(); - }); - - resultSet.next(); - assertThat(resultSet.getInt(1)).isEqualTo(3); - - testing.waitAndAssertTraces( - trace -> - trace.hasSpansSatisfyingExactly( - span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), - span -> - span.hasName(spanName) - .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), updatedColumnSanitized), - equalTo(maybeStable(DB_OPERATION), "SELECT"), - equalTo(maybeStable(DB_SQL_TABLE), table), - equalTo(DB_QUERY_PARAMETER.getAttributeKey("0"), "00:00:00")))); + String updatedQuery = query.replace("USER_NAME=?", "CURTIME()=?"); + String updatedQuerySanitized = sanitizedQuery.replace("USER_NAME=?", "CURTIME()=?"); + + test( + system, + connection, + username, + updatedQuery, + updatedQuerySanitized, + spanName, + url, + table, + statement -> statement.setTime(1, Time.valueOf("00:00:00")), + "00:00:00"); } @ParameterizedTest @@ -637,40 +402,20 @@ void testTime3PreparedStatementParameter( String url, String table) throws SQLException { - String updatedColumn = query.replace("USER_NAME=?", "CURTIME()=?"); - String updatedColumnSanitized = sanitizedQuery.replace("USER_NAME=?", "CURTIME()=?"); - PreparedStatement statement = connection.prepareStatement(updatedColumn); - cleanup.deferCleanup(statement); - - ResultSet resultSet = - testing.runWithSpan( - "parent", - () -> { - statement.setTime(1, Time.valueOf("00:00:00"), Calendar.getInstance()); - statement.execute(); - return statement.getResultSet(); - }); - - resultSet.next(); - assertThat(resultSet.getInt(1)).isEqualTo(3); - - testing.waitAndAssertTraces( - trace -> - trace.hasSpansSatisfyingExactly( - span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), - span -> - span.hasName(spanName) - .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), updatedColumnSanitized), - equalTo(maybeStable(DB_OPERATION), "SELECT"), - equalTo(maybeStable(DB_SQL_TABLE), table), - equalTo(DB_QUERY_PARAMETER.getAttributeKey("0"), "00:00:00")))); + String updatedQuery = query.replace("USER_NAME=?", "CURTIME()=?"); + String updatedQuerySanitized = sanitizedQuery.replace("USER_NAME=?", "CURTIME()=?"); + + test( + system, + connection, + username, + updatedQuery, + updatedQuerySanitized, + spanName, + url, + table, + statement -> statement.setTime(1, Time.valueOf("00:00:00"), Calendar.getInstance()), + "00:00:00"); } @ParameterizedTest @@ -685,42 +430,20 @@ void testTimestamp2PreparedStatementParameter( String url, String table) throws SQLException { - String updatedColumn = query.replace("USER_NAME=?", "NOW()=?"); - String updatedColumnSanitized = sanitizedQuery.replace("USER_NAME=?", "NOW()=?"); - PreparedStatement statement = connection.prepareStatement(updatedColumn); - cleanup.deferCleanup(statement); - - ResultSet resultSet = - testing.runWithSpan( - "parent", - () -> { - statement.setTimestamp(1, Timestamp.valueOf("2000-01-01 00:00:00")); - statement.execute(); - return statement.getResultSet(); - }); - - resultSet.next(); - assertThat(resultSet.getInt(1)).isEqualTo(3); - - testing.waitAndAssertTraces( - trace -> - trace.hasSpansSatisfyingExactly( - span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), - span -> - span.hasName(spanName) - .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), updatedColumnSanitized), - equalTo(maybeStable(DB_OPERATION), "SELECT"), - equalTo(maybeStable(DB_SQL_TABLE), table), - equalTo( - DB_QUERY_PARAMETER.getAttributeKey("0"), - "2000-01-01 00:00:00.0")))); + String updatedQuery = query.replace("USER_NAME=?", "NOW()=?"); + String updatedQuerySanitized = sanitizedQuery.replace("USER_NAME=?", "NOW()=?"); + + test( + system, + connection, + username, + updatedQuery, + updatedQuerySanitized, + spanName, + url, + table, + statement -> statement.setTimestamp(1, Timestamp.valueOf("2000-01-01 00:00:00")), + "2000-01-01 00:00:00.0"); } @ParameterizedTest @@ -735,17 +458,71 @@ void testTimestamp3PreparedStatementParameter( String url, String table) throws SQLException { - String updatedColumn = query.replace("USER_NAME=?", "NOW()=?"); - String updatedColumnSanitized = sanitizedQuery.replace("USER_NAME=?", "NOW()=?"); - PreparedStatement statement = connection.prepareStatement(updatedColumn); + String updatedQuery = query.replace("USER_NAME=?", "NOW()=?"); + String updatedQuerySanitized = sanitizedQuery.replace("USER_NAME=?", "NOW()=?"); + + test( + system, + connection, + username, + updatedQuery, + updatedQuerySanitized, + spanName, + url, + table, + statement -> + statement.setTimestamp( + 1, Timestamp.valueOf("2000-01-01 00:00:00"), Calendar.getInstance()), + "2000-01-01 00:00:00.0"); + } + + @ParameterizedTest + @MethodSource("preparedStatementStream") + void testNstringPreparedStatementParameter( + String system, + Connection connection, + String username, + String query, + String sanitizedQuery, + String spanName, + String url, + String table) + throws SQLException { + Assumptions.assumeFalse(system.equalsIgnoreCase("derby")); + + test( + system, + connection, + username, + query, + sanitizedQuery, + spanName, + url, + table, + statement -> statement.setNString(1, "S"), + "S"); + } + + private static void test( + String system, + Connection connection, + String username, + String query, + String sanitizedQuery, + String spanName, + String url, + String table, + ThrowingConsumer setParameter, + String expectedParameterValue) + throws SQLException { + PreparedStatement statement = connection.prepareStatement(query); cleanup.deferCleanup(statement); ResultSet resultSet = testing.runWithSpan( "parent", () -> { - statement.setTimestamp( - 1, Timestamp.valueOf("2000-01-01 00:00:00"), Calendar.getInstance()); + setParameter.accept(statement); statement.execute(); return statement.getResultSet(); }); @@ -766,60 +543,14 @@ void testTimestamp3PreparedStatementParameter( equalTo(maybeStable(DB_NAME), dbNameLower), equalTo(DB_USER, emitStableDatabaseSemconv() ? null : username), equalTo(DB_CONNECTION_STRING, emitStableDatabaseSemconv() ? null : url), - equalTo(maybeStable(DB_STATEMENT), updatedColumnSanitized), + equalTo(maybeStable(DB_STATEMENT), sanitizedQuery), equalTo(maybeStable(DB_OPERATION), "SELECT"), equalTo(maybeStable(DB_SQL_TABLE), table), equalTo( - DB_QUERY_PARAMETER.getAttributeKey("0"), - "2000-01-01 00:00:00.0")))); + DB_QUERY_PARAMETER.getAttributeKey("0"), expectedParameterValue)))); } - @ParameterizedTest - @MethodSource("preparedStatementStream") - void testNstringPreparedStatementParameter( - String system, - Connection connection, - String username, - String query, - String sanitizedQuery, - String spanName, - String url, - String table) - throws SQLException { - if (!system.equalsIgnoreCase("derby")) { - PreparedStatement statement = connection.prepareStatement(query); - cleanup.deferCleanup(statement); - - ResultSet resultSet = - testing.runWithSpan( - "parent", - () -> { - statement.setNString(1, "S"); - statement.execute(); - return statement.getResultSet(); - }); - - resultSet.next(); - assertThat(resultSet.getInt(1)).isEqualTo(3); - - testing.waitAndAssertTraces( - trace -> - trace.hasSpansSatisfyingExactly( - span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), - span -> - span.hasName(spanName) - .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), sanitizedQuery), - equalTo(maybeStable(DB_OPERATION), "SELECT"), - equalTo(maybeStable(DB_SQL_TABLE), table), - equalTo(DB_QUERY_PARAMETER.getAttributeKey("0"), "S")))); - } + public interface ThrowingConsumer { + void accept(T t) throws E; } } From ff936a45d15d6037cea679729a0dc2b81bd89f15 Mon Sep 17 00:00:00 2001 From: Lauri Tulmin Date: Wed, 14 May 2025 15:02:49 +0300 Subject: [PATCH 15/15] update metadata --- instrumentation/jdbc/metadata.yaml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/instrumentation/jdbc/metadata.yaml b/instrumentation/jdbc/metadata.yaml index a51ec8c20d42..7dd9f28340d7 100644 --- a/instrumentation/jdbc/metadata.yaml +++ b/instrumentation/jdbc/metadata.yaml @@ -24,9 +24,10 @@ configurations: default: "" - name: otel.instrumentation.jdbc.capture-query-parameters description: > - Enables the attribute db.query.parameter.. Enabling this option disables the statement sanitization. - WARNING: captured query parameters may contain sensitive information such as passwords, personally - identifiable information or protected health info. + Sets whether the query parameters should be captured as span attributes named + db.query.parameter.<key>. Enabling this option disables the statement + sanitization.

WARNING: captured query parameters may contain sensitive information such as + passwords, personally identifiable information or protected health info. type: boolean default: false - name: otel.instrumentation.jdbc-datasource.enabled