Skip to content

Commit cd268a3

Browse files
authored
(Android) Use root transaction instead of last active span (#2855)
1 parent bdf1379 commit cd268a3

File tree

22 files changed

+257
-12
lines changed

22 files changed

+257
-12
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ Breaking changes:
1515
- Apollo v2 BeforeSpanCallback now allows returning null ([#2890](https://github.com/getsentry/sentry-java/pull/2890))
1616
- Automatic user interaction tracking: every click now starts a new automatic transaction ([#2891](https://github.com/getsentry/sentry-java/pull/2891))
1717
- Previously performing a click on the same UI widget twice would keep the existing transaction running, the new behavior now better aligns with other SDKs
18+
- Android only: If global hub mode is enabled, Sentry.getSpan() returns the root span instead of the latest span ([#2855](https://github.com/getsentry/sentry-java/pull/2855))
1819

1920
### Fixes
2021

sentry-android-okhttp/src/main/java/io/sentry/android/okhttp/SentryOkHttpEvent.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import io.sentry.android.okhttp.SentryOkHttpEventListener.Companion.REQUEST_HEAD
1414
import io.sentry.android.okhttp.SentryOkHttpEventListener.Companion.RESPONSE_BODY_EVENT
1515
import io.sentry.android.okhttp.SentryOkHttpEventListener.Companion.RESPONSE_HEADERS_EVENT
1616
import io.sentry.android.okhttp.SentryOkHttpEventListener.Companion.SECURE_CONNECT_EVENT
17+
import io.sentry.util.Platform
1718
import io.sentry.util.UrlUtils
1819
import okhttp3.Request
1920
import okhttp3.Response
@@ -37,7 +38,8 @@ internal class SentryOkHttpEvent(private val hub: IHub, private val request: Req
3738
val method: String = request.method
3839

3940
// We start the call span that will contain all the others
40-
callRootSpan = hub.span?.startChild("http.client", "$method $url")
41+
val parentSpan = if (Platform.isAndroid()) hub.transaction else hub.span
42+
callRootSpan = parentSpan?.startChild("http.client", "$method $url")
4143
callRootSpan?.spanContext?.origin = TRACE_ORIGIN
4244
urlDetails.applyToSpan(callRootSpan)
4345

sentry-android-okhttp/src/main/java/io/sentry/android/okhttp/SentryOkHttpInterceptor.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import io.sentry.exception.ExceptionMechanismException
1919
import io.sentry.exception.SentryHttpClientException
2020
import io.sentry.protocol.Mechanism
2121
import io.sentry.util.HttpUtils
22+
import io.sentry.util.Platform
2223
import io.sentry.util.PropagationTargetsUtils
2324
import io.sentry.util.TracingUtils
2425
import io.sentry.util.UrlUtils
@@ -78,7 +79,8 @@ class SentryOkHttpInterceptor(
7879
isFromEventListener = true
7980
} else {
8081
// read the span from the bound scope
81-
span = hub.span?.startChild("http.client", "$method $url")
82+
val parentSpan = if (Platform.isAndroid()) hub.transaction else hub.span
83+
span = parentSpan?.startChild("http.client", "$method $url")
8284
isFromEventListener = false
8385
}
8486

sentry-apollo-3/src/main/java/io/sentry/apollo3/SentryApollo3HttpInterceptor.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import io.sentry.util.PropagationTargetsUtils
3232
import io.sentry.util.TracingUtils
3333
import io.sentry.util.UrlUtils
3434
import io.sentry.vendor.Base64
35+
import okhttp3.internal.platform.Platform
3536
import okio.Buffer
3637
import org.jetbrains.annotations.ApiStatus
3738

@@ -62,7 +63,7 @@ class SentryApollo3HttpInterceptor @JvmOverloads constructor(
6263
request: HttpRequest,
6364
chain: HttpInterceptorChain
6465
): HttpResponse {
65-
val activeSpan = hub.span
66+
val activeSpan = if (io.sentry.util.Platform.isAndroid()) hub.transaction else hub.span
6667

6768
val operationName = getHeader(HEADER_APOLLO_OPERATION_NAME, request.headers)
6869
val operationType = decodeHeaderValue(request, SENTRY_APOLLO_3_OPERATION_TYPE)

sentry-apollo-3/src/test/java/io/sentry/apollo3/SentryApollo3InterceptorTest.kt

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,13 @@ import io.sentry.TransactionContext
2525
import io.sentry.apollo3.SentryApollo3HttpInterceptor.BeforeSpanCallback
2626
import io.sentry.protocol.SdkVersion
2727
import io.sentry.protocol.SentryTransaction
28+
import io.sentry.util.Apollo3PlatformTestManipulator
2829
import kotlinx.coroutines.launch
2930
import kotlinx.coroutines.runBlocking
3031
import okhttp3.mockwebserver.MockResponse
3132
import okhttp3.mockwebserver.MockWebServer
3233
import okhttp3.mockwebserver.SocketPolicy
34+
import org.junit.Before
3335
import org.mockito.kotlin.any
3436
import org.mockito.kotlin.anyOrNull
3537
import org.mockito.kotlin.check
@@ -112,6 +114,11 @@ class SentryApollo3InterceptorTest {
112114

113115
private val fixture = Fixture()
114116

117+
@Before
118+
fun setup() {
119+
Apollo3PlatformTestManipulator.pretendIsAndroid(false)
120+
}
121+
115122
@Test
116123
fun `creates a span around the successful request`() {
117124
executeQuery()
@@ -307,6 +314,20 @@ class SentryApollo3InterceptorTest {
307314
assert(packageInfo.version == BuildConfig.VERSION_NAME)
308315
}
309316

317+
@Test
318+
fun `attaches to root transaction on Android`() {
319+
Apollo3PlatformTestManipulator.pretendIsAndroid(true)
320+
executeQuery(fixture.getSut())
321+
verify(fixture.hub).transaction
322+
}
323+
324+
@Test
325+
fun `attaches to child span on non-Android`() {
326+
Apollo3PlatformTestManipulator.pretendIsAndroid(false)
327+
executeQuery(fixture.getSut())
328+
verify(fixture.hub).span
329+
}
330+
310331
private fun assertTransactionDetails(it: SentryTransaction, httpStatusCode: Int? = 200, contentLength: Long? = 0L) {
311332
assertEquals(1, it.spans.size)
312333
val httpClientSpan = it.spans.first()
@@ -328,6 +349,7 @@ class SentryApollo3InterceptorTest {
328349
var tx: ITransaction? = null
329350
if (isSpanActive) {
330351
tx = SentryTracer(TransactionContext("op", "desc", TracesSamplingDecision(true)), fixture.hub)
352+
whenever(fixture.hub.transaction).thenReturn(tx)
331353
whenever(fixture.hub.span).thenReturn(tx)
332354
}
333355

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package io.sentry.util
2+
3+
object Apollo3PlatformTestManipulator {
4+
5+
fun pretendIsAndroid(isAndroid: Boolean) {
6+
Platform.isAndroid = isAndroid
7+
}
8+
}

sentry-apollo/src/main/java/io/sentry/apollo/SentryApolloInterceptor.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ class SentryApolloInterceptor(
4444
}
4545

4646
override fun interceptAsync(request: InterceptorRequest, chain: ApolloInterceptorChain, dispatcher: Executor, callBack: CallBack) {
47-
val activeSpan = hub.span
47+
val activeSpan = if (io.sentry.util.Platform.isAndroid()) hub.transaction else hub.span
4848
if (activeSpan == null) {
4949
val headers = addTracingHeaders(request, null)
5050
val modifiedRequest = request.toBuilder().requestHeaders(headers).build()

sentry-apollo/src/test/java/io/sentry/apollo/SentryApolloInterceptorTest.kt

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,13 @@ import io.sentry.TracesSamplingDecision
1919
import io.sentry.TransactionContext
2020
import io.sentry.protocol.SdkVersion
2121
import io.sentry.protocol.SentryTransaction
22+
import io.sentry.util.ApolloPlatformTestManipulator
2223
import kotlinx.coroutines.launch
2324
import kotlinx.coroutines.runBlocking
2425
import okhttp3.mockwebserver.MockResponse
2526
import okhttp3.mockwebserver.MockWebServer
2627
import okhttp3.mockwebserver.SocketPolicy
28+
import org.junit.Before
2729
import org.mockito.kotlin.any
2830
import org.mockito.kotlin.anyOrNull
2931
import org.mockito.kotlin.check
@@ -93,6 +95,11 @@ class SentryApolloInterceptorTest {
9395

9496
private val fixture = Fixture()
9597

98+
@Before
99+
fun setup() {
100+
ApolloPlatformTestManipulator.pretendIsAndroid(false)
101+
}
102+
96103
@Test
97104
fun `creates a span around the successful request`() {
98105
executeQuery()
@@ -234,6 +241,20 @@ class SentryApolloInterceptorTest {
234241
assert(packageInfo.version == BuildConfig.VERSION_NAME)
235242
}
236243

244+
@Test
245+
fun `attaches to root transaction on Android`() {
246+
ApolloPlatformTestManipulator.pretendIsAndroid(true)
247+
executeQuery(fixture.getSut())
248+
verify(fixture.hub).transaction
249+
}
250+
251+
@Test
252+
fun `attaches to child span on non-Android`() {
253+
ApolloPlatformTestManipulator.pretendIsAndroid(false)
254+
executeQuery(fixture.getSut())
255+
verify(fixture.hub).span
256+
}
257+
237258
private fun assertTransactionDetails(it: SentryTransaction) {
238259
assertEquals(1, it.spans.size)
239260
val httpClientSpan = it.spans.first()
@@ -250,6 +271,7 @@ class SentryApolloInterceptorTest {
250271
var tx: ITransaction? = null
251272
if (isSpanActive) {
252273
tx = SentryTracer(TransactionContext("op", "desc", TracesSamplingDecision(true)), fixture.hub)
274+
whenever(fixture.hub.transaction).thenReturn(tx)
253275
whenever(fixture.hub.span).thenReturn(tx)
254276
}
255277

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package io.sentry.util
2+
3+
object ApolloPlatformTestManipulator {
4+
5+
fun pretendIsAndroid(isAndroid: Boolean) {
6+
Platform.isAndroid = isAndroid
7+
}
8+
}

sentry/api/sentry.api

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,7 @@ public final class io/sentry/Hub : io/sentry/IHub {
364364
public fun getOptions ()Lio/sentry/SentryOptions;
365365
public fun getSpan ()Lio/sentry/ISpan;
366366
public fun getTraceparent ()Lio/sentry/SentryTraceHeader;
367+
public fun getTransaction ()Lio/sentry/ITransaction;
367368
public fun isCrashedLastRun ()Ljava/lang/Boolean;
368369
public fun isEnabled ()Z
369370
public fun popScope ()V
@@ -411,6 +412,7 @@ public final class io/sentry/HubAdapter : io/sentry/IHub {
411412
public fun getOptions ()Lio/sentry/SentryOptions;
412413
public fun getSpan ()Lio/sentry/ISpan;
413414
public fun getTraceparent ()Lio/sentry/SentryTraceHeader;
415+
public fun getTransaction ()Lio/sentry/ITransaction;
414416
public fun isCrashedLastRun ()Ljava/lang/Boolean;
415417
public fun isEnabled ()Z
416418
public fun popScope ()V
@@ -483,6 +485,7 @@ public abstract interface class io/sentry/IHub {
483485
public abstract fun getOptions ()Lio/sentry/SentryOptions;
484486
public abstract fun getSpan ()Lio/sentry/ISpan;
485487
public abstract fun getTraceparent ()Lio/sentry/SentryTraceHeader;
488+
public abstract fun getTransaction ()Lio/sentry/ITransaction;
486489
public abstract fun isCrashedLastRun ()Ljava/lang/Boolean;
487490
public abstract fun isEnabled ()Z
488491
public abstract fun popScope ()V
@@ -869,6 +872,7 @@ public final class io/sentry/NoOpHub : io/sentry/IHub {
869872
public fun getOptions ()Lio/sentry/SentryOptions;
870873
public fun getSpan ()Lio/sentry/ISpan;
871874
public fun getTraceparent ()Lio/sentry/SentryTraceHeader;
875+
public fun getTransaction ()Lio/sentry/ITransaction;
872876
public fun isCrashedLastRun ()Ljava/lang/Boolean;
873877
public fun isEnabled ()Z
874878
public fun popScope ()V

0 commit comments

Comments
 (0)