diff --git a/.github/config/latest-dep-versions.json b/.github/config/latest-dep-versions.json index faec1a471a82..1404f8cfda20 100644 --- a/.github/config/latest-dep-versions.json +++ b/.github/config/latest-dep-versions.json @@ -122,10 +122,10 @@ "com.typesafe.play:play_2.13#+": "2.9.11", "com.vaadin:flow-server#+": "25.1.10", "com.vaadin:vaadin-spring-boot-starter#14.11.+": "14.11.14", - "com.xuxueli:xxl-job-core#+": "3.4.0", + "com.xuxueli:xxl-job-core#+": "3.4.1", "com.xuxueli:xxl-job-core#2.2.+": "2.2.0", "com.xuxueli:xxl-job-core#3.2.+": "3.2.0", - "com.zaxxer:HikariCP#+": "7.0.2", + "com.zaxxer:HikariCP#+": "7.1.0", "commons-httpclient:commons-httpclient#+": "3.1", "commons-httpclient:commons-httpclient#3.+": "3.1", "dev.failsafe:failsafe#+": "3.3.2", @@ -138,7 +138,7 @@ "fish.payara.extras:payara-embedded-web#+": "7.2026.5", "io.activej:activej-http#+": "5.5", "io.activej:activej-http#6.+": "6.0-rc2", - "io.avaje:avaje-jex#+": "3.5", + "io.avaje:avaje-jex#+": "3.6", "io.awspring.cloud:spring-cloud-aws-sqs#+": "4.0.2", "io.awspring.cloud:spring-cloud-aws-starter-sqs#+": "4.0.2", "io.dropwizard.metrics:metrics-core#+": "4.2.39", @@ -353,7 +353,7 @@ "org.apache.tomee:openejb-cxf-rs#+": "10.1.5", "org.apache.wicket:wicket#+": "10.9.1", "org.apache.wicket:wicket#9.+": "9.23.0", - "org.asynchttpclient:async-http-client#+": "3.0.10", + "org.asynchttpclient:async-http-client#+": "3.0.11", "org.eclipse.jetty.ee10:jetty-ee10-servlet#+": "12.1.10", "org.eclipse.jetty:jetty-client#+": "12.1.10", "org.eclipse.jetty:jetty-client#9.+": "9.4.58.v20250814", @@ -558,20 +558,20 @@ "redis.clients:jedis#2.+": "2.10.2", "redis.clients:jedis#3.+": "3.10.0", "software.amazon.awssdk.crt:aws-crt#+": "0.47.0", - "software.amazon.awssdk:aws-core#+": "2.46.9", - "software.amazon.awssdk:aws-json-protocol#+": "2.46.9", - "software.amazon.awssdk:bedrockruntime#+": "2.46.9", - "software.amazon.awssdk:dynamodb#+": "2.46.9", - "software.amazon.awssdk:ec2#+": "2.46.9", - "software.amazon.awssdk:kinesis#+": "2.46.9", - "software.amazon.awssdk:lambda#+": "2.46.9", - "software.amazon.awssdk:rds#+": "2.46.9", - "software.amazon.awssdk:s3#+": "2.46.9", - "software.amazon.awssdk:secretsmanager#+": "2.46.9", - "software.amazon.awssdk:ses#+": "2.46.9", - "software.amazon.awssdk:sfn#+": "2.46.9", - "software.amazon.awssdk:sns#+": "2.46.9", - "software.amazon.awssdk:sqs#+": "2.46.9", + "software.amazon.awssdk:aws-core#+": "2.46.10", + "software.amazon.awssdk:aws-json-protocol#+": "2.46.10", + "software.amazon.awssdk:bedrockruntime#+": "2.46.10", + "software.amazon.awssdk:dynamodb#+": "2.46.10", + "software.amazon.awssdk:ec2#+": "2.46.10", + "software.amazon.awssdk:kinesis#+": "2.46.10", + "software.amazon.awssdk:lambda#+": "2.46.10", + "software.amazon.awssdk:rds#+": "2.46.10", + "software.amazon.awssdk:s3#+": "2.46.10", + "software.amazon.awssdk:secretsmanager#+": "2.46.10", + "software.amazon.awssdk:ses#+": "2.46.10", + "software.amazon.awssdk:sfn#+": "2.46.10", + "software.amazon.awssdk:sns#+": "2.46.10", + "software.amazon.awssdk:sqs#+": "2.46.10", "tech.powerjob:powerjob-official-processors#+": "5.1.2", "tech.powerjob:powerjob-worker#+": "5.1.2" } diff --git a/conventions/src/main/kotlin/otel.java-conventions.gradle.kts b/conventions/src/main/kotlin/otel.java-conventions.gradle.kts index 6277208a6ad0..e0fc826b0188 100644 --- a/conventions/src/main/kotlin/otel.java-conventions.gradle.kts +++ b/conventions/src/main/kotlin/otel.java-conventions.gradle.kts @@ -419,7 +419,7 @@ afterEvaluate { checkstyle { configFile = rootProject.file("buildscripts/checkstyle.xml") // this version should match the version of google_checks.xml used as basis for above configuration - toolVersion = "13.5.0" + toolVersion = "13.6.0" maxWarnings = 0 } diff --git a/docs/instrumentation-list.yaml b/docs/instrumentation-list.yaml index f343793d4da6..a36b5e4d8d74 100644 --- a/docs/instrumentation-list.yaml +++ b/docs/instrumentation-list.yaml @@ -3200,6 +3200,8 @@ libraries: type: STRING - name: db.namespace type: STRING + - name: db.operation.batch.size + type: LONG - name: db.operation.name type: STRING - name: db.query.summary @@ -3316,6 +3318,8 @@ libraries: type: STRING - name: db.namespace type: STRING + - name: db.operation.batch.size + type: LONG - name: db.operation.name type: STRING - name: db.query.summary @@ -3440,6 +3444,8 @@ libraries: type: STRING - name: db.namespace type: STRING + - name: db.operation.batch.size + type: LONG - name: db.operation.name type: STRING - name: db.query.summary diff --git a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/DbClientAttributesExtractor.java b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/DbClientAttributesExtractor.java index a52425662a3a..e2c5913b00c9 100644 --- a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/DbClientAttributesExtractor.java +++ b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/DbClientAttributesExtractor.java @@ -7,6 +7,7 @@ import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitOldDatabaseSemconv; import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitStableDatabaseSemconv; +import static io.opentelemetry.semconv.DbAttributes.DB_COLLECTION_NAME; import static io.opentelemetry.semconv.DbAttributes.DB_NAMESPACE; import static io.opentelemetry.semconv.DbAttributes.DB_OPERATION_BATCH_SIZE; import static io.opentelemetry.semconv.DbAttributes.DB_OPERATION_NAME; @@ -99,6 +100,7 @@ static void onStartCommon( attributes.put( DB_SYSTEM_NAME, SemconvStability.stableDbSystemName(getter.getDbSystemName(request))); attributes.put(DB_NAMESPACE, getter.getDbNamespace(request)); + attributes.put(DB_COLLECTION_NAME, getter.getDbCollectionName(request)); attributes.put(DB_QUERY_TEXT, getter.getDbQueryText(request)); attributes.put(DB_OPERATION_NAME, getter.getDbOperationName(request)); attributes.put(DB_QUERY_SUMMARY, getter.getDbQuerySummary(request)); @@ -112,7 +114,7 @@ static void onStartCommon( attributes.put(DB_NAME, getter.getDbName(request)); attributes.put(DB_CONNECTION_STRING, getter.getConnectionString(request)); attributes.put(DB_STATEMENT, getter.getDbQueryText(request)); - attributes.put(DB_OPERATION, getter.getDbOperationName(request)); + attributes.put(DB_OPERATION, getter.getDbOperation(request)); } // Query parameters are an incubating feature and work with both old and new semconv diff --git a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/DbClientAttributesGetter.java b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/DbClientAttributesGetter.java index 3a9aa460c635..400b32669939 100644 --- a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/DbClientAttributesGetter.java +++ b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/DbClientAttributesGetter.java @@ -38,6 +38,17 @@ default String getDbQuerySummary(REQUEST request) { @Nullable String getDbOperationName(REQUEST request); + /** + * Returns the old db.operation value. This is only used for old semantic conventions. + * + * @deprecated Use {@link #getDbOperationName(Object)} instead. + */ + @Deprecated // to be removed in 3.0 + @Nullable + default String getDbOperation(REQUEST request) { + return getDbOperationName(request); + } + // TODO: make this required to implement String getDbSystemName(REQUEST request); @@ -59,6 +70,12 @@ default String getDbSystem(REQUEST request) { @Nullable String getDbNamespace(REQUEST request); + // TODO: make this required to implement? + @Nullable + default String getDbCollectionName(REQUEST request) { + return null; + } + /** * Returns the old db.name value. This is only used for old semantic conventions. * diff --git a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/DbClientSpanNameExtractor.java b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/DbClientSpanNameExtractor.java index 28172e8cb82e..02bfdfc3720a 100644 --- a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/DbClientSpanNameExtractor.java +++ b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/DbClientSpanNameExtractor.java @@ -163,11 +163,15 @@ public String extract(REQUEST request) { if (querySummary != null) { return querySummary; } - String operationName = getter.getDbOperationName(request); - return computeSpanNameStable(getter, request, operationName, null, null); + return computeSpanNameStable( + getter, + request, + getter.getDbOperationName(request), + getter.getDbCollectionName(request), + null); } String dbName = getter.getDbName(request); - String operationName = getter.getDbOperationName(request); + String operationName = getter.getDbOperation(request); return computeSpanName(dbName, operationName, null, null); } } @@ -189,16 +193,10 @@ public String extract(REQUEST request) { if (rawQueryTexts.isEmpty()) { if (emitStableDatabaseSemconv()) { - String querySummary = getter.getDbQuerySummary(request); - if (querySummary != null) { - return querySummary; - } - String operationName = getter.getDbOperationName(request); - return computeSpanNameStable(getter, request, operationName, null, null); + return computeSpanNameStable(getter, request, null, null, null); } String dbName = getter.getDbName(request); - String operationName = getter.getDbOperationName(request); - return computeSpanName(dbName, operationName, null, null); + return computeSpanName(dbName, null, null, null); } if (!emitStableDatabaseSemconv()) { @@ -224,7 +222,11 @@ public String extract(REQUEST request) { return batch ? "BATCH " + querySummary : querySummary; } return computeSpanNameStable( - getter, request, batch ? "BATCH" : null, null, analyzedQuery.getStoredProcedureName()); + getter, + request, + batch ? "BATCH" : null, + analyzedQuery.getCollectionName(), + analyzedQuery.getStoredProcedureName()); } MultiQuery multiQuery = MultiQuery.analyzeWithSummary(rawQueryTexts, dialect); @@ -276,9 +278,6 @@ public String extract(REQUEST request) { SqlQuery analyzedQuery = SqlQueryAnalyzerUtil.analyze(rawQuery, dialect); operationName = analyzedQuery.getOperationName(); } - if (operationName == null) { - operationName = getter.getDbOperationName(request); - } return computeSpanName(dbName, operationName, null, null); } } 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 874de62b85f5..a995ee2e242b 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 @@ -7,12 +7,15 @@ import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitOldDatabaseSemconv; import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitStableDatabaseSemconv; +import static io.opentelemetry.instrumentation.api.internal.SemconvStability.stableDbSystemName; import static io.opentelemetry.semconv.DbAttributes.DB_COLLECTION_NAME; +import static io.opentelemetry.semconv.DbAttributes.DB_NAMESPACE; import static io.opentelemetry.semconv.DbAttributes.DB_OPERATION_BATCH_SIZE; import static io.opentelemetry.semconv.DbAttributes.DB_OPERATION_NAME; import static io.opentelemetry.semconv.DbAttributes.DB_QUERY_SUMMARY; import static io.opentelemetry.semconv.DbAttributes.DB_QUERY_TEXT; import static io.opentelemetry.semconv.DbAttributes.DB_STORED_PROCEDURE_NAME; +import static io.opentelemetry.semconv.DbAttributes.DB_SYSTEM_NAME; import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.AttributesBuilder; @@ -22,7 +25,9 @@ import io.opentelemetry.instrumentation.api.internal.SpanKeyProvider; import io.opentelemetry.instrumentation.api.semconv.network.ServerAttributesExtractor; import io.opentelemetry.instrumentation.api.semconv.network.internal.InternalNetworkAttributesExtractor; +import io.opentelemetry.semconv.AttributeKeyTemplate; import java.util.Collection; +import java.util.Map; import javax.annotation.Nullable; /** @@ -39,8 +44,15 @@ public final class SqlClientAttributesExtractor implements AttributesExtractor, SpanKeyProvider { // copied from DbIncubatingAttributes + private static final AttributeKey DB_NAME = AttributeKey.stringKey("db.name"); + private static final AttributeKey DB_SYSTEM = AttributeKey.stringKey("db.system"); + private static final AttributeKey DB_USER = AttributeKey.stringKey("db.user"); + private static final AttributeKey DB_CONNECTION_STRING = + AttributeKey.stringKey("db.connection_string"); private static final AttributeKey DB_OPERATION = AttributeKey.stringKey("db.operation"); private static final AttributeKey DB_STATEMENT = AttributeKey.stringKey("db.statement"); + 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( @@ -141,10 +153,24 @@ public void onStart(AttributesBuilder attributes, Context parentContext, REQUEST } } - // calling this last so explicit getDbOperationName(), getDbCollectionName(), - // getDbQueryText(), and getDbQuerySummary() implementations can override - // the parsed values from above - DbClientAttributesExtractor.onStartCommon(attributes, getter, request, captureQueryParameters); + if (emitStableDatabaseSemconv()) { + attributes.put(DB_SYSTEM_NAME, stableDbSystemName(getter.getDbSystemName(request))); + attributes.put(DB_NAMESPACE, getter.getDbNamespace(request)); + } + if (emitOldDatabaseSemconv()) { + attributes.put(DB_SYSTEM, getter.getDbSystem(request)); + attributes.put(DB_USER, getter.getUser(request)); + attributes.put(DB_NAME, getter.getDbName(request)); + attributes.put(DB_CONNECTION_STRING, getter.getConnectionString(request)); + } + if (captureQueryParameters && !isBatch) { + Map queryParameters = getter.getDbQueryParameters(request); + if (queryParameters != null && !queryParameters.isEmpty()) { + for (Map.Entry entry : queryParameters.entrySet()) { + attributes.put(DB_QUERY_PARAMETER.getAttributeKey(entry.getKey()), entry.getValue()); + } + } + } serverAttributesExtractor.onStart(attributes, parentContext, request); } 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 db1525cd7f04..e247d6c087cb 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 @@ -68,10 +68,12 @@ public SqlClientAttributesExtractorBuilder setCaptureQueryPar } /** - * Sets whether the instrumentation knows that its SQL-like language or request shape is limited - * to a single operation and collection. + * Sets whether {@code db.operation.name} and {@code db.collection.name} can be derived from + * {@code db.query.text}. * - *

For most instrumentations, enabling this will produce invalid semantic conventions. + *

Enable this only when the database system does not support query text with multiple + * operations or multiple collections in non-batch operations. For most instrumentations, enabling + * this will produce invalid semantic conventions. */ @CanIgnoreReturnValue public SqlClientAttributesExtractorBuilder setSingleOperationAndCollection( 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 c3b2ac869b47..eb7d63dc2549 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 @@ -6,7 +6,6 @@ package io.opentelemetry.instrumentation.api.incubator.semconv.db; import java.util.Collection; -import javax.annotation.Nullable; /** * An interface for getting SQL database client attributes. @@ -23,33 +22,65 @@ public interface SqlClientAttributesGetter extends DbClientAttributesGetter { /** - * SqlClientAttributesExtractor will try to populate db.operation.name based on {@link - * #getRawQueryTexts(REQUEST)}, but this can be used to explicitly provide the operation name. + * SQL instrumentations must not override or call this method. + * + *

Provide raw query text through {@link #getRawQueryTexts(REQUEST)} instead. When the database + * system does not support query text with multiple operations in non-batch operations, enable + * {@link SqlClientAttributesExtractorBuilder#setSingleOperationAndCollection(boolean)} and {@link + * SqlClientAttributesExtractor} will derive {@code db.operation.name} from {@code db.query.text}. + * + * @throws UnsupportedOperationException always */ @Override - @Nullable default String getDbOperationName(REQUEST request) { - return null; + throw new UnsupportedOperationException( + "SQL instrumentations derive db.operation.name from the raw query text"); } /** - * SqlClientAttributesExtractor will try to populate db.query.text based on {@link - * #getRawQueryTexts(REQUEST)}, but this can be used to explicitly provide the query text. + * SQL instrumentations must not override or call this method. + * + *

Provide raw query text through {@link #getRawQueryTexts(REQUEST)} instead. {@link + * SqlClientAttributesExtractor} will derive {@code db.query.text} from the raw query text. + * + * @throws UnsupportedOperationException always */ @Override - @Nullable default String getDbQueryText(REQUEST request) { - return null; + throw new UnsupportedOperationException( + "SQL instrumentations derive db.query.text from the raw query text"); } /** - * SqlClientAttributesExtractor will try to populate db.query.summary based on {@link - * #getRawQueryTexts(REQUEST)}, but this can be used to explicitly provide the query summary. + * SQL instrumentations must not override or call this method. + * + *

Provide raw query text through {@link #getRawQueryTexts(REQUEST)} instead. {@link + * SqlClientAttributesExtractor} will derive {@code db.query.summary} from the raw query text. + * + * @throws UnsupportedOperationException always */ @Override - @Nullable default String getDbQuerySummary(REQUEST request) { - return null; + throw new UnsupportedOperationException( + "SQL instrumentations derive db.query.summary from the raw query text"); + } + + /** + * SQL instrumentations must not override or call this method. + * + *

Provide raw query text through {@link #getRawQueryTexts(REQUEST)} instead. When the database + * system does not support query text with multiple collections in non-batch operations, enable + * {@link SqlClientAttributesExtractorBuilder#setSingleOperationAndCollection(boolean)} and {@link + * SqlClientAttributesExtractor} will derive {@code db.collection.name} from {@code + * db.query.text}. Do not enable that option when the database system supports query text with + * multiple collections in non-batch operations. + * + * @throws UnsupportedOperationException always + */ + @Override + default String getDbCollectionName(REQUEST request) { + throw new UnsupportedOperationException( + "SQL instrumentations derive db.collection.name from the raw query text"); } /** Returns the SQL dialect used by the database. */ diff --git a/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/DbClientAttributesExtractorTest.java b/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/DbClientAttributesExtractorTest.java index 81ec9bd856e1..7336b737b848 100644 --- a/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/DbClientAttributesExtractorTest.java +++ b/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/DbClientAttributesExtractorTest.java @@ -8,6 +8,7 @@ import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitOldDatabaseSemconv; import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitStableDatabaseSemconv; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; +import static io.opentelemetry.semconv.DbAttributes.DB_COLLECTION_NAME; import static io.opentelemetry.semconv.DbAttributes.DB_NAMESPACE; import static io.opentelemetry.semconv.DbAttributes.DB_OPERATION_NAME; import static io.opentelemetry.semconv.DbAttributes.DB_QUERY_SUMMARY; @@ -50,6 +51,11 @@ public String getDbNamespace(Map map) { return map.get("db.namespace"); } + @Override + public String getDbCollectionName(Map map) { + return map.get("db.collection.name"); + } + @Deprecated @Override public String getConnectionString(Map map) { @@ -71,6 +77,12 @@ public String getDbQuerySummary(Map map) { public String getDbOperationName(Map map) { return map.get("db.operation.name"); } + + @Deprecated + @Override + public String getDbOperation(Map map) { + return map.get("db.operation"); + } } @SuppressWarnings("deprecation") // TODO DB_CONNECTION_STRING deprecation @@ -81,9 +93,11 @@ void shouldExtractAllAvailableAttributes() { request.put("db.system", "myDb"); request.put("db.user", "username"); request.put("db.namespace", "potatoes"); + request.put("db.collection.name", "potato"); request.put("db.connection_string", "mydb:///potatoes"); request.put("db.query.text", "SELECT * FROM potato"); request.put("db.query_summary", "SELECT potato"); + request.put("db.operation", "old SELECT"); request.put("db.operation.name", "SELECT"); Context context = Context.root(); @@ -108,7 +122,8 @@ void shouldExtractAllAvailableAttributes() { entry(DB_NAME, "potatoes"), entry(DB_CONNECTION_STRING, "mydb:///potatoes"), entry(DB_STATEMENT, "SELECT * FROM potato"), - entry(DB_OPERATION, "SELECT"), + entry(DB_OPERATION, "old SELECT"), + entry(DB_COLLECTION_NAME, "potato"), entry(DB_NAMESPACE, "potatoes"), entry(DB_QUERY_TEXT, "SELECT * FROM potato"), entry(DB_QUERY_SUMMARY, "SELECT potato"), @@ -121,11 +136,12 @@ void shouldExtractAllAvailableAttributes() { entry(DB_NAME, "potatoes"), entry(DB_CONNECTION_STRING, "mydb:///potatoes"), entry(DB_STATEMENT, "SELECT * FROM potato"), - entry(DB_OPERATION, "SELECT")); + entry(DB_OPERATION, "old SELECT")); } else if (emitStableDatabaseSemconv()) { assertThat(startAttributes.build()) .containsOnly( entry(DB_SYSTEM_NAME, "myDb"), + entry(DB_COLLECTION_NAME, "potato"), entry(DB_NAMESPACE, "potatoes"), entry(DB_QUERY_TEXT, "SELECT * FROM potato"), entry(DB_QUERY_SUMMARY, "SELECT potato"), diff --git a/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/DbClientSpanNameExtractorTest.java b/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/DbClientSpanNameExtractorTest.java index f90a5f60ea49..99dd151dbea8 100644 --- a/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/DbClientSpanNameExtractorTest.java +++ b/instrumentation-api-incubator/src/test/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/DbClientSpanNameExtractorTest.java @@ -100,11 +100,12 @@ void shouldExtractOperationAndName() { // given DbRequest dbRequest = new DbRequest(); - when(dbAttributesGetter.getDbOperationName(dbRequest)).thenReturn("SELECT"); if (emitStableDatabaseSemconv()) { + when(dbAttributesGetter.getDbOperationName(dbRequest)).thenReturn("SELECT"); when(dbAttributesGetter.getDbNamespace(dbRequest)).thenReturn("database"); } if (emitOldDatabaseSemconv() && !emitStableDatabaseSemconv()) { + when(dbAttributesGetter.getDbOperation(dbRequest)).thenReturn("SELECT"); when(dbAttributesGetter.getDbName(dbRequest)).thenReturn("database"); } @@ -117,12 +118,42 @@ void shouldExtractOperationAndName() { assertThat(spanName).isEqualTo("SELECT database"); } + @Test + void shouldPreferCollectionNameOverNamespace() { + // given + DbRequest dbRequest = new DbRequest(); + + if (emitStableDatabaseSemconv()) { + when(dbAttributesGetter.getDbOperationName(dbRequest)).thenReturn("SELECT"); + lenient().when(dbAttributesGetter.getDbNamespace(dbRequest)).thenReturn("database"); + when(dbAttributesGetter.getDbCollectionName(dbRequest)).thenReturn("users"); + } + if (emitOldDatabaseSemconv() && !emitStableDatabaseSemconv()) { + when(dbAttributesGetter.getDbOperation(dbRequest)).thenReturn("SELECT"); + when(dbAttributesGetter.getDbName(dbRequest)).thenReturn("database"); + } + + SpanNameExtractor underTest = DbClientSpanNameExtractor.create(dbAttributesGetter); + + // when + String spanName = underTest.extract(dbRequest); + + // then + assertThat(spanName) + .isEqualTo(emitStableDatabaseSemconv() ? "SELECT users" : "SELECT database"); + } + @Test void shouldExtractOperation() { // given DbRequest dbRequest = new DbRequest(); - when(dbAttributesGetter.getDbOperationName(dbRequest)).thenReturn("SELECT"); + if (emitStableDatabaseSemconv()) { + when(dbAttributesGetter.getDbOperationName(dbRequest)).thenReturn("SELECT"); + } + if (emitOldDatabaseSemconv() && !emitStableDatabaseSemconv()) { + when(dbAttributesGetter.getDbOperation(dbRequest)).thenReturn("SELECT"); + } SpanNameExtractor underTest = DbClientSpanNameExtractor.create(dbAttributesGetter); @@ -177,7 +208,7 @@ void shouldUseQuerySummaryWhenAvailable() { when(dbAttributesGetter.getDbQuerySummary(dbRequest)).thenReturn("SELECT users"); } if (emitOldDatabaseSemconv() && !emitStableDatabaseSemconv()) { - when(dbAttributesGetter.getDbOperationName(dbRequest)).thenReturn("SELECT"); + when(dbAttributesGetter.getDbOperation(dbRequest)).thenReturn("SELECT"); when(dbAttributesGetter.getDbName(dbRequest)).thenReturn("database"); } @@ -236,12 +267,11 @@ void shouldExtractFullSpanNameForSingleQueryBatch() { } @Test - void shouldFallBackToExplicitOperationNameForEmptySqlQuery() { + void shouldFallBackToNamespaceForEmptySqlQuery() { // given DbRequest dbRequest = new DbRequest(); when(sqlAttributesGetter.getRawQueryTexts(dbRequest)).thenReturn(emptyList()); - when(sqlAttributesGetter.getDbOperationName(dbRequest)).thenReturn("WRITE"); if (emitStableDatabaseSemconv()) { when(sqlAttributesGetter.getDbNamespace(dbRequest)).thenReturn("mydb"); } @@ -255,7 +285,7 @@ void shouldFallBackToExplicitOperationNameForEmptySqlQuery() { String spanName = underTest.extract(dbRequest); // then - assertThat(spanName).isEqualTo("WRITE mydb"); + assertThat(spanName).isEqualTo("mydb"); } @Test @@ -283,12 +313,11 @@ void shouldPreserveOldSemconvSpanNameForMigration() { @Test @SuppressWarnings("deprecation") // testing deprecated method - void shouldFallBackToExplicitOperationForEmptySqlQueryInMigration() { + void shouldFallBackToNamespaceForEmptySqlQueryInMigration() { // given DbRequest dbRequest = new DbRequest(); when(sqlAttributesGetter.getRawQueryTexts(dbRequest)).thenReturn(emptyList()); - when(sqlAttributesGetter.getDbOperationName(dbRequest)).thenReturn("WRITE"); if (emitStableDatabaseSemconv()) { when(sqlAttributesGetter.getDbNamespace(dbRequest)).thenReturn("mydb"); } @@ -303,7 +332,7 @@ void shouldFallBackToExplicitOperationForEmptySqlQueryInMigration() { String spanName = underTest.extract(dbRequest); // then - assertThat(spanName).isEqualTo("WRITE mydb"); + assertThat(spanName).isEqualTo("mydb"); } static class DbRequest {} diff --git a/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/internal/DynamoDbAttributesExtractor.java b/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/internal/DynamoDbAttributesExtractor.java index 70bc6f6390f4..3a0694ad74a6 100644 --- a/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/internal/DynamoDbAttributesExtractor.java +++ b/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/internal/DynamoDbAttributesExtractor.java @@ -8,6 +8,7 @@ import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitOldDatabaseSemconv; import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitStableDatabaseSemconv; import static io.opentelemetry.semconv.DbAttributes.DB_COLLECTION_NAME; +import static io.opentelemetry.semconv.DbAttributes.DB_OPERATION_BATCH_SIZE; import static io.opentelemetry.semconv.DbAttributes.DB_OPERATION_NAME; import static io.opentelemetry.semconv.DbAttributes.DB_SYSTEM_NAME; import static java.util.Collections.singletonList; @@ -18,6 +19,7 @@ import io.opentelemetry.api.common.AttributesBuilder; import io.opentelemetry.context.Context; import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; +import java.util.Collection; import java.util.List; import java.util.Map; import javax.annotation.Nullable; @@ -46,8 +48,12 @@ public void onStart(AttributesBuilder attributes, Context parentContext, Request } String operation = getOperationName(request.getOriginalRequest()); + Long batchSize = extractBatchSize(operation, request.getOriginalRequest()); if (emitStableDatabaseSemconv()) { - attributes.put(DB_OPERATION_NAME, getStableOperationName(operation)); + attributes.put(DB_OPERATION_NAME, getStableOperationName(operation, batchSize)); + if (isBatch(batchSize)) { + attributes.put(DB_OPERATION_BATCH_SIZE, batchSize); + } } if (emitOldDatabaseSemconv()) { attributes.put(DB_OPERATION, operation); @@ -89,16 +95,71 @@ private static String getSingleCollectionName(Map requestItems) { } @Nullable - private static String getStableOperationName(@Nullable String operation) { + private static String getStableOperationName( + @Nullable String operation, @Nullable Long batchSize) { if ("BatchGetItem".equals(operation)) { - return "BATCH GetItem"; + return getStableBatchOperationName(batchSize, "GetItem", operation); } if ("BatchWriteItem".equals(operation)) { - return "BATCH WriteItem"; + return getStableBatchOperationName(batchSize, "WriteItem", operation); } return operation; } + private static String getStableBatchOperationName( + @Nullable Long batchSize, String itemOperation, String batchOperation) { + if (batchSize == null || batchSize == 0) { + return batchOperation; + } + if (batchSize == 1) { + return itemOperation; + } + return "BATCH " + itemOperation; + } + + @Nullable + private static Long extractBatchSize(@Nullable String operation, Object request) { + if (!"BatchGetItem".equals(operation) && !"BatchWriteItem".equals(operation)) { + return null; + } + + Map requestItems = RequestAccess.getRequestItems(request); + if (requestItems == null) { + return null; + } + + long batchSize = + "BatchGetItem".equals(operation) + ? countBatchGetItems(requestItems) + : countBatchWriteItems(requestItems); + return batchSize == 0 ? null : batchSize; + } + + private static long countBatchGetItems(Map requestItems) { + long count = 0; + for (Object keysAndAttributes : requestItems.values()) { + List keys = RequestAccess.getKeys(keysAndAttributes); + if (keys != null) { + count += keys.size(); + } + } + return count; + } + + private static long countBatchWriteItems(Map requestItems) { + long count = 0; + for (Object writeRequests : requestItems.values()) { + if (writeRequests instanceof Collection) { + count += ((Collection) writeRequests).size(); + } + } + return count; + } + + private static boolean isBatch(@Nullable Long batchSize) { + return batchSize != null && batchSize > 1; + } + @Nullable private static String getOperationName(Object request) { String name = request.getClass().getSimpleName(); diff --git a/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/internal/RequestAccess.java b/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/internal/RequestAccess.java index 3846c9212b9d..f4fe2066f243 100644 --- a/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/internal/RequestAccess.java +++ b/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/internal/RequestAccess.java @@ -8,6 +8,7 @@ import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; +import java.util.List; import java.util.Map; import javax.annotation.Nullable; @@ -104,6 +105,12 @@ static String getTableName(Object request) { return invokeOrNull(access.getRequestItems, request, Map.class); } + @Nullable + static List getKeys(Object request) { + RequestAccess access = REQUEST_ACCESSORS.get(request.getClass()); + return invokeOrNull(access.getKeys, request, List.class); + } + @Nullable static String getSnsTopicArn(Object request) { RequestAccess access = REQUEST_ACCESSORS.get(request.getClass()); @@ -135,6 +142,7 @@ private static T invokeOrNull( } @Nullable private final MethodHandle getBucketName; + @Nullable private final MethodHandle getKeys; @Nullable private final MethodHandle getLambdaConfiguration; @Nullable private final MethodHandle getLambdaName; @Nullable private final MethodHandle getLambdaResourceMappingId; @@ -155,6 +163,7 @@ private RequestAccess(Class clz) { getQueueName = findAccessorOrNull(clz, "getQueueName"); getStreamName = findAccessorOrNull(clz, "getStreamName"); getTableName = findAccessorOrNull(clz, "getTableName"); + getKeys = findAccessorOrNull(clz, "getKeys", List.class); getRequestItems = findAccessorOrNull(clz, "getRequestItems", Map.class); getTopicArn = findAccessorOrNull(clz, "getTopicArn"); getTargetArn = findAccessorOrNull(clz, "getTargetArn"); diff --git a/instrumentation/aws-sdk/aws-sdk-1.11/testing/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/AbstractDynamoDbClientTest.java b/instrumentation/aws-sdk/aws-sdk-1.11/testing/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/AbstractDynamoDbClientTest.java index 38febc0cd23f..be8bbaf96692 100644 --- a/instrumentation/aws-sdk/aws-sdk-1.11/testing/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/AbstractDynamoDbClientTest.java +++ b/instrumentation/aws-sdk/aws-sdk-1.11/testing/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/AbstractDynamoDbClientTest.java @@ -10,6 +10,7 @@ import static io.opentelemetry.instrumentation.testing.junit.db.SemconvStabilityUtil.maybeStable; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; import static io.opentelemetry.semconv.DbAttributes.DB_COLLECTION_NAME; +import static io.opentelemetry.semconv.DbAttributes.DB_OPERATION_BATCH_SIZE; import static io.opentelemetry.semconv.DbAttributes.DB_OPERATION_NAME; import static io.opentelemetry.semconv.DbAttributes.DB_SYSTEM_NAME; import static io.opentelemetry.semconv.ServerAttributes.SERVER_ADDRESS; @@ -27,8 +28,11 @@ import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClientBuilder; import com.amazonaws.services.dynamodbv2.model.AttributeValue; import com.amazonaws.services.dynamodbv2.model.BatchGetItemRequest; +import com.amazonaws.services.dynamodbv2.model.BatchWriteItemRequest; import com.amazonaws.services.dynamodbv2.model.CreateTableRequest; import com.amazonaws.services.dynamodbv2.model.KeysAndAttributes; +import com.amazonaws.services.dynamodbv2.model.PutRequest; +import com.amazonaws.services.dynamodbv2.model.WriteRequest; import io.opentelemetry.sdk.testing.assertj.AttributeAssertion; import io.opentelemetry.testing.internal.armeria.common.HttpResponse; import io.opentelemetry.testing.internal.armeria.common.HttpStatus; @@ -80,7 +84,8 @@ void sendRequestWithMockedResponse() throws ReflectiveOperationException { @SuppressWarnings("deprecation") // using deprecated semconv @Test - void batchGetItemWithSingleTableUsesStableBatchAttributes() throws ReflectiveOperationException { + void batchGetItemWithMultipleItemsUsesStableBatchAttributes() + throws ReflectiveOperationException { AmazonDynamoDB client = createClient(); server.enqueue(HttpResponse.of(HttpStatus.OK, MediaType.PLAIN_TEXT_UTF_8, "{}")); @@ -92,10 +97,52 @@ void batchGetItemWithSingleTableUsesStableBatchAttributes() throws ReflectiveOpe maybeStable(DB_SYSTEM), emitStableDatabaseSemconv() ? AWS_DYNAMODB : DYNAMODB), equalTo( maybeStable(DB_OPERATION), - emitStableDatabaseSemconv() ? "BATCH GetItem" : "BatchGetItem"))); - if (emitStableDatabaseSemconv()) { - additionalAttributes.add(equalTo(DB_COLLECTION_NAME, "sometable")); - } + emitStableDatabaseSemconv() ? "BATCH GetItem" : "BatchGetItem"), + equalTo( + DB_OPERATION_BATCH_SIZE, emitStableDatabaseSemconv() ? Long.valueOf(2) : null), + equalTo(DB_COLLECTION_NAME, emitStableDatabaseSemconv() ? "sometable" : null))); + + Object response = + client.batchGetItem( + new BatchGetItemRequest() + .withRequestItems( + singletonMap( + "sometable", + new KeysAndAttributes() + .withKeys( + asList( + singletonMap("key", new AttributeValue().withS("value")), + singletonMap( + "key", new AttributeValue().withS("anotherValue"))))))); + assertRequestWithMockedResponse( + response, client, "DynamoDBv2", "BatchGetItem", "POST", additionalAttributes); + + assertDurationMetric( + testing(), + "io.opentelemetry.aws-sdk-1.11", + DB_SYSTEM_NAME, + DB_OPERATION_NAME, + DB_COLLECTION_NAME, + SERVER_ADDRESS, + SERVER_PORT); + } + + @SuppressWarnings("deprecation") // using deprecated semconv + @Test + void batchGetItemWithSingleItemUsesStableItemOperation() throws ReflectiveOperationException { + AmazonDynamoDB client = createClient(); + + server.enqueue(HttpResponse.of(HttpStatus.OK, MediaType.PLAIN_TEXT_UTF_8, "{}")); + + List additionalAttributes = + new ArrayList<>( + asList( + equalTo( + maybeStable(DB_SYSTEM), emitStableDatabaseSemconv() ? AWS_DYNAMODB : DYNAMODB), + equalTo( + maybeStable(DB_OPERATION), + emitStableDatabaseSemconv() ? "GetItem" : "BatchGetItem"), + equalTo(DB_COLLECTION_NAME, emitStableDatabaseSemconv() ? "sometable" : null))); Object response = client.batchGetItem( @@ -120,6 +167,85 @@ void batchGetItemWithSingleTableUsesStableBatchAttributes() throws ReflectiveOpe SERVER_PORT); } + @SuppressWarnings("deprecation") // using deprecated semconv + @Test + void batchWriteItemWithMultipleItemsUsesStableBatchAttributes() + throws ReflectiveOperationException { + AmazonDynamoDB client = createClient(); + + server.enqueue(HttpResponse.of(HttpStatus.OK, MediaType.PLAIN_TEXT_UTF_8, "{}")); + + List additionalAttributes = + new ArrayList<>( + asList( + equalTo( + maybeStable(DB_SYSTEM), emitStableDatabaseSemconv() ? AWS_DYNAMODB : DYNAMODB), + equalTo( + maybeStable(DB_OPERATION), + emitStableDatabaseSemconv() ? "BATCH WriteItem" : "BatchWriteItem"), + equalTo( + DB_OPERATION_BATCH_SIZE, emitStableDatabaseSemconv() ? Long.valueOf(2) : null), + equalTo(DB_COLLECTION_NAME, emitStableDatabaseSemconv() ? "sometable" : null))); + + Object response = + client.batchWriteItem( + new BatchWriteItemRequest() + .withRequestItems( + singletonMap( + "sometable", asList(writeRequest("value"), writeRequest("anotherValue"))))); + assertRequestWithMockedResponse( + response, client, "DynamoDBv2", "BatchWriteItem", "POST", additionalAttributes); + + assertDurationMetric( + testing(), + "io.opentelemetry.aws-sdk-1.11", + DB_SYSTEM_NAME, + DB_OPERATION_NAME, + DB_COLLECTION_NAME, + SERVER_ADDRESS, + SERVER_PORT); + } + + @SuppressWarnings("deprecation") // using deprecated semconv + @Test + void batchWriteItemWithSingleItemUsesStableItemOperation() throws ReflectiveOperationException { + AmazonDynamoDB client = createClient(); + + server.enqueue(HttpResponse.of(HttpStatus.OK, MediaType.PLAIN_TEXT_UTF_8, "{}")); + + List additionalAttributes = + new ArrayList<>( + asList( + equalTo( + maybeStable(DB_SYSTEM), emitStableDatabaseSemconv() ? AWS_DYNAMODB : DYNAMODB), + equalTo( + maybeStable(DB_OPERATION), + emitStableDatabaseSemconv() ? "WriteItem" : "BatchWriteItem"), + equalTo(DB_COLLECTION_NAME, emitStableDatabaseSemconv() ? "sometable" : null))); + + Object response = + client.batchWriteItem( + new BatchWriteItemRequest() + .withRequestItems(singletonMap("sometable", singletonList(writeRequest("value"))))); + assertRequestWithMockedResponse( + response, client, "DynamoDBv2", "BatchWriteItem", "POST", additionalAttributes); + + assertDurationMetric( + testing(), + "io.opentelemetry.aws-sdk-1.11", + DB_SYSTEM_NAME, + DB_OPERATION_NAME, + DB_COLLECTION_NAME, + SERVER_ADDRESS, + SERVER_PORT); + } + + private static WriteRequest writeRequest(String value) { + return new WriteRequest() + .withPutRequest( + new PutRequest().withItem(singletonMap("key", new AttributeValue().withS(value)))); + } + private AmazonDynamoDB createClient() { AmazonDynamoDBClientBuilder clientBuilder = AmazonDynamoDBClientBuilder.standard(); return configureClient(clientBuilder) diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/internal/DynamoDbAttributesExtractor.java b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/internal/DynamoDbAttributesExtractor.java index a81b5bc17fdf..5e80b7b96062 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/internal/DynamoDbAttributesExtractor.java +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/internal/DynamoDbAttributesExtractor.java @@ -8,6 +8,7 @@ import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitOldDatabaseSemconv; import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitStableDatabaseSemconv; import static io.opentelemetry.semconv.DbAttributes.DB_COLLECTION_NAME; +import static io.opentelemetry.semconv.DbAttributes.DB_OPERATION_BATCH_SIZE; import static io.opentelemetry.semconv.DbAttributes.DB_OPERATION_NAME; import static io.opentelemetry.semconv.DbAttributes.DB_SYSTEM_NAME; @@ -15,6 +16,7 @@ import io.opentelemetry.api.common.AttributesBuilder; import io.opentelemetry.context.Context; import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; +import java.util.Collection; import java.util.Map; import java.util.Optional; import javax.annotation.Nullable; @@ -33,6 +35,8 @@ class DynamoDbAttributesExtractor implements AttributesExtractor requestItems = request.getValueForField("RequestItems", Object.class); + if (!requestItems.isPresent() || !(requestItems.get() instanceof Map)) { + return null; + } + + Map requestItemsMap = (Map) requestItems.get(); + return "BatchGetItem".equals(operation) + ? countBatchGetItems(requestItemsMap) + : countBatchWriteItems(requestItemsMap); + } + + private long countBatchGetItems(Map requestItems) { + long count = 0; + for (Object keysAndAttributes : requestItems.values()) { + Object keys = next(keysAndAttributes, "Keys"); + if (keys instanceof Collection) { + count += ((Collection) keys).size(); + } + } + return count; + } + + private static long countBatchWriteItems(Map requestItems) { + long count = 0; + for (Object writeRequests : requestItems.values()) { + if (writeRequests instanceof Collection) { + count += ((Collection) writeRequests).size(); + } + } + return count; + } + + private static boolean isBatch(@Nullable Long batchSize) { + return batchSize != null && batchSize > 1; + } + + @Nullable + private Object next(Object current, String fieldName) { + try { + return methodHandleFactory.forField(current.getClass(), fieldName).invoke(current); + } catch (Throwable ignored) { + return null; + } + } + @Nullable private static String extractTableName(ExecutionAttributes executionAttributes) { SdkRequest request = diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2ClientCoreTest.java b/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2ClientCoreTest.java index 92f693c87ef3..f17fabef54ca 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2ClientCoreTest.java +++ b/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2ClientCoreTest.java @@ -13,6 +13,7 @@ import static io.opentelemetry.instrumentation.testing.junit.db.SemconvStabilityUtil.maybeStableDbSystemName; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; import static io.opentelemetry.semconv.DbAttributes.DB_COLLECTION_NAME; +import static io.opentelemetry.semconv.DbAttributes.DB_OPERATION_BATCH_SIZE; import static io.opentelemetry.semconv.DbAttributes.DB_OPERATION_NAME; import static io.opentelemetry.semconv.DbAttributes.DB_SYSTEM_NAME; import static io.opentelemetry.semconv.HttpAttributes.HTTP_REQUEST_METHOD; @@ -293,6 +294,16 @@ private static void assertListTablesRequest(SpanDataAssert span) { @SuppressWarnings("deprecation") // uses deprecated semconv private static void assertDynamoDbRequest( SpanDataAssert span, String operation, List extraAttributes) { + assertDynamoDbRequest( + span, operation, extraAttributes, expectedDbOperationNameForSingleItemRequest(operation)); + } + + @SuppressWarnings("deprecation") // uses deprecated semconv + private static void assertDynamoDbRequest( + SpanDataAssert span, + String operation, + List extraAttributes, + String expectedStableOperationName) { List assertions = new ArrayList<>( asList( @@ -308,7 +319,9 @@ private static void assertDynamoDbRequest( equalTo(AWS_REQUEST_ID, "UNKNOWN"), equalTo(AWS_DYNAMODB_TABLE_NAMES, singletonList("sometable")), equalTo(maybeStable(DB_SYSTEM), maybeStableDbSystemName(DYNAMODB)), - equalTo(maybeStable(DB_OPERATION), expectedDbOperationName(operation)))); + equalTo( + maybeStable(DB_OPERATION), + emitStableDatabaseSemconv() ? expectedStableOperationName : operation))); if (emitStableDatabaseSemconv()) { assertions.add(equalTo(DB_COLLECTION_NAME, "sometable")); } @@ -553,15 +566,139 @@ void testBatchGetItemWithMultipleTablesOmitsDbCollectionName() { .doesNotContainKey(DB_COLLECTION_NAME)))); } - private static String expectedDbOperationName(String operation) { + @Test + @SuppressWarnings("deprecation") // uses deprecated semconv + void testBatchGetItemWithMultipleItemsUsesStableBatchAttributes() { + DynamoDbClientBuilder builder = DynamoDbClient.builder(); + configureSdkClient(builder); + DynamoDbClient client = + builder + .endpointOverride(server.httpUri()) + .region(Region.AP_NORTHEAST_1) + .credentialsProvider(CREDENTIALS_PROVIDER) + .build(); + server.enqueue( + HttpResponse.of( + HttpStatus.OK, MediaType.PLAIN_TEXT_UTF_8, getResponseContent("BatchGetItem"))); + + client.batchGetItem( + b -> + b.requestItems( + ImmutableMap.of( + "sometable", + KeysAndAttributes.builder() + .keys( + asList( + ImmutableMap.of("key", AttributeValue.builder().s("value").build()), + ImmutableMap.of( + "key", AttributeValue.builder().s("anotherValue").build()))) + .build()))); + + getTesting() + .waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> + assertDynamoDbRequest( + span, + "BatchGetItem", + asList( + equalTo( + AWS_DYNAMODB_CONSUMED_CAPACITY, + singletonList( + "{\"TableName\":\"sometable\",\"CapacityUnits\":1.0}")), + equalTo( + DB_OPERATION_BATCH_SIZE, + emitStableDatabaseSemconv() ? Long.valueOf(2) : null)), + "BATCH GetItem"))); + + assertDurationMetric( + getTesting(), + "io.opentelemetry.aws-sdk-2.2", + DB_SYSTEM_NAME, + DB_OPERATION_NAME, + DB_COLLECTION_NAME); + } + + @Test + @SuppressWarnings("deprecation") // uses deprecated semconv + void testBatchWriteItemWithMultipleItemsUsesStableBatchAttributes() { + DynamoDbClientBuilder builder = DynamoDbClient.builder(); + configureSdkClient(builder); + DynamoDbClient client = + builder + .endpointOverride(server.httpUri()) + .region(Region.AP_NORTHEAST_1) + .credentialsProvider(CREDENTIALS_PROVIDER) + .build(); + server.enqueue( + HttpResponse.of( + HttpStatus.OK, MediaType.PLAIN_TEXT_UTF_8, getResponseContent("BatchWriteItem"))); + + client.batchWriteItem( + b -> + b.requestItems( + ImmutableMap.of( + "sometable", + asList( + WriteRequest.builder() + .putRequest( + PutRequest.builder() + .item( + ImmutableMap.of( + "key", AttributeValue.builder().s("value").build())) + .build()) + .build(), + WriteRequest.builder() + .putRequest( + PutRequest.builder() + .item( + ImmutableMap.of( + "key", + AttributeValue.builder().s("anotherValue").build())) + .build()) + .build())))); + + getTesting() + .waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> + assertDynamoDbRequest( + span, + "BatchWriteItem", + asList( + equalTo( + AWS_DYNAMODB_CONSUMED_CAPACITY, + singletonList( + "{\"TableName\":\"sometable\",\"CapacityUnits\":1.0}")), + equalTo( + AWS_DYNAMODB_ITEM_COLLECTION_METRICS, + "[somekey1:[{\"ItemCollectionKey\":{\"somekey2\":{}}}]]"), + equalTo( + DB_OPERATION_BATCH_SIZE, + emitStableDatabaseSemconv() ? Long.valueOf(2) : null)), + "BATCH WriteItem"))); + + assertDurationMetric( + getTesting(), + "io.opentelemetry.aws-sdk-2.2", + DB_SYSTEM_NAME, + DB_OPERATION_NAME, + DB_COLLECTION_NAME); + } + + private static String expectedDbOperationNameForSingleItemRequest(String operation) { if (!emitStableDatabaseSemconv()) { return operation; } + // The parameterized Batch* requests contain one item. Stable DB semconv treats those as + // logical item operations; dedicated multi-item tests pass the BATCH operation name directly. switch (operation) { case "BatchGetItem": - return "BATCH GetItem"; + return "GetItem"; case "BatchWriteItem": - return "BATCH WriteItem"; + return "WriteItem"; default: return operation; } diff --git a/instrumentation/influxdb-2.4/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/influxdb/v2_4/InfluxDbAttributesGetter.java b/instrumentation/influxdb-2.4/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/influxdb/v2_4/InfluxDbAttributesGetter.java index 07f6606ffe6f..4236ffe08adc 100644 --- a/instrumentation/influxdb-2.4/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/influxdb/v2_4/InfluxDbAttributesGetter.java +++ b/instrumentation/influxdb-2.4/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/influxdb/v2_4/InfluxDbAttributesGetter.java @@ -5,58 +5,49 @@ package io.opentelemetry.javaagent.instrumentation.influxdb.v2_4; -import static io.opentelemetry.instrumentation.api.incubator.semconv.db.SqlDialect.DOUBLE_QUOTES_ARE_IDENTIFIERS; -import static java.util.Collections.emptyList; -import static java.util.Collections.singletonList; - -import io.opentelemetry.instrumentation.api.incubator.semconv.db.SqlClientAttributesGetter; -import io.opentelemetry.instrumentation.api.incubator.semconv.db.SqlDialect; -import java.util.Collection; +import io.opentelemetry.instrumentation.api.incubator.semconv.db.DbClientAttributesGetter; import javax.annotation.Nullable; -final class InfluxDbAttributesGetter implements SqlClientAttributesGetter { +final class InfluxDbAttributesGetter implements DbClientAttributesGetter { + @Nullable @Override - public Collection getRawQueryTexts(InfluxDbRequest request) { - String sql = request.getSql(); - if (sql == null) { - return emptyList(); - } - return singletonList(sql); + public String getDbOperationName(InfluxDbOperation request) { + return request.getOperation(); } + @Nullable @Override - public SqlDialect getSqlDialect(InfluxDbRequest request) { - // "String literals must be surrounded by single quotes." - // https://docs.influxdata.com/influxdb/v2/reference/syntax/influxql/spec/#strings - return DOUBLE_QUOTES_ARE_IDENTIFIERS; + @SuppressWarnings("deprecation") // old database semconv still use db.operation + public String getDbOperation(InfluxDbOperation request) { + String operation = request.getOperation(); + return "write".equals(operation) ? "WRITE" : operation; } - @Nullable @Override - public String getDbOperationName(InfluxDbRequest request) { - return request.getOperationName(); + public String getDbSystemName(InfluxDbOperation request) { + return "influxdb"; } + @Nullable @Override - public String getDbSystemName(InfluxDbRequest request) { - return "influxdb"; + public String getDbNamespace(InfluxDbOperation request) { + return request.getNamespace(); } @Nullable @Override - public String getDbNamespace(InfluxDbRequest request) { - String namespace = request.getNamespace(); - return "".equals(namespace) ? null : namespace; + public String getDbQueryText(InfluxDbOperation request) { + return null; } @Override - public String getServerAddress(InfluxDbRequest request) { + public String getServerAddress(InfluxDbOperation request) { return request.getHost(); } @Override - public Integer getServerPort(InfluxDbRequest request) { + public Integer getServerPort(InfluxDbOperation request) { return request.getPort(); } } diff --git a/instrumentation/influxdb-2.4/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/influxdb/v2_4/InfluxDbImplInstrumentation.java b/instrumentation/influxdb-2.4/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/influxdb/v2_4/InfluxDbImplInstrumentation.java index c2283901a673..f9f83c54fa9e 100644 --- a/instrumentation/influxdb-2.4/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/influxdb/v2_4/InfluxDbImplInstrumentation.java +++ b/instrumentation/influxdb-2.4/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/influxdb/v2_4/InfluxDbImplInstrumentation.java @@ -6,7 +6,8 @@ package io.opentelemetry.javaagent.instrumentation.influxdb.v2_4; import static io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge.currentContext; -import static io.opentelemetry.javaagent.instrumentation.influxdb.v2_4.InfluxDbSingletons.instrumenter; +import static io.opentelemetry.javaagent.instrumentation.influxdb.v2_4.InfluxDbSingletons.queryInstrumenter; +import static io.opentelemetry.javaagent.instrumentation.influxdb.v2_4.InfluxDbSingletons.requestInstrumenter; import static net.bytebuddy.matcher.ElementMatchers.isEnum; import static net.bytebuddy.matcher.ElementMatchers.named; import static net.bytebuddy.matcher.ElementMatchers.namedOneOf; @@ -84,11 +85,11 @@ public static Object[] onEnter( Context parentContext = currentContext(); HttpUrl httpUrl = retrofit.baseUrl(); - InfluxDbRequest influxDbRequest = - InfluxDbRequest.create( - httpUrl.host(), httpUrl.port(), query.getDatabase(), null, query.getCommand()); + InfluxDbQuery influxDbQuery = + InfluxDbQuery.create( + httpUrl.host(), httpUrl.port(), query.getDatabase(), query.getCommand()); - if (!instrumenter().shouldStart(parentContext, influxDbRequest)) { + if (!queryInstrumenter().shouldStart(parentContext, influxDbQuery)) { return null; } @@ -98,7 +99,9 @@ public static Object[] onEnter( newArguments[i] = InfluxDbObjectWrapper.wrap(arguments[i], parentContext); } - return new Object[] {newArguments, InfluxDbScope.start(parentContext, influxDbRequest)}; + return new Object[] { + newArguments, InfluxDbScope.start(queryInstrumenter(), parentContext, influxDbQuery) + }; } @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class, inline = false) @@ -109,7 +112,7 @@ public static void onExit( return; } - ((InfluxDbScope) enterArgs[1]).end(throwable); + ((InfluxDbScope) enterArgs[1]).end(throwable); } } @@ -117,7 +120,7 @@ public static void onExit( public static class InfluxDbModifyAdvice { @Advice.OnMethodEnter(suppress = Throwable.class, inline = false) - public static InfluxDbScope onEnter( + public static InfluxDbScope onEnter( @Advice.Origin("#m") String methodName, @Advice.Argument(0) Object arg0, @Advice.FieldValue(value = "retrofit") Retrofit retrofit) { @@ -137,30 +140,33 @@ public static InfluxDbScope onEnter( (arg0 instanceof BatchPoints) ? ((BatchPoints) arg0).getDatabase() // write data by UDP protocol, in this way, can't get database name. - : arg0 instanceof Integer ? "" : String.valueOf(arg0); + : arg0 instanceof Integer ? null : String.valueOf(arg0); String operationName; if ("createDatabase".equals(methodName)) { + // createDatabase emits a CREATE DATABASE query. operationName = "CREATE DATABASE"; } else if ("deleteDatabase".equals(methodName)) { + // deleteDatabase emits a DROP DATABASE query. operationName = "DROP DATABASE"; } else { - operationName = "WRITE"; + operationName = methodName; } - InfluxDbRequest influxDbRequest = - InfluxDbRequest.create(httpUrl.host(), httpUrl.port(), database, operationName, null); + InfluxDbOperation influxDbOperation = + InfluxDbOperation.create(httpUrl.host(), httpUrl.port(), database, operationName); - if (!instrumenter().shouldStart(parentContext, influxDbRequest)) { + if (!requestInstrumenter().shouldStart(parentContext, influxDbOperation)) { return null; } - return InfluxDbScope.start(parentContext, influxDbRequest); + return InfluxDbScope.start(requestInstrumenter(), parentContext, influxDbOperation); } @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class, inline = false) public static void onExit( - @Advice.Thrown @Nullable Throwable throwable, @Advice.Enter @Nullable InfluxDbScope scope) { + @Advice.Thrown @Nullable Throwable throwable, + @Advice.Enter @Nullable InfluxDbScope scope) { CallDepth callDepth = CallDepth.forClass(InfluxDBImpl.class); if (callDepth.decrementAndGet() > 0 || scope == null) { return; diff --git a/instrumentation/influxdb-2.4/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/influxdb/v2_4/InfluxDbRequest.java b/instrumentation/influxdb-2.4/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/influxdb/v2_4/InfluxDbOperation.java similarity index 50% rename from instrumentation/influxdb-2.4/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/influxdb/v2_4/InfluxDbRequest.java rename to instrumentation/influxdb-2.4/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/influxdb/v2_4/InfluxDbOperation.java index c2a231340018..9a989c247aa5 100644 --- a/instrumentation/influxdb-2.4/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/influxdb/v2_4/InfluxDbRequest.java +++ b/instrumentation/influxdb-2.4/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/influxdb/v2_4/InfluxDbOperation.java @@ -9,26 +9,20 @@ import javax.annotation.Nullable; @AutoValue -public abstract class InfluxDbRequest { - - public static InfluxDbRequest create( - String host, - int port, - String namespace, - @Nullable String operationName, - @Nullable String sql) { - return new AutoValue_InfluxDbRequest(host, port, namespace, operationName, sql); +public abstract class InfluxDbOperation { + + public static InfluxDbOperation create( + String host, int port, @Nullable String namespace, @Nullable String operation) { + return new AutoValue_InfluxDbOperation(host, port, namespace, operation); } public abstract String getHost(); public abstract int getPort(); - public abstract String getNamespace(); - @Nullable - public abstract String getOperationName(); + public abstract String getNamespace(); @Nullable - public abstract String getSql(); + public abstract String getOperation(); } diff --git a/instrumentation/influxdb-2.4/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/influxdb/v2_4/InfluxDbQuery.java b/instrumentation/influxdb-2.4/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/influxdb/v2_4/InfluxDbQuery.java new file mode 100644 index 000000000000..72fffd57cd5a --- /dev/null +++ b/instrumentation/influxdb-2.4/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/influxdb/v2_4/InfluxDbQuery.java @@ -0,0 +1,28 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.influxdb.v2_4; + +import com.google.auto.value.AutoValue; +import javax.annotation.Nullable; + +@AutoValue +public abstract class InfluxDbQuery { + + public static InfluxDbQuery create( + String host, int port, @Nullable String namespace, @Nullable String query) { + return new AutoValue_InfluxDbQuery(host, port, namespace, query); + } + + public abstract String getHost(); + + public abstract int getPort(); + + @Nullable + public abstract String getNamespace(); + + @Nullable + public abstract String getQuery(); +} diff --git a/instrumentation/influxdb-2.4/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/influxdb/v2_4/InfluxDbQueryAttributesGetter.java b/instrumentation/influxdb-2.4/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/influxdb/v2_4/InfluxDbQueryAttributesGetter.java new file mode 100644 index 000000000000..102e3c9deb46 --- /dev/null +++ b/instrumentation/influxdb-2.4/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/influxdb/v2_4/InfluxDbQueryAttributesGetter.java @@ -0,0 +1,56 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.influxdb.v2_4; + +import static io.opentelemetry.instrumentation.api.incubator.semconv.db.SqlDialect.DOUBLE_QUOTES_ARE_IDENTIFIERS; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; + +import io.opentelemetry.instrumentation.api.incubator.semconv.db.SqlClientAttributesGetter; +import io.opentelemetry.instrumentation.api.incubator.semconv.db.SqlDialect; +import java.util.Collection; +import javax.annotation.Nullable; + +final class InfluxDbQueryAttributesGetter + implements SqlClientAttributesGetter { + + @Override + public Collection getRawQueryTexts(InfluxDbQuery request) { + String query = request.getQuery(); + if (query == null) { + return emptyList(); + } + return singletonList(query); + } + + @Override + public SqlDialect getSqlDialect(InfluxDbQuery request) { + // "String literals must be surrounded by single quotes." + // https://docs.influxdata.com/influxdb/v2/reference/syntax/influxql/spec/#strings + return DOUBLE_QUOTES_ARE_IDENTIFIERS; + } + + @Override + public String getDbSystemName(InfluxDbQuery request) { + return "influxdb"; + } + + @Nullable + @Override + public String getDbNamespace(InfluxDbQuery request) { + return request.getNamespace(); + } + + @Override + public String getServerAddress(InfluxDbQuery request) { + return request.getHost(); + } + + @Override + public Integer getServerPort(InfluxDbQuery request) { + return request.getPort(); + } +} diff --git a/instrumentation/influxdb-2.4/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/influxdb/v2_4/InfluxDbScope.java b/instrumentation/influxdb-2.4/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/influxdb/v2_4/InfluxDbScope.java index 4aed1fd4401e..a89e46605262 100644 --- a/instrumentation/influxdb-2.4/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/influxdb/v2_4/InfluxDbScope.java +++ b/instrumentation/influxdb-2.4/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/influxdb/v2_4/InfluxDbScope.java @@ -5,32 +5,35 @@ package io.opentelemetry.javaagent.instrumentation.influxdb.v2_4; -import static io.opentelemetry.javaagent.instrumentation.influxdb.v2_4.InfluxDbSingletons.instrumenter; - import io.opentelemetry.context.Context; import io.opentelemetry.context.Scope; +import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; import javax.annotation.Nullable; /** Container used to carry state between enter and exit advices */ -public class InfluxDbScope { - private final InfluxDbRequest influxDbRequest; +public class InfluxDbScope { + private final Instrumenter instrumenter; + private final REQUEST request; private final Context context; private final Scope scope; - private InfluxDbScope(InfluxDbRequest influxDbRequest, Context context, Scope scope) { - this.influxDbRequest = influxDbRequest; + private InfluxDbScope( + Instrumenter instrumenter, REQUEST request, Context context, Scope scope) { + this.instrumenter = instrumenter; + this.request = request; this.context = context; this.scope = scope; } - public static InfluxDbScope start(Context parentContext, InfluxDbRequest influxDbRequest) { - Context context = instrumenter().start(parentContext, influxDbRequest); - return new InfluxDbScope(influxDbRequest, context, context.makeCurrent()); + public static InfluxDbScope start( + Instrumenter instrumenter, Context parentContext, REQUEST request) { + Context context = instrumenter.start(parentContext, request); + return new InfluxDbScope<>(instrumenter, request, context, context.makeCurrent()); } public void end(@Nullable Throwable throwable) { scope.close(); - instrumenter().end(context, influxDbRequest, null, throwable); + instrumenter.end(context, request, null, throwable); } } diff --git a/instrumentation/influxdb-2.4/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/influxdb/v2_4/InfluxDbSingletons.java b/instrumentation/influxdb-2.4/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/influxdb/v2_4/InfluxDbSingletons.java index 0110c8f0eb6a..482fc078b6b7 100644 --- a/instrumentation/influxdb-2.4/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/influxdb/v2_4/InfluxDbSingletons.java +++ b/instrumentation/influxdb-2.4/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/influxdb/v2_4/InfluxDbSingletons.java @@ -8,6 +8,7 @@ import static io.opentelemetry.instrumentation.api.incubator.semconv.db.internal.DbExceptionEventExtractors.setDbClientExceptionEventExtractor; import io.opentelemetry.api.GlobalOpenTelemetry; +import io.opentelemetry.instrumentation.api.incubator.semconv.db.DbClientAttributesExtractor; import io.opentelemetry.instrumentation.api.incubator.semconv.db.DbClientMetrics; import io.opentelemetry.instrumentation.api.incubator.semconv.db.DbClientSpanNameExtractor; import io.opentelemetry.instrumentation.api.incubator.semconv.db.SqlClientAttributesExtractor; @@ -18,28 +19,48 @@ @SuppressWarnings("deprecation") // to support old semconv public class InfluxDbSingletons { - private static final Instrumenter instrumenter; + private static final String INSTRUMENTATION_NAME = "io.opentelemetry.influxdb-2.4"; + + private static final Instrumenter queryInstrumenter; + private static final Instrumenter requestInstrumenter; static { - InfluxDbAttributesGetter dbAttributesGetter = new InfluxDbAttributesGetter(); + InfluxDbQueryAttributesGetter queryAttributesGetter = new InfluxDbQueryAttributesGetter(); - InstrumenterBuilder builder = - Instrumenter.builder( + InstrumenterBuilder queryBuilder = + Instrumenter.builder( GlobalOpenTelemetry.get(), - "io.opentelemetry.influxdb-2.4", - DbClientSpanNameExtractor.createWithGenericOldSpanName(dbAttributesGetter)) + INSTRUMENTATION_NAME, + DbClientSpanNameExtractor.createWithGenericOldSpanName(queryAttributesGetter)) .addAttributesExtractor( - SqlClientAttributesExtractor.builder(dbAttributesGetter) + SqlClientAttributesExtractor.builder(queryAttributesGetter) .setTableAttribute(null) .build()) .addOperationMetrics(DbClientMetrics.get()); - setDbClientExceptionEventExtractor(builder); + setDbClientExceptionEventExtractor(queryBuilder); + + queryInstrumenter = queryBuilder.buildInstrumenter(SpanKindExtractor.alwaysClient()); + + InfluxDbAttributesGetter dbAttributesGetter = new InfluxDbAttributesGetter(); + + InstrumenterBuilder modifyBuilder = + Instrumenter.builder( + GlobalOpenTelemetry.get(), + INSTRUMENTATION_NAME, + DbClientSpanNameExtractor.create(dbAttributesGetter)) + .addAttributesExtractor(DbClientAttributesExtractor.create(dbAttributesGetter)) + .addOperationMetrics(DbClientMetrics.get()); + setDbClientExceptionEventExtractor(modifyBuilder); + + requestInstrumenter = modifyBuilder.buildInstrumenter(SpanKindExtractor.alwaysClient()); + } - instrumenter = builder.buildInstrumenter(SpanKindExtractor.alwaysClient()); + public static Instrumenter queryInstrumenter() { + return queryInstrumenter; } - public static Instrumenter instrumenter() { - return instrumenter; + public static Instrumenter requestInstrumenter() { + return requestInstrumenter; } private InfluxDbSingletons() {} diff --git a/instrumentation/influxdb-2.4/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/influxdb/v2_4/InfluxDbClientTest.java b/instrumentation/influxdb-2.4/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/influxdb/v2_4/InfluxDbClientTest.java index a990c18b613e..bec54faddc6f 100644 --- a/instrumentation/influxdb-2.4/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/influxdb/v2_4/InfluxDbClientTest.java +++ b/instrumentation/influxdb-2.4/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/influxdb/v2_4/InfluxDbClientTest.java @@ -123,14 +123,17 @@ void testQueryAndModifyWithOneArgument() { trace -> trace.hasSpansSatisfyingExactly( span -> - span.hasName("WRITE " + dbName) + span.hasName( + emitStableDatabaseSemconv() ? "write " + dbName : "WRITE " + dbName) .hasKind(SpanKind.CLIENT) .hasAttributesSatisfyingExactly( equalTo(maybeStable(DB_SYSTEM), INFLUXDB), equalTo(maybeStable(DB_NAME), dbName), equalTo(SERVER_ADDRESS, host), equalTo(SERVER_PORT, port), - equalTo(maybeStable(DB_OPERATION), "WRITE"))), + equalTo( + maybeStable(DB_OPERATION), + emitStableDatabaseSemconv() ? "write" : "WRITE"))), trace -> trace.hasSpansSatisfyingExactly( span -> @@ -370,14 +373,19 @@ void testWriteWithFourArguments() { trace -> trace.hasSpansSatisfyingExactly( span -> - span.hasName("WRITE " + DATABASE_NAME) + span.hasName( + emitStableDatabaseSemconv() + ? "write " + DATABASE_NAME + : "WRITE " + DATABASE_NAME) .hasKind(SpanKind.CLIENT) .hasAttributesSatisfyingExactly( equalTo(maybeStable(DB_SYSTEM), INFLUXDB), equalTo(maybeStable(DB_NAME), DATABASE_NAME), equalTo(SERVER_ADDRESS, host), equalTo(SERVER_PORT, port), - equalTo(maybeStable(DB_OPERATION), "WRITE")))); + equalTo( + maybeStable(DB_OPERATION), + emitStableDatabaseSemconv() ? "write" : "WRITE")))); } @Test @@ -391,14 +399,19 @@ void testWriteWithFiveArguments() { trace -> trace.hasSpansSatisfyingExactly( span -> - span.hasName("WRITE " + DATABASE_NAME) + span.hasName( + emitStableDatabaseSemconv() + ? "write " + DATABASE_NAME + : "WRITE " + DATABASE_NAME) .hasKind(SpanKind.CLIENT) .hasAttributesSatisfyingExactly( equalTo(maybeStable(DB_SYSTEM), INFLUXDB), equalTo(maybeStable(DB_NAME), DATABASE_NAME), equalTo(SERVER_ADDRESS, host), equalTo(SERVER_PORT, port), - equalTo(maybeStable(DB_OPERATION), "WRITE")))); + equalTo( + maybeStable(DB_OPERATION), + emitStableDatabaseSemconv() ? "write" : "WRITE")))); } @Test @@ -415,13 +428,15 @@ void testWriteWithUdp() { trace.hasSpansSatisfyingExactly( span -> span.hasName( - emitStableDatabaseSemconv() ? "WRITE " + host + ":" + port : "WRITE") + emitStableDatabaseSemconv() ? "write " + host + ":" + port : "WRITE") .hasKind(SpanKind.CLIENT) .hasAttributesSatisfyingExactly( equalTo(maybeStable(DB_SYSTEM), INFLUXDB), equalTo(maybeStable(DB_NAME), null), equalTo(SERVER_ADDRESS, host), equalTo(SERVER_PORT, port), - equalTo(maybeStable(DB_OPERATION), "WRITE")))); + equalTo( + maybeStable(DB_OPERATION), + emitStableDatabaseSemconv() ? "write" : "WRITE")))); } } diff --git a/instrumentation/influxdb-2.4/javaagent/src/test24/java/io/opentelemetry/javaagent/instrumentation/influxdb/v2_4/InfluxDbClient24Test.java b/instrumentation/influxdb-2.4/javaagent/src/test24/java/io/opentelemetry/javaagent/instrumentation/influxdb/v2_4/InfluxDbClient24Test.java index 0322dcacfada..ea81c8caa761 100644 --- a/instrumentation/influxdb-2.4/javaagent/src/test24/java/io/opentelemetry/javaagent/instrumentation/influxdb/v2_4/InfluxDbClient24Test.java +++ b/instrumentation/influxdb-2.4/javaagent/src/test24/java/io/opentelemetry/javaagent/instrumentation/influxdb/v2_4/InfluxDbClient24Test.java @@ -113,14 +113,17 @@ void testQueryAndModifyWithOneArgument() { trace -> trace.hasSpansSatisfyingExactly( span -> - span.hasName("WRITE " + dbName) + span.hasName( + emitStableDatabaseSemconv() ? "write " + dbName : "WRITE " + dbName) .hasKind(SpanKind.CLIENT) .hasAttributesSatisfyingExactly( equalTo(maybeStable(DB_SYSTEM), INFLUXDB), equalTo(maybeStable(DB_NAME), dbName), equalTo(SERVER_ADDRESS, host), equalTo(SERVER_PORT, port), - equalTo(maybeStable(DB_OPERATION), "WRITE"))), + equalTo( + maybeStable(DB_OPERATION), + emitStableDatabaseSemconv() ? "write" : "WRITE"))), trace -> trace.hasSpansSatisfyingExactly( span -> diff --git a/instrumentation/jedis/jedis-1.4/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jedis/v1_4/JedisInstrumentation.java b/instrumentation/jedis/jedis-1.4/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jedis/v1_4/JedisInstrumentation.java index 1992c7d75d86..062e239c0883 100644 --- a/instrumentation/jedis/jedis-1.4/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jedis/v1_4/JedisInstrumentation.java +++ b/instrumentation/jedis/jedis-1.4/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jedis/v1_4/JedisInstrumentation.java @@ -5,6 +5,7 @@ package io.opentelemetry.javaagent.instrumentation.jedis.v1_4; +import static net.bytebuddy.matcher.ElementMatchers.isMethod; import static net.bytebuddy.matcher.ElementMatchers.isPublic; import static net.bytebuddy.matcher.ElementMatchers.isStatic; import static net.bytebuddy.matcher.ElementMatchers.namedOneOf; @@ -28,6 +29,7 @@ public ElementMatcher typeMatcher() { public void transform(TypeTransformer transformer) { transformer.applyAdviceToMethod( isPublic() + .and(isMethod()) .and(not(isStatic())) .and( not( diff --git a/instrumentation/jedis/jedis-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jedis/v3_0/JedisInstrumentation.java b/instrumentation/jedis/jedis-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jedis/v3_0/JedisInstrumentation.java index 3dc93a870f92..0d187de055b3 100644 --- a/instrumentation/jedis/jedis-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jedis/v3_0/JedisInstrumentation.java +++ b/instrumentation/jedis/jedis-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jedis/v3_0/JedisInstrumentation.java @@ -5,6 +5,7 @@ package io.opentelemetry.javaagent.instrumentation.jedis.v3_0; +import static net.bytebuddy.matcher.ElementMatchers.isMethod; import static net.bytebuddy.matcher.ElementMatchers.isPublic; import static net.bytebuddy.matcher.ElementMatchers.isStatic; import static net.bytebuddy.matcher.ElementMatchers.namedOneOf; @@ -28,6 +29,7 @@ public ElementMatcher typeMatcher() { public void transform(TypeTransformer transformer) { transformer.applyAdviceToMethod( isPublic() + .and(isMethod()) .and(not(isStatic())) .and( not( diff --git a/instrumentation/jedis/jedis-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jedis/v4_0/JedisInstrumentation.java b/instrumentation/jedis/jedis-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jedis/v4_0/JedisInstrumentation.java index a08d6c0c00c3..5a3b61c630c3 100644 --- a/instrumentation/jedis/jedis-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jedis/v4_0/JedisInstrumentation.java +++ b/instrumentation/jedis/jedis-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jedis/v4_0/JedisInstrumentation.java @@ -5,6 +5,7 @@ package io.opentelemetry.javaagent.instrumentation.jedis.v4_0; +import static net.bytebuddy.matcher.ElementMatchers.isMethod; import static net.bytebuddy.matcher.ElementMatchers.isPublic; import static net.bytebuddy.matcher.ElementMatchers.isStatic; import static net.bytebuddy.matcher.ElementMatchers.namedOneOf; @@ -28,6 +29,7 @@ public ElementMatcher typeMatcher() { public void transform(TypeTransformer transformer) { transformer.applyAdviceToMethod( isPublic() + .and(isMethod()) .and(not(isStatic())) .and( not( diff --git a/instrumentation/jedis/jedis-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jedis/v4_0/JedisSingletons.java b/instrumentation/jedis/jedis-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jedis/v4_0/JedisSingletons.java index 9293327e1014..0baa8a012fc9 100644 --- a/instrumentation/jedis/jedis-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jedis/v4_0/JedisSingletons.java +++ b/instrumentation/jedis/jedis-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jedis/v4_0/JedisSingletons.java @@ -28,6 +28,8 @@ public class JedisSingletons { static { JedisDbAttributesGetter dbAttributesGetter = new JedisDbAttributesGetter(); + // Redis semantic conventions don't follow the regular pattern of adding db.namespace to the + // span name. JedisDbAttributesGetter spanNameAttributesGetter = new JedisDbAttributesGetter() { @Override diff --git a/instrumentation/kafka/kafka-connect-2.6/testing/src/test/java/io/opentelemetry/instrumentation/kafkaconnect/v2_6/MongoKafkaConnectSinkTaskTest.java b/instrumentation/kafka/kafka-connect-2.6/testing/src/test/java/io/opentelemetry/instrumentation/kafkaconnect/v2_6/MongoKafkaConnectSinkTaskTest.java index 986e65ead1bc..b24171389ce3 100644 --- a/instrumentation/kafka/kafka-connect-2.6/testing/src/test/java/io/opentelemetry/instrumentation/kafkaconnect/v2_6/MongoKafkaConnectSinkTaskTest.java +++ b/instrumentation/kafka/kafka-connect-2.6/testing/src/test/java/io/opentelemetry/instrumentation/kafkaconnect/v2_6/MongoKafkaConnectSinkTaskTest.java @@ -6,6 +6,7 @@ package io.opentelemetry.instrumentation.kafkaconnect.v2_6; import static io.opentelemetry.api.trace.SpanKind.CONSUMER; +import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitStableDatabaseSemconv; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.satisfies; import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_BATCH_MESSAGE_COUNT; @@ -157,7 +158,10 @@ void testSingleMessage() throws IOException { satisfies(THREAD_ID, val -> val.isNotZero()), satisfies(THREAD_NAME, val -> val.isNotBlank())), span -> - span.hasName("update " + DATABASE_NAME + "." + COLLECTION_NAME) + span.hasName( + emitStableDatabaseSemconv() + ? "update " + COLLECTION_NAME + : "update " + DATABASE_NAME + "." + COLLECTION_NAME) .hasKind(SpanKind.CLIENT) .hasParent(trace.getSpan(0))), trace -> @@ -268,15 +272,24 @@ void testMultiTopic() throws IOException { satisfies(THREAD_ID, val -> val.isNotZero()), satisfies(THREAD_NAME, val -> val.isNotBlank())), span -> - span.hasName("update " + DATABASE_NAME + "." + COLLECTION_NAME) + span.hasName( + emitStableDatabaseSemconv() + ? "update " + COLLECTION_NAME + : "update " + DATABASE_NAME + "." + COLLECTION_NAME) .hasKind(SpanKind.CLIENT) .hasParent(trace.getSpan(0)), span -> - span.hasName("update " + DATABASE_NAME + "." + COLLECTION_NAME) + span.hasName( + emitStableDatabaseSemconv() + ? "update " + COLLECTION_NAME + : "update " + DATABASE_NAME + "." + COLLECTION_NAME) .hasKind(SpanKind.CLIENT) .hasParent(trace.getSpan(0)), span -> - span.hasName("update " + DATABASE_NAME + "." + COLLECTION_NAME) + span.hasName( + emitStableDatabaseSemconv() + ? "update " + COLLECTION_NAME + : "update " + DATABASE_NAME + "." + COLLECTION_NAME) .hasKind(SpanKind.CLIENT) .hasParent(trace.getSpan(0))), trace -> diff --git a/instrumentation/lettuce/lettuce-5.1/library/src/main/java/io/opentelemetry/instrumentation/lettuce/v5_1/LettuceDbAttributesGetter.java b/instrumentation/lettuce/lettuce-5.1/library/src/main/java/io/opentelemetry/instrumentation/lettuce/v5_1/LettuceDbAttributesGetter.java index 3a70339ee1fc..7d8c52a4cb7c 100644 --- a/instrumentation/lettuce/lettuce-5.1/library/src/main/java/io/opentelemetry/instrumentation/lettuce/v5_1/LettuceDbAttributesGetter.java +++ b/instrumentation/lettuce/lettuce-5.1/library/src/main/java/io/opentelemetry/instrumentation/lettuce/v5_1/LettuceDbAttributesGetter.java @@ -9,7 +9,7 @@ import java.net.InetSocketAddress; import javax.annotation.Nullable; -final class LettuceDbAttributesGetter +class LettuceDbAttributesGetter implements DbClientAttributesGetter { // copied from DbIncubatingAttributes.DbSystemIncubatingValues diff --git a/instrumentation/lettuce/lettuce-5.1/library/src/main/java/io/opentelemetry/instrumentation/lettuce/v5_1/LettuceTelemetryBuilder.java b/instrumentation/lettuce/lettuce-5.1/library/src/main/java/io/opentelemetry/instrumentation/lettuce/v5_1/LettuceTelemetryBuilder.java index 123acf2b4b07..5245c61bdd4e 100644 --- a/instrumentation/lettuce/lettuce-5.1/library/src/main/java/io/opentelemetry/instrumentation/lettuce/v5_1/LettuceTelemetryBuilder.java +++ b/instrumentation/lettuce/lettuce-5.1/library/src/main/java/io/opentelemetry/instrumentation/lettuce/v5_1/LettuceTelemetryBuilder.java @@ -18,6 +18,7 @@ import io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder; import io.opentelemetry.instrumentation.api.instrumenter.SpanKindExtractor; import io.opentelemetry.instrumentation.api.instrumenter.SpanStatusExtractor; +import javax.annotation.Nullable; /** A builder of {@link LettuceTelemetry}. */ public final class LettuceTelemetryBuilder { @@ -59,12 +60,22 @@ public LettuceTelemetryBuilder setEncodingSpanEventsEnabled(boolean encodingEven */ public LettuceTelemetry build() { LettuceDbAttributesGetter dbAttributesGetter = new LettuceDbAttributesGetter(); + // Redis semantic conventions don't follow the regular pattern of adding db.namespace to the + // span name. + LettuceDbAttributesGetter spanNameAttributesGetter = + new LettuceDbAttributesGetter() { + @Override + @Nullable + public String getDbNamespace(LettuceRequest request) { + return null; + } + }; InstrumenterBuilder builder = Instrumenter.builder( openTelemetry, INSTRUMENTATION_NAME, - DbClientSpanNameExtractor.create(dbAttributesGetter)) + DbClientSpanNameExtractor.create(spanNameAttributesGetter)) .addAttributesExtractor(DbClientAttributesExtractor.create(dbAttributesGetter)) .addOperationMetrics(DbClientMetrics.get()) .setSpanStatusExtractor( diff --git a/instrumentation/mongo/mongo-3.1/library/src/main/java/io/opentelemetry/instrumentation/mongo/v3_1/internal/MongoAttributesExtractor.java b/instrumentation/mongo/mongo-3.1/library/src/main/java/io/opentelemetry/instrumentation/mongo/v3_1/internal/MongoAttributesExtractor.java index 521b29240c78..985681fbce12 100644 --- a/instrumentation/mongo/mongo-3.1/library/src/main/java/io/opentelemetry/instrumentation/mongo/v3_1/internal/MongoAttributesExtractor.java +++ b/instrumentation/mongo/mongo-3.1/library/src/main/java/io/opentelemetry/instrumentation/mongo/v3_1/internal/MongoAttributesExtractor.java @@ -6,53 +6,31 @@ package io.opentelemetry.instrumentation.mongo.v3_1.internal; import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitOldDatabaseSemconv; -import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitStableDatabaseSemconv; -import static io.opentelemetry.semconv.DbAttributes.DB_COLLECTION_NAME; -import static java.util.Arrays.asList; import com.mongodb.event.CommandStartedEvent; import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.AttributesBuilder; import io.opentelemetry.context.Context; import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; -import java.util.HashSet; -import java.util.Set; import javax.annotation.Nullable; -import org.bson.BsonValue; class MongoAttributesExtractor implements AttributesExtractor { + // copied from DbIncubatingAttributes private static final AttributeKey DB_MONGODB_COLLECTION = AttributeKey.stringKey("db.mongodb.collection"); - private static final Set COMMANDS_WITH_COLLECTION_NAME_AS_VALUE = - new HashSet<>( - asList( - "aggregate", - "count", - "distinct", - "mapReduce", - "geoSearch", - "delete", - "find", - "killCursors", - "findAndModify", - "insert", - "update", - "create", - "drop", - "createIndexes", - "listIndexes")); + private final MongoDbAttributesGetter getter; + + MongoAttributesExtractor(MongoDbAttributesGetter getter) { + this.getter = getter; + } @Override public void onStart( AttributesBuilder attributes, Context parentContext, CommandStartedEvent event) { - String collectionName = collectionName(event); - if (emitStableDatabaseSemconv()) { - attributes.put(DB_COLLECTION_NAME, collectionName); - } if (emitOldDatabaseSemconv()) { - attributes.put(DB_MONGODB_COLLECTION, collectionName); + attributes.put(DB_MONGODB_COLLECTION, getter.getDbCollectionName(event)); } } @@ -63,22 +41,4 @@ public void onEnd( CommandStartedEvent event, @Nullable Void unused, @Nullable Throwable error) {} - - @Nullable - String collectionName(CommandStartedEvent event) { - if (event.getCommandName().equals("getMore")) { - BsonValue collectionValue = event.getCommand().get("collection"); - if (collectionValue != null) { - if (collectionValue.isString()) { - return collectionValue.asString().getValue(); - } - } - } else if (COMMANDS_WITH_COLLECTION_NAME_AS_VALUE.contains(event.getCommandName())) { - BsonValue commandValue = event.getCommand().get(event.getCommandName()); - if (commandValue != null && commandValue.isString()) { - return commandValue.asString().getValue(); - } - } - return null; - } } diff --git a/instrumentation/mongo/mongo-3.1/library/src/main/java/io/opentelemetry/instrumentation/mongo/v3_1/internal/MongoDbAttributesGetter.java b/instrumentation/mongo/mongo-3.1/library/src/main/java/io/opentelemetry/instrumentation/mongo/v3_1/internal/MongoDbAttributesGetter.java index 4fe95398862b..63ec497c001c 100644 --- a/instrumentation/mongo/mongo-3.1/library/src/main/java/io/opentelemetry/instrumentation/mongo/v3_1/internal/MongoDbAttributesGetter.java +++ b/instrumentation/mongo/mongo-3.1/library/src/main/java/io/opentelemetry/instrumentation/mongo/v3_1/internal/MongoDbAttributesGetter.java @@ -5,6 +5,8 @@ package io.opentelemetry.instrumentation.mongo.v3_1.internal; +import static java.util.Arrays.asList; + import com.mongodb.MongoException; import com.mongodb.ServerAddress; import com.mongodb.connection.ConnectionDescription; @@ -13,8 +15,10 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Arrays; +import java.util.HashSet; import java.util.Map; import java.util.Optional; +import java.util.Set; import javax.annotation.Nullable; import org.bson.BsonArray; import org.bson.BsonDocument; @@ -29,8 +33,27 @@ class MongoDbAttributesGetter implements DbClientAttributesGetter COMMANDS_WITH_COLLECTION_NAME_AS_VALUE = + new HashSet<>( + asList( + "aggregate", + "count", + "distinct", + "mapReduce", + "geoSearch", + "delete", + "find", + "killCursors", + "findAndModify", + "insert", + "update", + "create", + "drop", + "createIndexes", + "listIndexes")); + + @Nullable private static final Method IS_TRUNCATED_METHOD; static { IS_TRUNCATED_METHOD = @@ -61,6 +84,25 @@ public String getDbNamespace(CommandStartedEvent event) { return event.getDatabaseName(); } + @Override + @Nullable + public String getDbCollectionName(CommandStartedEvent event) { + if (event.getCommandName().equals("getMore")) { + BsonValue collectionValue = event.getCommand().get("collection"); + if (collectionValue != null) { + if (collectionValue.isString()) { + return collectionValue.asString().getValue(); + } + } + } else if (COMMANDS_WITH_COLLECTION_NAME_AS_VALUE.contains(event.getCommandName())) { + BsonValue commandValue = event.getCommand().get(event.getCommandName()); + if (commandValue != null && commandValue.isString()) { + return commandValue.asString().getValue(); + } + } + return null; + } + @Deprecated // to be removed in 3.0 @Override @Nullable diff --git a/instrumentation/mongo/mongo-3.1/library/src/main/java/io/opentelemetry/instrumentation/mongo/v3_1/internal/MongoInstrumenterFactory.java b/instrumentation/mongo/mongo-3.1/library/src/main/java/io/opentelemetry/instrumentation/mongo/v3_1/internal/MongoInstrumenterFactory.java index 439cce33f170..99efa6ed4a21 100644 --- a/instrumentation/mongo/mongo-3.1/library/src/main/java/io/opentelemetry/instrumentation/mongo/v3_1/internal/MongoInstrumenterFactory.java +++ b/instrumentation/mongo/mongo-3.1/library/src/main/java/io/opentelemetry/instrumentation/mongo/v3_1/internal/MongoInstrumenterFactory.java @@ -39,17 +39,16 @@ public static Instrumenter createInstrumenter( boolean querySanitizationEnabled, int maxNormalizedQueryLength) { - MongoAttributesExtractor attributesExtractor = new MongoAttributesExtractor(); MongoDbAttributesGetter dbAttributesGetter = new MongoDbAttributesGetter(querySanitizationEnabled, maxNormalizedQueryLength); SpanNameExtractor spanNameExtractor = - new MongoSpanNameExtractor(dbAttributesGetter, attributesExtractor); + new MongoSpanNameExtractor(dbAttributesGetter); InstrumenterBuilder builder = Instrumenter.builder( openTelemetry, instrumentationName, spanNameExtractor) .addAttributesExtractor(DbClientAttributesExtractor.create(dbAttributesGetter)) - .addAttributesExtractor(attributesExtractor) + .addAttributesExtractor(new MongoAttributesExtractor(dbAttributesGetter)) .addOperationMetrics(DbClientMetrics.get()); setDbClientExceptionEventExtractor(builder); return builder.buildInstrumenter(SpanKindExtractor.alwaysClient()); diff --git a/instrumentation/mongo/mongo-3.1/library/src/main/java/io/opentelemetry/instrumentation/mongo/v3_1/internal/MongoSpanNameExtractor.java b/instrumentation/mongo/mongo-3.1/library/src/main/java/io/opentelemetry/instrumentation/mongo/v3_1/internal/MongoSpanNameExtractor.java index 658e2b95ddfb..147e47fe1d43 100644 --- a/instrumentation/mongo/mongo-3.1/library/src/main/java/io/opentelemetry/instrumentation/mongo/v3_1/internal/MongoSpanNameExtractor.java +++ b/instrumentation/mongo/mongo-3.1/library/src/main/java/io/opentelemetry/instrumentation/mongo/v3_1/internal/MongoSpanNameExtractor.java @@ -5,43 +5,49 @@ package io.opentelemetry.instrumentation.mongo.v3_1.internal; +import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitStableDatabaseSemconv; + import com.mongodb.event.CommandStartedEvent; +import io.opentelemetry.instrumentation.api.incubator.semconv.db.DbClientSpanNameExtractor; import io.opentelemetry.instrumentation.api.instrumenter.SpanNameExtractor; class MongoSpanNameExtractor implements SpanNameExtractor { private static final String DEFAULT_SPAN_NAME = "DB Query"; private final MongoDbAttributesGetter dbAttributesGetter; - private final MongoAttributesExtractor attributesExtractor; + private final SpanNameExtractor stableDelegate; - MongoSpanNameExtractor( - MongoDbAttributesGetter dbAttributesGetter, MongoAttributesExtractor attributesExtractor) { + MongoSpanNameExtractor(MongoDbAttributesGetter dbAttributesGetter) { this.dbAttributesGetter = dbAttributesGetter; - this.attributesExtractor = attributesExtractor; + stableDelegate = DbClientSpanNameExtractor.create(dbAttributesGetter); } + @SuppressWarnings("deprecation") // getDbName/getDbOperation are used for old semconv span names @Override public String extract(CommandStartedEvent event) { - String operation = dbAttributesGetter.getDbOperationName(event); - String dbName = dbAttributesGetter.getDbNamespace(event); + if (emitStableDatabaseSemconv()) { + return stableDelegate.extract(event); + } + + String operation = dbAttributesGetter.getDbOperation(event); + String dbName = dbAttributesGetter.getDbName(event); if (operation == null) { return dbName == null ? DEFAULT_SPAN_NAME : dbName; } - String table = attributesExtractor.collectionName(event); + String collectionName = dbAttributesGetter.getDbCollectionName(event); StringBuilder name = new StringBuilder(operation); - if (dbName != null || table != null) { + if (dbName != null || collectionName != null) { name.append(' '); } - // skip db name if table already has a db name prefixed to it - if (dbName != null && (table == null || table.indexOf('.') == -1)) { + if (dbName != null && (collectionName == null || collectionName.indexOf('.') == -1)) { name.append(dbName); - if (table != null) { + if (collectionName != null) { name.append('.'); } } - if (table != null) { - name.append(table); + if (collectionName != null) { + name.append(collectionName); } return name.toString(); } diff --git a/instrumentation/mongo/mongo-3.1/library/src/test/java/io/opentelemetry/instrumentation/mongo/v3_1/internal/MongoSpanNameExtractorTest.java b/instrumentation/mongo/mongo-3.1/library/src/test/java/io/opentelemetry/instrumentation/mongo/v3_1/internal/MongoSpanNameExtractorTest.java index dfb5cf0cd487..2b12d1bd8ba2 100644 --- a/instrumentation/mongo/mongo-3.1/library/src/test/java/io/opentelemetry/instrumentation/mongo/v3_1/internal/MongoSpanNameExtractorTest.java +++ b/instrumentation/mongo/mongo-3.1/library/src/test/java/io/opentelemetry/instrumentation/mongo/v3_1/internal/MongoSpanNameExtractorTest.java @@ -21,8 +21,7 @@ class MongoSpanNameExtractorTest { void testSpanNameWithNoDbName() { MongoSpanNameExtractor nameExtractor = new MongoSpanNameExtractor( - new MongoDbAttributesGetter(true, DEFAULT_MAX_NORMALIZED_QUERY_LENGTH), - new MongoAttributesExtractor()); + new MongoDbAttributesGetter(true, DEFAULT_MAX_NORMALIZED_QUERY_LENGTH)); String command = "listDatabases"; CommandStartedEvent event = diff --git a/instrumentation/mongo/mongo-common/testing/src/main/java/io/opentelemetry/instrumentation/mongo/testing/AbstractMongoClientTest.java b/instrumentation/mongo/mongo-common/testing/src/main/java/io/opentelemetry/instrumentation/mongo/testing/AbstractMongoClientTest.java index f1c05ca3b01c..1e1fbc518233 100644 --- a/instrumentation/mongo/mongo-common/testing/src/main/java/io/opentelemetry/instrumentation/mongo/testing/AbstractMongoClientTest.java +++ b/instrumentation/mongo/mongo-common/testing/src/main/java/io/opentelemetry/instrumentation/mongo/testing/AbstractMongoClientTest.java @@ -543,7 +543,11 @@ void mongoSpan( String dbName, SpanData parentSpan, List statements) { - span.hasName(operation + " " + dbName + "." + collection).hasKind(CLIENT); + span.hasName( + emitStableDatabaseSemconv() + ? operation + " " + collection + : operation + " " + dbName + "." + collection) + .hasKind(CLIENT); if (parentSpan == null) { span.hasNoParent(); } else { diff --git a/instrumentation/r2dbc-1.0/library/src/main/java/io/opentelemetry/instrumentation/r2dbc/v1_0/internal/DbExecution.java b/instrumentation/r2dbc-1.0/library/src/main/java/io/opentelemetry/instrumentation/r2dbc/v1_0/internal/DbExecution.java index 870a98646e9d..96dd807309cd 100644 --- a/instrumentation/r2dbc-1.0/library/src/main/java/io/opentelemetry/instrumentation/r2dbc/v1_0/internal/DbExecution.java +++ b/instrumentation/r2dbc-1.0/library/src/main/java/io/opentelemetry/instrumentation/r2dbc/v1_0/internal/DbExecution.java @@ -11,7 +11,7 @@ import static io.r2dbc.spi.ConnectionFactoryOptions.PORT; import static io.r2dbc.spi.ConnectionFactoryOptions.PROTOCOL; import static io.r2dbc.spi.ConnectionFactoryOptions.USER; -import static java.util.stream.Collectors.joining; +import static java.util.stream.Collectors.toList; import io.opentelemetry.context.Context; import io.r2dbc.proxy.core.QueryExecutionInfo; @@ -19,6 +19,7 @@ import io.r2dbc.spi.Connection; import io.r2dbc.spi.ConnectionFactoryOptions; import java.util.HashMap; +import java.util.List; import java.util.Locale; import java.util.Map; import javax.annotation.Nullable; @@ -64,7 +65,8 @@ private static Map buildDriverToSystemName() { @Nullable private final String serverAddress; @Nullable private final Integer serverPort; private final String connectionString; - private final String rawQueryText; + private final List rawQueryTexts; + @Nullable private final Long batchSize; private final boolean parameterizedQuery; @Nullable private Context context; @@ -100,13 +102,15 @@ public DbExecution(QueryExecutionInfo queryInfo, ConnectionFactoryOptions factor protocol != null ? ":" + protocol : "", serverAddress != null ? "//" + serverAddress : "", serverPort != null ? ":" + serverPort : ""); - this.rawQueryText = + this.rawQueryTexts = queryInfo.getQueries().stream() .map(QueryInfo::getQuery) .map( query -> R2dbcSqlCommenterUtil.getOriginalQuery(queryInfo.getConnectionInfo(), query)) - .collect(joining(";\n")); + .collect(toList()); + int queryInfoBatchSize = queryInfo.getBatchSize(); + this.batchSize = queryInfoBatchSize > 1 ? (long) queryInfoBatchSize : null; this.parameterizedQuery = queryInfo.getQueries().stream() .anyMatch(queryInfo1 -> !queryInfo1.getBindingsList().isEmpty()); @@ -146,8 +150,13 @@ public String getConnectionString() { return connectionString; } - public String getRawQueryText() { - return rawQueryText; + public List getRawQueryTexts() { + return rawQueryTexts; + } + + @Nullable + public Long getBatchSize() { + return batchSize; } public boolean isParameterizedQuery() { diff --git a/instrumentation/r2dbc-1.0/library/src/main/java/io/opentelemetry/instrumentation/r2dbc/v1_0/internal/R2dbcSqlAttributesGetter.java b/instrumentation/r2dbc-1.0/library/src/main/java/io/opentelemetry/instrumentation/r2dbc/v1_0/internal/R2dbcSqlAttributesGetter.java index da4ac1d66c5d..e8b56f563d3a 100644 --- a/instrumentation/r2dbc-1.0/library/src/main/java/io/opentelemetry/instrumentation/r2dbc/v1_0/internal/R2dbcSqlAttributesGetter.java +++ b/instrumentation/r2dbc-1.0/library/src/main/java/io/opentelemetry/instrumentation/r2dbc/v1_0/internal/R2dbcSqlAttributesGetter.java @@ -6,6 +6,7 @@ package io.opentelemetry.instrumentation.r2dbc.v1_0.internal; import static io.opentelemetry.instrumentation.api.incubator.semconv.db.SqlDialect.DOUBLE_QUOTES_ARE_STRING_LITERALS; +import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitStableDatabaseSemconv; import static java.util.Collections.singleton; import io.opentelemetry.instrumentation.api.incubator.semconv.db.SqlClientAttributesGetter; @@ -64,7 +65,33 @@ public String getConnectionString(DbExecution request) { @Override public Collection getRawQueryTexts(DbExecution request) { - return singleton(request.getRawQueryText()); + Collection rawQueryTexts = request.getRawQueryTexts(); + // In old-only mode, join multi-query batches into a single query to preserve the legacy + // db.statement and db.operation extraction behavior. In database/dup mode, favor stable + // multi-query batch attributes because the shared SQL extractor can only use one raw query + // collection. + return emitStableDatabaseSemconv() || rawQueryTexts.size() == 1 + ? rawQueryTexts + : singleton(join(";\n", rawQueryTexts)); + } + + private static String join(String delimiter, Collection collection) { + StringBuilder builder = new StringBuilder(); + for (String string : collection) { + if (builder.length() != 0) { + builder.append(delimiter); + } + builder.append(string); + } + return builder.toString(); + } + + @Override + @Nullable + public Long getDbOperationBatchSize(DbExecution request) { + // Batch size is a stable database semconv signal. Keep it hidden from old-only mode so legacy + // extraction does not start treating existing requests as batches. + return emitStableDatabaseSemconv() ? request.getBatchSize() : null; } @Nullable diff --git a/instrumentation/r2dbc-1.0/library/src/test/java/io/opentelemetry/instrumentation/r2dbc/v1_0/DbExecutionTest.java b/instrumentation/r2dbc-1.0/library/src/test/java/io/opentelemetry/instrumentation/r2dbc/v1_0/DbExecutionTest.java index 070f1e1408fe..d986cc5997bb 100644 --- a/instrumentation/r2dbc-1.0/library/src/test/java/io/opentelemetry/instrumentation/r2dbc/v1_0/DbExecutionTest.java +++ b/instrumentation/r2dbc-1.0/library/src/test/java/io/opentelemetry/instrumentation/r2dbc/v1_0/DbExecutionTest.java @@ -49,8 +49,45 @@ void dbExecution() { assertThat(dbExecution.getServerAddress()).isEqualTo("localhost"); assertThat(dbExecution.getServerPort()).isEqualTo(3306); assertThat(dbExecution.getConnectionString()).isEqualTo("mariadb://localhost:3306"); - assertThat(dbExecution.getRawQueryText()) - .isEqualTo("SELECT * from person where last_name = 'tom'"); + assertThat(dbExecution.getRawQueryTexts()) + .containsExactly("SELECT * from person where last_name = 'tom'"); + assertThat(dbExecution.getBatchSize()).isNull(); + } + + @Test + void dbExecutionWithBatch() { + QueryExecutionInfo queryExecutionInfo = + MockQueryExecutionInfo.builder() + .queryInfo(new QueryInfo("INSERT INTO person VALUES(1)")) + .queryInfo(new QueryInfo("INSERT INTO person VALUES(2)")) + .batchSize(2) + .connectionInfo(MockConnectionInfo.builder().build()) + .build(); + ConnectionFactoryOptions factoryOptions = + ConnectionFactoryOptions.parse("r2dbc:postgresql://localhost/db"); + + DbExecution dbExecution = new DbExecution(queryExecutionInfo, factoryOptions); + + assertThat(dbExecution.getRawQueryTexts()) + .containsExactly("INSERT INTO person VALUES(1)", "INSERT INTO person VALUES(2)"); + assertThat(dbExecution.getBatchSize()).isEqualTo(2); + } + + @Test + void dbExecutionWithBatchSizeOne() { + QueryExecutionInfo queryExecutionInfo = + MockQueryExecutionInfo.builder() + .queryInfo(new QueryInfo("INSERT INTO person VALUES(1)")) + .batchSize(1) + .connectionInfo(MockConnectionInfo.builder().build()) + .build(); + ConnectionFactoryOptions factoryOptions = + ConnectionFactoryOptions.parse("r2dbc:postgresql://localhost/db"); + + DbExecution dbExecution = new DbExecution(queryExecutionInfo, factoryOptions); + + assertThat(dbExecution.getRawQueryTexts()).containsExactly("INSERT INTO person VALUES(1)"); + assertThat(dbExecution.getBatchSize()).isNull(); } @SuppressWarnings("deprecation") // testing deprecated semconv diff --git a/instrumentation/r2dbc-1.0/library/src/test/java/io/opentelemetry/instrumentation/r2dbc/v1_0/R2dbcSqlAttributesGetterTest.java b/instrumentation/r2dbc-1.0/library/src/test/java/io/opentelemetry/instrumentation/r2dbc/v1_0/R2dbcSqlAttributesGetterTest.java new file mode 100644 index 000000000000..07665eea080d --- /dev/null +++ b/instrumentation/r2dbc-1.0/library/src/test/java/io/opentelemetry/instrumentation/r2dbc/v1_0/R2dbcSqlAttributesGetterTest.java @@ -0,0 +1,67 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.r2dbc.v1_0; + +import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitStableDatabaseSemconv; +import static org.assertj.core.api.Assertions.assertThat; + +import io.opentelemetry.instrumentation.r2dbc.v1_0.internal.DbExecution; +import io.opentelemetry.instrumentation.r2dbc.v1_0.internal.R2dbcSqlAttributesGetter; +import io.r2dbc.proxy.core.QueryExecutionInfo; +import io.r2dbc.proxy.core.QueryInfo; +import io.r2dbc.proxy.test.MockConnectionInfo; +import io.r2dbc.proxy.test.MockQueryExecutionInfo; +import io.r2dbc.spi.ConnectionFactoryOptions; +import java.util.Collection; +import org.junit.jupiter.api.Test; + +class R2dbcSqlAttributesGetterTest { + + private final R2dbcSqlAttributesGetter getter = new R2dbcSqlAttributesGetter(); + + @Test + void rawQueryTextsForSingleQuery() { + QueryExecutionInfo queryExecutionInfo = + MockQueryExecutionInfo.builder() + .queryInfo(new QueryInfo("INSERT INTO person VALUES(1)")) + .connectionInfo(MockConnectionInfo.builder().build()) + .build(); + ConnectionFactoryOptions factoryOptions = + ConnectionFactoryOptions.parse("r2dbc:postgresql://localhost/db"); + DbExecution dbExecution = new DbExecution(queryExecutionInfo, factoryOptions); + + Collection rawQueryTexts = getter.getRawQueryTexts(dbExecution); + + assertThat(rawQueryTexts).isSameAs(dbExecution.getRawQueryTexts()); + assertThat(rawQueryTexts).containsExactly("INSERT INTO person VALUES(1)"); + } + + @Test + void rawQueryTextsForBatch() { + QueryExecutionInfo queryExecutionInfo = + MockQueryExecutionInfo.builder() + .queryInfo(new QueryInfo("INSERT INTO person VALUES(1)")) + .queryInfo(new QueryInfo("INSERT INTO person VALUES(2)")) + .batchSize(2) + .connectionInfo(MockConnectionInfo.builder().build()) + .build(); + ConnectionFactoryOptions factoryOptions = + ConnectionFactoryOptions.parse("r2dbc:postgresql://localhost/db"); + DbExecution dbExecution = new DbExecution(queryExecutionInfo, factoryOptions); + + Collection rawQueryTexts = getter.getRawQueryTexts(dbExecution); + + if (emitStableDatabaseSemconv()) { + assertThat(rawQueryTexts) + .containsExactly("INSERT INTO person VALUES(1)", "INSERT INTO person VALUES(2)"); + assertThat(getter.getDbOperationBatchSize(dbExecution)).isEqualTo(2); + } else { + assertThat(rawQueryTexts) + .containsExactly("INSERT INTO person VALUES(1);\nINSERT INTO person VALUES(2)"); + assertThat(getter.getDbOperationBatchSize(dbExecution)).isNull(); + } + } +} diff --git a/instrumentation/r2dbc-1.0/testing/src/main/java/io/opentelemetry/instrumentation/r2dbc/v1_0/AbstractR2dbcStatementTest.java b/instrumentation/r2dbc-1.0/testing/src/main/java/io/opentelemetry/instrumentation/r2dbc/v1_0/AbstractR2dbcStatementTest.java index 7d78fc6840ca..0732c04799f5 100644 --- a/instrumentation/r2dbc-1.0/testing/src/main/java/io/opentelemetry/instrumentation/r2dbc/v1_0/AbstractR2dbcStatementTest.java +++ b/instrumentation/r2dbc-1.0/testing/src/main/java/io/opentelemetry/instrumentation/r2dbc/v1_0/AbstractR2dbcStatementTest.java @@ -11,8 +11,10 @@ import static io.opentelemetry.instrumentation.testing.junit.service.SemconvServiceStabilityUtil.maybeStablePeerService; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; import static io.opentelemetry.semconv.DbAttributes.DB_NAMESPACE; +import static io.opentelemetry.semconv.DbAttributes.DB_OPERATION_BATCH_SIZE; import static io.opentelemetry.semconv.DbAttributes.DB_OPERATION_NAME; import static io.opentelemetry.semconv.DbAttributes.DB_QUERY_SUMMARY; +import static io.opentelemetry.semconv.DbAttributes.DB_QUERY_TEXT; import static io.opentelemetry.semconv.DbAttributes.DB_SYSTEM_NAME; import static io.opentelemetry.semconv.ServerAttributes.SERVER_ADDRESS; import static io.opentelemetry.semconv.ServerAttributes.SERVER_PORT; @@ -30,6 +32,7 @@ import static io.r2dbc.spi.ConnectionFactoryOptions.PASSWORD; import static io.r2dbc.spi.ConnectionFactoryOptions.PORT; import static io.r2dbc.spi.ConnectionFactoryOptions.USER; +import static org.junit.jupiter.api.Assumptions.assumeTrue; import static org.junit.jupiter.api.Named.named; import com.google.errorprone.annotations.CanIgnoreReturnValue; @@ -54,6 +57,7 @@ import org.testcontainers.containers.GenericContainer; import org.testcontainers.containers.output.Slf4jLogConsumer; import org.testcontainers.containers.wait.strategy.Wait; +import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @TestInstance(TestInstance.Lifecycle.PER_CLASS) @@ -294,6 +298,62 @@ void testMetrics() { SERVER_PORT); } + @Test + void testBatchQueries() { + assumeTrue(emitStableDatabaseSemconv()); + + DbSystemProps props = systems.get(MARIADB.system); + startContainer(props); + ConnectionFactory connectionFactory = + createProxyConnectionFactory( + ConnectionFactoryOptions.builder() + .option(DRIVER, props.system) + .option(HOST, container.getHost()) + .option(PORT, port) + .option(USER, USER_DB) + .option(PASSWORD, PW_DB) + .option(DATABASE, DB) + .option(CONNECT_TIMEOUT, Duration.ofSeconds(30)) + .build()); + + getTesting() + .runWithSpan( + "parent", + () -> { + Mono.from(connectionFactory.create()) + .flatMapMany( + connection -> + Flux.from( + connection + .createBatch() + .add("SELECT 1") + .add("SELECT 2") + .execute()) + .flatMap(result -> result.map((row, metadata) -> "")) + .concatWith(Mono.from(connection.close()).cast(String.class))) + .blockLast(Duration.ofMinutes(1)); + }); + + getTesting() + .waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("parent").hasKind(SpanKind.INTERNAL), + span -> + span.hasName("BATCH SELECT") + .hasKind(SpanKind.CLIENT) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly( + equalTo(DB_SYSTEM_NAME, MARIADB.system), + equalTo(DB_NAMESPACE, DB), + equalTo(DB_QUERY_TEXT, "SELECT ?"), + equalTo(DB_QUERY_SUMMARY, "BATCH SELECT"), + equalTo(DB_OPERATION_BATCH_SIZE, 2), + equalTo(maybeStablePeerService(), "test-peer-service"), + equalTo(SERVER_ADDRESS, container.getHost()), + equalTo(SERVER_PORT, port)))); + } + private static class Parameter { private final String system; diff --git a/instrumentation/redisson/redisson-common-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/redisson/common/v3_0/RedissonRequest.java b/instrumentation/redisson/redisson-common-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/redisson/common/v3_0/RedissonRequest.java index 6d19e1297c04..6784bac6794b 100644 --- a/instrumentation/redisson/redisson-common-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/redisson/common/v3_0/RedissonRequest.java +++ b/instrumentation/redisson/redisson-common-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/redisson/common/v3_0/RedissonRequest.java @@ -5,6 +5,7 @@ package io.opentelemetry.javaagent.instrumentation.redisson.common.v3_0; +import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitStableDatabaseSemconv; import static java.util.Collections.emptyList; import static java.util.Collections.singletonList; import static java.util.logging.Level.FINE; @@ -32,6 +33,9 @@ public abstract class RedissonRequest { private static final Logger logger = Logger.getLogger(RedissonRequest.class.getName()); + private static final String MULTI = "MULTI"; + private static final String PIPELINE = "PIPELINE"; + private static final RedisCommandSanitizer sanitizer = RedisCommandSanitizer.create( DbConfig.isQuerySanitizationEnabled(GlobalOpenTelemetry.get(), "redisson")); @@ -86,13 +90,30 @@ public String getOperationName() { return ((CommandData) command).getCommand().getName(); } else if (command instanceof CommandsData) { CommandsData commandsData = (CommandsData) command; - if (commandsData.getCommands().size() == 1) { - return commandsData.getCommands().get(0).getCommand().getName(); + List> commands = commandsData.getCommands(); + if (commands.size() == 1) { + return commands.get(0).getCommand().getName(); } + return emitStableDatabaseSemconv() ? getBatchOperationName(commands) : null; } return null; } + @Nullable + private static String getBatchOperationName(List> commands) { + if (commands.size() < 2) { + return null; + } + + String firstCommandName = commands.get(0).getCommand().getName(); + String batchOperationName = firstCommandName.equals(MULTI) ? MULTI : PIPELINE; + int firstBatchCommandIndex = firstCommandName.equals(MULTI) ? 1 : 0; + String commonCommandName = getCommonCommandName(commands, firstBatchCommandIndex); + return commonCommandName == null + ? batchOperationName + : batchOperationName + " " + commonCommandName; + } + @Nullable public String getQueryText() { List sanitizedQueries = sanitizeQuery(); @@ -129,6 +150,23 @@ private List sanitizeQuery() { return emptyList(); } + @Nullable + private static String getCommonCommandName( + List> commands, int firstBatchCommandIndex) { + if (firstBatchCommandIndex >= commands.size()) { + return null; + } + + String commonCommandName = commands.get(firstBatchCommandIndex).getCommand().getName(); + for (int i = firstBatchCommandIndex + 1; i < commands.size(); i++) { + String commandName = commands.get(i).getCommand().getName(); + if (!commandName.equals(commonCommandName)) { + return null; + } + } + return commonCommandName; + } + private static String normalizeSingleCommand(CommandData command) { Object[] commandParams = command.getParams(); List args = new ArrayList<>(commandParams.length + 1); diff --git a/instrumentation/redisson/redisson-common-3.0/testing/src/main/java/io/opentelemetry/javaagent/instrumentation/redisson/AbstractRedissonAsyncClientTest.java b/instrumentation/redisson/redisson-common-3.0/testing/src/main/java/io/opentelemetry/javaagent/instrumentation/redisson/AbstractRedissonAsyncClientTest.java index 1922a6df8e12..fb752b3cdec3 100644 --- a/instrumentation/redisson/redisson-common-3.0/testing/src/main/java/io/opentelemetry/javaagent/instrumentation/redisson/AbstractRedissonAsyncClientTest.java +++ b/instrumentation/redisson/redisson-common-3.0/testing/src/main/java/io/opentelemetry/javaagent/instrumentation/redisson/AbstractRedissonAsyncClientTest.java @@ -19,6 +19,7 @@ import static io.opentelemetry.semconv.NetworkAttributes.NETWORK_TYPE; import static io.opentelemetry.semconv.NetworkAttributes.NetworkTypeValues.IPV4; import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_OPERATION; +import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_OPERATION_NAME; 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.DbSystemNameIncubatingValues.REDIS; @@ -245,13 +246,16 @@ void atomicBatchCommand() { trace.hasSpansSatisfyingExactly( span -> span.hasName("parent").hasKind(INTERNAL).hasNoParent(), span -> - span.hasName(emitStableDatabaseSemconv() ? "redis" : "DB Query") + span.hasName(emitStableDatabaseSemconv() ? "MULTI SET" : "DB Query") .hasKind(CLIENT) .hasAttributesSatisfyingExactly( equalTo(NETWORK_TYPE, emitOldDatabaseSemconv() ? IPV4 : null), equalTo(NETWORK_PEER_ADDRESS, ip), equalTo(NETWORK_PEER_PORT, port), equalTo(maybeStable(DB_SYSTEM), REDIS), + equalTo( + DB_OPERATION_NAME, + emitStableDatabaseSemconv() ? "MULTI SET" : null), equalTo(maybeStable(DB_STATEMENT), "MULTI;SET batch1 ?")) .hasParent(trace.getSpan(0)), span -> diff --git a/instrumentation/redisson/redisson-common-3.0/testing/src/main/java/io/opentelemetry/javaagent/instrumentation/redisson/AbstractRedissonClientTest.java b/instrumentation/redisson/redisson-common-3.0/testing/src/main/java/io/opentelemetry/javaagent/instrumentation/redisson/AbstractRedissonClientTest.java index 4792f5572a9f..4f72444adb31 100644 --- a/instrumentation/redisson/redisson-common-3.0/testing/src/main/java/io/opentelemetry/javaagent/instrumentation/redisson/AbstractRedissonClientTest.java +++ b/instrumentation/redisson/redisson-common-3.0/testing/src/main/java/io/opentelemetry/javaagent/instrumentation/redisson/AbstractRedissonClientTest.java @@ -258,13 +258,16 @@ void batchCommand() throws ReflectiveOperationException { trace -> trace.hasSpansSatisfyingExactly( span -> - span.hasName(emitStableDatabaseSemconv() ? "redis" : "DB Query") + span.hasName(emitStableDatabaseSemconv() ? "PIPELINE SET" : "DB Query") .hasKind(CLIENT) .hasAttributesSatisfyingExactly( equalTo(NETWORK_TYPE, emitOldDatabaseSemconv() ? IPV4 : null), equalTo(NETWORK_PEER_ADDRESS, ip), equalTo(NETWORK_PEER_PORT, port), equalTo(maybeStable(DB_SYSTEM), REDIS), + equalTo( + DB_OPERATION_NAME, + emitStableDatabaseSemconv() ? "PIPELINE SET" : null), equalTo(maybeStable(DB_STATEMENT), "SET batch1 ?;SET batch2 ?")))); } @@ -272,6 +275,31 @@ private static void invokeExecute(RBatch batch) throws ReflectiveOperationExcept batch.getClass().getMethod("execute").invoke(batch); } + @Test + void mixedBatchCommand() throws ReflectiveOperationException { + RBatch batch = createBatch(redisson); + assertThat(batch).isNotNull(); + batch.getBucket("batch1").setAsync("v1"); + batch.getBucket("batch1").getAsync(); + // Adapt different method signature: + // `BatchResult execute()` and `List execute()` + invokeExecute(batch); + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasName(emitStableDatabaseSemconv() ? "PIPELINE" : "DB Query") + .hasKind(CLIENT) + .hasAttributesSatisfyingExactly( + equalTo(NETWORK_TYPE, emitOldDatabaseSemconv() ? IPV4 : null), + equalTo(NETWORK_PEER_ADDRESS, ip), + equalTo(NETWORK_PEER_PORT, port), + equalTo(maybeStable(DB_SYSTEM), REDIS), + equalTo( + DB_OPERATION_NAME, emitStableDatabaseSemconv() ? "PIPELINE" : null), + equalTo(maybeStable(DB_STATEMENT), "SET batch1 ?;GET batch1")))); + } + @Test void largeBatchCommand() throws ReflectiveOperationException { RBatch batch = createBatch(redisson); @@ -292,13 +320,16 @@ void largeBatchCommand() throws ReflectiveOperationException { trace -> trace.hasSpansSatisfyingExactly( span -> - span.hasName(emitStableDatabaseSemconv() ? "redis" : "DB Query") + span.hasName(emitStableDatabaseSemconv() ? "PIPELINE SET" : "DB Query") .hasKind(CLIENT) .hasAttributesSatisfyingExactly( equalTo(NETWORK_TYPE, emitOldDatabaseSemconv() ? IPV4 : null), equalTo(NETWORK_PEER_ADDRESS, ip), equalTo(NETWORK_PEER_PORT, port), equalTo(maybeStable(DB_SYSTEM), REDIS), + equalTo( + DB_OPERATION_NAME, + emitStableDatabaseSemconv() ? "PIPELINE SET" : null), equalTo( maybeStable(DB_STATEMENT), "SET " + bucketName + " ?;SET " + bucketName + " ?")))); @@ -324,18 +355,21 @@ void atomicBatchCommand() { batch.execute(); }); testing.waitAndAssertSortedTraces( - orderByRootSpanName("redis", "DB Query", "SET", "EXEC"), + orderByRootSpanName("MULTI SET", "DB Query", "SET", "EXEC"), trace -> trace.hasSpansSatisfyingExactly( span -> span.hasName("parent").hasNoParent().hasKind(INTERNAL), span -> - span.hasName(emitStableDatabaseSemconv() ? "redis" : "DB Query") + span.hasName(emitStableDatabaseSemconv() ? "MULTI SET" : "DB Query") .hasKind(CLIENT) .hasAttributesSatisfyingExactly( equalTo(NETWORK_TYPE, emitOldDatabaseSemconv() ? IPV4 : null), equalTo(NETWORK_PEER_ADDRESS, ip), equalTo(NETWORK_PEER_PORT, port), equalTo(maybeStable(DB_SYSTEM), REDIS), + equalTo( + DB_OPERATION_NAME, + emitStableDatabaseSemconv() ? "MULTI SET" : null), equalTo(maybeStable(DB_STATEMENT), "MULTI;SET batch1 ?")) .hasParent(trace.getSpan(0)), span -> diff --git a/instrumentation/vertx/vertx-redis-client-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/redisclient/v4_0/VertxRedisClientAttributesGetter.java b/instrumentation/vertx/vertx-redis-client-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/redisclient/v4_0/VertxRedisClientAttributesGetter.java index 91c40c32c4a8..b65d073a1e57 100644 --- a/instrumentation/vertx/vertx-redis-client-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/redisclient/v4_0/VertxRedisClientAttributesGetter.java +++ b/instrumentation/vertx/vertx-redis-client-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/redisclient/v4_0/VertxRedisClientAttributesGetter.java @@ -14,7 +14,7 @@ import io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DbSystemNameIncubatingValues; import javax.annotation.Nullable; -final class VertxRedisClientAttributesGetter +class VertxRedisClientAttributesGetter implements DbClientAttributesGetter { private static final RedisCommandSanitizer sanitizer = diff --git a/instrumentation/vertx/vertx-redis-client-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/redisclient/v4_0/VertxRedisClientSingletons.java b/instrumentation/vertx/vertx-redis-client-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/redisclient/v4_0/VertxRedisClientSingletons.java index a04611854b68..d7edaba15395 100644 --- a/instrumentation/vertx/vertx-redis-client-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/redisclient/v4_0/VertxRedisClientSingletons.java +++ b/instrumentation/vertx/vertx-redis-client-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/redisclient/v4_0/VertxRedisClientSingletons.java @@ -12,11 +12,11 @@ import io.opentelemetry.context.Scope; import io.opentelemetry.instrumentation.api.incubator.semconv.db.DbClientAttributesExtractor; import io.opentelemetry.instrumentation.api.incubator.semconv.db.DbClientMetrics; +import io.opentelemetry.instrumentation.api.incubator.semconv.db.DbClientSpanNameExtractor; import io.opentelemetry.instrumentation.api.incubator.semconv.service.peer.ServicePeerAttributesExtractor; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; import io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder; import io.opentelemetry.instrumentation.api.instrumenter.SpanKindExtractor; -import io.opentelemetry.instrumentation.api.instrumenter.SpanNameExtractor; import io.opentelemetry.instrumentation.api.util.VirtualField; import io.vertx.core.Future; import io.vertx.redis.client.Command; @@ -36,15 +36,23 @@ public class VertxRedisClientSingletons { VirtualField.find(RedisStandaloneConnection.class, RedisURI.class); static { - // Redis semantic conventions don't follow the regular pattern of adding the db.namespace to - // the span name - SpanNameExtractor spanNameExtractor = - VertxRedisClientRequest::getCommand; VertxRedisClientAttributesGetter getter = new VertxRedisClientAttributesGetter(); + // Redis semantic conventions don't follow the regular pattern of adding db.namespace to the + // span name. + VertxRedisClientAttributesGetter spanNameAttributesGetter = + new VertxRedisClientAttributesGetter() { + @Override + @Nullable + public String getDbNamespace(VertxRedisClientRequest request) { + return null; + } + }; InstrumenterBuilder builder = Instrumenter.builder( - GlobalOpenTelemetry.get(), INSTRUMENTATION_NAME, spanNameExtractor) + GlobalOpenTelemetry.get(), + INSTRUMENTATION_NAME, + DbClientSpanNameExtractor.create(spanNameAttributesGetter)) .addAttributesExtractor(DbClientAttributesExtractor.create(getter)) .addAttributesExtractor(new VertxRedisClientAttributesExtractor()) .addAttributesExtractor( diff --git a/instrumentation/vertx/vertx-redis-client-4.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/vertx/redisclient/v4_0/VertxRedisClientTest.java b/instrumentation/vertx/vertx-redis-client-4.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/vertx/redisclient/v4_0/VertxRedisClientTest.java index 84897ae381a4..c4b81286cae0 100644 --- a/instrumentation/vertx/vertx-redis-client-4.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/vertx/redisclient/v4_0/VertxRedisClientTest.java +++ b/instrumentation/vertx/vertx-redis-client-4.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/vertx/redisclient/v4_0/VertxRedisClientTest.java @@ -85,7 +85,7 @@ void setCommand() throws Exception { trace -> trace.hasSpansSatisfyingExactly( span -> - span.hasName("SET") + span.hasName(emitStableDatabaseSemconv() ? "SET " + host + ":" + port : "SET") .hasKind(SpanKind.CLIENT) .hasAttributesSatisfyingExactly(redisSpanAttributes("SET", "SET foo ?")))); @@ -108,13 +108,13 @@ void getCommand() throws Exception { trace -> trace.hasSpansSatisfyingExactly( span -> - span.hasName("SET") + span.hasName(emitStableDatabaseSemconv() ? "SET " + host + ":" + port : "SET") .hasKind(SpanKind.CLIENT) .hasAttributesSatisfyingExactly(redisSpanAttributes("SET", "SET foo ?"))), trace -> trace.hasSpansSatisfyingExactly( span -> - span.hasName("GET") + span.hasName(emitStableDatabaseSemconv() ? "GET " + host + ":" + port : "GET") .hasKind(SpanKind.CLIENT) .hasAttributesSatisfyingExactly(redisSpanAttributes("GET", "GET foo")))); } @@ -150,14 +150,14 @@ void getCommandWithParent() throws Exception { trace -> trace.hasSpansSatisfyingExactly( span -> - span.hasName("SET") + span.hasName(emitStableDatabaseSemconv() ? "SET " + host + ":" + port : "SET") .hasKind(SpanKind.CLIENT) .hasAttributesSatisfyingExactly(redisSpanAttributes("SET", "SET foo ?"))), trace -> trace.hasSpansSatisfyingExactly( span -> span.hasName("parent").hasKind(SpanKind.INTERNAL), span -> - span.hasName("GET") + span.hasName(emitStableDatabaseSemconv() ? "GET " + host + ":" + port : "GET") .hasKind(SpanKind.CLIENT) .hasParent(trace.getSpan(0)) .hasAttributesSatisfyingExactly(redisSpanAttributes("GET", "GET foo")), @@ -180,13 +180,16 @@ void commandWithNoArguments() throws Exception { trace -> trace.hasSpansSatisfyingExactly( span -> - span.hasName("SET") + span.hasName(emitStableDatabaseSemconv() ? "SET " + host + ":" + port : "SET") .hasKind(SpanKind.CLIENT) .hasAttributesSatisfyingExactly(redisSpanAttributes("SET", "SET foo ?"))), trace -> trace.hasSpansSatisfyingExactly( span -> - span.hasName("RANDOMKEY") + span.hasName( + emitStableDatabaseSemconv() + ? "RANDOMKEY " + host + ":" + port + : "RANDOMKEY") .hasKind(SpanKind.CLIENT) .hasAttributesSatisfyingExactly( redisSpanAttributes("RANDOMKEY", "RANDOMKEY")))); diff --git a/instrumentation/xxl-job/xxl-job-2.3.0/javaagent/src/xxlJob33Test/java/io/opentelemetry/javaagent/instrumentation/xxljob/v3_3_0/XxlJobTest.java b/instrumentation/xxl-job/xxl-job-2.3.0/javaagent/src/xxlJob33Test/java/io/opentelemetry/javaagent/instrumentation/xxljob/v3_3_0/XxlJobTest.java index b58dc7980118..7ccf60b71971 100644 --- a/instrumentation/xxl-job/xxl-job-2.3.0/javaagent/src/xxlJob33Test/java/io/opentelemetry/javaagent/instrumentation/xxljob/v3_3_0/XxlJobTest.java +++ b/instrumentation/xxl-job/xxl-job-2.3.0/javaagent/src/xxlJob33Test/java/io/opentelemetry/javaagent/instrumentation/xxljob/v3_3_0/XxlJobTest.java @@ -9,6 +9,7 @@ import static io.opentelemetry.instrumentation.xxljob.common.v1_9_2.XxlJobTestingConstants.GLUE_JOB_GROOVY_SOURCE; import static io.opentelemetry.instrumentation.xxljob.common.v1_9_2.XxlJobTestingConstants.GLUE_JOB_SHELL_SCRIPT; +import com.xxl.job.core.executor.XxlJobExecutor; import com.xxl.job.core.glue.GlueFactory; import com.xxl.job.core.glue.GlueTypeEnum; import com.xxl.job.core.handler.IJobHandler; @@ -18,6 +19,8 @@ import com.xxl.job.core.openapi.model.TriggerRequest; import com.xxl.job.core.thread.JobThread; import io.opentelemetry.instrumentation.xxljob.common.v1_9_2.AbstractXxlJobTest; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; class XxlJobTest extends AbstractXxlJobTest { @@ -34,6 +37,7 @@ class XxlJobTest extends AbstractXxlJobTest { private static final ScriptJobHandler scriptJobHandler = new ScriptJobHandler( 2, DEFAULT_GLUE_UPDATE_TIME, GLUE_JOB_SHELL_SCRIPT, GlueTypeEnum.GLUE_SHELL); + private static final XxlJobExecutor xxlJobExecutor = new XxlJobExecutor(); private static IJobHandler createGroovyHandler() { try { @@ -43,6 +47,16 @@ private static IJobHandler createGroovyHandler() { } } + @BeforeAll + static void start() throws Exception { + xxlJobExecutor.start(); + } + + @AfterAll + static void stop() { + xxlJobExecutor.destroy(); + } + @Override protected String getPackageName() { return "io.opentelemetry.javaagent.instrumentation.xxljob.v3_3_0";