Skip to content

Commit

Permalink
add origin to span context (#2803)
Browse files Browse the repository at this point in the history
* add origin to span context

* add traceorigin to webflux

* add trace origin to spans and transactions

* fix tests, start moving trace origin to constants

* use constants for trace origin string

* add changelog

* remove duplicate constant, dump api

* CR

* remove duplicate trace origin for okhttp integration

* remove spy.log, make api

* fix changelog

* cr, add origin to UIElement to differentiate between old android view system and jetpack compose

* Format code

* add optional appendix for trace origin to SentryNavigationListener

* fix line length

* add / adapt tests to verify trace origin

* fix changelog after merge

* Update CHANGELOG.md

---------

Co-authored-by: Sentry Github Bot <bot+github-bot@sentry.io>
Co-authored-by: Roman Zavarnitsyn <rom4ek93@gmail.com>
  • Loading branch information
3 people committed Jul 21, 2023
1 parent 101f707 commit 695d3a3
Show file tree
Hide file tree
Showing 67 changed files with 271 additions and 24 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## Unreleased

### Features

- Add TraceOrigin to Transactions and Spans ([#2803](https://github.com/getsentry/sentry-java/pull/2803))

### Fixes

- Deduplicate events happening in multiple threads simultaneously (e.g. `OutOfMemoryError`) ([#2845](https://github.com/getsentry/sentry-java/pull/2845))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ public final class ActivityLifecycleIntegration
static final String TTID_OP = "ui.load.initial_display";
static final String TTFD_OP = "ui.load.full_display";
static final long TTFD_TIMEOUT_MILLIS = 30000;
private static final String TRACE_ORIGIN = "auto.ui.activity";

private final @NotNull Application application;
private final @NotNull BuildInfoProvider buildInfoProvider;
Expand Down Expand Up @@ -231,6 +232,7 @@ private void startTracing(final @NotNull Activity activity) {
hub.startTransaction(
new TransactionContext(activityName, TransactionNameSource.COMPONENT, UI_LOAD_OP),
transactionOptions);
setSpanOrigin(transaction);

// in case appStartTime isn't available, we don't create a span for it.
if (!(firstActivityCreated || appStartTime == null || coldStart == null)) {
Expand All @@ -241,6 +243,7 @@ private void startTracing(final @NotNull Activity activity) {
getAppStartDesc(coldStart),
appStartTime,
Instrumenter.SENTRY);
setSpanOrigin(appStartSpan);

// in case there's already an end time (e.g. due to deferred SDK init)
// we can finish the app-start span
Expand All @@ -250,11 +253,13 @@ private void startTracing(final @NotNull Activity activity) {
transaction.startChild(
TTID_OP, getTtidDesc(activityName), ttidStartTime, Instrumenter.SENTRY);
ttidSpanMap.put(activity, ttidSpan);
setSpanOrigin(ttidSpan);

if (timeToFullDisplaySpanEnabled && fullyDisplayedReporter != null && options != null) {
final @NotNull ISpan ttfdSpan =
transaction.startChild(
TTFD_OP, getTtfdDesc(activityName), ttidStartTime, Instrumenter.SENTRY);
setSpanOrigin(ttfdSpan);
try {
ttfdSpanMap.put(activity, ttfdSpan);
ttfdAutoCloseFuture =
Expand Down Expand Up @@ -283,6 +288,12 @@ private void startTracing(final @NotNull Activity activity) {
}
}

private void setSpanOrigin(ISpan span) {
if (span != null) {
span.getSpanContext().setOrigin(TRACE_ORIGIN);
}
}

@VisibleForTesting
void applyScope(final @NotNull Scope scope, final @NotNull ITransaction transaction) {
scope.withTransaction(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
@ApiStatus.Internal
public final class AndroidViewGestureTargetLocator implements GestureTargetLocator {

private static final String ORIGIN = "old_view_system";

private final boolean isAndroidXAvailable;
private final int[] coordinates = new int[2];

Expand Down Expand Up @@ -46,7 +48,7 @@ private UiElement createUiElement(final @NotNull View targetView) {
if (className == null) {
className = targetView.getClass().getSimpleName();
}
return new UiElement(targetView, className, resourceName, null);
return new UiElement(targetView, className, resourceName, null, ORIGIN);
} catch (Resources.NotFoundException ignored) {
return null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
public final class SentryGestureListener implements GestureDetector.OnGestureListener {

static final String UI_ACTION = "ui.action";
private static final String TRACE_ORIGIN = "auto.ui.gesture_listener";

private final @NotNull WeakReference<Activity> activityRef;
private final @NotNull IHub hub;
Expand Down Expand Up @@ -243,6 +244,8 @@ private void startTracing(final @NotNull UiElement target, final @NotNull String
hub.startTransaction(
new TransactionContext(name, TransactionNameSource.COMPONENT, op), transactionOptions);

transaction.getSpanContext().setOrigin(TRACE_ORIGIN + "." + target.getOrigin());

hub.configureScope(
scope -> {
applyScope(scope, transaction);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,13 @@ import io.sentry.IHub
import io.sentry.Scope
import io.sentry.ScopeCallback
import io.sentry.SentryTracer
import io.sentry.SpanContext
import io.sentry.SpanId
import io.sentry.SpanStatus
import io.sentry.TransactionContext
import io.sentry.TransactionOptions
import io.sentry.android.core.SentryAndroidOptions
import io.sentry.protocol.SentryId
import io.sentry.protocol.TransactionNameSource
import org.mockito.kotlin.any
import org.mockito.kotlin.check
Expand Down Expand Up @@ -307,6 +310,10 @@ class SentryGestureListenerTracingTest {
val transaction = mock<SentryTracer>()
val sut = fixture.getSut<View>(transaction = transaction)

whenever(transaction.spanContext).thenReturn(
SpanContext(SentryId.EMPTY_ID, SpanId.EMPTY_ID, "op", null, null)
)

sut.onSingleTapUp(fixture.event)

verify(fixture.hub).startTransaction(
Expand All @@ -323,6 +330,15 @@ class SentryGestureListenerTracingTest {
verify(fixture.transaction).scheduleFinish()
}

@Test
fun `captures transaction and sets trace origin`() {
val sut = fixture.getSut<View>()

sut.onSingleTapUp(fixture.event)

assertEquals("auto.ui.gesture_listener.old_view_system", fixture.transaction.spanContext.origin)
}

internal open class ScrollableListView : AbsListView(mock()) {
override fun getAdapter(): ListAdapter = mock()
override fun setSelection(position: Int) = Unit
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import io.sentry.SpanStatus
import io.sentry.TypeCheckHint.ANDROID_FRAGMENT
import java.util.WeakHashMap

private const val TRACE_ORIGIN = "auto.ui.fragment"

@Suppress("TooManyFunctions")
class SentryFragmentLifecycleCallbacks(
private val hub: IHub = HubAdapter.getInstance(),
Expand Down Expand Up @@ -164,6 +166,7 @@ class SentryFragmentLifecycleCallbacks(

span?.let {
fragmentsWithOngoingTransactions[fragment] = it
it.spanContext.origin = TRACE_ORIGIN
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@ import io.sentry.Scope
import io.sentry.ScopeCallback
import io.sentry.SentryLevel.INFO
import io.sentry.SentryOptions
import io.sentry.SpanContext
import io.sentry.SpanId
import io.sentry.SpanStatus
import io.sentry.protocol.SentryId
import org.mockito.kotlin.any
import org.mockito.kotlin.anyOrNull
import org.mockito.kotlin.check
Expand Down Expand Up @@ -47,6 +50,9 @@ class SentryFragmentLifecycleCallbacksTest {
setTracesSampleRate(tracesSampleRate)
}
)
whenever(span.spanContext).thenReturn(
SpanContext(SentryId.EMPTY_ID, SpanId.EMPTY_ID, "op", null, null)
)
whenever(transaction.startChild(any(), any())).thenReturn(span)
whenever(scope.transaction).thenReturn(transaction)
whenever(hub.configureScope(any())).thenAnswer {
Expand Down
3 changes: 2 additions & 1 deletion sentry-android-navigation/api/sentry-android-navigation.api
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ public final class io/sentry/android/navigation/SentryNavigationListener : andro
public fun <init> (Lio/sentry/IHub;)V
public fun <init> (Lio/sentry/IHub;Z)V
public fun <init> (Lio/sentry/IHub;ZZ)V
public synthetic fun <init> (Lio/sentry/IHub;ZZILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun <init> (Lio/sentry/IHub;ZZLjava/lang/String;)V
public synthetic fun <init> (Lio/sentry/IHub;ZZLjava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun onDestinationChanged (Landroidx/navigation/NavController;Landroidx/navigation/NavDestination;Landroid/os/Bundle;)V
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import io.sentry.protocol.TransactionNameSource
import io.sentry.util.TracingUtils
import java.lang.ref.WeakReference

private const val TRACE_ORIGIN = "auto.navigation"

/**
* A [NavController.OnDestinationChangedListener] that captures a [Breadcrumb] and starts an
* [ITransaction] and sends them to Sentry for each [onDestinationChanged] call.
Expand All @@ -34,7 +36,8 @@ import java.lang.ref.WeakReference
class SentryNavigationListener @JvmOverloads constructor(
private val hub: IHub = HubAdapter.getInstance(),
private val enableNavigationBreadcrumbs: Boolean = true,
private val enableNavigationTracing: Boolean = true
private val enableNavigationTracing: Boolean = true,
private val traceOriginAppendix: String? = null
) : NavController.OnDestinationChangedListener, IntegrationName {

private var previousDestinationRef: WeakReference<NavDestination>? = null
Expand Down Expand Up @@ -140,6 +143,10 @@ class SentryNavigationListener @JvmOverloads constructor(
transactonOptions
)

transaction.spanContext.origin = traceOriginAppendix?.let {
"$TRACE_ORIGIN.$traceOriginAppendix"
} ?: TRACE_ORIGIN

if (arguments.isNotEmpty()) {
transaction.setData("arguments", arguments)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,8 @@ class SentryNavigationListenerTest {
enableTracing: Boolean = true,
tracesSampleRate: Double? = 1.0,
hasViewIdInRes: Boolean = true,
transaction: SentryTracer? = null
transaction: SentryTracer? = null,
traceOriginAppendix: String? = null
): SentryNavigationListener {
options = SentryOptions().apply {
dsn = "http://key@localhost/proj"
Expand Down Expand Up @@ -93,7 +94,7 @@ class SentryNavigationListenerTest {
whenever(context.resources).thenReturn(resources)
whenever(navController.context).thenReturn(context)
whenever(destination.route).thenReturn(toRoute)
return SentryNavigationListener(hub, enableBreadcrumbs, enableTracing)
return SentryNavigationListener(hub, enableBreadcrumbs, enableTracing, traceOriginAppendix)
}
}

Expand Down Expand Up @@ -366,4 +367,22 @@ class SentryNavigationListenerTest {
verify(fixture.hub).configureScope(any())
assertNotSame(propagationContextAtStart, scope.propagationContext)
}

@Test
fun `onDestinationChanged sets trace origin`() {
val sut = fixture.getSut()

sut.onDestinationChanged(fixture.navController, fixture.destination, null)

assertEquals("auto.navigation", fixture.transaction.spanContext.origin)
}

@Test
fun `onDestinationChanged sets trace origin with appendix`() {
val sut = fixture.getSut(traceOriginAppendix = "jetpack_compose")

sut.onDestinationChanged(fixture.navController, fixture.destination, null)

assertEquals("auto.navigation.jetpack_compose", fixture.transaction.spanContext.origin)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import java.util.concurrent.ConcurrentHashMap

private const val PROTOCOL_KEY = "protocol"
private const val ERROR_MESSAGE_KEY = "error_message"
internal const val TRACE_ORIGIN = "auto.http.okhttp"

internal class SentryOkHttpEvent(private val hub: IHub, private val request: Request) {
private val eventSpans: MutableMap<String, ISpan> = ConcurrentHashMap()
Expand All @@ -37,7 +38,7 @@ internal class SentryOkHttpEvent(private val hub: IHub, private val request: Req

// We start the call span that will contain all the others
callRootSpan = hub.span?.startChild("http.client", "$method $url")

callRootSpan?.spanContext?.origin = TRACE_ORIGIN
urlDetails.applyToSpan(callRootSpan)

// We setup a breadcrumb with all meaningful data
Expand Down Expand Up @@ -107,6 +108,7 @@ internal class SentryOkHttpEvent(private val hub: IHub, private val request: Req
else -> callRootSpan
} ?: callRootSpan
val span = parentSpan?.startChild("http.client.$event") ?: return
span.spanContext.origin = TRACE_ORIGIN
eventSpans[event] = span
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,9 @@ class SentryOkHttpInterceptor(
span = hub.span?.startChild("http.client", "$method $url")
isFromEventListener = false
}

span?.spanContext?.origin = TRACE_ORIGIN

urlDetails.applyToSpan(span)

var response: Response? = null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,7 @@ class SentryOkHttpInterceptorTest {
assertEquals("http.client", httpClientSpan.operation)
assertEquals("GET ${request.url}", httpClientSpan.description)
assertEquals(201, httpClientSpan.data[SpanDataConvention.HTTP_STATUS_CODE_KEY])
assertEquals("auto.http.okhttp", httpClientSpan.spanContext.origin)
assertEquals(SpanStatus.OK, httpClientSpan.status)
assertTrue(httpClientSpan.isFinished)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import io.sentry.SentryStackTraceFactory
import io.sentry.SpanDataConvention
import io.sentry.SpanStatus

private const val TRACE_ORIGIN = "auto.db.sqlite"

internal class SQLiteSpanManager(
private val hub: IHub = HubAdapter.getInstance()
) {
Expand All @@ -28,6 +30,7 @@ internal class SQLiteSpanManager(
@Throws(SQLException::class)
fun <T> performSql(sql: String, operation: () -> T): T {
val span = hub.span?.startChild("db.sql.query", sql)
span?.spanContext?.origin = TRACE_ORIGIN
return try {
val result = operation()
span?.status = SpanStatus.OK
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ class SQLiteSpanManagerTest {
val span = fixture.sentryTracer.children.firstOrNull()
assertNotNull(span)
assertEquals("db.sql.query", span.operation)
assertEquals("auto.db.sqlite", span.spanContext.origin)
assertEquals("sql", span.description)
assertEquals(SpanStatus.OK, span.status)
assertTrue(span.isFinished)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ import io.sentry.vendor.Base64
import okio.Buffer
import org.jetbrains.annotations.ApiStatus

private const val TRACE_ORIGIN = "auto.graphql.apollo3"

class SentryApollo3HttpInterceptor @JvmOverloads constructor(
@ApiStatus.Internal private val hub: IHub = HubAdapter.getInstance(),
private val beforeSpan: BeforeSpanCallback? = null,
Expand Down Expand Up @@ -160,6 +162,8 @@ class SentryApollo3HttpInterceptor @JvmOverloads constructor(
return activeSpan.startChild(operation, description).apply {
urlDetails.applyToSpan(this)

spanContext.origin = TRACE_ORIGIN

operationId?.let {
setData("operationId", it)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ class SentryApollo3InterceptorWithVariablesTest {
val httpClientSpan = it.spans.first()
assertEquals("http.graphql.query", httpClientSpan.op)
assertEquals("query LaunchDetails", httpClientSpan.description)
assertEquals("auto.graphql.apollo3", httpClientSpan.origin)
assertNotNull(httpClientSpan.data) {
assertNotNull(it["operationId"])
assertNotNull(it["variables"])
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ import io.sentry.TypeCheckHint.APOLLO_RESPONSE
import io.sentry.util.TracingUtils
import java.util.concurrent.Executor

private const val TRACE_ORIGIN = "auto.graphql.apollo"

class SentryApolloInterceptor(
private val hub: IHub = HubAdapter.getInstance(),
private val beforeSpan: BeforeSpanCallback? = null
Expand All @@ -49,6 +51,7 @@ class SentryApolloInterceptor(
chain.proceedAsync(modifiedRequest, dispatcher, callBack)
} else {
val span = startChild(request, activeSpan)
span.spanContext.origin = TRACE_ORIGIN

val headers = addTracingHeaders(request, span)
val requestWithHeader = request.toBuilder().requestHeaders(headers).build()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,7 @@ class SentryApolloInterceptorTest {
val httpClientSpan = it.spans.first()
assertEquals("http.graphql.query", httpClientSpan.op)
assertEquals("query LaunchDetails", httpClientSpan.description)
assertEquals("auto.graphql.apollo", httpClientSpan.origin)
assertNotNull(httpClientSpan.data) {
assertNotNull(it["operationId"])
assertEquals("{id=83}", it["variables"])
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
@SuppressWarnings("KotlinInternalInJava")
public final class ComposeGestureTargetLocator implements GestureTargetLocator {

private static final String ORIGIN = "jetpack_compose";

private final @NotNull ILogger logger;
private volatile @Nullable SentryComposeHelper composeHelper;

Expand Down Expand Up @@ -103,7 +105,7 @@ public ComposeGestureTargetLocator(final @NotNull ILogger logger) {
if (targetTag == null) {
return null;
} else {
return new UiElement(null, null, null, targetTag);
return new UiElement(null, null, null, targetTag, ORIGIN);
}
}

Expand Down

0 comments on commit 695d3a3

Please sign in to comment.