diff --git a/instrumentation/elasticsearch/elasticsearch-rest-5.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/elasticsearch/rest/v5_0/ElasticsearchRest5Test.java b/instrumentation/elasticsearch/elasticsearch-rest-5.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/elasticsearch/rest/v5_0/ElasticsearchRest5Test.java index 92db87193d90..e83218990400 100644 --- a/instrumentation/elasticsearch/elasticsearch-rest-5.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/elasticsearch/rest/v5_0/ElasticsearchRest5Test.java +++ b/instrumentation/elasticsearch/elasticsearch-rest-5.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/elasticsearch/rest/v5_0/ElasticsearchRest5Test.java @@ -5,17 +5,20 @@ package io.opentelemetry.javaagent.instrumentation.elasticsearch.rest.v5_0; +import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitStableDatabaseSemconv; import static io.opentelemetry.instrumentation.testing.junit.db.DbClientMetricsTestUtil.assertDurationMetric; import static io.opentelemetry.instrumentation.testing.junit.db.SemconvStabilityUtil.maybeStable; import static io.opentelemetry.instrumentation.testing.junit.service.SemconvServiceStabilityUtil.maybeStablePeerService; import static io.opentelemetry.instrumentation.testing.util.TestLatestDeps.testLatestDeps; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; +import static io.opentelemetry.semconv.DbAttributes.DB_OPERATION_NAME; import static io.opentelemetry.semconv.HttpAttributes.HTTP_REQUEST_METHOD; import static io.opentelemetry.semconv.HttpAttributes.HTTP_RESPONSE_STATUS_CODE; import static io.opentelemetry.semconv.NetworkAttributes.NETWORK_PROTOCOL_VERSION; import static io.opentelemetry.semconv.ServerAttributes.SERVER_ADDRESS; import static io.opentelemetry.semconv.ServerAttributes.SERVER_PORT; import static io.opentelemetry.semconv.UrlAttributes.URL_FULL; +import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_OPERATION; import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_SYSTEM; import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_SYSTEM_NAME; import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DbSystemNameIncubatingValues.ELASTICSEARCH; @@ -99,11 +102,14 @@ void elasticsearchStatus() throws IOException { trace -> trace.hasSpansSatisfyingExactly( span -> - span.hasName("GET") + span.hasName(emitStableDatabaseSemconv() ? "cluster.health" : "GET") .hasKind(SpanKind.CLIENT) .hasNoParent() .hasAttributesSatisfyingExactly( equalTo(maybeStable(DB_SYSTEM), ELASTICSEARCH), + equalTo( + maybeStable(DB_OPERATION), + emitStableDatabaseSemconv() ? "cluster.health" : null), equalTo(HTTP_REQUEST_METHOD, "GET"), equalTo(SERVER_ADDRESS, httpHost.getHostName()), equalTo(SERVER_PORT, httpHost.getPort()), @@ -125,6 +131,7 @@ void elasticsearchStatus() throws IOException { testing, "io.opentelemetry.elasticsearch-rest-5.0", DB_SYSTEM_NAME, + DB_OPERATION_NAME, SERVER_ADDRESS, SERVER_PORT); } @@ -174,11 +181,14 @@ public void onFailure(Exception e) { trace.hasSpansSatisfyingExactly( span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), span -> - span.hasName("GET") + span.hasName(emitStableDatabaseSemconv() ? "cluster.health" : "GET") .hasKind(SpanKind.CLIENT) .hasParent(trace.getSpan(0)) .hasAttributesSatisfyingExactly( equalTo(maybeStable(DB_SYSTEM), ELASTICSEARCH), + equalTo( + maybeStable(DB_OPERATION), + emitStableDatabaseSemconv() ? "cluster.health" : null), equalTo(HTTP_REQUEST_METHOD, "GET"), equalTo(SERVER_ADDRESS, httpHost.getHostName()), equalTo(SERVER_PORT, httpHost.getPort()), diff --git a/instrumentation/elasticsearch/elasticsearch-rest-6.4/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/elasticsearch/rest/v6_4/ElasticsearchRest6Test.java b/instrumentation/elasticsearch/elasticsearch-rest-6.4/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/elasticsearch/rest/v6_4/ElasticsearchRest6Test.java index b9e6298c8404..7d121d913ed5 100644 --- a/instrumentation/elasticsearch/elasticsearch-rest-6.4/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/elasticsearch/rest/v6_4/ElasticsearchRest6Test.java +++ b/instrumentation/elasticsearch/elasticsearch-rest-6.4/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/elasticsearch/rest/v6_4/ElasticsearchRest6Test.java @@ -5,16 +5,19 @@ package io.opentelemetry.javaagent.instrumentation.elasticsearch.rest.v6_4; +import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitStableDatabaseSemconv; import static io.opentelemetry.instrumentation.testing.junit.db.DbClientMetricsTestUtil.assertDurationMetric; import static io.opentelemetry.instrumentation.testing.junit.db.SemconvStabilityUtil.maybeStable; 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_OPERATION_NAME; import static io.opentelemetry.semconv.HttpAttributes.HTTP_REQUEST_METHOD; import static io.opentelemetry.semconv.HttpAttributes.HTTP_RESPONSE_STATUS_CODE; import static io.opentelemetry.semconv.NetworkAttributes.NETWORK_PROTOCOL_VERSION; import static io.opentelemetry.semconv.ServerAttributes.SERVER_ADDRESS; import static io.opentelemetry.semconv.ServerAttributes.SERVER_PORT; import static io.opentelemetry.semconv.UrlAttributes.URL_FULL; +import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_OPERATION; import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_SYSTEM; import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_SYSTEM_NAME; import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DbSystemNameIncubatingValues.ELASTICSEARCH; @@ -93,11 +96,14 @@ void elasticsearchStatus() throws IOException { trace -> trace.hasSpansSatisfyingExactly( span -> - span.hasName("GET") + span.hasName(emitStableDatabaseSemconv() ? "cluster.health" : "GET") .hasKind(SpanKind.CLIENT) .hasNoParent() .hasAttributesSatisfyingExactly( equalTo(maybeStable(DB_SYSTEM), ELASTICSEARCH), + equalTo( + maybeStable(DB_OPERATION), + emitStableDatabaseSemconv() ? "cluster.health" : null), equalTo(HTTP_REQUEST_METHOD, "GET"), equalTo(SERVER_ADDRESS, httpHost.getHostName()), equalTo(SERVER_PORT, httpHost.getPort()), @@ -119,6 +125,7 @@ void elasticsearchStatus() throws IOException { testing, "io.opentelemetry.elasticsearch-rest-6.4", DB_SYSTEM_NAME, + DB_OPERATION_NAME, SERVER_ADDRESS, SERVER_PORT); } @@ -168,11 +175,14 @@ public void onFailure(Exception e) { trace.hasSpansSatisfyingExactly( span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), span -> - span.hasName("GET") + span.hasName(emitStableDatabaseSemconv() ? "cluster.health" : "GET") .hasKind(SpanKind.CLIENT) .hasParent(trace.getSpan(0)) .hasAttributesSatisfyingExactly( equalTo(maybeStable(DB_SYSTEM), ELASTICSEARCH), + equalTo( + maybeStable(DB_OPERATION), + emitStableDatabaseSemconv() ? "cluster.health" : null), equalTo(HTTP_REQUEST_METHOD, "GET"), equalTo(SERVER_ADDRESS, httpHost.getHostName()), equalTo(SERVER_PORT, httpHost.getPort()), diff --git a/instrumentation/elasticsearch/elasticsearch-rest-7.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/elasticsearch/rest/v7_0/ElasticsearchRest7Test.java b/instrumentation/elasticsearch/elasticsearch-rest-7.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/elasticsearch/rest/v7_0/ElasticsearchRest7Test.java index 145ea48257e9..dcb6da7e5306 100644 --- a/instrumentation/elasticsearch/elasticsearch-rest-7.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/elasticsearch/rest/v7_0/ElasticsearchRest7Test.java +++ b/instrumentation/elasticsearch/elasticsearch-rest-7.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/elasticsearch/rest/v7_0/ElasticsearchRest7Test.java @@ -5,17 +5,20 @@ package io.opentelemetry.javaagent.instrumentation.elasticsearch.rest.v7_0; +import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitStableDatabaseSemconv; import static io.opentelemetry.instrumentation.testing.GlobalTraceUtil.runWithSpan; import static io.opentelemetry.instrumentation.testing.junit.db.DbClientMetricsTestUtil.assertDurationMetric; import static io.opentelemetry.instrumentation.testing.junit.db.SemconvStabilityUtil.maybeStable; 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_OPERATION_NAME; import static io.opentelemetry.semconv.HttpAttributes.HTTP_REQUEST_METHOD; import static io.opentelemetry.semconv.HttpAttributes.HTTP_RESPONSE_STATUS_CODE; import static io.opentelemetry.semconv.NetworkAttributes.NETWORK_PROTOCOL_VERSION; import static io.opentelemetry.semconv.ServerAttributes.SERVER_ADDRESS; import static io.opentelemetry.semconv.ServerAttributes.SERVER_PORT; import static io.opentelemetry.semconv.UrlAttributes.URL_FULL; +import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_OPERATION; import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_SYSTEM; import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_SYSTEM_NAME; import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DbSystemNameIncubatingValues.ELASTICSEARCH; @@ -90,11 +93,14 @@ void elasticsearchStatus() throws IOException { trace -> trace.hasSpansSatisfyingExactly( span -> - span.hasName("GET") + span.hasName(emitStableDatabaseSemconv() ? "cluster.health" : "GET") .hasKind(SpanKind.CLIENT) .hasNoParent() .hasAttributesSatisfyingExactly( equalTo(maybeStable(DB_SYSTEM), ELASTICSEARCH), + equalTo( + maybeStable(DB_OPERATION), + emitStableDatabaseSemconv() ? "cluster.health" : null), equalTo(HTTP_REQUEST_METHOD, "GET"), equalTo(SERVER_ADDRESS, httpHost.getHostName()), equalTo(SERVER_PORT, httpHost.getPort()), @@ -116,6 +122,7 @@ void elasticsearchStatus() throws IOException { testing, "io.opentelemetry.elasticsearch-rest-7.0", DB_SYSTEM_NAME, + DB_OPERATION_NAME, SERVER_ADDRESS, SERVER_PORT); } @@ -166,11 +173,14 @@ public void onFailure(Exception e) { trace.hasSpansSatisfyingExactly( span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), span -> - span.hasName("GET") + span.hasName(emitStableDatabaseSemconv() ? "cluster.health" : "GET") .hasKind(SpanKind.CLIENT) .hasParent(trace.getSpan(0)) .hasAttributesSatisfyingExactly( equalTo(maybeStable(DB_SYSTEM), ELASTICSEARCH), + equalTo( + maybeStable(DB_OPERATION), + emitStableDatabaseSemconv() ? "cluster.health" : null), equalTo(HTTP_REQUEST_METHOD, "GET"), equalTo(SERVER_ADDRESS, httpHost.getHostName()), equalTo(SERVER_PORT, httpHost.getPort()), diff --git a/instrumentation/elasticsearch/elasticsearch-rest-7.0/library/src/test/java/io/opentelemetry/instrumentation/elasticsearch/rest/v7_0/ElasticsearchRest7Test.java b/instrumentation/elasticsearch/elasticsearch-rest-7.0/library/src/test/java/io/opentelemetry/instrumentation/elasticsearch/rest/v7_0/ElasticsearchRest7Test.java index 1676c3191e44..b301c2d9b871 100644 --- a/instrumentation/elasticsearch/elasticsearch-rest-7.0/library/src/test/java/io/opentelemetry/instrumentation/elasticsearch/rest/v7_0/ElasticsearchRest7Test.java +++ b/instrumentation/elasticsearch/elasticsearch-rest-7.0/library/src/test/java/io/opentelemetry/instrumentation/elasticsearch/rest/v7_0/ElasticsearchRest7Test.java @@ -5,6 +5,7 @@ package io.opentelemetry.instrumentation.elasticsearch.rest.v7_0; +import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitStableDatabaseSemconv; import static io.opentelemetry.instrumentation.testing.GlobalTraceUtil.runWithSpan; import static io.opentelemetry.instrumentation.testing.junit.db.SemconvStabilityUtil.maybeStable; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; @@ -12,6 +13,7 @@ import static io.opentelemetry.semconv.ServerAttributes.SERVER_ADDRESS; import static io.opentelemetry.semconv.ServerAttributes.SERVER_PORT; import static io.opentelemetry.semconv.UrlAttributes.URL_FULL; +import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_OPERATION; import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_SYSTEM; import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DbSystemNameIncubatingValues.ELASTICSEARCH; import static java.util.concurrent.TimeUnit.SECONDS; @@ -87,11 +89,14 @@ void elasticsearchStatus() throws IOException { trace -> trace.hasSpansSatisfyingExactly( span -> - span.hasName("GET") + span.hasName(emitStableDatabaseSemconv() ? "cluster.health" : "GET") .hasKind(SpanKind.CLIENT) .hasNoParent() .hasAttributesSatisfyingExactly( equalTo(maybeStable(DB_SYSTEM), ELASTICSEARCH), + equalTo( + maybeStable(DB_OPERATION), + emitStableDatabaseSemconv() ? "cluster.health" : null), equalTo(HTTP_REQUEST_METHOD, "GET"), equalTo(SERVER_ADDRESS, httpHost.getHostName()), equalTo(SERVER_PORT, httpHost.getPort()), @@ -143,11 +148,14 @@ public void onFailure(Exception e) { trace.hasSpansSatisfyingExactly( span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), span -> - span.hasName("GET") + span.hasName(emitStableDatabaseSemconv() ? "cluster.health" : "GET") .hasKind(SpanKind.CLIENT) .hasParent(trace.getSpan(0)) .hasAttributesSatisfyingExactly( equalTo(maybeStable(DB_SYSTEM), ELASTICSEARCH), + equalTo( + maybeStable(DB_OPERATION), + emitStableDatabaseSemconv() ? "cluster.health" : null), equalTo(HTTP_REQUEST_METHOD, "GET"), equalTo(SERVER_ADDRESS, httpHost.getHostName()), equalTo(SERVER_PORT, httpHost.getPort()), diff --git a/instrumentation/elasticsearch/elasticsearch-rest-common-5.0/library/src/main/java/io/opentelemetry/instrumentation/elasticsearch/rest/common/v5_0/internal/ElasticsearchDbAttributesGetter.java b/instrumentation/elasticsearch/elasticsearch-rest-common-5.0/library/src/main/java/io/opentelemetry/instrumentation/elasticsearch/rest/common/v5_0/internal/ElasticsearchDbAttributesGetter.java index 3db28a748748..e36a83daa2d0 100644 --- a/instrumentation/elasticsearch/elasticsearch-rest-common-5.0/library/src/main/java/io/opentelemetry/instrumentation/elasticsearch/rest/common/v5_0/internal/ElasticsearchDbAttributesGetter.java +++ b/instrumentation/elasticsearch/elasticsearch-rest-common-5.0/library/src/main/java/io/opentelemetry/instrumentation/elasticsearch/rest/common/v5_0/internal/ElasticsearchDbAttributesGetter.java @@ -73,10 +73,139 @@ public String getDbQueryText(ElasticsearchRestRequest request) { @Override @Nullable public String getDbOperationName(ElasticsearchRestRequest request) { + ElasticsearchEndpointDefinition endpointDefinition = request.getEndpointDefinition(); + if (endpointDefinition != null) { + return endpointDefinition.getEndpointName(); + } + return inferOperationName(request.getMethod(), request.getEndpoint()); + } + + @Deprecated // to be removed in 3.0 + @Override + @Nullable + public String getDbOperation(ElasticsearchRestRequest request) { ElasticsearchEndpointDefinition endpointDefinition = request.getEndpointDefinition(); return endpointDefinition != null ? endpointDefinition.getEndpointName() : null; } + @Nullable + static String inferOperationName(String method, String endpoint) { + int end = endpoint.indexOf('?'); + if (end < 0) { + end = endpoint.length(); + } + + int start = 0; + if (start < end && endpoint.charAt(start) == '/') { + // low-level REST client endpoints conventionally start with a single leading slash + start++; + } + + // Only the first three path segments are needed to infer the operation name, so extract them + // in a single pass instead of allocating an array for the whole path on this hot path. + String segment0 = null; + String segment1 = null; + String segment2 = null; + int segmentStart = start; + int found = 0; + for (int i = start; i <= end && found < 3; i++) { + if (i == end || endpoint.charAt(i) == '/') { + String segment = endpoint.substring(segmentStart, i); + switch (found++) { + case 0: + segment0 = segment; + break; + case 1: + segment1 = segment; + break; + default: + segment2 = segment; + break; + } + segmentStart = i + 1; + } + } + + if (segment0 == null || segment0.isEmpty()) { + return null; + } + if (segment0.startsWith("_")) { + return inferOperationNameFromApiSegments(method, segment0, segment1); + } + if (segment1 != null && segment1.startsWith("_")) { + return inferOperationNameFromApiSegments(method, segment1, segment2); + } + return null; + } + + @Nullable + private static String inferOperationNameFromApiSegments( + String method, String apiSegmentRaw, @Nullable String nextSegment) { + String apiSegment = stripLeadingUnderscores(apiSegmentRaw); + if (apiSegment.isEmpty()) { + return null; + } + String documentOperation = inferDocumentOperationName(method, apiSegment); + if (documentOperation != null) { + return documentOperation; + } + if (isGroupedApi(apiSegment) + && nextSegment != null + && !nextSegment.isEmpty() + && !nextSegment.startsWith("_")) { + return apiSegment + "." + nextSegment; + } + return apiSegment; + } + + @Nullable + private static String inferDocumentOperationName(String method, String apiSegment) { + switch (apiSegment) { + case "create": + case "update": + return apiSegment; + case "doc": + return inferDocOperationName(method); + default: + return null; + } + } + + @Nullable + private static String inferDocOperationName(String method) { + switch (method) { + case "DELETE": + return "delete"; + case "GET": + return "get"; + case "POST": + case "PUT": + return "index"; + default: + return null; + } + } + + private static boolean isGroupedApi(String apiSegment) { + switch (apiSegment) { + case "cat": + case "cluster": + case "nodes": + case "snapshot": + case "tasks": + return true; + default: + return false; + } + } + + private static String stripLeadingUnderscores(String value) { + while (value.startsWith("_")) { + value = value.substring(1); + } + return value; + } + @Override @Nullable public String getErrorType( diff --git a/instrumentation/elasticsearch/elasticsearch-rest-common-5.0/library/src/main/java/io/opentelemetry/instrumentation/elasticsearch/rest/common/v5_0/internal/ElasticsearchSpanNameExtractor.java b/instrumentation/elasticsearch/elasticsearch-rest-common-5.0/library/src/main/java/io/opentelemetry/instrumentation/elasticsearch/rest/common/v5_0/internal/ElasticsearchSpanNameExtractor.java index 8184a3ea400d..59274986053c 100644 --- a/instrumentation/elasticsearch/elasticsearch-rest-common-5.0/library/src/main/java/io/opentelemetry/instrumentation/elasticsearch/rest/common/v5_0/internal/ElasticsearchSpanNameExtractor.java +++ b/instrumentation/elasticsearch/elasticsearch-rest-common-5.0/library/src/main/java/io/opentelemetry/instrumentation/elasticsearch/rest/common/v5_0/internal/ElasticsearchSpanNameExtractor.java @@ -5,6 +5,8 @@ package io.opentelemetry.instrumentation.elasticsearch.rest.common.v5_0.internal; +import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitStableDatabaseSemconv; + import io.opentelemetry.instrumentation.api.instrumenter.SpanNameExtractor; /** @@ -19,9 +21,13 @@ final class ElasticsearchSpanNameExtractor implements SpanNameExtractor