diff --git a/CHANGELOG.md b/CHANGELOG.md index f81d76ab606..52dddc8b44a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ - The `ignoredErrors` option is now configurable via the manifest property `io.sentry.traces.ignored-errors` ([#4178](https://github.com/getsentry/sentry-java/pull/4178)) - A list of active Spring profiles is attached to payloads sent to Sentry (errors, traces, etc.) and displayed in the UI when using our Spring or Spring Boot integrations ([#4147](https://github.com/getsentry/sentry-java/pull/4147)) - This consists of an empty list when only the default profile is active +- Added `enableTraceIdGeneration` to the AndroidOptions. This allows Hybrid SDKs to "freeze" and control the trace and connect errors on different layers of the application ([4188](https://github.com/getsentry/sentry-java/pull/4188)) - Move to a single NetworkCallback listener to reduce number of IPC calls on Android ([#4164](https://github.com/getsentry/sentry-java/pull/4164)) - Add GraphQL Apollo Kotlin 4 integration ([#4166](https://github.com/getsentry/sentry-java/pull/4166)) diff --git a/sentry-android-core/api/sentry-android-core.api b/sentry-android-core/api/sentry-android-core.api index ff982b51f69..59caf171564 100644 --- a/sentry-android-core/api/sentry-android-core.api +++ b/sentry-android-core/api/sentry-android-core.api @@ -292,6 +292,7 @@ public final class io/sentry/android/core/SentryAndroidOptions : io/sentry/Sentr public fun isEnableAppComponentBreadcrumbs ()Z public fun isEnableAppLifecycleBreadcrumbs ()Z public fun isEnableAutoActivityLifecycleTracing ()Z + public fun isEnableAutoTraceIdGeneration ()Z public fun isEnableFramesTracking ()Z public fun isEnableNdk ()Z public fun isEnableNetworkEventBreadcrumbs ()Z @@ -315,6 +316,7 @@ public final class io/sentry/android/core/SentryAndroidOptions : io/sentry/Sentr public fun setEnableAppComponentBreadcrumbs (Z)V public fun setEnableAppLifecycleBreadcrumbs (Z)V public fun setEnableAutoActivityLifecycleTracing (Z)V + public fun setEnableAutoTraceIdGeneration (Z)V public fun setEnableFramesTracking (Z)V public fun setEnableNdk (Z)V public fun setEnableNetworkEventBreadcrumbs (Z)V diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/ActivityLifecycleIntegration.java b/sentry-android-core/src/main/java/io/sentry/android/core/ActivityLifecycleIntegration.java index 3c0d8b3a5c7..0bdfee71fd4 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/ActivityLifecycleIntegration.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/ActivityLifecycleIntegration.java @@ -161,7 +161,9 @@ private void startTracing(final @NotNull Activity activity) { if (scopes != null && !isRunningTransactionOrTrace(activity)) { if (!performanceEnabled) { activitiesWithOngoingTransactions.put(activity, NoOpTransaction.getInstance()); - TracingUtils.startNewTrace(scopes); + if (options.isEnableAutoTraceIdGeneration()) { + TracingUtils.startNewTrace(scopes); + } } else { // as we allow a single transaction running on the bound Scope, we finish the previous ones stopPreviousTransactions(); diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/ManifestMetadataReader.java b/sentry-android-core/src/main/java/io/sentry/android/core/ManifestMetadataReader.java index e2389e60492..86d9d6aa292 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/ManifestMetadataReader.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/ManifestMetadataReader.java @@ -107,6 +107,9 @@ final class ManifestMetadataReader { static final String IGNORED_ERRORS = "io.sentry.ignored-errors"; + static final String ENABLE_AUTO_TRACE_ID_GENERATION = + "io.sentry.traces.enable-auto-id-generation"; + /** ManifestMetadataReader ctor */ private ManifestMetadataReader() {} @@ -380,6 +383,13 @@ static void applyMetadata( readBool( metadata, logger, ENABLE_SCOPE_PERSISTENCE, options.isEnableScopePersistence())); + options.setEnableAutoTraceIdGeneration( + readBool( + metadata, + logger, + ENABLE_AUTO_TRACE_ID_GENERATION, + options.isEnableAutoTraceIdGeneration())); + if (options.getSessionReplay().getSessionSampleRate() == null) { final Double sessionSampleRate = readDouble(metadata, logger, REPLAYS_SESSION_SAMPLE_RATE); diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/SentryAndroidOptions.java b/sentry-android-core/src/main/java/io/sentry/android/core/SentryAndroidOptions.java index 9c32920be89..f9de207b7e3 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/SentryAndroidOptions.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/SentryAndroidOptions.java @@ -166,6 +166,12 @@ public final class SentryAndroidOptions extends SentryOptions { */ private boolean enableScopeSync = true; + /** + * Whether to enable automatic trace ID generation. This is mainly used by the Hybrid SDKs to + * control the trace ID generation from the outside. + */ + private boolean enableAutoTraceIdGeneration = true; + public interface BeforeCaptureCallback { /** @@ -594,4 +600,12 @@ public void setFrameMetricsCollector( final @Nullable SentryFrameMetricsCollector frameMetricsCollector) { this.frameMetricsCollector = frameMetricsCollector; } + + public boolean isEnableAutoTraceIdGeneration() { + return enableAutoTraceIdGeneration; + } + + public void setEnableAutoTraceIdGeneration(final boolean enableAutoTraceIdGeneration) { + this.enableAutoTraceIdGeneration = enableAutoTraceIdGeneration; + } } diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/internal/gestures/SentryGestureListener.java b/sentry-android-core/src/main/java/io/sentry/android/core/internal/gestures/SentryGestureListener.java index cd80f5ced7d..ab90f82df41 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/internal/gestures/SentryGestureListener.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/internal/gestures/SentryGestureListener.java @@ -202,7 +202,9 @@ private void startTracing(final @NotNull UiElement target, final @NotNull Gestur if (!(options.isTracingEnabled() && options.isEnableUserInteractionTracing())) { if (isNewInteraction) { - TracingUtils.startNewTrace(scopes); + if (options.isEnableAutoTraceIdGeneration()) { + TracingUtils.startNewTrace(scopes); + } activeUiElement = target; activeEventType = eventType; } diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/ActivityLifecycleIntegrationTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/ActivityLifecycleIntegrationTest.kt index a14f62c3f03..317dbc843c2 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/ActivityLifecycleIntegrationTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/ActivityLifecycleIntegrationTest.kt @@ -1385,10 +1385,11 @@ class ActivityLifecycleIntegrationTest { } @Test - fun `starts new trace if performance is disabled`() { + fun `starts new trace if performance is disabled and trace ID generation is enabled`() { val sut = fixture.getSut() val activity = mock() fixture.options.tracesSampleRate = null + fixture.options.isEnableAutoTraceIdGeneration = true val argumentCaptor: ArgumentCaptor = ArgumentCaptor.forClass(ScopeCallback::class.java) val scope = Scope(fixture.options) @@ -1405,6 +1406,28 @@ class ActivityLifecycleIntegrationTest { assertNotSame(propagationContextAtStart, scope.propagationContext) } + @Test + fun `does not start a new trace if performance is disabled and trace ID generation is disabled`() { + val sut = fixture.getSut() + val activity = mock() + fixture.options.tracesSampleRate = null + fixture.options.isEnableAutoTraceIdGeneration = false + + val argumentCaptor: ArgumentCaptor = ArgumentCaptor.forClass(ScopeCallback::class.java) + val scope = Scope(fixture.options) + val propagationContextAtStart = scope.propagationContext + whenever(fixture.scopes.configureScope(argumentCaptor.capture())).thenAnswer { + argumentCaptor.value.run(scope) + } + + sut.register(fixture.scopes, fixture.options) + sut.onActivityCreated(activity, fixture.bundle) + + // once for the screen + verify(fixture.scopes).configureScope(any()) + assertSame(propagationContextAtStart, scope.propagationContext) + } + @Test fun `sets the activity as the current screen`() { val sut = fixture.getSut() diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/internal/gestures/SentryGestureListenerTracingTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/internal/gestures/SentryGestureListenerTracingTest.kt index 07dde15e8f1..c41a5151931 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/internal/gestures/SentryGestureListenerTracingTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/internal/gestures/SentryGestureListenerTracingTest.kt @@ -35,6 +35,7 @@ import org.mockito.kotlin.verify import org.mockito.kotlin.whenever import kotlin.test.Test import kotlin.test.assertEquals +import kotlin.test.assertNotEquals import kotlin.test.assertNotNull import kotlin.test.assertNull @@ -59,12 +60,14 @@ class SentryGestureListenerTracingTest { hasViewIdInRes: Boolean = true, tracesSampleRate: Double? = 1.0, isEnableUserInteractionTracing: Boolean = true, - transaction: SentryTracer? = null + transaction: SentryTracer? = null, + isEnableAutoTraceIdGeneration: Boolean = true ): SentryGestureListener { options.tracesSampleRate = tracesSampleRate options.isEnableUserInteractionTracing = isEnableUserInteractionTracing options.isEnableUserInteractionBreadcrumbs = true options.gestureTargetLocators = listOf(AndroidViewGestureTargetLocator(true)) + options.isEnableAutoTraceIdGeneration = isEnableAutoTraceIdGeneration whenever(scopes.options).thenReturn(options) @@ -370,6 +373,33 @@ class SentryGestureListenerTracingTest { assertEquals(OUT_OF_RANGE, fixture.transaction.status) } + @Test + fun `when tracing is disabled and auto trace id generation is disabled, does not start a new trace`() { + val sut = fixture.getSut(tracesSampleRate = null, isEnableAutoTraceIdGeneration = false) + + sut.onSingleTapUp(fixture.event) + + verify(fixture.scopes, never()).configureScope(any()) + } + + @Test + fun `when tracing is disabled and auto trace id generation is enabled, starts a new trace`() { + val sut = fixture.getSut(tracesSampleRate = null, isEnableAutoTraceIdGeneration = true) + val scope = Scope(fixture.options) + val initialPropagationContext = scope.propagationContext + + sut.onSingleTapUp(fixture.event) + + verify(fixture.scopes).configureScope( + check { callback -> + callback.run(scope) + // Verify that a new propagation context was set and it's different from the initial one + assertNotNull(scope.propagationContext) + assertNotEquals(initialPropagationContext, scope.propagationContext) + } + ) + } + internal open class ScrollableListView : AbsListView(mock()) { override fun getAdapter(): ListAdapter = mock() override fun setSelection(position: Int) = Unit