Skip to content

Commit 9ab9244

Browse files
authored
Merge c02df17 into 02931bf
2 parents 02931bf + c02df17 commit 9ab9244

File tree

17 files changed

+421
-4
lines changed

17 files changed

+421
-4
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@
44

55
### Features
66

7+
- Support `globalHubMode` for OpenTelemetry ([#4349](https://github.com/getsentry/sentry-java/pull/4349))
8+
- Sentry now adds OpenTelemetry spans without a parent to the last known unfinished root span (transaction)
9+
- Previously those spans would end up in Sentry as separate transactions
10+
- Spans created via Sentry API are preferred over those created through OpenTelemetry API or auto instrumentation
11+
- New option `ignoreStandaloneClientSpans` that prevents Sentry from creating transactions for OpenTelemetry spans with kind `CLIENT` ([#4349](https://github.com/getsentry/sentry-java/pull/4349))
12+
- Defaults to `false` meaning standalone OpenTelemetry spans with kind `CLIENT` will be turned into Sentry transactions
713
- Make `RequestDetailsResolver` public ([#4326](https://github.com/getsentry/sentry-java/pull/4326))
814
- `RequestDetailsResolver` is now public and has an additional constructor, making it easier to use a custom `TransportFactory`
915

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

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
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 getOpenTelemetrySpan ()Lio/opentelemetry/api/trace/Span;
45
public abstract fun getOpenTelemetrySpanAttributes ()Lio/opentelemetry/api/common/Attributes;
56
public abstract fun getScopes ()Lio/sentry/IScopes;
67
public abstract fun getTags ()Ljava/util/Map;
78
public abstract fun getTraceId ()Lio/sentry/protocol/SentryId;
89
public abstract fun getTransactionName ()Ljava/lang/String;
910
public abstract fun getTransactionNameSource ()Lio/sentry/protocol/TransactionNameSource;
1011
public abstract fun isProfileSampled ()Ljava/lang/Boolean;
12+
public abstract fun isRoot ()Z
1113
public abstract fun setTransactionName (Ljava/lang/String;)V
1214
public abstract fun setTransactionName (Ljava/lang/String;Lio/sentry/protocol/TransactionNameSource;)V
1315
public abstract fun storeInContext (Lio/opentelemetry/context/Context;)Lio/opentelemetry/context/Context;
@@ -16,6 +18,7 @@ public abstract interface class io/sentry/opentelemetry/IOtelSpanWrapper : io/se
1618
public final class io/sentry/opentelemetry/InternalSemanticAttributes {
1719
public static final field BAGGAGE Lio/opentelemetry/api/common/AttributeKey;
1820
public static final field BAGGAGE_MUTABLE Lio/opentelemetry/api/common/AttributeKey;
21+
public static final field CREATED_VIA_SENTRY_API Lio/opentelemetry/api/common/AttributeKey;
1922
public static final field IS_REMOTE_PARENT Lio/opentelemetry/api/common/AttributeKey;
2023
public static final field PARENT_SAMPLED Lio/opentelemetry/api/common/AttributeKey;
2124
public static final field PROFILE_SAMPLED Lio/opentelemetry/api/common/AttributeKey;
@@ -52,6 +55,7 @@ public final class io/sentry/opentelemetry/OtelStrongRefSpanWrapper : io/sentry/
5255
public fun getDescription ()Ljava/lang/String;
5356
public fun getFinishDate ()Lio/sentry/SentryDate;
5457
public fun getMeasurements ()Ljava/util/Map;
58+
public fun getOpenTelemetrySpan ()Lio/opentelemetry/api/trace/Span;
5559
public fun getOpenTelemetrySpanAttributes ()Lio/opentelemetry/api/common/Attributes;
5660
public fun getOperation ()Ljava/lang/String;
5761
public fun getSamplingDecision ()Lio/sentry/TracesSamplingDecision;
@@ -68,6 +72,7 @@ public final class io/sentry/opentelemetry/OtelStrongRefSpanWrapper : io/sentry/
6872
public fun isFinished ()Z
6973
public fun isNoOp ()Z
7074
public fun isProfileSampled ()Ljava/lang/Boolean;
75+
public fun isRoot ()Z
7176
public fun isSampled ()Ljava/lang/Boolean;
7277
public fun makeCurrent ()Lio/sentry/ISentryLifecycleToken;
7378
public fun setContext (Ljava/lang/String;Ljava/lang/Object;)V
@@ -151,6 +156,7 @@ public final class io/sentry/opentelemetry/SentryContextStorage : io/opentelemet
151156
public fun <init> (Lio/opentelemetry/context/ContextStorage;)V
152157
public fun attach (Lio/opentelemetry/context/Context;)Lio/opentelemetry/context/Scope;
153158
public fun current ()Lio/opentelemetry/context/Context;
159+
public fun root ()Lio/opentelemetry/context/Context;
154160
}
155161

156162
public final class io/sentry/opentelemetry/SentryContextStorageProvider : io/opentelemetry/context/ContextStorageProvider {
@@ -165,6 +171,20 @@ public final class io/sentry/opentelemetry/SentryContextWrapper : io/opentelemet
165171
public static fun wrap (Lio/opentelemetry/context/Context;)Lio/sentry/opentelemetry/SentryContextWrapper;
166172
}
167173

174+
public final class io/sentry/opentelemetry/SentryOtelGlobalHubModeSpan : io/opentelemetry/api/trace/Span {
175+
public fun <init> ()V
176+
public fun addEvent (Ljava/lang/String;Lio/opentelemetry/api/common/Attributes;)Lio/opentelemetry/api/trace/Span;
177+
public fun addEvent (Ljava/lang/String;Lio/opentelemetry/api/common/Attributes;JLjava/util/concurrent/TimeUnit;)Lio/opentelemetry/api/trace/Span;
178+
public fun end ()V
179+
public fun end (JLjava/util/concurrent/TimeUnit;)V
180+
public fun getSpanContext ()Lio/opentelemetry/api/trace/SpanContext;
181+
public fun isRecording ()Z
182+
public fun recordException (Ljava/lang/Throwable;Lio/opentelemetry/api/common/Attributes;)Lio/opentelemetry/api/trace/Span;
183+
public fun setAttribute (Lio/opentelemetry/api/common/AttributeKey;Ljava/lang/Object;)Lio/opentelemetry/api/trace/Span;
184+
public fun setStatus (Lio/opentelemetry/api/trace/StatusCode;Ljava/lang/String;)Lio/opentelemetry/api/trace/Span;
185+
public fun updateName (Ljava/lang/String;)Lio/opentelemetry/api/trace/Span;
186+
}
187+
168188
public final class io/sentry/opentelemetry/SentryOtelKeys {
169189
public static final field SENTRY_BAGGAGE_KEY Lio/opentelemetry/context/ContextKey;
170190
public static final field SENTRY_SCOPES_KEY Lio/opentelemetry/context/ContextKey;
@@ -181,6 +201,7 @@ public final class io/sentry/opentelemetry/SentryOtelThreadLocalStorage : io/ope
181201
public final class io/sentry/opentelemetry/SentryWeakSpanStorage {
182202
public fun clear ()V
183203
public static fun getInstance ()Lio/sentry/opentelemetry/SentryWeakSpanStorage;
204+
public fun getLastKnownUnfinishedRootSpan ()Lio/sentry/opentelemetry/IOtelSpanWrapper;
184205
public fun getSentrySpan (Lio/opentelemetry/api/trace/SpanContext;)Lio/sentry/opentelemetry/IOtelSpanWrapper;
185206
public fun storeSentrySpan (Lio/opentelemetry/api/trace/SpanContext;Lio/sentry/opentelemetry/IOtelSpanWrapper;)V
186207
}

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

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

33
import io.opentelemetry.api.common.Attributes;
4+
import io.opentelemetry.api.trace.Span;
45
import io.opentelemetry.context.Context;
56
import io.sentry.IScopes;
67
import io.sentry.ISpan;
@@ -52,4 +53,9 @@ public interface IOtelSpanWrapper extends ISpan {
5253
@ApiStatus.Internal
5354
@Nullable
5455
Attributes getOpenTelemetrySpanAttributes();
56+
57+
boolean isRoot();
58+
59+
@Nullable
60+
Span getOpenTelemetrySpan();
5561
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,6 @@ public final class InternalSemanticAttributes {
2121
public static final AttributeKey<String> BAGGAGE = AttributeKey.stringKey("sentry.baggage");
2222
public static final AttributeKey<Boolean> BAGGAGE_MUTABLE =
2323
AttributeKey.booleanKey("sentry.baggage_mutable");
24+
public static final AttributeKey<Boolean> CREATED_VIA_SENTRY_API =
25+
AttributeKey.booleanKey("sentry.is_created_via_sentry_api");
2426
}

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -310,4 +310,14 @@ public void setContext(@Nullable String key, @Nullable Object context) {
310310
public @Nullable Attributes getOpenTelemetrySpanAttributes() {
311311
return delegate.getOpenTelemetrySpanAttributes();
312312
}
313+
314+
@Override
315+
public boolean isRoot() {
316+
return delegate.isRoot();
317+
}
318+
319+
@Override
320+
public @Nullable Span getOpenTelemetrySpan() {
321+
return delegate.getOpenTelemetrySpan();
322+
}
313323
}

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import io.opentelemetry.context.Context;
44
import io.opentelemetry.context.ContextStorage;
55
import io.opentelemetry.context.Scope;
6+
import io.sentry.Sentry;
67
import org.jetbrains.annotations.ApiStatus;
78
import org.jetbrains.annotations.NotNull;
89

@@ -38,4 +39,16 @@ public Scope attach(Context toAttach) {
3839
public Context current() {
3940
return contextStorage.current();
4041
}
42+
43+
@Override
44+
public Context root() {
45+
final @NotNull Context originalRoot = contextStorage.root();
46+
47+
if (Sentry.isGlobalHubMode()) {
48+
return new SentryOtelGlobalHubModeSpan()
49+
.storeInContext(SentryContextWrapper.wrap(originalRoot));
50+
}
51+
52+
return originalRoot;
53+
}
4154
}

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

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,43 @@ private SentryContextWrapper(final @NotNull Context delegate) {
2121
}
2222

2323
@Override
24-
public <V> V get(final @NotNull ContextKey<V> contextKey) {
25-
return delegate.get(contextKey);
24+
public <V> @Nullable V get(final @NotNull ContextKey<V> contextKey) {
25+
final @Nullable V result = delegate.get(contextKey);
26+
if (shouldReturnRootSpanInstead(contextKey, result)) {
27+
return returnUnfinishedRootSpanIfAvailable(result);
28+
}
29+
return result;
30+
}
31+
32+
private <V> boolean shouldReturnRootSpanInstead(
33+
final @NotNull ContextKey<V> contextKey, final @Nullable V result) {
34+
if (!Sentry.isGlobalHubMode()) {
35+
return false;
36+
}
37+
if (!isOpentelemetrySpan(contextKey)) {
38+
return false;
39+
}
40+
if (result == null) {
41+
return true;
42+
}
43+
if (result instanceof SentryOtelGlobalHubModeSpan) {
44+
return true;
45+
}
46+
return result instanceof Span && !((Span) result).getSpanContext().isValid();
47+
}
48+
49+
@SuppressWarnings("unchecked")
50+
private <V> @Nullable V returnUnfinishedRootSpanIfAvailable(final @Nullable V result) {
51+
final @Nullable IOtelSpanWrapper sentrySpan =
52+
SentryWeakSpanStorage.getInstance().getLastKnownUnfinishedRootSpan();
53+
if (sentrySpan != null) {
54+
try {
55+
return (V) sentrySpan.getOpenTelemetrySpan();
56+
} catch (Throwable t) {
57+
return result;
58+
}
59+
}
60+
return result;
2661
}
2762

2863
@Override
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package io.sentry.opentelemetry;
2+
3+
import io.opentelemetry.api.common.AttributeKey;
4+
import io.opentelemetry.api.common.Attributes;
5+
import io.opentelemetry.api.trace.Span;
6+
import io.opentelemetry.api.trace.SpanContext;
7+
import io.opentelemetry.api.trace.StatusCode;
8+
import java.util.concurrent.TimeUnit;
9+
import org.jetbrains.annotations.ApiStatus;
10+
import org.jetbrains.annotations.NotNull;
11+
import org.jetbrains.annotations.Nullable;
12+
13+
@ApiStatus.Experimental
14+
public final class SentryOtelGlobalHubModeSpan implements Span {
15+
16+
private @NotNull Span getOtelSpan() {
17+
final @Nullable IOtelSpanWrapper lastKnownUnfinishedRootSpan =
18+
SentryWeakSpanStorage.getInstance().getLastKnownUnfinishedRootSpan();
19+
if (lastKnownUnfinishedRootSpan != null) {
20+
final @Nullable Span openTelemetrySpan = lastKnownUnfinishedRootSpan.getOpenTelemetrySpan();
21+
if (openTelemetrySpan != null) {
22+
return openTelemetrySpan;
23+
}
24+
}
25+
26+
return Span.getInvalid();
27+
}
28+
29+
@Override
30+
public <T> Span setAttribute(AttributeKey<T> key, T value) {
31+
return getOtelSpan().setAttribute(key, value);
32+
}
33+
34+
@Override
35+
public Span addEvent(String name, Attributes attributes) {
36+
return getOtelSpan().addEvent(name, attributes);
37+
}
38+
39+
@Override
40+
public Span addEvent(String name, Attributes attributes, long timestamp, TimeUnit unit) {
41+
return getOtelSpan().addEvent(name, attributes, timestamp, unit);
42+
}
43+
44+
@Override
45+
public Span setStatus(StatusCode statusCode, String description) {
46+
return getOtelSpan().setStatus(statusCode, description);
47+
}
48+
49+
@Override
50+
public Span recordException(Throwable exception, Attributes additionalAttributes) {
51+
return getOtelSpan().recordException(exception, additionalAttributes);
52+
}
53+
54+
@Override
55+
public Span updateName(String name) {
56+
return getOtelSpan().updateName(name);
57+
}
58+
59+
@Override
60+
public void end() {
61+
getOtelSpan().end();
62+
}
63+
64+
@Override
65+
public void end(long timestamp, TimeUnit unit) {
66+
getOtelSpan().end(timestamp, unit);
67+
}
68+
69+
@Override
70+
public SpanContext getSpanContext() {
71+
return getOtelSpan().getSpanContext();
72+
}
73+
74+
@Override
75+
public boolean isRecording() {
76+
return getOtelSpan().isRecording();
77+
}
78+
}

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

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

3+
import io.opentelemetry.api.common.Attributes;
34
import io.opentelemetry.api.trace.SpanContext;
45
import io.opentelemetry.context.internal.shaded.WeakConcurrentMap;
56
import io.sentry.ISentryLifecycleToken;
67
import io.sentry.util.AutoClosableReentrantLock;
8+
import java.lang.ref.WeakReference;
79
import org.jetbrains.annotations.ApiStatus;
810
import org.jetbrains.annotations.NotNull;
911
import org.jetbrains.annotations.Nullable;
@@ -34,6 +36,8 @@ public final class SentryWeakSpanStorage {
3436
// weak keys, spawns a thread to clean up values that have been garbage collected
3537
private final @NotNull WeakConcurrentMap<SpanContext, IOtelSpanWrapper> sentrySpans =
3638
new WeakConcurrentMap<>(true);
39+
private volatile @NotNull WeakReference<IOtelSpanWrapper> lastKnownRootSpan =
40+
new WeakReference<>(null);
3741

3842
private SentryWeakSpanStorage() {}
3943

@@ -43,11 +47,45 @@ private SentryWeakSpanStorage() {}
4347

4448
public void storeSentrySpan(
4549
final @NotNull SpanContext otelSpan, final @NotNull IOtelSpanWrapper sentrySpan) {
50+
System.out.println("storing span: " + sentrySpan.getOperation());
4651
this.sentrySpans.put(otelSpan, sentrySpan);
52+
if (shouldStoreSpanAsRootSpan(sentrySpan)) {
53+
System.out.println("storing span as last known root: " + sentrySpan.getOperation());
54+
lastKnownRootSpan = new WeakReference<>(sentrySpan);
55+
}
56+
}
57+
58+
private boolean shouldStoreSpanAsRootSpan(final @NotNull IOtelSpanWrapper sentrySpan) {
59+
if (!sentrySpan.isRoot()) {
60+
return false;
61+
}
62+
63+
final @Nullable IOtelSpanWrapper previousRootSpan = getLastKnownUnfinishedRootSpan();
64+
if (previousRootSpan == null) {
65+
return true;
66+
}
67+
68+
final @Nullable Attributes attributes = previousRootSpan.getOpenTelemetrySpanAttributes();
69+
if (attributes == null) {
70+
return true;
71+
}
72+
73+
final @Nullable Boolean isCreatedViaSentryApi =
74+
attributes.get(InternalSemanticAttributes.CREATED_VIA_SENTRY_API);
75+
return isCreatedViaSentryApi != null && isCreatedViaSentryApi == true;
76+
}
77+
78+
public @Nullable IOtelSpanWrapper getLastKnownUnfinishedRootSpan() {
79+
final @Nullable IOtelSpanWrapper span = lastKnownRootSpan.get();
80+
if (span != null && !span.isFinished()) {
81+
return span;
82+
}
83+
return null;
4784
}
4885

4986
@TestOnly
5087
public void clear() {
5188
sentrySpans.clear();
89+
lastKnownRootSpan.clear();
5290
}
5391
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ public final class io/sentry/opentelemetry/OtelSpanWrapper : io/sentry/opentelem
6161
public fun getDescription ()Ljava/lang/String;
6262
public fun getFinishDate ()Lio/sentry/SentryDate;
6363
public fun getMeasurements ()Ljava/util/Map;
64+
public fun getOpenTelemetrySpan ()Lio/opentelemetry/api/trace/Span;
6465
public fun getOpenTelemetrySpanAttributes ()Lio/opentelemetry/api/common/Attributes;
6566
public fun getOperation ()Ljava/lang/String;
6667
public fun getSamplingDecision ()Lio/sentry/TracesSamplingDecision;
@@ -77,6 +78,7 @@ public final class io/sentry/opentelemetry/OtelSpanWrapper : io/sentry/opentelem
7778
public fun isFinished ()Z
7879
public fun isNoOp ()Z
7980
public fun isProfileSampled ()Ljava/lang/Boolean;
81+
public fun isRoot ()Z
8082
public fun isSampled ()Ljava/lang/Boolean;
8183
public fun makeCurrent ()Lio/sentry/ISentryLifecycleToken;
8284
public fun setContext (Ljava/lang/String;Ljava/lang/Object;)V

0 commit comments

Comments
 (0)