From f92bf74bfeceec9efc35441b82696944f4f5a8f5 Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Fri, 21 Feb 2025 09:48:09 +0100 Subject: [PATCH 1/5] Check tracePropagationTargets in OpenTelemetry propagator --- .../api/sentry-opentelemetry-bootstrap.api | 2 + .../opentelemetry/IOtelSpanWrapper.java | 5 + .../OtelStrongRefSpanWrapper.java | 7 ++ .../api/sentry-opentelemetry-core.api | 2 + .../OpenTelemetryAttributesExtractor.java | 30 +++-- .../opentelemetry/OtelSentryPropagator.java | 17 ++- .../sentry/opentelemetry/OtelSpanWrapper.java | 4 +- .../OpenTelemetryAttributesExtractorTest.kt | 116 ++++++++++++++++++ .../spring/boot/jakarta/ApiService.java | 23 ++++ .../spring/boot/jakarta/AsyncService.java | 48 ++++++++ 10 files changed, 240 insertions(+), 14 deletions(-) create mode 100644 sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/main/java/io/sentry/samples/spring/boot/jakarta/ApiService.java create mode 100644 sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/main/java/io/sentry/samples/spring/boot/jakarta/AsyncService.java diff --git a/sentry-opentelemetry/sentry-opentelemetry-bootstrap/api/sentry-opentelemetry-bootstrap.api b/sentry-opentelemetry/sentry-opentelemetry-bootstrap/api/sentry-opentelemetry-bootstrap.api index adb976adc05..e10514e3602 100644 --- a/sentry-opentelemetry/sentry-opentelemetry-bootstrap/api/sentry-opentelemetry-bootstrap.api +++ b/sentry-opentelemetry/sentry-opentelemetry-bootstrap/api/sentry-opentelemetry-bootstrap.api @@ -2,6 +2,7 @@ public abstract interface class io/sentry/opentelemetry/IOtelSpanWrapper : io/se public abstract fun getData ()Ljava/util/Map; public abstract fun getMeasurements ()Ljava/util/Map; public abstract fun getScopes ()Lio/sentry/IScopes; + public abstract fun getSpan ()Lio/opentelemetry/sdk/trace/ReadWriteSpan; public abstract fun getTags ()Ljava/util/Map; public abstract fun getTraceId ()Lio/sentry/protocol/SentryId; public abstract fun getTransactionName ()Ljava/lang/String; @@ -54,6 +55,7 @@ public final class io/sentry/opentelemetry/OtelStrongRefSpanWrapper : io/sentry/ public fun getOperation ()Ljava/lang/String; public fun getSamplingDecision ()Lio/sentry/TracesSamplingDecision; public fun getScopes ()Lio/sentry/IScopes; + public fun getSpan ()Lio/opentelemetry/sdk/trace/ReadWriteSpan; public fun getSpanContext ()Lio/sentry/SpanContext; public fun getStartDate ()Lio/sentry/SentryDate; public fun getStatus ()Lio/sentry/SpanStatus; diff --git a/sentry-opentelemetry/sentry-opentelemetry-bootstrap/src/main/java/io/sentry/opentelemetry/IOtelSpanWrapper.java b/sentry-opentelemetry/sentry-opentelemetry-bootstrap/src/main/java/io/sentry/opentelemetry/IOtelSpanWrapper.java index 0184db0eabe..c271751e717 100644 --- a/sentry-opentelemetry/sentry-opentelemetry-bootstrap/src/main/java/io/sentry/opentelemetry/IOtelSpanWrapper.java +++ b/sentry-opentelemetry/sentry-opentelemetry-bootstrap/src/main/java/io/sentry/opentelemetry/IOtelSpanWrapper.java @@ -1,6 +1,7 @@ package io.sentry.opentelemetry; import io.opentelemetry.context.Context; +import io.opentelemetry.sdk.trace.ReadWriteSpan; import io.sentry.IScopes; import io.sentry.ISpan; import io.sentry.protocol.MeasurementValue; @@ -47,4 +48,8 @@ public interface IOtelSpanWrapper extends ISpan { @NotNull Context storeInContext(Context context); + + @ApiStatus.Internal + @Nullable + ReadWriteSpan getSpan(); } diff --git a/sentry-opentelemetry/sentry-opentelemetry-bootstrap/src/main/java/io/sentry/opentelemetry/OtelStrongRefSpanWrapper.java b/sentry-opentelemetry/sentry-opentelemetry-bootstrap/src/main/java/io/sentry/opentelemetry/OtelStrongRefSpanWrapper.java index f2ea37b3350..d4b3be788c7 100644 --- a/sentry-opentelemetry/sentry-opentelemetry-bootstrap/src/main/java/io/sentry/opentelemetry/OtelStrongRefSpanWrapper.java +++ b/sentry-opentelemetry/sentry-opentelemetry-bootstrap/src/main/java/io/sentry/opentelemetry/OtelStrongRefSpanWrapper.java @@ -2,6 +2,7 @@ import io.opentelemetry.api.trace.Span; import io.opentelemetry.context.Context; +import io.opentelemetry.sdk.trace.ReadWriteSpan; import io.sentry.BaggageHeader; import io.sentry.IScopes; import io.sentry.ISentryLifecycleToken; @@ -303,4 +304,10 @@ public void setContext(@NotNull String key, @NotNull Object context) { public @NotNull ISentryLifecycleToken makeCurrent() { return delegate.makeCurrent(); } + + @ApiStatus.Internal + @Override + public @Nullable ReadWriteSpan getSpan() { + return delegate.getSpan(); + } } diff --git a/sentry-opentelemetry/sentry-opentelemetry-core/api/sentry-opentelemetry-core.api b/sentry-opentelemetry/sentry-opentelemetry-core/api/sentry-opentelemetry-core.api index 1e9bb60416b..7260b7accf2 100644 --- a/sentry-opentelemetry/sentry-opentelemetry-core/api/sentry-opentelemetry-core.api +++ b/sentry-opentelemetry/sentry-opentelemetry-core/api/sentry-opentelemetry-core.api @@ -1,6 +1,7 @@ public final class io/sentry/opentelemetry/OpenTelemetryAttributesExtractor { public fun ()V public fun extract (Lio/opentelemetry/sdk/trace/data/SpanData;Lio/sentry/ISpan;Lio/sentry/IScope;)V + public fun extractUrl (Lio/opentelemetry/api/common/Attributes;)Ljava/lang/String; } public final class io/sentry/opentelemetry/OpenTelemetryLinkErrorEventProcessor : io/sentry/EventProcessor { @@ -63,6 +64,7 @@ public final class io/sentry/opentelemetry/OtelSpanWrapper : io/sentry/opentelem public fun getOperation ()Ljava/lang/String; public fun getSamplingDecision ()Lio/sentry/TracesSamplingDecision; public fun getScopes ()Lio/sentry/IScopes; + public fun getSpan ()Lio/opentelemetry/sdk/trace/ReadWriteSpan; public fun getSpanContext ()Lio/sentry/SpanContext; public fun getStartDate ()Lio/sentry/SentryDate; public fun getStatus ()Lio/sentry/SpanStatus; diff --git a/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/OpenTelemetryAttributesExtractor.java b/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/OpenTelemetryAttributesExtractor.java index 431b4d274e3..65757765184 100644 --- a/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/OpenTelemetryAttributesExtractor.java +++ b/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/OpenTelemetryAttributesExtractor.java @@ -24,7 +24,8 @@ public void extract( addRequestAttributesToScope(attributes, scope); } - private void addRequestAttributesToScope(Attributes attributes, IScope scope) { + private void addRequestAttributesToScope( + final @NotNull Attributes attributes, final @NotNull IScope scope) { if (scope.getRequest() == null) { scope.setRequest(new Request()); } @@ -36,20 +37,13 @@ private void addRequestAttributesToScope(Attributes attributes, IScope scope) { } if (request.getUrl() == null) { - final @Nullable String urlFull = attributes.get(UrlAttributes.URL_FULL); - if (urlFull != null) { - final @NotNull UrlUtils.UrlDetails urlDetails = UrlUtils.parse(urlFull); + final @Nullable String url = extractUrl(attributes); + if (url != null) { + final @NotNull UrlUtils.UrlDetails urlDetails = UrlUtils.parse(url); urlDetails.applyToRequest(request); } } - if (request.getUrl() == null) { - final String urlString = buildUrlString(attributes); - if (!urlString.isEmpty()) { - request.setUrl(urlString); - } - } - if (request.getQueryString() == null) { final @Nullable String query = attributes.get(UrlAttributes.URL_QUERY); if (query != null) { @@ -59,6 +53,20 @@ private void addRequestAttributesToScope(Attributes attributes, IScope scope) { } } + public @Nullable String extractUrl(final @NotNull Attributes attributes) { + final @Nullable String urlFull = attributes.get(UrlAttributes.URL_FULL); + if (urlFull != null) { + return urlFull; + } + + final String urlString = buildUrlString(attributes); + if (!urlString.isEmpty()) { + return urlString; + } + + return null; + } + private @NotNull String buildUrlString(final @NotNull Attributes attributes) { final @Nullable String scheme = attributes.get(UrlAttributes.URL_SCHEME); final @Nullable String serverAddress = attributes.get(ServerAttributes.SERVER_ADDRESS); diff --git a/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/OtelSentryPropagator.java b/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/OtelSentryPropagator.java index fc2e3d426b7..608f650d9b5 100644 --- a/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/OtelSentryPropagator.java +++ b/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/OtelSentryPropagator.java @@ -10,6 +10,7 @@ import io.opentelemetry.context.propagation.TextMapGetter; import io.opentelemetry.context.propagation.TextMapPropagator; import io.opentelemetry.context.propagation.TextMapSetter; +import io.opentelemetry.sdk.trace.ReadWriteSpan; import io.sentry.Baggage; import io.sentry.BaggageHeader; import io.sentry.IScopes; @@ -32,6 +33,8 @@ public final class OtelSentryPropagator implements TextMapPropagator { Arrays.asList(SentryTraceHeader.SENTRY_TRACE_HEADER, BaggageHeader.BAGGAGE_HEADER); private final @NotNull SentryWeakSpanStorage spanStorage = SentryWeakSpanStorage.getInstance(); private final @NotNull IScopes scopes; + private final @NotNull OpenTelemetryAttributesExtractor attributesExtractor = + new OpenTelemetryAttributesExtractor(); public OtelSentryPropagator() { this(ScopesAdapter.getInstance()); @@ -73,9 +76,11 @@ public void inject(final Context context, final C carrier, final TextMapSett return; } - // TODO can we use traceIfAllowed? do we have the URL here? need to access span attrs + final @Nullable String url = getUrl(sentrySpan); final @Nullable TracingUtils.TracingHeaders tracingHeaders = - TracingUtils.trace(scopes, Collections.emptyList(), sentrySpan); + url == null + ? TracingUtils.trace(scopes, Collections.emptyList(), sentrySpan) + : TracingUtils.traceIfAllowed(scopes, url, Collections.emptyList(), sentrySpan); if (tracingHeaders != null) { final @NotNull SentryTraceHeader sentryTraceHeader = tracingHeaders.getSentryTraceHeader(); @@ -87,6 +92,14 @@ public void inject(final Context context, final C carrier, final TextMapSett } } + private @Nullable String getUrl(final @NotNull IOtelSpanWrapper sentrySpan) { + final @Nullable ReadWriteSpan otelReadableSpan = sentrySpan.getSpan(); + if (otelReadableSpan == null) { + return null; + } + return attributesExtractor.extractUrl(otelReadableSpan.getAttributes()); + } + @Override public Context extract( final Context context, final C carrier, final TextMapGetter getter) { diff --git a/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/OtelSpanWrapper.java b/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/OtelSpanWrapper.java index 34f2d2a4d7c..6908e8989b3 100644 --- a/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/OtelSpanWrapper.java +++ b/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/OtelSpanWrapper.java @@ -194,7 +194,9 @@ public OtelSpanWrapper( return context.getSpanId(); } - private @Nullable ReadWriteSpan getSpan() { + @ApiStatus.Internal + @Override + public @Nullable ReadWriteSpan getSpan() { return span.get(); } diff --git a/sentry-opentelemetry/sentry-opentelemetry-core/src/test/kotlin/OpenTelemetryAttributesExtractorTest.kt b/sentry-opentelemetry/sentry-opentelemetry-core/src/test/kotlin/OpenTelemetryAttributesExtractorTest.kt index f962cfa594d..80631406713 100644 --- a/sentry-opentelemetry/sentry-opentelemetry-core/src/test/kotlin/OpenTelemetryAttributesExtractorTest.kt +++ b/sentry-opentelemetry/sentry-opentelemetry-core/src/test/kotlin/OpenTelemetryAttributesExtractorTest.kt @@ -173,6 +173,118 @@ class OpenTelemetryAttributesExtractorTest { thenUrlIsNotSet() } + @Test + fun `returns null if no URL in attributes`() { + givenAttributes(mapOf()) + + val url = whenExtractingUrl() + + assertNull(url) + } + + @Test + fun `returns full URL if present`() { + givenAttributes( + mapOf( + UrlAttributes.URL_FULL to "https://sentry.io/some/path" + ) + ) + + val url = whenExtractingUrl() + + assertEquals("https://sentry.io/some/path", url) + } + + @Test + fun `returns reconstructed URL if attributes present`() { + givenAttributes( + mapOf( + UrlAttributes.URL_SCHEME to "https", + ServerAttributes.SERVER_ADDRESS to "sentry.io", + ServerAttributes.SERVER_PORT to 8082L, + UrlAttributes.URL_PATH to "/some/path" + ) + ) + + val url = whenExtractingUrl() + + assertEquals("https://sentry.io:8082/some/path", url) + } + + @Test + fun `returns reconstructed URL if attributes present without port`() { + givenAttributes( + mapOf( + UrlAttributes.URL_SCHEME to "https", + ServerAttributes.SERVER_ADDRESS to "sentry.io", + UrlAttributes.URL_PATH to "/some/path" + ) + ) + + val url = whenExtractingUrl() + + assertEquals("https://sentry.io/some/path", url) + } + + @Test + fun `returns null URL if scheme missing`() { + givenAttributes( + mapOf( + ServerAttributes.SERVER_ADDRESS to "sentry.io", + ServerAttributes.SERVER_PORT to 8082L, + UrlAttributes.URL_PATH to "/some/path" + ) + ) + + val url = whenExtractingUrl() + + assertNull(url) + } + + @Test + fun `returns null URL if server address missing`() { + givenAttributes( + mapOf( + UrlAttributes.URL_SCHEME to "https", + ServerAttributes.SERVER_PORT to 8082L, + UrlAttributes.URL_PATH to "/some/path" + ) + ) + + val url = whenExtractingUrl() + + assertNull(url) + } + + @Test + fun `returns reconstructed URL if attributes present without port and path`() { + givenAttributes( + mapOf( + UrlAttributes.URL_SCHEME to "https", + ServerAttributes.SERVER_ADDRESS to "sentry.io" + ) + ) + + val url = whenExtractingUrl() + + assertEquals("https://sentry.io", url) + } + + @Test + fun `returns reconstructed URL if attributes present without path`() { + givenAttributes( + mapOf( + UrlAttributes.URL_SCHEME to "https", + ServerAttributes.SERVER_ADDRESS to "sentry.io", + ServerAttributes.SERVER_PORT to 8082L + ) + ) + + val url = whenExtractingUrl() + + assertEquals("https://sentry.io:8082", url) + } + private fun givenAttributes(map: Map, Any>) { map.forEach { k, v -> fixture.attributes.put(k, v) @@ -183,6 +295,10 @@ class OpenTelemetryAttributesExtractorTest { OpenTelemetryAttributesExtractor().extract(fixture.spanData, fixture.sentrySpan, fixture.scope) } + private fun whenExtractingUrl(): String? { + return OpenTelemetryAttributesExtractor().extractUrl(fixture.attributes) + } + private fun thenRequestIsSet() { assertNotNull(fixture.scope.request) } diff --git a/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/main/java/io/sentry/samples/spring/boot/jakarta/ApiService.java b/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/main/java/io/sentry/samples/spring/boot/jakarta/ApiService.java new file mode 100644 index 00000000000..c895b8b979c --- /dev/null +++ b/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/main/java/io/sentry/samples/spring/boot/jakarta/ApiService.java @@ -0,0 +1,23 @@ +package io.sentry.samples.spring.boot.jakarta; + +import io.sentry.spring.jakarta.tracing.SentrySpan; +import org.jetbrains.annotations.NotNull; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestClient; + +@Service +public class ApiService { + + private final RestClient restClient; + + public ApiService(RestClient restClient) { + this.restClient = restClient; + } + + @SentrySpan("annotation-span") + void apiRequest(final @NotNull String name) { + // restClient.get().uri("http://localhost:8000?q={name}", + // name).retrieve().body(String.class); + restClient.get().uri("http://localhost:8081/articles").retrieve().body(String.class); + } +} diff --git a/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/main/java/io/sentry/samples/spring/boot/jakarta/AsyncService.java b/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/main/java/io/sentry/samples/spring/boot/jakarta/AsyncService.java new file mode 100644 index 00000000000..b597c36fd28 --- /dev/null +++ b/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/main/java/io/sentry/samples/spring/boot/jakarta/AsyncService.java @@ -0,0 +1,48 @@ +package io.sentry.samples.spring.boot.jakarta; + +import io.sentry.ITransaction; +import io.sentry.Sentry; +import io.sentry.SpanStatus; +import io.sentry.TransactionContext; +import io.sentry.TransactionOptions; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; + +@Service +public class AsyncService { + + private final ApiService apiService; + + public AsyncService(ApiService apiService) { + this.apiService = apiService; + } + + @Async + public void doAsync() { + TransactionContext transactionContext = + // Sentry.continueTrace( + // "b9118105af4a2d42b4124532cd1065aa-636cffc8f94fbbbb-1", + // Arrays.asList( + // + // "sentry-public_key=502f25099c204a2fbf4cb16edc5975d1,sentry-sample_rate=0.654,sentry-trace_id=b9118105af4a2d42b4124532cd1065aa,sentry-transaction=continued-transaction-from-baggage")); + new TransactionContext("async-transaction-no-headers", "async-op-no-headers"); + // transactionContext.setName("async-transaction"); + // transactionContext.setOperation("async-op"); + TransactionOptions transactionOptions = new TransactionOptions(); + transactionOptions.setBindToScope(true); + ITransaction iTransaction = Sentry.startTransaction(transactionContext, transactionOptions); + // ITransaction iTransaction = + // Sentry.startTransaction("async-transaction", "async-op", transactionOptions); + + System.out.println("running transaction ..."); + try { + Thread.sleep(1000); + apiService.apiRequest("def-async"); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } finally { + System.out.println("transaction done"); + iTransaction.finish(SpanStatus.UNIMPLEMENTED); + } + } +} From 73b7ef6f8261db17b028663f8c0a3f8f4643c8a8 Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Fri, 21 Feb 2025 09:59:22 +0100 Subject: [PATCH 2/5] expose Attributes instead of ReadWriteSpan --- .../api/sentry-opentelemetry-bootstrap.api | 4 ++-- .../io/sentry/opentelemetry/IOtelSpanWrapper.java | 4 ++-- .../opentelemetry/OtelStrongRefSpanWrapper.java | 6 +++--- .../api/sentry-opentelemetry-core.api | 2 +- .../sentry/opentelemetry/OtelSentryPropagator.java | 8 ++++---- .../io/sentry/opentelemetry/OtelSpanWrapper.java | 13 +++++++++++-- 6 files changed, 23 insertions(+), 14 deletions(-) diff --git a/sentry-opentelemetry/sentry-opentelemetry-bootstrap/api/sentry-opentelemetry-bootstrap.api b/sentry-opentelemetry/sentry-opentelemetry-bootstrap/api/sentry-opentelemetry-bootstrap.api index e10514e3602..124990420b8 100644 --- a/sentry-opentelemetry/sentry-opentelemetry-bootstrap/api/sentry-opentelemetry-bootstrap.api +++ b/sentry-opentelemetry/sentry-opentelemetry-bootstrap/api/sentry-opentelemetry-bootstrap.api @@ -1,8 +1,8 @@ public abstract interface class io/sentry/opentelemetry/IOtelSpanWrapper : io/sentry/ISpan { public abstract fun getData ()Ljava/util/Map; public abstract fun getMeasurements ()Ljava/util/Map; + public abstract fun getOpenTelemetrySpanAttributes ()Lio/opentelemetry/api/common/Attributes; public abstract fun getScopes ()Lio/sentry/IScopes; - public abstract fun getSpan ()Lio/opentelemetry/sdk/trace/ReadWriteSpan; public abstract fun getTags ()Ljava/util/Map; public abstract fun getTraceId ()Lio/sentry/protocol/SentryId; public abstract fun getTransactionName ()Ljava/lang/String; @@ -52,10 +52,10 @@ public final class io/sentry/opentelemetry/OtelStrongRefSpanWrapper : io/sentry/ public fun getDescription ()Ljava/lang/String; public fun getFinishDate ()Lio/sentry/SentryDate; public fun getMeasurements ()Ljava/util/Map; + public fun getOpenTelemetrySpanAttributes ()Lio/opentelemetry/api/common/Attributes; public fun getOperation ()Ljava/lang/String; public fun getSamplingDecision ()Lio/sentry/TracesSamplingDecision; public fun getScopes ()Lio/sentry/IScopes; - public fun getSpan ()Lio/opentelemetry/sdk/trace/ReadWriteSpan; public fun getSpanContext ()Lio/sentry/SpanContext; public fun getStartDate ()Lio/sentry/SentryDate; public fun getStatus ()Lio/sentry/SpanStatus; diff --git a/sentry-opentelemetry/sentry-opentelemetry-bootstrap/src/main/java/io/sentry/opentelemetry/IOtelSpanWrapper.java b/sentry-opentelemetry/sentry-opentelemetry-bootstrap/src/main/java/io/sentry/opentelemetry/IOtelSpanWrapper.java index c271751e717..1eefc854a8a 100644 --- a/sentry-opentelemetry/sentry-opentelemetry-bootstrap/src/main/java/io/sentry/opentelemetry/IOtelSpanWrapper.java +++ b/sentry-opentelemetry/sentry-opentelemetry-bootstrap/src/main/java/io/sentry/opentelemetry/IOtelSpanWrapper.java @@ -1,7 +1,7 @@ package io.sentry.opentelemetry; +import io.opentelemetry.api.common.Attributes; import io.opentelemetry.context.Context; -import io.opentelemetry.sdk.trace.ReadWriteSpan; import io.sentry.IScopes; import io.sentry.ISpan; import io.sentry.protocol.MeasurementValue; @@ -51,5 +51,5 @@ public interface IOtelSpanWrapper extends ISpan { @ApiStatus.Internal @Nullable - ReadWriteSpan getSpan(); + Attributes getOpenTelemetrySpanAttributes(); } diff --git a/sentry-opentelemetry/sentry-opentelemetry-bootstrap/src/main/java/io/sentry/opentelemetry/OtelStrongRefSpanWrapper.java b/sentry-opentelemetry/sentry-opentelemetry-bootstrap/src/main/java/io/sentry/opentelemetry/OtelStrongRefSpanWrapper.java index d4b3be788c7..7f026742e9f 100644 --- a/sentry-opentelemetry/sentry-opentelemetry-bootstrap/src/main/java/io/sentry/opentelemetry/OtelStrongRefSpanWrapper.java +++ b/sentry-opentelemetry/sentry-opentelemetry-bootstrap/src/main/java/io/sentry/opentelemetry/OtelStrongRefSpanWrapper.java @@ -1,8 +1,8 @@ package io.sentry.opentelemetry; +import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.trace.Span; import io.opentelemetry.context.Context; -import io.opentelemetry.sdk.trace.ReadWriteSpan; import io.sentry.BaggageHeader; import io.sentry.IScopes; import io.sentry.ISentryLifecycleToken; @@ -307,7 +307,7 @@ public void setContext(@NotNull String key, @NotNull Object context) { @ApiStatus.Internal @Override - public @Nullable ReadWriteSpan getSpan() { - return delegate.getSpan(); + public @Nullable Attributes getOpenTelemetrySpanAttributes() { + return delegate.getOpenTelemetrySpanAttributes(); } } diff --git a/sentry-opentelemetry/sentry-opentelemetry-core/api/sentry-opentelemetry-core.api b/sentry-opentelemetry/sentry-opentelemetry-core/api/sentry-opentelemetry-core.api index 7260b7accf2..739fc7eb1a3 100644 --- a/sentry-opentelemetry/sentry-opentelemetry-core/api/sentry-opentelemetry-core.api +++ b/sentry-opentelemetry/sentry-opentelemetry-core/api/sentry-opentelemetry-core.api @@ -61,10 +61,10 @@ public final class io/sentry/opentelemetry/OtelSpanWrapper : io/sentry/opentelem public fun getDescription ()Ljava/lang/String; public fun getFinishDate ()Lio/sentry/SentryDate; public fun getMeasurements ()Ljava/util/Map; + public fun getOpenTelemetrySpanAttributes ()Lio/opentelemetry/api/common/Attributes; public fun getOperation ()Ljava/lang/String; public fun getSamplingDecision ()Lio/sentry/TracesSamplingDecision; public fun getScopes ()Lio/sentry/IScopes; - public fun getSpan ()Lio/opentelemetry/sdk/trace/ReadWriteSpan; public fun getSpanContext ()Lio/sentry/SpanContext; public fun getStartDate ()Lio/sentry/SentryDate; public fun getStatus ()Lio/sentry/SpanStatus; diff --git a/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/OtelSentryPropagator.java b/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/OtelSentryPropagator.java index 608f650d9b5..c36f1829926 100644 --- a/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/OtelSentryPropagator.java +++ b/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/OtelSentryPropagator.java @@ -2,6 +2,7 @@ import static io.sentry.opentelemetry.SentryOtelKeys.SENTRY_SCOPES_KEY; +import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.trace.Span; import io.opentelemetry.api.trace.SpanContext; import io.opentelemetry.api.trace.TraceFlags; @@ -10,7 +11,6 @@ import io.opentelemetry.context.propagation.TextMapGetter; import io.opentelemetry.context.propagation.TextMapPropagator; import io.opentelemetry.context.propagation.TextMapSetter; -import io.opentelemetry.sdk.trace.ReadWriteSpan; import io.sentry.Baggage; import io.sentry.BaggageHeader; import io.sentry.IScopes; @@ -93,11 +93,11 @@ public void inject(final Context context, final C carrier, final TextMapSett } private @Nullable String getUrl(final @NotNull IOtelSpanWrapper sentrySpan) { - final @Nullable ReadWriteSpan otelReadableSpan = sentrySpan.getSpan(); - if (otelReadableSpan == null) { + final @Nullable Attributes attributes = sentrySpan.getOpenTelemetrySpanAttributes(); + if (attributes == null) { return null; } - return attributesExtractor.extractUrl(otelReadableSpan.getAttributes()); + return attributesExtractor.extractUrl(attributes); } @Override diff --git a/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/OtelSpanWrapper.java b/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/OtelSpanWrapper.java index 6908e8989b3..8d11cb8b772 100644 --- a/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/OtelSpanWrapper.java +++ b/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/OtelSpanWrapper.java @@ -1,5 +1,6 @@ package io.sentry.opentelemetry; +import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.trace.Span; import io.opentelemetry.context.Context; import io.opentelemetry.context.Scope; @@ -194,10 +195,18 @@ public OtelSpanWrapper( return context.getSpanId(); } + private @Nullable ReadWriteSpan getSpan() { + return span.get(); + } + @ApiStatus.Internal @Override - public @Nullable ReadWriteSpan getSpan() { - return span.get(); + public @Nullable Attributes getOpenTelemetrySpanAttributes() { + final @Nullable ReadWriteSpan readWriteSpan = span.get(); + if (readWriteSpan != null) { + return readWriteSpan.getAttributes(); + } + return null; } @Override From 94231c6a3986fcaff330471a9bb37c496dcffcd5 Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Mon, 24 Feb 2025 06:09:29 +0100 Subject: [PATCH 3/5] add test for propagator --- .../api/sentry-opentelemetry-bootstrap.api | 1 + .../opentelemetry/SentryWeakSpanStorage.java | 6 + .../test/kotlin/OtelSentryPropagatorTest.kt | 320 ++++++++++++++++++ 3 files changed, 327 insertions(+) create mode 100644 sentry-opentelemetry/sentry-opentelemetry-core/src/test/kotlin/OtelSentryPropagatorTest.kt diff --git a/sentry-opentelemetry/sentry-opentelemetry-bootstrap/api/sentry-opentelemetry-bootstrap.api b/sentry-opentelemetry/sentry-opentelemetry-bootstrap/api/sentry-opentelemetry-bootstrap.api index 124990420b8..8eeb9936f7d 100644 --- a/sentry-opentelemetry/sentry-opentelemetry-bootstrap/api/sentry-opentelemetry-bootstrap.api +++ b/sentry-opentelemetry/sentry-opentelemetry-bootstrap/api/sentry-opentelemetry-bootstrap.api @@ -179,6 +179,7 @@ public final class io/sentry/opentelemetry/SentryOtelThreadLocalStorage : io/ope } public final class io/sentry/opentelemetry/SentryWeakSpanStorage { + public fun clear ()V public static fun getInstance ()Lio/sentry/opentelemetry/SentryWeakSpanStorage; public fun getSentrySpan (Lio/opentelemetry/api/trace/SpanContext;)Lio/sentry/opentelemetry/IOtelSpanWrapper; public fun storeSentrySpan (Lio/opentelemetry/api/trace/SpanContext;Lio/sentry/opentelemetry/IOtelSpanWrapper;)V diff --git a/sentry-opentelemetry/sentry-opentelemetry-bootstrap/src/main/java/io/sentry/opentelemetry/SentryWeakSpanStorage.java b/sentry-opentelemetry/sentry-opentelemetry-bootstrap/src/main/java/io/sentry/opentelemetry/SentryWeakSpanStorage.java index c28d4ed7ffb..5096c011e2a 100644 --- a/sentry-opentelemetry/sentry-opentelemetry-bootstrap/src/main/java/io/sentry/opentelemetry/SentryWeakSpanStorage.java +++ b/sentry-opentelemetry/sentry-opentelemetry-bootstrap/src/main/java/io/sentry/opentelemetry/SentryWeakSpanStorage.java @@ -7,6 +7,7 @@ import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.TestOnly; /** * Weakly references wrappers for OpenTelemetry spans meaning they'll be cleaned up when the @@ -44,4 +45,9 @@ public void storeSentrySpan( final @NotNull SpanContext otelSpan, final @NotNull IOtelSpanWrapper sentrySpan) { this.sentrySpans.put(otelSpan, sentrySpan); } + + @TestOnly + public void clear() { + sentrySpans.clear(); + } } diff --git a/sentry-opentelemetry/sentry-opentelemetry-core/src/test/kotlin/OtelSentryPropagatorTest.kt b/sentry-opentelemetry/sentry-opentelemetry-core/src/test/kotlin/OtelSentryPropagatorTest.kt new file mode 100644 index 00000000000..21ff416bc3f --- /dev/null +++ b/sentry-opentelemetry/sentry-opentelemetry-core/src/test/kotlin/OtelSentryPropagatorTest.kt @@ -0,0 +1,320 @@ +package io.sentry.opentelemetry + +import io.opentelemetry.api.common.Attributes +import io.opentelemetry.api.trace.Span +import io.opentelemetry.api.trace.SpanContext +import io.opentelemetry.api.trace.TraceFlags +import io.opentelemetry.api.trace.TraceState +import io.opentelemetry.context.Context +import io.opentelemetry.context.propagation.TextMapGetter +import io.opentelemetry.context.propagation.TextMapSetter +import io.opentelemetry.semconv.UrlAttributes +import io.sentry.BaggageHeader +import io.sentry.Sentry +import io.sentry.SentryTraceHeader +import io.sentry.opentelemetry.SentryOtelKeys.SENTRY_BAGGAGE_KEY +import io.sentry.opentelemetry.SentryOtelKeys.SENTRY_SCOPES_KEY +import io.sentry.opentelemetry.SentryOtelKeys.SENTRY_TRACE_KEY +import org.mockito.kotlin.anyOrNull +import org.mockito.kotlin.mock +import org.mockito.kotlin.whenever +import kotlin.test.AfterTest +import kotlin.test.BeforeTest +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertNotNull +import kotlin.test.assertNull +import kotlin.test.assertSame +import kotlin.test.assertTrue + +class OtelSentryPropagatorTest { + + val spanStorage: SentryWeakSpanStorage = SentryWeakSpanStorage.getInstance() + + @BeforeTest + fun setup() { + Sentry.init("https://key@sentry.io/proj") + } + + @AfterTest + fun cleanup() { + spanStorage.clear() + } + + @Test + fun `propagator registers for sentry-trace and baggage`() { + val propagator = OtelSentryPropagator() + assertEquals(listOf("sentry-trace", "baggage"), propagator.fields()) + } + + @Test + fun `forks root scopes if none in context without headers`() { + val propagator = OtelSentryPropagator() + val carrier: Map = mapOf() + + val newContext = propagator.extract(Context.root(), carrier, MapGetter()) + + val scopes = newContext.get(SENTRY_SCOPES_KEY) + assertNotNull(scopes) + assertSame(Sentry.forkedRootScopes("test").parentScopes, scopes.parentScopes) + } + + @Test + fun `forks scopes from context if present without headers`() { + val propagator = OtelSentryPropagator() + val carrier: Map = mapOf() + val scopeInContext = Sentry.forkedRootScopes("test") + + val newContext = propagator.extract(Context.root().with(SENTRY_SCOPES_KEY, scopeInContext), carrier, MapGetter()) + + val scopes = newContext.get(SENTRY_SCOPES_KEY) + assertNotNull(scopes) + assertSame(scopeInContext, scopes.parentScopes) + } + + @Test + fun `forks root scopes if none in context with headers`() { + val propagator = OtelSentryPropagator() + val carrier: Map = mapOf( + "sentry-trace" to "f9118105af4a2d42b4124532cd1065ff-424cffc8f94feeee-1", + "baggage" to "sentry-environment=production,sentry-public_key=502f25099c204a2fbf4cb16edc5975d1,sentry-sample_rand=0.456789,sentry-sample_rate=0.5,sentry-sampled=true,sentry-trace_id=df71f5972f754b4c85af13ff5c07017d" + ) + + val newContext = propagator.extract(Context.root(), carrier, MapGetter()) + + val scopes = newContext.get(SENTRY_SCOPES_KEY) + assertNotNull(scopes) + assertSame(Sentry.forkedRootScopes("test").parentScopes, scopes.parentScopes) + } + + @Test + fun `forks scopes from context if present with headers`() { + val propagator = OtelSentryPropagator() + val carrier: Map = mapOf( + "sentry-trace" to "f9118105af4a2d42b4124532cd1065ff-424cffc8f94feeee-1", + "baggage" to "sentry-environment=production,sentry-public_key=502f25099c204a2fbf4cb16edc5975d1,sentry-sample_rand=0.456789,sentry-sample_rate=0.5,sentry-sampled=true,sentry-trace_id=df71f5972f754b4c85af13ff5c07017d" + ) + val scopeInContext = Sentry.forkedRootScopes("test") + + val newContext = propagator.extract(Context.root().with(SENTRY_SCOPES_KEY, scopeInContext), carrier, MapGetter()) + + val scopes = newContext.get(SENTRY_SCOPES_KEY) + assertNotNull(scopes) + assertSame(scopeInContext, scopes.parentScopes) + } + + @Test + fun `invalid sentry trace header returns context without modification`() { + val propagator = OtelSentryPropagator() + val carrier: Map = mapOf( + "sentry-trace" to "wrong", + "baggage" to "sentry-environment=production,sentry-public_key=502f25099c204a2fbf4cb16edc5975d1,sentry-sample_rand=0.456789,sentry-sample_rate=0.5,sentry-sampled=true,sentry-trace_id=df71f5972f754b4c85af13ff5c07017d" + ) + val scopeInContext = Sentry.forkedRootScopes("test") + + val newContext = propagator.extract(Context.root().with(SENTRY_SCOPES_KEY, scopeInContext), carrier, MapGetter()) + + val scopes = newContext.get(SENTRY_SCOPES_KEY) + assertNotNull(scopes) + assertSame(scopeInContext, scopes) + } + + @Test + fun `uses incoming headers`() { + val propagator = OtelSentryPropagator() + val carrier: Map = mapOf( + "sentry-trace" to "f9118105af4a2d42b4124532cd1065ff-424cffc8f94feeee-1", + "baggage" to "sentry-environment=production,sentry-public_key=502f25099c204a2fbf4cb16edc5975d1,sentry-sample_rand=0.456789,sentry-sample_rate=0.5,sentry-sampled=true,sentry-trace_id=df71f5972f754b4c85af13ff5c07017d" + ) + val newContext = propagator.extract(Context.root(), carrier, MapGetter()) + + val span = Span.fromContext(newContext) + assertEquals("f9118105af4a2d42b4124532cd1065ff", span.spanContext.traceId) + assertEquals("424cffc8f94feeee", span.spanContext.spanId) + assertTrue(span.spanContext.isSampled) + + assertEquals("f9118105af4a2d42b4124532cd1065ff-424cffc8f94feeee-1", newContext.get(SENTRY_TRACE_KEY)?.value) + assertEquals("sentry-environment=production,sentry-public_key=502f25099c204a2fbf4cb16edc5975d1,sentry-sample_rand=0.456789,sentry-sample_rate=0.5,sentry-sampled=true,sentry-trace_id=df71f5972f754b4c85af13ff5c07017d", newContext.get(SENTRY_BAGGAGE_KEY)?.toHeaderString(null)) + } + + @Test + fun `injects headers if no URL`() { + val propagator = OtelSentryPropagator() + val carrier = mutableMapOf() + + val sentrySpan = mock() + whenever(sentrySpan.toSentryTrace()).thenReturn(SentryTraceHeader("f9118105af4a2d42b4124532cd1065ff-424cffc8f94feeee-1")) + whenever(sentrySpan.toBaggageHeader(anyOrNull())).thenReturn(BaggageHeader("sentry-environment=production,sentry-public_key=502f25099c204a2fbf4cb16edc5975d1,sentry-sample_rand=0.456789,sentry-sample_rate=0.5,sentry-sampled=true,sentry-trace_id=df71f5972f754b4c85af13ff5c07017d")) + val otelSpanContext = SpanContext.create("f9118105af4a2d42b4124532cd1065ff", "424cffc8f94feeee", TraceFlags.getSampled(), TraceState.getDefault()) + val otelSpan = Span.wrap(otelSpanContext) + spanStorage.storeSentrySpan(otelSpanContext, sentrySpan) + + propagator.inject(Context.root().with(otelSpan), carrier, MapSetter()) + + assertEquals("f9118105af4a2d42b4124532cd1065ff-424cffc8f94feeee-1", carrier["sentry-trace"]) + assertEquals("sentry-environment=production,sentry-public_key=502f25099c204a2fbf4cb16edc5975d1,sentry-sample_rand=0.456789,sentry-sample_rate=0.5,sentry-sampled=true,sentry-trace_id=df71f5972f754b4c85af13ff5c07017d", carrier["baggage"]) + } + + @Test + fun `injects headers if URL in span attributes with default options`() { + val propagator = OtelSentryPropagator() + val carrier = mutableMapOf() + + val otelAttributes = Attributes.of(UrlAttributes.URL_FULL, "https://sentry.io/some/path") + val sentrySpan = mock() + whenever(sentrySpan.toSentryTrace()).thenReturn(SentryTraceHeader("f9118105af4a2d42b4124532cd1065ff-424cffc8f94feeee-1")) + whenever(sentrySpan.toBaggageHeader(anyOrNull())).thenReturn(BaggageHeader("sentry-environment=production,sentry-public_key=502f25099c204a2fbf4cb16edc5975d1,sentry-sample_rand=0.456789,sentry-sample_rate=0.5,sentry-sampled=true,sentry-trace_id=df71f5972f754b4c85af13ff5c07017d")) + whenever(sentrySpan.openTelemetrySpanAttributes).thenReturn(otelAttributes) + val otelSpanContext = SpanContext.create("f9118105af4a2d42b4124532cd1065ff", "424cffc8f94feeee", TraceFlags.getSampled(), TraceState.getDefault()) + val otelSpan = Span.wrap(otelSpanContext) + spanStorage.storeSentrySpan(otelSpanContext, sentrySpan) + + propagator.inject(Context.root().with(otelSpan), carrier, MapSetter()) + + assertEquals("f9118105af4a2d42b4124532cd1065ff-424cffc8f94feeee-1", carrier["sentry-trace"]) + assertEquals("sentry-environment=production,sentry-public_key=502f25099c204a2fbf4cb16edc5975d1,sentry-sample_rand=0.456789,sentry-sample_rate=0.5,sentry-sampled=true,sentry-trace_id=df71f5972f754b4c85af13ff5c07017d", carrier["baggage"]) + } + + @Test + fun `injects headers if URL in span attributes with tracePropagationTargets set to same url`() { + Sentry.init { options -> + options.dsn = "https://key@sentry.io/proj" + options.setTracePropagationTargets(listOf("sentry.io")) + } + val propagator = OtelSentryPropagator() + val carrier = mutableMapOf() + + val otelAttributes = Attributes.of(UrlAttributes.URL_FULL, "https://sentry.io/some/path") + val sentrySpan = mock() + whenever(sentrySpan.toSentryTrace()).thenReturn(SentryTraceHeader("f9118105af4a2d42b4124532cd1065ff-424cffc8f94feeee-1")) + whenever(sentrySpan.toBaggageHeader(anyOrNull())).thenReturn(BaggageHeader("sentry-environment=production,sentry-public_key=502f25099c204a2fbf4cb16edc5975d1,sentry-sample_rand=0.456789,sentry-sample_rate=0.5,sentry-sampled=true,sentry-trace_id=df71f5972f754b4c85af13ff5c07017d")) + whenever(sentrySpan.openTelemetrySpanAttributes).thenReturn(otelAttributes) + val otelSpanContext = SpanContext.create("f9118105af4a2d42b4124532cd1065ff", "424cffc8f94feeee", TraceFlags.getSampled(), TraceState.getDefault()) + val otelSpan = Span.wrap(otelSpanContext) + spanStorage.storeSentrySpan(otelSpanContext, sentrySpan) + + propagator.inject(Context.root().with(otelSpan), carrier, MapSetter()) + + assertEquals("f9118105af4a2d42b4124532cd1065ff-424cffc8f94feeee-1", carrier["sentry-trace"]) + assertEquals("sentry-environment=production,sentry-public_key=502f25099c204a2fbf4cb16edc5975d1,sentry-sample_rand=0.456789,sentry-sample_rate=0.5,sentry-sampled=true,sentry-trace_id=df71f5972f754b4c85af13ff5c07017d", carrier["baggage"]) + } + + @Test + fun `does not inject headers if URL in span attributes with tracePropagationTargets set to different url`() { + Sentry.init { options -> + options.dsn = "https://key@sentry.io/proj" + options.setTracePropagationTargets(listOf("github.com")) + } + val propagator = OtelSentryPropagator() + val carrier = mutableMapOf() + + val otelAttributes = Attributes.of(UrlAttributes.URL_FULL, "https://sentry.io/some/path") + val sentrySpan = mock() + whenever(sentrySpan.toSentryTrace()).thenReturn(SentryTraceHeader("f9118105af4a2d42b4124532cd1065ff-424cffc8f94feeee-1")) + whenever(sentrySpan.toBaggageHeader(anyOrNull())).thenReturn(BaggageHeader("sentry-environment=production,sentry-public_key=502f25099c204a2fbf4cb16edc5975d1,sentry-sample_rand=0.456789,sentry-sample_rate=0.5,sentry-sampled=true,sentry-trace_id=df71f5972f754b4c85af13ff5c07017d")) + whenever(sentrySpan.openTelemetrySpanAttributes).thenReturn(otelAttributes) + val otelSpanContext = SpanContext.create("f9118105af4a2d42b4124532cd1065ff", "424cffc8f94feeee", TraceFlags.getSampled(), TraceState.getDefault()) + val otelSpan = Span.wrap(otelSpanContext) + spanStorage.storeSentrySpan(otelSpanContext, sentrySpan) + + propagator.inject(Context.root().with(otelSpan), carrier, MapSetter()) + + assertNull(carrier["sentry-trace"]) + assertNull(carrier["baggage"]) + } + + @Test + fun `does not inject headers if URL in span attributes with tracePropagationTargets set to same url but trace sampling disabled`() { + Sentry.init { options -> + options.dsn = "https://key@sentry.io/proj" + options.setTracePropagationTargets(listOf("sentry.io")) + options.isTraceSampling = false + } + val propagator = OtelSentryPropagator() + val carrier = mutableMapOf() + + val otelAttributes = Attributes.of(UrlAttributes.URL_FULL, "https://sentry.io/some/path") + val sentrySpan = mock() + whenever(sentrySpan.toSentryTrace()).thenReturn(SentryTraceHeader("f9118105af4a2d42b4124532cd1065ff-424cffc8f94feeee-1")) + whenever(sentrySpan.toBaggageHeader(anyOrNull())).thenReturn(BaggageHeader("sentry-environment=production,sentry-public_key=502f25099c204a2fbf4cb16edc5975d1,sentry-sample_rand=0.456789,sentry-sample_rate=0.5,sentry-sampled=true,sentry-trace_id=df71f5972f754b4c85af13ff5c07017d")) + whenever(sentrySpan.openTelemetrySpanAttributes).thenReturn(otelAttributes) + val otelSpanContext = SpanContext.create("f9118105af4a2d42b4124532cd1065ff", "424cffc8f94feeee", TraceFlags.getSampled(), TraceState.getDefault()) + val otelSpan = Span.wrap(otelSpanContext) + spanStorage.storeSentrySpan(otelSpanContext, sentrySpan) + + propagator.inject(Context.root().with(otelSpan), carrier, MapSetter()) + + assertNull(carrier["sentry-trace"]) + assertNull(carrier["baggage"]) + } + + @Test + fun `does not inject headers if sentry span missing`() { + val propagator = OtelSentryPropagator() + val carrier = mutableMapOf() + + val otelSpanContext = SpanContext.create("f9118105af4a2d42b4124532cd1065ff", "424cffc8f94feeee", TraceFlags.getSampled(), TraceState.getDefault()) + val otelSpan = Span.wrap(otelSpanContext) + + propagator.inject(Context.root().with(otelSpan), carrier, MapSetter()) + + assertNull(carrier["sentry-trace"]) + assertNull(carrier["baggage"]) + } + + @Test + fun `does not inject headers if sentry span noop`() { + val propagator = OtelSentryPropagator() + val carrier = mutableMapOf() + + val sentrySpan = mock() + whenever(sentrySpan.isNoOp).thenReturn(true) + val otelSpanContext = SpanContext.create("f9118105af4a2d42b4124532cd1065ff", "424cffc8f94feeee", TraceFlags.getSampled(), TraceState.getDefault()) + val otelSpan = Span.wrap(otelSpanContext) + spanStorage.storeSentrySpan(otelSpanContext, sentrySpan) + + propagator.inject(Context.root().with(otelSpan), carrier, MapSetter()) + + assertNull(carrier["sentry-trace"]) + assertNull(carrier["baggage"]) + } + + @Test + fun `does not inject headers if span is missing`() { + val propagator = OtelSentryPropagator() + val carrier = mutableMapOf() + + propagator.inject(Context.root(), carrier, MapSetter()) + + assertNull(carrier["sentry-trace"]) + assertNull(carrier["baggage"]) + } + + @Test + fun `does not inject headers if span is invalid`() { + val propagator = OtelSentryPropagator() + val carrier = mutableMapOf() + + propagator.inject(Context.root().with(Span.getInvalid()), carrier, MapSetter()) + + assertNull(carrier["sentry-trace"]) + assertNull(carrier["baggage"]) + } +} + +class MapGetter() : TextMapGetter> { + + override fun keys(carrier: Map): MutableIterable { + return carrier.keys.toMutableList() + } + + override fun get(carrier: Map?, key: String): String? { + return carrier?.get(key) + } +} + +class MapSetter() : TextMapSetter> { + override fun set(carrier: MutableMap?, key: String, value: String) { + carrier?.set(key, value) + } +} From 6b0b95e0ab497742f2ea3546298c253bac037e55 Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Mon, 24 Feb 2025 06:17:00 +0100 Subject: [PATCH 4/5] changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e9babf5adab..f7a6cfa5ca9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,9 @@ ### Fixes - `SentryOptions.setTracePropagationTargets` is no longer marked internal ([#4170](https://github.com/getsentry/sentry-java/pull/4170)) +- Check `tracePropagationTargets` in OpenTelemetry propagator ([#4191](https://github.com/getsentry/sentry-java/pull/4191)) + - If a URL can be retrieved from OpenTelemetry span attributes, we check it against `tracePropagationTargets` before attaching `sentry-trace` and `baggage` headers to outgoing requests + - If no URL can be retrieved we always attach the headers ### Behavioural Changes From c53c6072fe561e8ab359907f7f8e7f863e23a312 Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Mon, 24 Feb 2025 06:26:43 +0100 Subject: [PATCH 5/5] remove testing files --- .../spring/boot/jakarta/ApiService.java | 23 --------- .../spring/boot/jakarta/AsyncService.java | 48 ------------------- 2 files changed, 71 deletions(-) delete mode 100644 sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/main/java/io/sentry/samples/spring/boot/jakarta/ApiService.java delete mode 100644 sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/main/java/io/sentry/samples/spring/boot/jakarta/AsyncService.java diff --git a/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/main/java/io/sentry/samples/spring/boot/jakarta/ApiService.java b/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/main/java/io/sentry/samples/spring/boot/jakarta/ApiService.java deleted file mode 100644 index c895b8b979c..00000000000 --- a/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/main/java/io/sentry/samples/spring/boot/jakarta/ApiService.java +++ /dev/null @@ -1,23 +0,0 @@ -package io.sentry.samples.spring.boot.jakarta; - -import io.sentry.spring.jakarta.tracing.SentrySpan; -import org.jetbrains.annotations.NotNull; -import org.springframework.stereotype.Service; -import org.springframework.web.client.RestClient; - -@Service -public class ApiService { - - private final RestClient restClient; - - public ApiService(RestClient restClient) { - this.restClient = restClient; - } - - @SentrySpan("annotation-span") - void apiRequest(final @NotNull String name) { - // restClient.get().uri("http://localhost:8000?q={name}", - // name).retrieve().body(String.class); - restClient.get().uri("http://localhost:8081/articles").retrieve().body(String.class); - } -} diff --git a/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/main/java/io/sentry/samples/spring/boot/jakarta/AsyncService.java b/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/main/java/io/sentry/samples/spring/boot/jakarta/AsyncService.java deleted file mode 100644 index b597c36fd28..00000000000 --- a/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/main/java/io/sentry/samples/spring/boot/jakarta/AsyncService.java +++ /dev/null @@ -1,48 +0,0 @@ -package io.sentry.samples.spring.boot.jakarta; - -import io.sentry.ITransaction; -import io.sentry.Sentry; -import io.sentry.SpanStatus; -import io.sentry.TransactionContext; -import io.sentry.TransactionOptions; -import org.springframework.scheduling.annotation.Async; -import org.springframework.stereotype.Service; - -@Service -public class AsyncService { - - private final ApiService apiService; - - public AsyncService(ApiService apiService) { - this.apiService = apiService; - } - - @Async - public void doAsync() { - TransactionContext transactionContext = - // Sentry.continueTrace( - // "b9118105af4a2d42b4124532cd1065aa-636cffc8f94fbbbb-1", - // Arrays.asList( - // - // "sentry-public_key=502f25099c204a2fbf4cb16edc5975d1,sentry-sample_rate=0.654,sentry-trace_id=b9118105af4a2d42b4124532cd1065aa,sentry-transaction=continued-transaction-from-baggage")); - new TransactionContext("async-transaction-no-headers", "async-op-no-headers"); - // transactionContext.setName("async-transaction"); - // transactionContext.setOperation("async-op"); - TransactionOptions transactionOptions = new TransactionOptions(); - transactionOptions.setBindToScope(true); - ITransaction iTransaction = Sentry.startTransaction(transactionContext, transactionOptions); - // ITransaction iTransaction = - // Sentry.startTransaction("async-transaction", "async-op", transactionOptions); - - System.out.println("running transaction ..."); - try { - Thread.sleep(1000); - apiService.apiRequest("def-async"); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } finally { - System.out.println("transaction done"); - iTransaction.finish(SpanStatus.UNIMPLEMENTED); - } - } -}