Skip to content

Support globalHubMode for OpenTelemetry #4349

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 22 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 3 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 @@ -5,6 +5,9 @@
### Features

- Add `SentryWrapper.wrapRunnable` to wrap `Runnable` for use with Sentry ([#4332](https://github.com/getsentry/sentry-java/pull/4332))
- Support `globalHubMode` for OpenTelemetry
- Sentry now adds OpenTelemetry spans without a parent to the last known unfinished root span (transaction)
- Previously those spans would end up in Sentry as separate transactions

### Fixes

Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
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 getOpenTelemetrySpan ()Lio/opentelemetry/api/trace/Span;
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;
public abstract fun getTransactionName ()Ljava/lang/String;
public abstract fun getTransactionNameSource ()Lio/sentry/protocol/TransactionNameSource;
public abstract fun isProfileSampled ()Ljava/lang/Boolean;
public abstract fun isRoot ()Z
public abstract fun setTransactionName (Ljava/lang/String;)V
public abstract fun setTransactionName (Ljava/lang/String;Lio/sentry/protocol/TransactionNameSource;)V
public abstract fun storeInContext (Lio/opentelemetry/context/Context;)Lio/opentelemetry/context/Context;
Expand Down Expand Up @@ -52,6 +54,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 getOpenTelemetrySpan ()Lio/opentelemetry/api/trace/Span;
public fun getOpenTelemetrySpanAttributes ()Lio/opentelemetry/api/common/Attributes;
public fun getOperation ()Ljava/lang/String;
public fun getSamplingDecision ()Lio/sentry/TracesSamplingDecision;
Expand All @@ -68,6 +71,7 @@ public final class io/sentry/opentelemetry/OtelStrongRefSpanWrapper : io/sentry/
public fun isFinished ()Z
public fun isNoOp ()Z
public fun isProfileSampled ()Ljava/lang/Boolean;
public fun isRoot ()Z
public fun isSampled ()Ljava/lang/Boolean;
public fun makeCurrent ()Lio/sentry/ISentryLifecycleToken;
public fun setContext (Ljava/lang/String;Ljava/lang/Object;)V
Expand Down Expand Up @@ -151,6 +155,7 @@ public final class io/sentry/opentelemetry/SentryContextStorage : io/opentelemet
public fun <init> (Lio/opentelemetry/context/ContextStorage;)V
public fun attach (Lio/opentelemetry/context/Context;)Lio/opentelemetry/context/Scope;
public fun current ()Lio/opentelemetry/context/Context;
public fun root ()Lio/opentelemetry/context/Context;
}

public final class io/sentry/opentelemetry/SentryContextStorageProvider : io/opentelemetry/context/ContextStorageProvider {
Expand Down Expand Up @@ -181,6 +186,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 getLastKnownUnfinishedRootSpan ()Lio/sentry/opentelemetry/IOtelSpanWrapper;
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,6 +1,7 @@
package io.sentry.opentelemetry;

import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.context.Context;
import io.sentry.IScopes;
import io.sentry.ISpan;
Expand Down Expand Up @@ -52,4 +53,9 @@ public interface IOtelSpanWrapper extends ISpan {
@ApiStatus.Internal
@Nullable
Attributes getOpenTelemetrySpanAttributes();

boolean isRoot();

@Nullable
Span getOpenTelemetrySpan();
}
Original file line number Diff line number Diff line change
Expand Up @@ -310,4 +310,14 @@ public void setContext(@Nullable String key, @Nullable Object context) {
public @Nullable Attributes getOpenTelemetrySpanAttributes() {
return delegate.getOpenTelemetrySpanAttributes();
}

@Override
public boolean isRoot() {
return delegate.isRoot();
}

@Override
public @Nullable Span getOpenTelemetrySpan() {
return delegate.getOpenTelemetrySpan();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import io.opentelemetry.context.Context;
import io.opentelemetry.context.ContextStorage;
import io.opentelemetry.context.Scope;
import io.sentry.Sentry;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;

Expand Down Expand Up @@ -38,4 +39,15 @@ public Scope attach(Context toAttach) {
public Context current() {
return contextStorage.current();
}

@Override
public Context root() {
final @NotNull Context originalRoot = ContextStorage.super.root();

if (Sentry.isGlobalHubMode()) {
return SentryContextWrapper.wrap(originalRoot);
}

return originalRoot;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,40 @@ private SentryContextWrapper(final @NotNull Context delegate) {
}

@Override
public <V> V get(final @NotNull ContextKey<V> contextKey) {
return delegate.get(contextKey);
public <V> @Nullable V get(final @NotNull ContextKey<V> contextKey) {
final @Nullable V result = delegate.get(contextKey);
if (shouldReturnRootSpanInstead(contextKey, result)) {
return returnUnfinishedRootSpanIfAvailable(result);
}
return result;
}

private <V> boolean shouldReturnRootSpanInstead(
final @NotNull ContextKey<V> contextKey, final @Nullable V result) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(l) not sure about the param naming. Would something other than result be more fitting?

if (!Sentry.isGlobalHubMode()) {
return false;
}
if (!isOpentelemetrySpan(contextKey)) {
return false;
}
if (result == null) {
return true;
}
return result instanceof Span && !((Span) result).getSpanContext().isValid();
}

@SuppressWarnings("unchecked")
private <V> @Nullable V returnUnfinishedRootSpanIfAvailable(final @Nullable V result) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(l) not sure about the param naming. Would something other than result be more fitting?

final @Nullable IOtelSpanWrapper sentrySpan =
SentryWeakSpanStorage.getInstance().getLastKnownUnfinishedRootSpan();
if (sentrySpan != null) {
try {
return (V) sentrySpan.getOpenTelemetrySpan();
} catch (Throwable t) {
return result;
}
}
return result;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import io.opentelemetry.context.internal.shaded.WeakConcurrentMap;
import io.sentry.ISentryLifecycleToken;
import io.sentry.util.AutoClosableReentrantLock;
import java.lang.ref.WeakReference;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
Expand Down Expand Up @@ -34,6 +35,8 @@ public final class SentryWeakSpanStorage {
// weak keys, spawns a thread to clean up values that have been garbage collected
private final @NotNull WeakConcurrentMap<SpanContext, IOtelSpanWrapper> sentrySpans =
new WeakConcurrentMap<>(true);
private volatile @NotNull WeakReference<IOtelSpanWrapper> lastKnownRootSpan =
new WeakReference<>(null);

private SentryWeakSpanStorage() {}

Expand All @@ -44,6 +47,17 @@ private SentryWeakSpanStorage() {}
public void storeSentrySpan(
final @NotNull SpanContext otelSpan, final @NotNull IOtelSpanWrapper sentrySpan) {
this.sentrySpans.put(otelSpan, sentrySpan);
if (sentrySpan.isRoot()) {
lastKnownRootSpan = new WeakReference<>(sentrySpan);
}
}

public @Nullable IOtelSpanWrapper getLastKnownUnfinishedRootSpan() {
final @Nullable IOtelSpanWrapper span = lastKnownRootSpan.get();
if (span != null && !span.isFinished()) {
return span;
}
return null;
}

@TestOnly
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,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 getOpenTelemetrySpan ()Lio/opentelemetry/api/trace/Span;
public fun getOpenTelemetrySpanAttributes ()Lio/opentelemetry/api/common/Attributes;
public fun getOperation ()Ljava/lang/String;
public fun getSamplingDecision ()Lio/sentry/TracesSamplingDecision;
Expand All @@ -77,6 +78,7 @@ public final class io/sentry/opentelemetry/OtelSpanWrapper : io/sentry/opentelem
public fun isFinished ()Z
public fun isNoOp ()Z
public fun isProfileSampled ()Ljava/lang/Boolean;
public fun isRoot ()Z
public fun isSampled ()Ljava/lang/Boolean;
public fun makeCurrent ()Lio/sentry/ISentryLifecycleToken;
public fun setContext (Ljava/lang/String;Ljava/lang/Object;)V
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -515,6 +515,25 @@ public Map<String, MeasurementValue> getMeasurements() {
}
}

@Override
public boolean isRoot() {
if (context.getParentSpanId() == null) {
return true;
}

final @Nullable ReadWriteSpan readWriteSpan = span.get();
if (readWriteSpan != null) {
return readWriteSpan.getParentSpanContext().isRemote();
}

return false;
}

@Override
public @Nullable Span getOpenTelemetrySpan() {
return span.get();
}

@SuppressWarnings("MustBeClosedChecker")
@ApiStatus.Internal
@Override
Expand Down
1 change: 1 addition & 0 deletions sentry/api/sentry.api
Original file line number Diff line number Diff line change
Expand Up @@ -2529,6 +2529,7 @@ public final class io/sentry/Sentry {
public static fun init (Ljava/lang/String;)V
public static fun isCrashedLastRun ()Ljava/lang/Boolean;
public static fun isEnabled ()Z
public static fun isGlobalHubMode ()Z
public static fun isHealthy ()Z
public static fun popScope ()V
public static fun pushIsolationScope ()Lio/sentry/ISentryLifecycleToken;
Expand Down
4 changes: 4 additions & 0 deletions sentry/src/main/java/io/sentry/Sentry.java
Original file line number Diff line number Diff line change
Expand Up @@ -1225,4 +1225,8 @@ public interface OptionsConfiguration<T extends SentryOptions> {
public static @NotNull SentryId captureCheckIn(final @NotNull CheckIn checkIn) {
return getCurrentScopes().captureCheckIn(checkIn);
}

public static boolean isGlobalHubMode() {
return globalHubMode;
}
}
Loading