Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,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

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
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 getTags ()Ljava/util/Map;
public abstract fun getTraceId ()Lio/sentry/protocol/SentryId;
Expand Down Expand Up @@ -51,6 +52,7 @@ 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;
Expand Down Expand Up @@ -177,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
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.sentry.opentelemetry;

import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.context.Context;
import io.sentry.IScopes;
import io.sentry.ISpan;
Expand Down Expand Up @@ -47,4 +48,8 @@ public interface IOtelSpanWrapper extends ISpan {

@NotNull
Context storeInContext(Context context);

@ApiStatus.Internal
@Nullable
Attributes getOpenTelemetrySpanAttributes();
}
Original file line number Diff line number Diff line change
@@ -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.sentry.BaggageHeader;
Expand Down Expand Up @@ -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 Attributes getOpenTelemetrySpanAttributes() {
return delegate.getOpenTelemetrySpanAttributes();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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();
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
public final class io/sentry/opentelemetry/OpenTelemetryAttributesExtractor {
public fun <init> ()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 {
Expand Down Expand Up @@ -60,6 +61,7 @@ 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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}
Expand All @@ -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) {
Expand All @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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());
Expand Down Expand Up @@ -73,9 +76,11 @@ public <C> 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();
Expand All @@ -87,6 +92,14 @@ public <C> void inject(final Context context, final C carrier, final TextMapSett
}
}

private @Nullable String getUrl(final @NotNull IOtelSpanWrapper sentrySpan) {
final @Nullable Attributes attributes = sentrySpan.getOpenTelemetrySpanAttributes();
if (attributes == null) {
return null;
}
return attributesExtractor.extractUrl(attributes);
}

@Override
public <C> Context extract(
final Context context, final C carrier, final TextMapGetter<C> getter) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -198,6 +199,16 @@ public OtelSpanWrapper(
return span.get();
}

@ApiStatus.Internal
@Override
public @Nullable Attributes getOpenTelemetrySpanAttributes() {
final @Nullable ReadWriteSpan readWriteSpan = span.get();
if (readWriteSpan != null) {
return readWriteSpan.getAttributes();
}
return null;
}

@Override
public @Nullable TraceContext traceContext() {
if (scopes.getOptions().isTraceSampling()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<AttributeKey<out Any>, Any>) {
map.forEach { k, v ->
fixture.attributes.put(k, v)
Expand All @@ -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)
}
Expand Down
Loading