diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b7c2efd7a0..b7376d03922 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Changelog +### Features + +- The SDK now automatically propagates the trace-context to the native layer. This allows to connect errors on different layers of the application. ([#4137](https://github.com/getsentry/sentry-java/pull/4137)) + +### Dependencies + +- Bump Native SDK from v0.7.20 to v0.8.1 ([#4137](https://github.com/getsentry/sentry-java/pull/4137)) + - [changelog](https://github.com/getsentry/sentry-native/blob/master/CHANGELOG.md#0810) + - [diff](https://github.com/getsentry/sentry-native/compare/v0.7.20...0.8.1) + ## 8.3.0 ### Features diff --git a/buildSrc/src/main/java/Config.kt b/buildSrc/src/main/java/Config.kt index 0a3c62a1555..03b6849a6ba 100644 --- a/buildSrc/src/main/java/Config.kt +++ b/buildSrc/src/main/java/Config.kt @@ -159,7 +159,7 @@ object Config { val apolloKotlin = "com.apollographql.apollo3:apollo-runtime:3.8.2" val apolloKotlin4 = "com.apollographql.apollo:apollo-runtime:4.1.1" - val sentryNativeNdk = "io.sentry:sentry-native-ndk:0.7.20" + val sentryNativeNdk = "io.sentry:sentry-native-ndk:0.8.1" object OpenTelemetry { val otelVersion = "1.44.1" diff --git a/sentry-android-core/api/sentry-android-core.api b/sentry-android-core/api/sentry-android-core.api index 59caf171564..85197f80380 100644 --- a/sentry-android-core/api/sentry-android-core.api +++ b/sentry-android-core/api/sentry-android-core.api @@ -221,6 +221,7 @@ public final class io/sentry/android/core/InternalSentrySdk { public static fun getAppStartMeasurement ()Ljava/util/Map; public static fun getCurrentScope ()Lio/sentry/IScope; public static fun serializeScope (Landroid/content/Context;Lio/sentry/android/core/SentryAndroidOptions;Lio/sentry/IScope;)Ljava/util/Map; + public static fun setTrace (Ljava/lang/String;Ljava/lang/String;Ljava/lang/Double;Ljava/lang/Double;)V } public final class io/sentry/android/core/LoadClass : io/sentry/util/LoadClass { diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/InternalSentrySdk.java b/sentry-android-core/src/main/java/io/sentry/android/core/InternalSentrySdk.java index 841edf8109a..cae558f0d43 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/InternalSentrySdk.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/InternalSentrySdk.java @@ -1,5 +1,6 @@ package io.sentry.android.core; +import static io.sentry.Sentry.getCurrentScopes; import static io.sentry.SentryLevel.DEBUG; import static io.sentry.SentryLevel.INFO; import static io.sentry.SentryLevel.WARNING; @@ -13,6 +14,7 @@ import io.sentry.IScopes; import io.sentry.ISerializer; import io.sentry.ObjectWriter; +import io.sentry.PropagationContext; import io.sentry.ScopeType; import io.sentry.ScopesAdapter; import io.sentry.SentryEnvelope; @@ -30,6 +32,7 @@ import io.sentry.protocol.SentryId; import io.sentry.protocol.User; import io.sentry.util.MapObjectWriter; +import io.sentry.util.TracingUtils; import java.io.ByteArrayInputStream; import java.io.File; import java.io.InputStream; @@ -329,4 +332,22 @@ private static Session updateSession( }); return sessionRef.get(); } + + /** + * Allows a Hybrid SDK to set the trace on the native layer + * + * @param traceId the trace ID + * @param spanId the trace origin's span ID + * @param sampleRate the sample rate used by the origin of the trace + * @param sampleRand the random value used to sample with by the origin of the trace + */ + public static void setTrace( + final @NotNull String traceId, + final @NotNull String spanId, + final @Nullable Double sampleRate, + final @Nullable Double sampleRand) { + TracingUtils.setTrace( + getCurrentScopes(), + PropagationContext.fromExistingTrace(traceId, spanId, sampleRate, sampleRand)); + } } diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/InternalSentrySdkTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/InternalSentrySdkTest.kt index 01b9845a9fe..7ddabb84ea7 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/InternalSentrySdkTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/InternalSentrySdkTest.kt @@ -19,6 +19,7 @@ import io.sentry.SentryExceptionFactory import io.sentry.SentryItemType import io.sentry.SentryOptions import io.sentry.Session +import io.sentry.SpanId import io.sentry.android.core.performance.ActivityLifecycleTimeSpan import io.sentry.android.core.performance.AppStartMetrics import io.sentry.exception.ExceptionMechanismException @@ -505,4 +506,25 @@ class InternalSentrySdkTest { assertEquals(20.toLong(), actualProcessSpan["start_timestamp_ms"]) assertEquals(100.toLong(), actualProcessSpan["end_timestamp_ms"]) } + + @Test + fun `setTrace sets correct propagation context`() { + val fixture = Fixture() + fixture.init(context) + + val traceId = "771a43a4192642f0b136d5159a501700" + val spanId = "771a43a4192642f0" + val sampleRate = 0.5 + val sampleRand = 0.3 + + InternalSentrySdk.setTrace(traceId, spanId, sampleRate, sampleRand) + + Sentry.configureScope { scope -> + val propagationContext = scope.propagationContext + assertEquals(SentryId(traceId), propagationContext.traceId) + assertEquals(SpanId(spanId), propagationContext.parentSpanId) + assertEquals(sampleRate, propagationContext.baggage.sampleRateDouble) + assertEquals(sampleRand, propagationContext.baggage.sampleRandDouble) + } + } } diff --git a/sentry-android-ndk/api/sentry-android-ndk.api b/sentry-android-ndk/api/sentry-android-ndk.api index eb5f48a9bd9..44c153a71fe 100644 --- a/sentry-android-ndk/api/sentry-android-ndk.api +++ b/sentry-android-ndk/api/sentry-android-ndk.api @@ -20,6 +20,7 @@ public final class io/sentry/android/ndk/NdkScopeObserver : io/sentry/ScopeObser public fun removeTag (Ljava/lang/String;)V public fun setExtra (Ljava/lang/String;Ljava/lang/String;)V public fun setTag (Ljava/lang/String;Ljava/lang/String;)V + public fun setTrace (Lio/sentry/SpanContext;Lio/sentry/IScope;)V public fun setUser (Lio/sentry/protocol/User;)V } diff --git a/sentry-android-ndk/src/main/java/io/sentry/android/ndk/NdkScopeObserver.java b/sentry-android-ndk/src/main/java/io/sentry/android/ndk/NdkScopeObserver.java index 118b1f68511..023ce965f51 100644 --- a/sentry-android-ndk/src/main/java/io/sentry/android/ndk/NdkScopeObserver.java +++ b/sentry-android-ndk/src/main/java/io/sentry/android/ndk/NdkScopeObserver.java @@ -2,9 +2,11 @@ import io.sentry.Breadcrumb; import io.sentry.DateUtils; +import io.sentry.IScope; import io.sentry.ScopeObserverAdapter; import io.sentry.SentryLevel; import io.sentry.SentryOptions; +import io.sentry.SpanContext; import io.sentry.ndk.INativeScope; import io.sentry.ndk.NativeScope; import io.sentry.protocol.User; @@ -125,4 +127,22 @@ public void removeExtra(final @NotNull String key) { .log(SentryLevel.ERROR, e, "Scope sync removeExtra(%s) has an error.", key); } } + + @Override + public void setTrace(@Nullable SpanContext spanContext, @NotNull IScope scope) { + if (spanContext == null) { + return; + } + + try { + options + .getExecutorService() + .submit( + () -> + nativeScope.setTrace( + spanContext.getTraceId().toString(), spanContext.getSpanId().toString())); + } catch (Throwable e) { + options.getLogger().log(SentryLevel.ERROR, e, "Scope sync setTrace failed."); + } + } } diff --git a/sentry/api/sentry.api b/sentry/api/sentry.api index 82e18268c99..1c4883e078e 100644 --- a/sentry/api/sentry.api +++ b/sentry/api/sentry.api @@ -1952,6 +1952,7 @@ public final class io/sentry/PropagationContext { public fun ()V public fun (Lio/sentry/PropagationContext;)V public fun (Lio/sentry/protocol/SentryId;Lio/sentry/SpanId;Lio/sentry/SpanId;Lio/sentry/Baggage;Ljava/lang/Boolean;)V + public static fun fromExistingTrace (Ljava/lang/String;Ljava/lang/String;Ljava/lang/Double;Ljava/lang/Double;)Lio/sentry/PropagationContext; public static fun fromHeaders (Lio/sentry/ILogger;Ljava/lang/String;Ljava/lang/String;)Lio/sentry/PropagationContext; public static fun fromHeaders (Lio/sentry/ILogger;Ljava/lang/String;Ljava/util/List;)Lio/sentry/PropagationContext; public static fun fromHeaders (Lio/sentry/SentryTraceHeader;Lio/sentry/Baggage;Lio/sentry/SpanId;)Lio/sentry/PropagationContext; @@ -6360,6 +6361,7 @@ public final class io/sentry/util/TracingUtils { public static fun ensureBaggage (Lio/sentry/Baggage;Ljava/lang/Boolean;Ljava/lang/Double;Ljava/lang/Double;)Lio/sentry/Baggage; public static fun isIgnored (Ljava/util/List;Ljava/lang/String;)Z public static fun maybeUpdateBaggage (Lio/sentry/IScope;Lio/sentry/SentryOptions;)Lio/sentry/PropagationContext; + public static fun setTrace (Lio/sentry/IScopes;Lio/sentry/PropagationContext;)V public static fun startNewTrace (Lio/sentry/IScopes;)V public static fun trace (Lio/sentry/IScopes;Ljava/util/List;Lio/sentry/ISpan;)Lio/sentry/util/TracingUtils$TracingHeaders; public static fun traceIfAllowed (Lio/sentry/IScopes;Ljava/lang/String;Ljava/util/List;Lio/sentry/ISpan;)Lio/sentry/util/TracingUtils$TracingHeaders; diff --git a/sentry/src/main/java/io/sentry/PropagationContext.java b/sentry/src/main/java/io/sentry/PropagationContext.java index 791cb1d3d36..547b09f3861 100644 --- a/sentry/src/main/java/io/sentry/PropagationContext.java +++ b/sentry/src/main/java/io/sentry/PropagationContext.java @@ -51,6 +51,19 @@ public static PropagationContext fromHeaders( sentryTraceHeader.isSampled()); } + public static @NotNull PropagationContext fromExistingTrace( + final @NotNull String traceId, + final @NotNull String spanId, + final @Nullable Double decisionSampleRate, + final @Nullable Double decisionSampleRand) { + return new PropagationContext( + new SentryId(traceId), + new SpanId(), + new SpanId(spanId), + TracingUtils.ensureBaggage(null, null, decisionSampleRate, decisionSampleRand), + null); + } + private @NotNull SentryId traceId; private @NotNull SpanId spanId; private @Nullable SpanId parentSpanId; diff --git a/sentry/src/main/java/io/sentry/util/TracingUtils.java b/sentry/src/main/java/io/sentry/util/TracingUtils.java index 8673b358a99..bae78476aa2 100644 --- a/sentry/src/main/java/io/sentry/util/TracingUtils.java +++ b/sentry/src/main/java/io/sentry/util/TracingUtils.java @@ -28,6 +28,17 @@ public static void startNewTrace(final @NotNull IScopes scopes) { }); } + public static void setTrace( + final @NotNull IScopes scopes, final @NotNull PropagationContext propagationContext) { + scopes.configureScope( + scope -> { + scope.withPropagationContext( + oldPropagationContext -> { + scope.setPropagationContext(propagationContext); + }); + }); + } + public static @Nullable TracingHeaders traceIfAllowed( final @NotNull IScopes scopes, final @NotNull String requestUrl,