diff --git a/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/SpanDescriptionExtractor.java b/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/SpanDescriptionExtractor.java index b66555d68c9..c9927ae6b3f 100644 --- a/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/SpanDescriptionExtractor.java +++ b/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/SpanDescriptionExtractor.java @@ -6,8 +6,10 @@ import io.opentelemetry.semconv.HttpAttributes; import io.opentelemetry.semconv.UrlAttributes; import io.opentelemetry.semconv.incubating.DbIncubatingAttributes; +import io.opentelemetry.semconv.incubating.GraphqlIncubatingAttributes; import io.opentelemetry.semconv.incubating.HttpIncubatingAttributes; import io.sentry.protocol.TransactionNameSource; +import java.util.Arrays; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -15,9 +17,18 @@ @ApiStatus.Internal public final class SpanDescriptionExtractor { - @SuppressWarnings("deprecation") public @NotNull OtelSpanInfo extractSpanInfo( final @NotNull SpanData otelSpan, final @Nullable IOtelSpanWrapper sentrySpan) { + OtelSpanInfo spanInfo = extractSpanInfoInternal(otelSpan); + if (spanInfo != null) { + return spanInfo; + } + + return defaultInfo(otelSpan, sentrySpan); + } + + @SuppressWarnings("deprecation") + private @Nullable OtelSpanInfo extractSpanInfoInternal(final @NotNull SpanData otelSpan) { final @NotNull Attributes attributes = otelSpan.getAttributes(); final @Nullable String httpMethod = attributes.get(HttpAttributes.HTTP_REQUEST_METHOD); @@ -30,6 +41,17 @@ public final class SpanDescriptionExtractor { return descriptionForDbSystem(otelSpan); } + final @Nullable String graphqlOperationType = + attributes.get(GraphqlIncubatingAttributes.GRAPHQL_OPERATION_TYPE); + if (graphqlOperationType != null) { + return descriptionForGraphql(otelSpan); + } + + return null; + } + + private @NotNull OtelSpanInfo defaultInfo( + final @NotNull SpanData otelSpan, final @Nullable IOtelSpanWrapper sentrySpan) { final @NotNull String name = otelSpan.getName(); final @Nullable String maybeDescription = sentrySpan != null ? sentrySpan.getDescription() : name; @@ -105,4 +127,25 @@ private OtelSpanInfo descriptionForDbSystem(final @NotNull SpanData otelSpan) { return new OtelSpanInfo("db", otelSpan.getName(), TransactionNameSource.TASK); } + + private @Nullable OtelSpanInfo descriptionForGraphql(final @NotNull SpanData otelSpan) { + final @NotNull String name = otelSpan.getName(); + // do not replace span name set by customer + if (!Arrays.asList("query", "mutation", "subscription").contains(name)) { + return null; + } + final @NotNull Attributes attributes = otelSpan.getAttributes(); + @Nullable + String graphqlOperationType = + attributes.get(GraphqlIncubatingAttributes.GRAPHQL_OPERATION_TYPE); + @Nullable + String graphqlOperationName = + attributes.get(GraphqlIncubatingAttributes.GRAPHQL_OPERATION_NAME); + if (graphqlOperationType != null && graphqlOperationName != null) { + String description = graphqlOperationType + " " + graphqlOperationName; + return new OtelSpanInfo(description, description, TransactionNameSource.TASK); + } + + return null; + } } diff --git a/sentry-opentelemetry/sentry-opentelemetry-core/src/test/kotlin/SpanDescriptionExtractorTest.kt b/sentry-opentelemetry/sentry-opentelemetry-core/src/test/kotlin/SpanDescriptionExtractorTest.kt index af04914e278..fb5920b174d 100644 --- a/sentry-opentelemetry/sentry-opentelemetry-core/src/test/kotlin/SpanDescriptionExtractorTest.kt +++ b/sentry-opentelemetry/sentry-opentelemetry-core/src/test/kotlin/SpanDescriptionExtractorTest.kt @@ -10,6 +10,7 @@ import io.opentelemetry.sdk.trace.data.SpanData import io.opentelemetry.semconv.HttpAttributes import io.opentelemetry.semconv.UrlAttributes import io.opentelemetry.semconv.incubating.DbIncubatingAttributes +import io.opentelemetry.semconv.incubating.GraphqlIncubatingAttributes import io.opentelemetry.semconv.incubating.HttpIncubatingAttributes import io.sentry.protocol.TransactionNameSource import kotlin.test.Test @@ -253,6 +254,39 @@ class SpanDescriptionExtractorTest { assertEquals(TransactionNameSource.CUSTOM, info.transactionNameSource) } + @Test + fun `sets op to graphql for span with graphql operation type`() { + givenAttributes( + mapOf( + GraphqlIncubatingAttributes.GRAPHQL_OPERATION_TYPE to "query", + GraphqlIncubatingAttributes.GRAPHQL_OPERATION_NAME to "GreetingQuery", + ) + ) + + val info = whenExtractingSpanInfo() + + assertEquals("query GreetingQuery", info.op) + assertEquals("query GreetingQuery", info.description) + assertEquals(TransactionNameSource.TASK, info.transactionNameSource) + } + + @Test + fun `does not set op to graphql for span with graphql operation type if span name has been overridden`() { + givenAttributes( + mapOf( + GraphqlIncubatingAttributes.GRAPHQL_OPERATION_TYPE to "query", + GraphqlIncubatingAttributes.GRAPHQL_OPERATION_NAME to "GreetingQuery", + ) + ) + givenSpanName("my-custom-span-name") + + val info = whenExtractingSpanInfo() + + assertEquals("my-custom-span-name", info.op) + assertEquals("my-custom-span-name", info.description) + assertEquals(TransactionNameSource.CUSTOM, info.transactionNameSource) + } + private fun createSpanContext( isRemote: Boolean, traceId: String = "f9118105af4a2d42b4124532cd1065ff",