Skip to content

Commit c8461d4

Browse files
authored
Check tracePropagationTargets in OpenTelemetry propagator (#4191)
* Check tracePropagationTargets in OpenTelemetry propagator * expose Attributes instead of ReadWriteSpan * add test for propagator * changelog * remove testing files
1 parent afe9d2c commit c8461d4

File tree

11 files changed

+507
-13
lines changed

11 files changed

+507
-13
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@
1414
### Fixes
1515

1616
- `SentryOptions.setTracePropagationTargets` is no longer marked internal ([#4170](https://github.com/getsentry/sentry-java/pull/4170))
17+
- Check `tracePropagationTargets` in OpenTelemetry propagator ([#4191](https://github.com/getsentry/sentry-java/pull/4191))
18+
- 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
19+
- If no URL can be retrieved we always attach the headers
1720
- Fix `ignoredErrors`, `ignoredTransactions` and `ignoredCheckIns` being unset by external options like `sentry.properties` or ENV vars ([#4207](https://github.com/getsentry/sentry-java/pull/4207))
1821
- Whenever parsing of external options was enabled (`enableExternalConfiguration`), which is the default for many integrations, the values set on `SentryOptions` passed to `Sentry.init` would be lost
1922
- Even if the value was not set in any external configuration it would still be set to an empty list

sentry-opentelemetry/sentry-opentelemetry-bootstrap/api/sentry-opentelemetry-bootstrap.api

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
public abstract interface class io/sentry/opentelemetry/IOtelSpanWrapper : io/sentry/ISpan {
22
public abstract fun getData ()Ljava/util/Map;
33
public abstract fun getMeasurements ()Ljava/util/Map;
4+
public abstract fun getOpenTelemetrySpanAttributes ()Lio/opentelemetry/api/common/Attributes;
45
public abstract fun getScopes ()Lio/sentry/IScopes;
56
public abstract fun getTags ()Ljava/util/Map;
67
public abstract fun getTraceId ()Lio/sentry/protocol/SentryId;
@@ -51,6 +52,7 @@ public final class io/sentry/opentelemetry/OtelStrongRefSpanWrapper : io/sentry/
5152
public fun getDescription ()Ljava/lang/String;
5253
public fun getFinishDate ()Lio/sentry/SentryDate;
5354
public fun getMeasurements ()Ljava/util/Map;
55+
public fun getOpenTelemetrySpanAttributes ()Lio/opentelemetry/api/common/Attributes;
5456
public fun getOperation ()Ljava/lang/String;
5557
public fun getSamplingDecision ()Lio/sentry/TracesSamplingDecision;
5658
public fun getScopes ()Lio/sentry/IScopes;
@@ -177,6 +179,7 @@ public final class io/sentry/opentelemetry/SentryOtelThreadLocalStorage : io/ope
177179
}
178180

179181
public final class io/sentry/opentelemetry/SentryWeakSpanStorage {
182+
public fun clear ()V
180183
public static fun getInstance ()Lio/sentry/opentelemetry/SentryWeakSpanStorage;
181184
public fun getSentrySpan (Lio/opentelemetry/api/trace/SpanContext;)Lio/sentry/opentelemetry/IOtelSpanWrapper;
182185
public fun storeSentrySpan (Lio/opentelemetry/api/trace/SpanContext;Lio/sentry/opentelemetry/IOtelSpanWrapper;)V

sentry-opentelemetry/sentry-opentelemetry-bootstrap/src/main/java/io/sentry/opentelemetry/IOtelSpanWrapper.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package io.sentry.opentelemetry;
22

3+
import io.opentelemetry.api.common.Attributes;
34
import io.opentelemetry.context.Context;
45
import io.sentry.IScopes;
56
import io.sentry.ISpan;
@@ -47,4 +48,8 @@ public interface IOtelSpanWrapper extends ISpan {
4748

4849
@NotNull
4950
Context storeInContext(Context context);
51+
52+
@ApiStatus.Internal
53+
@Nullable
54+
Attributes getOpenTelemetrySpanAttributes();
5055
}

sentry-opentelemetry/sentry-opentelemetry-bootstrap/src/main/java/io/sentry/opentelemetry/OtelStrongRefSpanWrapper.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package io.sentry.opentelemetry;
22

3+
import io.opentelemetry.api.common.Attributes;
34
import io.opentelemetry.api.trace.Span;
45
import io.opentelemetry.context.Context;
56
import io.sentry.BaggageHeader;
@@ -303,4 +304,10 @@ public void setContext(@NotNull String key, @NotNull Object context) {
303304
public @NotNull ISentryLifecycleToken makeCurrent() {
304305
return delegate.makeCurrent();
305306
}
307+
308+
@ApiStatus.Internal
309+
@Override
310+
public @Nullable Attributes getOpenTelemetrySpanAttributes() {
311+
return delegate.getOpenTelemetrySpanAttributes();
312+
}
306313
}

sentry-opentelemetry/sentry-opentelemetry-bootstrap/src/main/java/io/sentry/opentelemetry/SentryWeakSpanStorage.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import org.jetbrains.annotations.ApiStatus;
88
import org.jetbrains.annotations.NotNull;
99
import org.jetbrains.annotations.Nullable;
10+
import org.jetbrains.annotations.TestOnly;
1011

1112
/**
1213
* Weakly references wrappers for OpenTelemetry spans meaning they'll be cleaned up when the
@@ -44,4 +45,9 @@ public void storeSentrySpan(
4445
final @NotNull SpanContext otelSpan, final @NotNull IOtelSpanWrapper sentrySpan) {
4546
this.sentrySpans.put(otelSpan, sentrySpan);
4647
}
48+
49+
@TestOnly
50+
public void clear() {
51+
sentrySpans.clear();
52+
}
4753
}

sentry-opentelemetry/sentry-opentelemetry-core/api/sentry-opentelemetry-core.api

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
public final class io/sentry/opentelemetry/OpenTelemetryAttributesExtractor {
22
public fun <init> ()V
33
public fun extract (Lio/opentelemetry/sdk/trace/data/SpanData;Lio/sentry/ISpan;Lio/sentry/IScope;)V
4+
public fun extractUrl (Lio/opentelemetry/api/common/Attributes;)Ljava/lang/String;
45
}
56

67
public final class io/sentry/opentelemetry/OpenTelemetryLinkErrorEventProcessor : io/sentry/EventProcessor {
@@ -60,6 +61,7 @@ public final class io/sentry/opentelemetry/OtelSpanWrapper : io/sentry/opentelem
6061
public fun getDescription ()Ljava/lang/String;
6162
public fun getFinishDate ()Lio/sentry/SentryDate;
6263
public fun getMeasurements ()Ljava/util/Map;
64+
public fun getOpenTelemetrySpanAttributes ()Lio/opentelemetry/api/common/Attributes;
6365
public fun getOperation ()Ljava/lang/String;
6466
public fun getSamplingDecision ()Lio/sentry/TracesSamplingDecision;
6567
public fun getScopes ()Lio/sentry/IScopes;

sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/OpenTelemetryAttributesExtractor.java

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ public void extract(
2424
addRequestAttributesToScope(attributes, scope);
2525
}
2626

27-
private void addRequestAttributesToScope(Attributes attributes, IScope scope) {
27+
private void addRequestAttributesToScope(
28+
final @NotNull Attributes attributes, final @NotNull IScope scope) {
2829
if (scope.getRequest() == null) {
2930
scope.setRequest(new Request());
3031
}
@@ -36,20 +37,13 @@ private void addRequestAttributesToScope(Attributes attributes, IScope scope) {
3637
}
3738

3839
if (request.getUrl() == null) {
39-
final @Nullable String urlFull = attributes.get(UrlAttributes.URL_FULL);
40-
if (urlFull != null) {
41-
final @NotNull UrlUtils.UrlDetails urlDetails = UrlUtils.parse(urlFull);
40+
final @Nullable String url = extractUrl(attributes);
41+
if (url != null) {
42+
final @NotNull UrlUtils.UrlDetails urlDetails = UrlUtils.parse(url);
4243
urlDetails.applyToRequest(request);
4344
}
4445
}
4546

46-
if (request.getUrl() == null) {
47-
final String urlString = buildUrlString(attributes);
48-
if (!urlString.isEmpty()) {
49-
request.setUrl(urlString);
50-
}
51-
}
52-
5347
if (request.getQueryString() == null) {
5448
final @Nullable String query = attributes.get(UrlAttributes.URL_QUERY);
5549
if (query != null) {
@@ -59,6 +53,20 @@ private void addRequestAttributesToScope(Attributes attributes, IScope scope) {
5953
}
6054
}
6155

56+
public @Nullable String extractUrl(final @NotNull Attributes attributes) {
57+
final @Nullable String urlFull = attributes.get(UrlAttributes.URL_FULL);
58+
if (urlFull != null) {
59+
return urlFull;
60+
}
61+
62+
final String urlString = buildUrlString(attributes);
63+
if (!urlString.isEmpty()) {
64+
return urlString;
65+
}
66+
67+
return null;
68+
}
69+
6270
private @NotNull String buildUrlString(final @NotNull Attributes attributes) {
6371
final @Nullable String scheme = attributes.get(UrlAttributes.URL_SCHEME);
6472
final @Nullable String serverAddress = attributes.get(ServerAttributes.SERVER_ADDRESS);

sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/OtelSentryPropagator.java

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import static io.sentry.opentelemetry.SentryOtelKeys.SENTRY_SCOPES_KEY;
44

5+
import io.opentelemetry.api.common.Attributes;
56
import io.opentelemetry.api.trace.Span;
67
import io.opentelemetry.api.trace.SpanContext;
78
import io.opentelemetry.api.trace.TraceFlags;
@@ -32,6 +33,8 @@ public final class OtelSentryPropagator implements TextMapPropagator {
3233
Arrays.asList(SentryTraceHeader.SENTRY_TRACE_HEADER, BaggageHeader.BAGGAGE_HEADER);
3334
private final @NotNull SentryWeakSpanStorage spanStorage = SentryWeakSpanStorage.getInstance();
3435
private final @NotNull IScopes scopes;
36+
private final @NotNull OpenTelemetryAttributesExtractor attributesExtractor =
37+
new OpenTelemetryAttributesExtractor();
3538

3639
public OtelSentryPropagator() {
3740
this(ScopesAdapter.getInstance());
@@ -73,9 +76,11 @@ public <C> void inject(final Context context, final C carrier, final TextMapSett
7376
return;
7477
}
7578

76-
// TODO can we use traceIfAllowed? do we have the URL here? need to access span attrs
79+
final @Nullable String url = getUrl(sentrySpan);
7780
final @Nullable TracingUtils.TracingHeaders tracingHeaders =
78-
TracingUtils.trace(scopes, Collections.emptyList(), sentrySpan);
81+
url == null
82+
? TracingUtils.trace(scopes, Collections.emptyList(), sentrySpan)
83+
: TracingUtils.traceIfAllowed(scopes, url, Collections.emptyList(), sentrySpan);
7984

8085
if (tracingHeaders != null) {
8186
final @NotNull SentryTraceHeader sentryTraceHeader = tracingHeaders.getSentryTraceHeader();
@@ -87,6 +92,14 @@ public <C> void inject(final Context context, final C carrier, final TextMapSett
8792
}
8893
}
8994

95+
private @Nullable String getUrl(final @NotNull IOtelSpanWrapper sentrySpan) {
96+
final @Nullable Attributes attributes = sentrySpan.getOpenTelemetrySpanAttributes();
97+
if (attributes == null) {
98+
return null;
99+
}
100+
return attributesExtractor.extractUrl(attributes);
101+
}
102+
90103
@Override
91104
public <C> Context extract(
92105
final Context context, final C carrier, final TextMapGetter<C> getter) {

sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/OtelSpanWrapper.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package io.sentry.opentelemetry;
22

3+
import io.opentelemetry.api.common.Attributes;
34
import io.opentelemetry.api.trace.Span;
45
import io.opentelemetry.context.Context;
56
import io.opentelemetry.context.Scope;
@@ -198,6 +199,16 @@ public OtelSpanWrapper(
198199
return span.get();
199200
}
200201

202+
@ApiStatus.Internal
203+
@Override
204+
public @Nullable Attributes getOpenTelemetrySpanAttributes() {
205+
final @Nullable ReadWriteSpan readWriteSpan = span.get();
206+
if (readWriteSpan != null) {
207+
return readWriteSpan.getAttributes();
208+
}
209+
return null;
210+
}
211+
201212
@Override
202213
public @Nullable TraceContext traceContext() {
203214
if (scopes.getOptions().isTraceSampling()) {

sentry-opentelemetry/sentry-opentelemetry-core/src/test/kotlin/OpenTelemetryAttributesExtractorTest.kt

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,118 @@ class OpenTelemetryAttributesExtractorTest {
173173
thenUrlIsNotSet()
174174
}
175175

176+
@Test
177+
fun `returns null if no URL in attributes`() {
178+
givenAttributes(mapOf())
179+
180+
val url = whenExtractingUrl()
181+
182+
assertNull(url)
183+
}
184+
185+
@Test
186+
fun `returns full URL if present`() {
187+
givenAttributes(
188+
mapOf(
189+
UrlAttributes.URL_FULL to "https://sentry.io/some/path"
190+
)
191+
)
192+
193+
val url = whenExtractingUrl()
194+
195+
assertEquals("https://sentry.io/some/path", url)
196+
}
197+
198+
@Test
199+
fun `returns reconstructed URL if attributes present`() {
200+
givenAttributes(
201+
mapOf(
202+
UrlAttributes.URL_SCHEME to "https",
203+
ServerAttributes.SERVER_ADDRESS to "sentry.io",
204+
ServerAttributes.SERVER_PORT to 8082L,
205+
UrlAttributes.URL_PATH to "/some/path"
206+
)
207+
)
208+
209+
val url = whenExtractingUrl()
210+
211+
assertEquals("https://sentry.io:8082/some/path", url)
212+
}
213+
214+
@Test
215+
fun `returns reconstructed URL if attributes present without port`() {
216+
givenAttributes(
217+
mapOf(
218+
UrlAttributes.URL_SCHEME to "https",
219+
ServerAttributes.SERVER_ADDRESS to "sentry.io",
220+
UrlAttributes.URL_PATH to "/some/path"
221+
)
222+
)
223+
224+
val url = whenExtractingUrl()
225+
226+
assertEquals("https://sentry.io/some/path", url)
227+
}
228+
229+
@Test
230+
fun `returns null URL if scheme missing`() {
231+
givenAttributes(
232+
mapOf(
233+
ServerAttributes.SERVER_ADDRESS to "sentry.io",
234+
ServerAttributes.SERVER_PORT to 8082L,
235+
UrlAttributes.URL_PATH to "/some/path"
236+
)
237+
)
238+
239+
val url = whenExtractingUrl()
240+
241+
assertNull(url)
242+
}
243+
244+
@Test
245+
fun `returns null URL if server address missing`() {
246+
givenAttributes(
247+
mapOf(
248+
UrlAttributes.URL_SCHEME to "https",
249+
ServerAttributes.SERVER_PORT to 8082L,
250+
UrlAttributes.URL_PATH to "/some/path"
251+
)
252+
)
253+
254+
val url = whenExtractingUrl()
255+
256+
assertNull(url)
257+
}
258+
259+
@Test
260+
fun `returns reconstructed URL if attributes present without port and path`() {
261+
givenAttributes(
262+
mapOf(
263+
UrlAttributes.URL_SCHEME to "https",
264+
ServerAttributes.SERVER_ADDRESS to "sentry.io"
265+
)
266+
)
267+
268+
val url = whenExtractingUrl()
269+
270+
assertEquals("https://sentry.io", url)
271+
}
272+
273+
@Test
274+
fun `returns reconstructed URL if attributes present without path`() {
275+
givenAttributes(
276+
mapOf(
277+
UrlAttributes.URL_SCHEME to "https",
278+
ServerAttributes.SERVER_ADDRESS to "sentry.io",
279+
ServerAttributes.SERVER_PORT to 8082L
280+
)
281+
)
282+
283+
val url = whenExtractingUrl()
284+
285+
assertEquals("https://sentry.io:8082", url)
286+
}
287+
176288
private fun givenAttributes(map: Map<AttributeKey<out Any>, Any>) {
177289
map.forEach { k, v ->
178290
fixture.attributes.put(k, v)
@@ -183,6 +295,10 @@ class OpenTelemetryAttributesExtractorTest {
183295
OpenTelemetryAttributesExtractor().extract(fixture.spanData, fixture.sentrySpan, fixture.scope)
184296
}
185297

298+
private fun whenExtractingUrl(): String? {
299+
return OpenTelemetryAttributesExtractor().extractUrl(fixture.attributes)
300+
}
301+
186302
private fun thenRequestIsSet() {
187303
assertNotNull(fixture.scope.request)
188304
}

0 commit comments

Comments
 (0)